-
Notifications
You must be signed in to change notification settings - Fork 18
Add optional listen param to dial() to support starting calls using a single method. #1289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
83e5f77
099fcc8
988b6e6
c6b294a
b10928c
6bf0bb1
e2d0430
642bf26
929c388
00796e0
a442c28
b192709
afdc78b
2998cfe
6d3ace2
d712597
e6537bf
f65cc55
60b0802
c97f250
4b79a55
508617e
6b0a5fb
3801f24
3aee8e4
ea2fd0b
5fe5a18
3fa0161
8fccca4
4de97ff
b6c97ad
00d61c2
4d505a0
eb674cd
ec87cdd
9647410
4603951
ca12a92
018908d
f2e4726
71271ce
3db030a
2dd6307
acaa0be
aeb90ca
6a6e1f9
0f34e6e
73dde3d
ffc4e68
362f939
96dcbdd
2fe8fa1
76eda13
34918a0
427fab7
2373b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@signalwire/client': patch | ||
--- | ||
|
||
added a `listen` parameter to the Dial funtion allowing developer to pass the event listeners callback and start the call in the `dial()` function. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../signalwire-js/.serena |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
export class CallStateManager { | ||
public history: any[] = [] | ||
|
||
constructor() {} | ||
|
||
update(event: string, payload: any) { | ||
|
||
let safePayload = {} | ||
|
||
try { | ||
safePayload = JSON.parse(JSON.stringify(payload)) | ||
} catch { } | ||
|
||
const timestamp = Date.now() | ||
|
||
let newState | ||
|
||
if (!event.startsWith('member')) { | ||
newState = { | ||
...this.getState(), | ||
...safePayload, | ||
} | ||
} else { | ||
newState = { | ||
...this.getState(), | ||
} | ||
const memberIndex = newState.room_session.members.findIndex( | ||
(m: any) => m.member_id == payload.member.member_id | ||
) | ||
if (memberIndex >= 0) { | ||
newState.room_session.members[memberIndex] = { | ||
...newState.room_session.members[memberIndex], | ||
...payload.member, | ||
} | ||
} | ||
} | ||
|
||
const entry = { event, payload, timestamp, state: newState } | ||
|
||
// Add to history | ||
this.history.push(entry) | ||
} | ||
|
||
getState() { | ||
return this.history.length | ||
? { ...this.history[this.history.length - 1].state } | ||
: null | ||
} | ||
|
||
getSelfState() { | ||
const state = this.getState() | ||
return state?.room_session?.members.find( | ||
(m: any) => m.member_id == state.member_id | ||
) | ||
} | ||
|
||
logHistory() { | ||
console.log('Call State History:', JSON.stringify(this.history, null, 2)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
import { CallStateManager } from './CallStateManage' | ||
|
||
describe('CallStateManager', () => { | ||
let callStateManager: CallStateManager | ||
|
||
beforeEach(() => { | ||
callStateManager = new CallStateManager() | ||
}) | ||
|
||
describe('initialization', () => { | ||
it('should initialize with empty history', () => { | ||
expect(callStateManager.history).toEqual([]) | ||
}) | ||
|
||
it('should return null for initial state', () => { | ||
expect(callStateManager.getState()).toBeNull() | ||
}) | ||
|
||
it('should return undefined for initial self state', () => { | ||
expect(callStateManager.getSelfState()).toBeUndefined() | ||
}) | ||
}) | ||
|
||
describe('update method', () => { | ||
it('should add non-member events to history with updated state', () => { | ||
const event = 'call.joined' | ||
const payload = { | ||
member_id: 'member-123', | ||
room_session: { | ||
members: [], | ||
}, | ||
} | ||
|
||
callStateManager.update(event, payload) | ||
|
||
expect(callStateManager.history).toHaveLength(1) | ||
expect(callStateManager.history[0]).toMatchObject({ | ||
event, | ||
payload, | ||
state: payload, | ||
}) | ||
expect(callStateManager.history[0].timestamp).toBeDefined() | ||
expect(typeof callStateManager.history[0].timestamp).toBe('number') | ||
}) | ||
|
||
it('should handle member events by updating member in state', () => { | ||
// First, set up initial state with members | ||
const initialPayload = { | ||
member_id: 'member-123', | ||
room_session: { | ||
members: [ | ||
{ member_id: 'member-123', name: 'John', visible: true }, | ||
{ member_id: 'member-456', name: 'Jane', visible: true }, | ||
], | ||
}, | ||
} | ||
callStateManager.update('call.joined', initialPayload) | ||
|
||
// Update a member | ||
const memberUpdatePayload = { | ||
member: { | ||
member_id: 'member-123', | ||
visible: false, | ||
muted: true, | ||
}, | ||
} | ||
callStateManager.update('member.updated', memberUpdatePayload) | ||
|
||
const state = callStateManager.getState() | ||
expect(state.room_session.members).toHaveLength(2) | ||
expect(state.room_session.members[0]).toMatchObject({ | ||
member_id: 'member-123', | ||
name: 'John', | ||
visible: false, | ||
muted: true, | ||
}) | ||
expect(state.room_session.members[1]).toMatchObject({ | ||
member_id: 'member-456', | ||
name: 'Jane', | ||
visible: true, | ||
}) | ||
}) | ||
|
||
it('should preserve history order', () => { | ||
callStateManager.update('event1', { data: 'first' }) | ||
callStateManager.update('event2', { data: 'second' }) | ||
callStateManager.update('event3', { data: 'third' }) | ||
|
||
expect(callStateManager.history).toHaveLength(3) | ||
expect(callStateManager.history[0].event).toBe('event1') | ||
expect(callStateManager.history[1].event).toBe('event2') | ||
expect(callStateManager.history[2].event).toBe('event3') | ||
}) | ||
|
||
it('should merge state for consecutive non-member events', () => { | ||
callStateManager.update('call.joined', { | ||
member_id: '123', | ||
status: 'joined', | ||
}) | ||
callStateManager.update('call.updated', { quality: 'HD' }) | ||
|
||
const state = callStateManager.getState() | ||
expect(state).toMatchObject({ | ||
member_id: '123', | ||
status: 'joined', | ||
quality: 'HD', | ||
}) | ||
}) | ||
}) | ||
|
||
describe('getState method', () => { | ||
it('should return null when history is empty', () => { | ||
expect(callStateManager.getState()).toBeNull() | ||
}) | ||
|
||
it('should return a copy of the latest state', () => { | ||
const payload = { test: 'data' } | ||
callStateManager.update('event', payload) | ||
|
||
const state1 = callStateManager.getState() | ||
const state2 = callStateManager.getState() | ||
|
||
expect(state1).toEqual(payload) | ||
expect(state2).toEqual(payload) | ||
expect(state1).not.toBe(state2) // Should be different object references | ||
}) | ||
|
||
it('should return the last state after multiple updates', () => { | ||
callStateManager.update('event1', { step: 1 }) | ||
callStateManager.update('event2', { step: 2, extra: 'data' }) | ||
|
||
const state = callStateManager.getState() | ||
expect(state).toMatchObject({ | ||
step: 2, | ||
extra: 'data', | ||
}) | ||
}) | ||
}) | ||
|
||
describe('getSelfState method', () => { | ||
it('should return undefined when state is null', () => { | ||
expect(callStateManager.getSelfState()).toBeUndefined() | ||
}) | ||
|
||
it('should return undefined when room_session.members is not present', () => { | ||
callStateManager.update('event', { member_id: '123' }) | ||
expect(callStateManager.getSelfState()).toBeUndefined() | ||
}) | ||
|
||
it('should return the self member from members array', () => { | ||
const payload = { | ||
member_id: 'self-123', | ||
room_session: { | ||
members: [ | ||
{ member_id: 'self-123', name: 'Self', role: 'moderator' }, | ||
{ member_id: 'other-456', name: 'Other', role: 'participant' }, | ||
], | ||
}, | ||
} | ||
callStateManager.update('call.joined', payload) | ||
|
||
const selfState = callStateManager.getSelfState() | ||
expect(selfState).toMatchObject({ | ||
member_id: 'self-123', | ||
name: 'Self', | ||
role: 'moderator', | ||
}) | ||
}) | ||
|
||
it('should return undefined when self member is not found', () => { | ||
const payload = { | ||
member_id: 'self-123', | ||
room_session: { | ||
members: [ | ||
{ member_id: 'other-456', name: 'Other', role: 'participant' }, | ||
], | ||
}, | ||
} | ||
callStateManager.update('call.joined', payload) | ||
|
||
expect(callStateManager.getSelfState()).toBeUndefined() | ||
}) | ||
}) | ||
|
||
describe('logHistory method', () => { | ||
it('should log history to console', () => { | ||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation() | ||
|
||
callStateManager.update('event1', { data: 'test1' }) | ||
callStateManager.update('event2', { data: 'test2' }) | ||
|
||
callStateManager.logHistory() | ||
|
||
expect(consoleSpy).toHaveBeenCalledWith( | ||
'Call State History:', | ||
expect.stringContaining('event1') | ||
) | ||
expect(consoleSpy).toHaveBeenCalledWith( | ||
'Call State History:', | ||
expect.stringContaining('event2') | ||
) | ||
|
||
consoleSpy.mockRestore() | ||
}) | ||
}) | ||
|
||
describe('member event handling edge cases', () => { | ||
it('should handle member event when member does not exist in array', () => { | ||
const initialPayload = { | ||
member_id: 'member-123', | ||
room_session: { | ||
members: [{ member_id: 'member-123', name: 'John' }], | ||
}, | ||
} | ||
callStateManager.update('call.joined', initialPayload) | ||
|
||
// Try to update non-existing member | ||
const memberUpdatePayload = { | ||
member: { | ||
member_id: 'member-999', | ||
visible: false, | ||
}, | ||
} | ||
callStateManager.update('member.updated', memberUpdatePayload) | ||
|
||
const state = callStateManager.getState() | ||
expect(state.room_session.members).toHaveLength(1) | ||
expect(state.room_session.members[0]).toMatchObject({ | ||
member_id: 'member-123', | ||
name: 'John', | ||
}) | ||
}) | ||
|
||
it('should handle member events with empty members array', () => { | ||
const initialPayload = { | ||
member_id: 'member-123', | ||
room_session: { | ||
members: [], | ||
}, | ||
} | ||
callStateManager.update('call.joined', initialPayload) | ||
|
||
const memberUpdatePayload = { | ||
member: { | ||
member_id: 'member-123', | ||
visible: false, | ||
}, | ||
} | ||
callStateManager.update('member.updated', memberUpdatePayload) | ||
|
||
const state = callStateManager.getState() | ||
expect(state.room_session.members).toHaveLength(0) | ||
}) | ||
}) | ||
|
||
describe('timestamp handling', () => { | ||
it('should add timestamp to each history entry', () => { | ||
const beforeTimestamp = Date.now() | ||
|
||
callStateManager.update('event1', { data: 'test' }) | ||
|
||
const afterTimestamp = Date.now() | ||
|
||
expect(callStateManager.history[0].timestamp).toBeGreaterThanOrEqual( | ||
beforeTimestamp | ||
) | ||
expect(callStateManager.history[0].timestamp).toBeLessThanOrEqual( | ||
afterTimestamp | ||
) | ||
}) | ||
|
||
it('should have increasing timestamps for consecutive events', (done) => { | ||
callStateManager.update('event1', { data: 'test1' }) | ||
const timestamp1 = callStateManager.history[0].timestamp | ||
|
||
// Small delay to ensure different timestamp | ||
setTimeout(() => { | ||
callStateManager.update('event2', { data: 'test2' }) | ||
const timestamp2 = callStateManager.history[1].timestamp | ||
|
||
expect(timestamp2).toBeGreaterThanOrEqual(timestamp1) | ||
done() | ||
}, 1) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -526,7 +526,7 @@ | |
|
||
const dialer = reattach ? client.reattach : client.dial | ||
|
||
const call = dialer({ | ||
const call = await dialer({ | ||
to: address, | ||
...(shouldPassRootElement && { | ||
rootElement: document.getElementById('rootElement')!, | ||
|
@@ -1867,7 +1867,7 @@ | |
timeout: 10_000, | ||
...options, | ||
} | ||
return await expect(assertion, assertionMessage).toPass(mergedOptions) | ||
Check failure on line 1870 in internal/e2e-client/utils.ts
|
||
} | ||
/** | ||
* @description | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
"changeset": "changeset", | ||
"clean": "npm exec --ws -- npx rimraf node_modules && npm exec --ws -- npx rimraf dist && npx rimraf node_modules", | ||
"clean:vite-cache": "npx rimraf ./internal/**/node_modules/.vite", | ||
"test": "npm exec --ws npm run test", | ||
"test": "npm exec --ws npm run test && npx jest internal/e2e-client/*.test.ts", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like a mistake? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are no |
||
"build": "npm run clean:vite-cache && npm exec --ws -- npx rimraf dist && sw-build-all", | ||
"prettier": "prettier --write .", | ||
"release:dev": "sw-release --development", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignore this changes I will revert them