Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ name: build
on:
push:
tags:
- "v*"
- 'v*'

jobs:
build:
runs-on: [self-hosted, M1]
permissions:
contents: write

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: actions/setup-node@v2
- uses: actions/setup-node@v4
with:
node-version: "18"
node-version: '20'
cache: 'yarn'

- name: Build
env:
Expand All @@ -25,12 +26,13 @@ jobs:
NOT_APPLE_ID: ${{ secrets.APPLE_ID }}
NOT_APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }}
run: |
yarn install
yarn install --frozen-lockfile
yarn run build-desktop

- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: desktop-builds
retention-days: 1
path: |
packages/008desktop/build/008-desktop-win.exe
Expand All @@ -45,15 +47,15 @@ jobs:

steps:
- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: artifact
name: desktop-builds
path: bin

- name: Sign win
shell: bash
run: |
dotnet tool install --global AzureSignTool
dotnet tool install --global AzureSignTool
AzureSignTool sign -kvu "${{ secrets.AZURE_KEY_VAULT_URI }}" -kvi "${{ secrets.AZURE_CLIENT_ID }}" -kvt "${{ secrets.AZURE_TENANT_ID }}" -kvs "${{ secrets.AZURE_CLIENT_SECRET }}" -kvc ${{ secrets.AZURE_CERT_NAME }} -tr http://timestamp.digicert.com -v "bin/008-desktop-win.exe"

- name: Release
Expand Down
61 changes: 45 additions & 16 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
name: Test
on: pull_request
on:
pull_request:
push:
branches: [master]

jobs:
test:
env:
SDL_VIDEODRIVER: "dummy"
SDL_AUDIODRIVER: "disk"
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'

- run: yarn install --frozen-lockfile

- run: yarn lint

unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
cache: 'yarn'

- name: prerequisites
run: |
yarn install
- run: yarn install --frozen-lockfile

- name: unit tests
run: yarn test

e2e:
runs-on: ubuntu-latest
env:
SDL_VIDEODRIVER: 'dummy'
SDL_AUDIODRIVER: 'disk'

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'

- name: lint
run: |
yarn lint
- run: yarn install --frozen-lockfile

- name: tests
run: |
yarn run e2e
- name: e2e
run: yarn run e2e
11 changes: 11 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
build
lib
dist
app
web-build
packages/008/web-build
packages/008desktop/app
packages/008desktop/build
packages/web-llm
yarn.lock
File renamed without changes.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
"start": "yarn start-desktop",
"start-desktop": "lerna run web --scope @kunzite/008 & lerna run start --scope 008desktop",
"build-web": "lerna run build-web",
"build-desktop": "IS_ELECTRON=yes lerna run build-web && rm -rf packages/008desktop/app && mv packages/008/web-build packages/008desktop/app && cp -R packages/008desktop/app/static/ packages/008desktop/app/static2/ && mv packages/008desktop/app/static2 packages/008desktop/app/static/js/static && lerna run build --scope 008desktop",
"build-desktop": "node scripts/build-desktop.js",
"clean": "lerna clean",
"prepare": "husky install",
"lint": "lerna run lint",
"test": "lerna run test --no-bail",
"e2e": "lerna run e2e --scope @kunzite/008"
},
"lint-staged": {
"**/*.{js}": [
"**/*.js": [
"lerna run lint -- --fix --quiet",
"lerna run format -- --write ."
],
Expand Down
3 changes: 0 additions & 3 deletions packages/008/.prettierignore

This file was deleted.

10 changes: 0 additions & 10 deletions packages/008/.prettierrc

This file was deleted.

7 changes: 7 additions & 0 deletions packages/008/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: 'jest-expo',
testMatch: ['**/src/**/*.test.js'],
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|sentry-expo|native-base|react-native-svg|p-retry|retry|@szmarczak|p-timeout|is-network-error))'
]
};
4 changes: 4 additions & 0 deletions packages/008/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"storybook-web": "start-storybook -p 6006 -c .storybook-web",
"build-storybook": "build-storybook -c .storybook-web",
"lint": "eslint ./",
"test": "jest",
"test:watch": "jest --watch",
"e2e": "yarn web & cypress run"
},
"dependencies": {
Expand Down Expand Up @@ -74,6 +76,8 @@
"eslint": "^8.49.0",
"eslint-config-universe": "^12.0.0",
"eslint-plugin-cypress": "^2.15.1",
"jest": "^29.7.0",
"jest-expo": "^48.0.0",
"prettier": "^3.0.3",
"prop-types": "^15.8.1",
"react-native-safe-area-context": "^4.7.1"
Expand Down
111 changes: 111 additions & 0 deletions packages/008/src/store/Cdr.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Cdr } from './Cdr';

