Skip to content

Commit

Permalink
Fix parsing of nested fields (#512)
Browse files Browse the repository at this point in the history
* add tests for room.joined event handlers

* temp fix for parse nested fields

* update js playground

* changeset

* fix jest hangs

* update toExternalJSON util
  • Loading branch information
edolix authored Apr 26, 2022
1 parent 5cacd75 commit f69ef58
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 31 deletions.
7 changes: 7 additions & 0 deletions .changeset/cool-boxes-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sw-internal/playground-js': patch
'@signalwire/core': patch
'@signalwire/js': patch
---

[internal] fix parse nested fields
9 changes: 9 additions & 0 deletions internal/playground-js/src/heroku/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ window.connect = () => {
roomObj.on('room.started', (params) =>
console.debug('>> room.started', params)
)
const handler = (params) => {
console.warn('Debug', params)
}
roomObj.on('room.joined', handler)
roomObj.on('room.joined', handler)
roomObj.on('room.joined', console.log)
roomObj.on('room.joined', console.warn)
roomObj.on('room.joined', (params) => {
Expand All @@ -216,6 +221,10 @@ window.connect = () => {
params.room_session.members
)

if (params.room_session.recordings) {
window.__recording = params.room_session.recordings[0]
}

btnConnect.classList.add('d-none')
btnDisconnect.classList.remove('d-none')
connectStatus.innerHTML = 'Connected'
Expand Down
31 changes: 7 additions & 24 deletions packages/core/src/BaseComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,35 +423,22 @@ export class BaseComponent<
obj: unknown,
transform: EventTransform,
process = (p: any) => p,
result: any = undefined,
// `level` keeps track of how deep we are in the
// processing line.
level = 0,
// max depth level we'll get into. this value could
// change depending on how soon we detect the first
// Proxy.
maxDepth = 5
result: any = undefined
): any {
if (!transform.nestedFieldsToProcess) {
return transform.payloadTransform(obj)
}

// It's non trivial to detect when the end of the line
// is since we can be dealing with multiple levels of
// nested Proxies at a time.
// 4 = Max Depth
if (level > maxDepth) {
return result
// @ts-expect-error
} else if (obj.__sw_proxy) {
maxDepth = level
// @ts-expect-error
if (obj.__sw_proxy) {
return obj
}

// First time we ran this util we'll apply the top level
// transform
if (!result) {
const r = transform.payloadTransform(obj)
return this._parseNestedFields(r, transform, process, r, level + 1, maxDepth)
return this._parseNestedFields(r, transform, process, r)
}

if (Array.isArray(obj)) {
Expand All @@ -464,9 +451,7 @@ export class BaseComponent<
// reference a transform. This process comes from
// a previous iteration (since we don't support
// top level arrays)
obj[index],
level + 1,
maxDepth
obj[index]
)
})
} else if (obj && typeof obj === 'object') {
Expand Down Expand Up @@ -495,9 +480,7 @@ export class BaseComponent<

return p
},
result[key],
level + 1,
maxDepth
result[key]
)
} else {
result[key] = process(value)
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/utils/toExternalJSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export const toExternalJSON = <T>(
input: T,
options: typeof DEFAULT_OPTIONS = DEFAULT_OPTIONS
) => {
// @ts-expect-error
if (input?.__sw_symbol || input?.__sw_proxy) {
// Return if the input is a BaseComponent or a Proxy
return input as unknown as ToExternalJSONResult<T>
}

return Object.entries(input).reduce((reducer, [key, value]) => {
const prop = fromSnakeToCamelCase(key) as any
const propType = typeof value
Expand Down
131 changes: 126 additions & 5 deletions packages/js/src/BaseRoomSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Room Object', () => {

describe('getRecordings', () => {
it('should return an array of recordings', async () => {
const { store, session, emitter } = configureFullStack()
const { store, session, emitter, destroy } = configureFullStack()
const recordingList = [{ id: 'recordingOne' }, { id: 'recordingTwo' }]

session.execute = jest.fn().mockResolvedValue({
Expand All @@ -80,6 +80,8 @@ describe('Room Object', () => {
expect(result).toStrictEqual({
recordings: recordingList,
})

destroy()
})
})

Expand Down Expand Up @@ -183,7 +185,7 @@ describe('Room Object', () => {

describe('playback methods', () => {
it('getPlaybacks should return an array of playbacks', async () => {
const { store, session, emitter } = configureFullStack()
const { store, session, emitter, destroy } = configureFullStack()
const playbacks = [{ id: 'playbackOne' }, { id: 'playbackTwo' }]

session.execute = jest.fn().mockResolvedValue({
Expand All @@ -210,6 +212,8 @@ describe('Room Object', () => {
expect(result).toStrictEqual({
playbacks,
})

destroy()
})

it('play should return an interactive object', async () => {
Expand Down Expand Up @@ -345,7 +349,7 @@ describe('Room Object', () => {

describe('as event emitter', () => {
it('should listen on the talking events', () => {
const { store, session, emitter } = configureFullStack()
const { store, session, emitter, destroy } = configureFullStack()
room = createBaseRoomSessionObject({
store,
// @ts-expect-error
Expand Down Expand Up @@ -399,12 +403,125 @@ describe('Room Object', () => {
2,
talkingFalse.params.params
)
destroy()
})

it('should handle the room.subscribed event with nested fields', (done) => {
const roomId = 'd8caec4b-ddc9-4806-b2d0-e7c7d5cefe79'
const roomSessionId = '638a54a7-61d8-4db0-bc24-426aee5cebcd'
const recordingId = 'd1ae1822-5a5d-4950-8693-e59dc5dd96e0'
const memberId = '465ea212-c456-423b-9bcc-838c5e1b2851'

const { store, session, emitter, destroy } = configureFullStack()
room = createBaseRoomSessionObject({
store,
// @ts-expect-error
emitter,
})
// @ts-expect-error
room.execute = jest.fn()
// const startedHandler = jest.fn()
room.on('room.joined', (params) => {
/** Test same keys between room_session and room for backwards compat. */
const keys = ['room_session', 'room'] as const
keys.forEach(async (key) => {
expect(params[key].name).toEqual('bu')
expect(params[key].room_id).toEqual(roomId)
expect(params[key].recording).toBe(true)
expect(params[key].hide_video_muted).toBe(false)
// @ts-expect-error
expect(params[key].meta).toStrictEqual({})
// @ts-expect-error
const { members, recordings } = params[key]

// Test members and member object
expect(members).toHaveLength(1)
expect(members[0].id).toEqual(memberId)
expect(members[0].name).toEqual('edo')
expect(members[0].visible).toEqual(false)
expect(members[0].audio_muted).toEqual(false)
expect(members[0].video_muted).toEqual(false)
expect(members[0].deaf).toEqual(false)
expect(members[0].input_volume).toEqual(0)
expect(members[0].output_volume).toEqual(0)
expect(members[0].input_sensitivity).toEqual(11.11111111111111)
expect(members[0].meta).toStrictEqual({})

// Test recordings and recording object
expect(recordings).toHaveLength(1)
const recordingObj = recordings[0]
expect(recordingObj.id).toEqual(recordingId)
expect(recordingObj.state).toEqual('recording')
expect(recordingObj.duration).toBeNull()
expect(recordingObj.startedAt).toBeInstanceOf(Date)
expect(recordingObj.endedAt).toBeInstanceOf(Date)

const execMock = jest.fn()
const _clearMock = () => {
execMock.mockClear()
recordingObj.execute = execMock
}
_clearMock()
await recordingObj.pause()
expect(execMock).toHaveBeenCalledTimes(1)
expect(execMock).toHaveBeenCalledWith({
method: 'video.recording.pause',
params: {
recording_id: recordingId,
room_session_id: roomSessionId,
},
})

_clearMock()
await recordingObj.resume()
expect(execMock).toHaveBeenCalledTimes(1)
expect(execMock).toHaveBeenCalledWith({
method: 'video.recording.resume',
params: {
recording_id: recordingId,
room_session_id: roomSessionId,
},
})

_clearMock()
await recordingObj.stop()
expect(execMock).toHaveBeenCalledTimes(1)
expect(execMock).toHaveBeenCalledWith({
method: 'video.recording.stop',
params: {
recording_id: recordingId,
room_session_id: roomSessionId,
},
})
})
/** Test specific keys between room_session and room for backwards compat. */
expect(params.room.room_session_id).toEqual(roomSessionId)
expect(params.room_session.id).toEqual(roomSessionId)

/** Test RoomSession properties */
expect(room.roomId).toEqual(roomId)
expect(room.roomSessionId).toEqual(roomSessionId)
expect(room.memberId).toEqual(memberId)

destroy()
done()
})

/**
* Mock `call_id` to match the event with "room.__uuid"
*/
const roomSubscribed = JSON.parse(
// @ts-expect-error
`{"jsonrpc":"2.0","id":"d8a9fb9a-ad28-4a0a-8caa-5e06ec22f856","method":"signalwire.event","params":{"event_type":"video.room.subscribed","timestamp":1650960870.216,"event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","params":{"call_id":"${room.id}","member_id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_session":{"room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","name":"bu","recording":true,"hide_video_muted":false,"display_name":"bu","meta":{},"recordings":[{"id":"d1ae1822-5a5d-4950-8693-e59dc5dd96e0","state":"recording","duration":null,"started_at":1650960870.033,"ended_at":null}],"members":[{"id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","name":"edo","type":"member","parent_id":"","requested_position":"auto","visible":false,"audio_muted":false,"video_muted":false,"deaf":false,"input_volume":0,"output_volume":0,"input_sensitivity":11.11111111111111,"meta":{}}]},"room":{"room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","event_channel":"EC_4d2c491d-bf96-4802-9008-c360a51155a2","name":"bu","recording":true,"hide_video_muted":false,"display_name":"bu","meta":{},"recordings":[{"id":"d1ae1822-5a5d-4950-8693-e59dc5dd96e0","state":"recording","duration":null,"started_at":1650960870.033,"ended_at":null}],"members":[{"id":"465ea212-c456-423b-9bcc-838c5e1b2851","room_id":"d8caec4b-ddc9-4806-b2d0-e7c7d5cefe79","room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd","name":"edo","type":"member","parent_id":"","requested_position":"auto","visible":false,"audio_muted":false,"video_muted":false,"deaf":false,"input_volume":0,"output_volume":0,"input_sensitivity":11.11111111111111,"meta":{}}],"room_session_id":"638a54a7-61d8-4db0-bc24-426aee5cebcd"}}}}`
)
// mock a room.subscribed event
session.dispatch(actions.socketMessageAction(roomSubscribed))
})
})

describe('meta methods', () => {
it('should allow to set the meta field on the RoomSession', async () => {
const { store, session, emitter } = configureFullStack()
const { store, session, emitter, destroy } = configureFullStack()

session.execute = jest.fn().mockResolvedValue({
code: '200',
Expand Down Expand Up @@ -437,10 +554,12 @@ describe('Room Object', () => {
meta: { foo: 'bar' },
},
})

destroy()
})

it('should allow to set the meta field on the Member', async () => {
const { store, session, emitter } = configureFullStack()
const { store, session, emitter, destroy } = configureFullStack()

session.execute = jest.fn().mockResolvedValue({
code: '200',
Expand Down Expand Up @@ -477,6 +596,8 @@ describe('Room Object', () => {
meta: { displayName: 'jest' },
},
})

destroy()
})
})
})
6 changes: 4 additions & 2 deletions packages/js/src/BaseRoomSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ export class RoomSessionConnection
{
type: 'roomSession',
instanceFactory: (payload: VideoRoomSubscribedEventParams) => {
return payload
// FIXME:
return JSON.parse(JSON.stringify(payload))
},
payloadTransform: (payload: VideoRoomSubscribedEventParams) => {
return payload
// FIXME:
return JSON.parse(JSON.stringify(payload))
},
nestedFieldsToProcess: {
recordings: {
Expand Down

0 comments on commit f69ef58

Please sign in to comment.