diff --git a/package.json b/package.json index 129b9efd..3c582f5c 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "yargs": "^17.7.2" }, "dependencies": { - "@superviz/socket-client": "1.4.0", + "@superviz/socket-client": "1.5.1", "ably": "^1.2.45", "bowser": "^2.11.0", "bowser-jr": "^1.0.6", diff --git a/src/common/types/events.types.ts b/src/common/types/events.types.ts index bf9cb45c..1f0c526c 100644 --- a/src/common/types/events.types.ts +++ b/src/common/types/events.types.ts @@ -40,7 +40,7 @@ export enum MeetingControlsEvent { TOGGLE_MICROPHONE = 'meeting-controls.toggle-microphone', TOGGLE_CAM = 'meeting-controls.toggle-cam', TOGGLE_SCREENSHARE = 'meeting-controls.toggle-screenshare', - TOGGLE_TRANSCRIPT = 'meeting-controls.toggle-transcript', + TOGGLE_RECORDING = 'meeting-controls.toggle-recording', HANG_UP = 'hang-up', CALLBACK_CALLED = 'callback-called', } diff --git a/src/common/types/stores.types.ts b/src/common/types/stores.types.ts index fdca291b..92d17b30 100644 --- a/src/common/types/stores.types.ts +++ b/src/common/types/stores.types.ts @@ -13,7 +13,7 @@ export enum StoreType { type Subject any, K extends keyof ReturnType> = ReturnType[K]; -type StoreApiWithoutDestroy any> = { +type IncompleteStoreApi any> = { [K in keyof ReturnType]: { subscribe(callback?: (value: Subject['value']) => void): void; subject: Subject; @@ -22,8 +22,9 @@ type StoreApiWithoutDestroy any> = { }; }; -type StoreApi any> = Omit, 'destroy'> & { +type StoreApi any> = IncompleteStoreApi & { destroy(): void; + restart(): void; }; type GlobalStore = StoreType.GLOBAL | `${StoreType.GLOBAL}`; diff --git a/src/components/realtime/channel.ts b/src/components/realtime/channel.ts index 76ac0704..07e05baf 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -133,9 +133,10 @@ export class Channel extends Observable { this.logger.log('message received', event); this.publishEventToClient(event.data.name, { data: event.data.payload, - participantId: event.presence.id, + participantId: event?.presence?.id || null, name: event.data.name, timestamp: event.timestamp, + connectionId: event.connectionId, } as RealtimeMessage); }); } @@ -172,10 +173,11 @@ export class Channel extends Observable { group[event.data.name].push({ data: event.data.payload, + connectionId: event.connectionId, name: event.data.name, - participantId: event.presence.id, + participantId: event.presence?.id, timestamp: event.timestamp, - }); + } as RealtimeMessage); return group; }, diff --git a/src/components/realtime/types.ts b/src/components/realtime/types.ts index 7b88dadf..e755ff6f 100644 --- a/src/components/realtime/types.ts +++ b/src/components/realtime/types.ts @@ -24,7 +24,8 @@ export type RealtimeData = { export type RealtimeMessage = { name: string; - participantId: string; + connectionId: string; + participantId: string | null; data: unknown; timestamp: number; }; diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index f27b0c8d..86ddae59 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -7,7 +7,6 @@ import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock'; import { MOCK_OBSERVER_HELPER } from '../../../__mocks__/observer-helper.mock'; import { MOCK_AVATAR, MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock'; import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock'; -import { ROOM_STATE_MOCK } from '../../../__mocks__/roomState.mock'; import { DeviceEvent, FrameEvent, @@ -25,7 +24,6 @@ import { useStore } from '../../common/utils/use-store'; import { IOC } from '../../services/io'; import { Presence3DManager } from '../../services/presence-3d-manager'; import { ParticipantInfo } from '../../services/realtime/base/types'; -import { RoomStateService } from '../../services/roomState'; import { VideoFrameState } from '../../services/video-conference-manager/types'; import { ComponentNames } from '../types'; @@ -97,12 +95,13 @@ describe('VideoConference', () => { let VideoConferenceInstance: VideoConference; const { localParticipant, hasJoinedRoom } = useStore(StoreType.GLOBAL); - localParticipant.publish(MOCK_LOCAL_PARTICIPANT); - hasJoinedRoom.publish(true); beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); + localParticipant.publish(MOCK_LOCAL_PARTICIPANT); + hasJoinedRoom.publish(true); + VideoConferenceInstance = new VideoConference({ userType: 'host', allowGuests: false, @@ -118,6 +117,7 @@ describe('VideoConference', () => { useStore, }); + VideoConferenceInstance['startVideo'](); VideoConferenceInstance['onFrameStateChange'](VideoFrameState.INITIALIZED); }); @@ -870,10 +870,10 @@ describe('VideoConference', () => { }); test('should toggle transcript', () => { - VideoConferenceInstance['toggleTranscript'](); + VideoConferenceInstance['toggleRecording'](); expect(VIDEO_MANAGER_MOCK.publishMessageToFrame).toBeCalledWith( - MeetingControlsEvent.TOGGLE_TRANSCRIPT, + MeetingControlsEvent.TOGGLE_RECORDING, ); }); diff --git a/src/components/video/index.ts b/src/components/video/index.ts index df91dc52..f59a5d85 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -1,4 +1,4 @@ -import { PresenceEvent, PresenceEvents, SocketEvent } from '@superviz/socket-client'; +import { PresenceEvent, PresenceEvents } from '@superviz/socket-client'; import { ColorsVariables } from '../../common/types/colors.types'; import { @@ -117,12 +117,12 @@ export class VideoConference extends BaseComponent { } /** - * @function toggleTranscript - * @description open/close meeting transcript + * @function toggleRecording + * @description open/close meeting recording * @returns {void} */ - public toggleTranscript(): void { - return this.videoManager?.publishMessageToFrame(MeetingControlsEvent.TOGGLE_TRANSCRIPT); + public toggleRecording(): void { + return this.videoManager?.publishMessageToFrame(MeetingControlsEvent.TOGGLE_RECORDING); } /** @@ -178,7 +178,7 @@ export class VideoConference extends BaseComponent { private startVideo = (): void => { this.videoConfig = { language: this.params?.language, - canUseTranscription: this.params?.transcriptOff === false, + canUseRecording: !!this.params?.enableRecording, canShowAudienceList: this.params?.showAudienceList ?? true, canUseChat: !this.params?.chatOff, canUseCams: !this.params?.camsOff, @@ -523,12 +523,12 @@ export class VideoConference extends BaseComponent { private onParticipantLeft = (_: Participant): void => { this.logger.log('video conference @ on participant left', this.localParticipant); - this.videoManager.leave(); this.connectionService.removeListeners(); this.publish(MeetingEvent.DESTROY); this.publish(MeetingEvent.MY_PARTICIPANT_LEFT, this.localParticipant); this.unsubscribeFromVideoEvents(); + this.videoManager.leave(); this.videoManager = undefined; this.connectionService = undefined; diff --git a/src/components/video/types.ts b/src/components/video/types.ts index a755b219..cf5957fd 100644 --- a/src/components/video/types.ts +++ b/src/components/video/types.ts @@ -13,7 +13,7 @@ export interface VideoComponentOptions { camsOff?: boolean; screenshareOff?: boolean; chatOff?: boolean; - transcriptOff?: boolean; + enableRecording?: boolean; defaultAvatars?: boolean; offset?: Offset; enableFollow?: boolean; @@ -38,7 +38,7 @@ export interface VideoComponentOptions { callbacks?: { onToggleMicrophone?: () => void; onToggleCam?: () => void; - onToggleTranscript?: () => void; + onToggleRecording?: () => void; onToggleChat?: () => void; onToggleScreenShare?: () => void; onLeaveMeeting?: () => void; diff --git a/src/components/who-is-online/index.test.ts b/src/components/who-is-online/index.test.ts index c7974a16..ba4c28a0 100644 --- a/src/components/who-is-online/index.test.ts +++ b/src/components/who-is-online/index.test.ts @@ -7,7 +7,6 @@ import { MOCK_ABLY_PARTICIPANT_DATA_1, } from '../../../__mocks__/participants.mock'; import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock'; -import { ROOM_STATE_MOCK } from '../../../__mocks__/roomState.mock'; import { RealtimeEvent, WhoIsOnlineEvent } from '../../common/types/events.types'; import { MeetingColorsHex } from '../../common/types/meeting-colors.types'; import { StoreType } from '../../common/types/stores.types'; diff --git a/src/core/index.ts b/src/core/index.ts index 77893f99..1fd25576 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -141,7 +141,7 @@ const init = async (apiKey: string, options: SuperVizSdkOptions): Promise = CreateSingleton(); export class GlobalStore { - public localParticipant = subject(null); + public localParticipant = subject({} as Participant); public participants = subject>({}); public group = subject(null); public isDomainWhitelisted = subject(true); diff --git a/src/services/stores/presence3D/index.ts b/src/services/stores/presence3D/index.ts index df8b0a9c..f3a71ae9 100644 --- a/src/services/stores/presence3D/index.ts +++ b/src/services/stores/presence3D/index.ts @@ -20,6 +20,7 @@ class Presence3DStore { public destroy() { this.hasJoined3D.destroy(); + this.participants.destroy(); } } diff --git a/src/services/stores/subject/index.test.ts b/src/services/stores/subject/index.test.ts index db09bfec..db8aaa73 100644 --- a/src/services/stores/subject/index.test.ts +++ b/src/services/stores/subject/index.test.ts @@ -82,7 +82,7 @@ describe('base subject for all stores', () => { const unsubscribe = jest.fn(); instance['subscribe'](testValues.id, callback); - instance['subscriptions'].get(testValues.id)!.unsubscribe = unsubscribe; + instance['subscriptions'].get(testValues.id)![0].unsubscribe = unsubscribe; instance['unsubscribe'](testValues.id); @@ -97,16 +97,14 @@ describe('base subject for all stores', () => { instance = subject(testValues.str1); const callback = jest.fn(); - const unsubscribe = jest.fn(); instance['subscribe'](testValues.id, callback); - instance['subscriptions'].get(testValues.id)!.unsubscribe = unsubscribe; instance['destroy'](); expect(instance['subscriptions'].size).toBe(0); expect(instance['subscriptions'].get(testValues.id)).toBeUndefined(); - expect(unsubscribe).toHaveBeenCalled(); + expect(instance.state).toBe(instance['firstState']); }); }); diff --git a/src/services/stores/subject/index.ts b/src/services/stores/subject/index.ts index a64a09be..b413acb3 100644 --- a/src/services/stores/subject/index.ts +++ b/src/services/stores/subject/index.ts @@ -5,11 +5,15 @@ import { PublicSubject } from '../common/types'; export class Subject { public state: T; + private firstState: T; + private subject: BehaviorSubject; - private subscriptions: Map = new Map(); + private subscriptions: Map = new Map(); constructor(state: T, subject: BehaviorSubject) { this.state = state; + this.firstState = state; + this.subject = subject.pipe( distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }), @@ -27,17 +31,34 @@ export class Subject { public subscribe = (subscriptionId: string | this, callback: (value: T) => void) => { const subscription = this.subject.subscribe(callback); - this.subscriptions.set(subscriptionId, subscription); + + if (this.subscriptions.has(subscriptionId)) { + this.subscriptions.get(subscriptionId).push(subscription); + return; + } + + this.subscriptions.set(subscriptionId, [subscription]); }; public unsubscribe(subscriptionId: string) { - this.subscriptions.get(subscriptionId)?.unsubscribe(); + this.subscriptions.get(subscriptionId)?.forEach((subscription) => subscription.unsubscribe()); this.subscriptions.delete(subscriptionId); } public destroy() { - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); this.subscriptions.clear(); + this.subject.complete(); + + this.restart(); + } + + private restart() { + this.state = this.firstState; + + this.subject = new BehaviorSubject(this.firstState).pipe( + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ) as BehaviorSubject; } public expose(): PublicSubject { diff --git a/src/services/stores/who-is-online/index.ts b/src/services/stores/who-is-online/index.ts index 3e547dc2..0132145a 100644 --- a/src/services/stores/who-is-online/index.ts +++ b/src/services/stores/who-is-online/index.ts @@ -42,8 +42,6 @@ export class WhoIsOnlineStore { this.everyoneFollowsMe.destroy(); this.privateMode.destroy(); this.following.destroy(); - - instance.value = null; } } diff --git a/src/services/video-conference-manager/index.test.ts b/src/services/video-conference-manager/index.test.ts index aab70326..bbf4f596 100644 --- a/src/services/video-conference-manager/index.test.ts +++ b/src/services/video-conference-manager/index.test.ts @@ -19,7 +19,7 @@ const createVideoConfrenceManager = (options?: VideoManagerOptions) => { const defaultOptions: VideoManagerOptions = { browserService: new BrowserService(), camerasPosition: CamerasPosition.RIGHT, - canUseTranscription: true, + canUseRecording: true, canShowAudienceList: true, canUseCams: true, canUseChat: true, @@ -284,7 +284,7 @@ describe('VideoConferenceManager', () => { const callbacks = { onToggleMicrophone: jest.fn(), onToggleCam: jest.fn(), - onToggleTranscript: jest.fn(), + onToggleRecording: jest.fn(), onToggleChat: jest.fn(), onToggleScreenShare: jest.fn(), onClickHangup: jest.fn(), @@ -304,7 +304,7 @@ describe('VideoConferenceManager', () => { JSON.stringify({ onToggleMicrophone: true, onToggleCam: true, - onToggleTranscript: true, + onToggleRecording: true, onToggleChat: true, onToggleScreenShare: true, onClickHangup: true, diff --git a/src/services/video-conference-manager/index.ts b/src/services/video-conference-manager/index.ts index e682ede2..b1d8216c 100644 --- a/src/services/video-conference-manager/index.ts +++ b/src/services/video-conference-manager/index.ts @@ -78,7 +78,7 @@ export default class VideoConfereceManager { canUseGather, canShowAudienceList, canUseDefaultToolbar, - canUseTranscription, + canUseRecording, browserService, offset, locales, @@ -116,7 +116,7 @@ export default class VideoConfereceManager { canUseGather, canUseScreenshare, canUseDefaultAvatars, - canUseTranscription, + canUseRecording, canShowAudienceList, camerasPosition: positions.camerasPosition ?? CamerasPosition.RIGHT, canUseDefaultToolbar, @@ -323,7 +323,7 @@ export default class VideoConfereceManager { const callbacks = { onToggleMicrophone: !!this.callbacks.onToggleMicrophone, onToggleCam: !!this.callbacks.onToggleCam, - onToggleTranscript: !!this.callbacks.onToggleTranscript, + onToggleRecording: !!this.callbacks.onToggleRecording, onToggleChat: !!this.callbacks.onToggleChat, onToggleScreenShare: !!this.callbacks.onToggleScreenShare, onClickHangup: !!this.callbacks.onClickHangup, diff --git a/src/services/video-conference-manager/types.ts b/src/services/video-conference-manager/types.ts index 33dff86a..6e3e97a3 100644 --- a/src/services/video-conference-manager/types.ts +++ b/src/services/video-conference-manager/types.ts @@ -9,7 +9,7 @@ export interface VideoManagerOptions { canUseChat: boolean; canUseCams: boolean; canShowAudienceList: boolean; - canUseTranscription: boolean; + canUseRecording: boolean; canUseScreenshare: boolean; canUseDefaultAvatars: boolean; canUseGather: boolean; @@ -36,7 +36,7 @@ export interface VideoManagerOptions { callbacks?: { onToggleMicrophone?: () => void; onToggleCam?: () => void; - onToggleTranscript?: () => void; + onToggleRecording?: () => void; onToggleChat?: () => void; onToggleScreenShare?: () => void; onClickHangup?: () => void; @@ -73,7 +73,7 @@ export interface FrameConfig { canUseCams: boolean; canUseScreenshare: boolean; canUseDefaultAvatars: boolean; - canUseTranscription: boolean; + canUseRecording: boolean; canUseFollow: boolean; canUseGoTo: boolean; canUseGather: boolean; diff --git a/yarn.lock b/yarn.lock index b8619724..8afc4b16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2481,10 +2481,10 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== -"@superviz/socket-client@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@superviz/socket-client/-/socket-client-1.4.0.tgz#374326131dd4a89f2d8f71e8be1305ed55311921" - integrity sha512-Ue66/5dxhGaPxqiFE3KWjusfp2FCBmT8vPJfq8FMD/ZCR6ac/tqnKqLrMtpz3oA6/51CQW9tfoSuqaXC+hrKWw== +"@superviz/socket-client@1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@superviz/socket-client/-/socket-client-1.5.1.tgz#db1d14f6fbd29ac5e8cf3241dcf8b80cc42e814e" + integrity sha512-T+9vbYsKDPuHHrtKWZHLqHJTQ9uL4rQm8xm9OqqIr2E+c6kXkSIZsW40BKK6r45BTX4FT2i8rIS31BxptG5PRQ== dependencies: "@reactivex/rxjs" "^6.6.7" debug "^4.3.4"