const buildSession = ({
callId = 'call-123',
xCallId,
direction = 'inbound',
displayName = 'Jane Doe',
user = '+34917370224',
pAssertedIdentity = '+34917370225',
headers = { 'X-Foo': 'bar' },
video = false
} = {}) => {
const requestHeaders = {
'Call-ID': callId,
...(xCallId ? { 'X-Call-ID': xCallId } : {}),
'P-Asserted-Identity': pAssertedIdentity
};

return {
request: { getHeader: name => requestHeaders[name] },
remoteIdentity: { displayName, uri: { user, headers } },
isInbound: () => direction === 'inbound',
isVideo: () => video
};
};

describe('Cdr', () => {
it('prefers X-Call-ID over Call-ID when present', () => {
const cdr = new Cdr({
session: buildSession({ callId: 'call-1', xCallId: 'x-call-1' })
});
expect(cdr.id).toBe('x-call-1');
});

it('falls back to Call-ID', () => {
const cdr = new Cdr({ session: buildSession({ callId: 'call-2' }) });
expect(cdr.id).toBe('call-2');
});

it('builds an inbound CDR with from=user and to=displayName', () => {
const cdr = new Cdr({
session: buildSession({
direction: 'inbound',
user: '+1',
displayName: 'Caller'
})
});

expect(cdr.direction).toBe('inbound');
expect(cdr.from).toBe('+1');
expect(cdr.to).toBe('Caller');
expect(cdr.status).toBe('ringing');
});

it('builds an outbound CDR with from=P-Asserted-Identity and to=user', () => {
const cdr = new Cdr({
session: buildSession({
direction: 'outbound',
user: '+2',
pAssertedIdentity: '+99'
})
});

expect(cdr.direction).toBe('outbound');
expect(cdr.from).toBe('+99');
expect(cdr.to).toBe('+2');
});

it('marks status answered and stores wait on accepted()', () => {
jest.useFakeTimers().setSystemTime(new Date('2026-01-01T00:00:00Z'));
const cdr = new Cdr({ session: buildSession() });
jest.setSystemTime(new Date('2026-01-01T00:00:03Z'));

cdr.accepted();

expect(cdr.status).toBe('answered');
expect(cdr.wait).toBe(3);
jest.useRealTimers();
});

it('on terminated without accepted() marks the call as missed', () => {
jest.useFakeTimers().setSystemTime(new Date('2026-01-01T00:00:00Z'));
const cdr = new Cdr({ session: buildSession() });
jest.setSystemTime(new Date('2026-01-01T00:00:05Z'));

cdr.terminated();

expect(cdr.status).toBe('missed');
expect(cdr.wait).toBe(5);
expect(cdr.total).toBe(5);
expect(cdr.duration).toBe(0);
jest.useRealTimers();
});

it('on terminated after accepted() preserves answered status and computes duration', () => {
jest.useFakeTimers().setSystemTime(new Date('2026-01-01T00:00:00Z'));
const cdr = new Cdr({ session: buildSession() });

jest.setSystemTime(new Date('2026-01-01T00:00:02Z'));
cdr.accepted();

jest.setSystemTime(new Date('2026-01-01T00:00:10Z'));
cdr.terminated();

expect(cdr.status).toBe('answered');
expect(cdr.wait).toBe(2);
expect(cdr.total).toBe(10);
expect(cdr.duration).toBe(8);
jest.useRealTimers();
});
});
33 changes: 33 additions & 0 deletions packages/008/src/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { cleanPhoneNumber, genId, sleep } from './utils';

describe('cleanPhoneNumber', () => {
it('strips spaces and parentheses', () => {
expect(cleanPhoneNumber('+34 (917) 370 224')).toBe('+34917370224');
});

it('keeps dashes and other characters intact', () => {
expect(cleanPhoneNumber('+1-555-0100')).toBe('+1-555-0100');
});
});

describe('genId', () => {
it('produces a string with a timestamp and a random suffix', () => {
const id = genId();
expect(id).toMatch(/^\d+\.\d{6}$/);
});

it('returns unique values when called in quick succession', () => {
const ids = new Set(Array.from({ length: 50 }, () => genId()));
expect(ids.size).toBe(50);
});
});

describe('sleep', () => {
it('resolves after roughly the requested seconds', async () => {
jest.useFakeTimers();
const promise = sleep(2);
jest.advanceTimersByTime(2000);
await expect(promise).resolves.toBeUndefined();
jest.useRealTimers();
});
});
4 changes: 0 additions & 4 deletions packages/008desktop/.prettierignore

This file was deleted.

12 changes: 12 additions & 0 deletions scripts/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"root": true,
"env": {
"node": true,
"es2022": true
},
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "script"
},
"rules": {}
}
Loading