From 63be3b28d5b74a098bc544bb39e80cb096ed9a52 Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Fri, 24 May 2024 07:28:21 -0300 Subject: [PATCH 1/8] fix: properly unsubscribe from subject that was subscribed to twice --- src/common/types/stores.types.ts | 5 ++-- src/components/video/index.test.ts | 8 +++--- src/components/who-is-online/index.test.ts | 1 - src/core/launcher/index.test.ts | 1 - src/services/stores/presence3D/index.ts | 1 + src/services/stores/subject/index.test.ts | 6 ++--- src/services/stores/subject/index.ts | 29 +++++++++++++++++++--- src/services/stores/who-is-online/index.ts | 2 -- 8 files changed, 35 insertions(+), 18 deletions(-) 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/video/index.test.ts b/src/components/video/index.test.ts index f27b0c8d..09af54b4 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); }); 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/launcher/index.test.ts b/src/core/launcher/index.test.ts index e726c441..cdf80056 100644 --- a/src/core/launcher/index.test.ts +++ b/src/core/launcher/index.test.ts @@ -13,7 +13,6 @@ import { DefaultAttachComponentOptions } from '../../components/base/types'; import { ComponentNames } from '../../components/types'; import { IOC } from '../../services/io'; import LimitsService from '../../services/limits'; -import { AblyParticipant } from '../../services/realtime/ably/types'; import { useGlobalStore } from '../../services/stores'; import { LauncherFacade, LauncherOptions } from './types'; 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; } } From d2752c9afe1df7ab410f44d770cd4502a2efe2e2 Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Mon, 27 May 2024 10:46:12 -0300 Subject: [PATCH 2/8] feat: add connectionId to RealtimeMessage type --- src/components/realtime/channel.ts | 1 + src/components/realtime/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/realtime/channel.ts b/src/components/realtime/channel.ts index 76ac0704..038527fd 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -172,6 +172,7 @@ 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, timestamp: event.timestamp, diff --git a/src/components/realtime/types.ts b/src/components/realtime/types.ts index 7b88dadf..d0ce95a0 100644 --- a/src/components/realtime/types.ts +++ b/src/components/realtime/types.ts @@ -24,6 +24,7 @@ export type RealtimeData = { export type RealtimeMessage = { name: string; + connectionId: string; participantId: string; data: unknown; timestamp: number; From ee8286b2707f1eb93b25494d755d04b78d2bac2b Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Tue, 28 May 2024 12:04:03 -0300 Subject: [PATCH 3/8] feat: change callback and flag to reference recording instead of transcription --- src/common/types/events.types.ts | 2 +- src/components/video/index.test.ts | 4 ++-- src/components/video/index.ts | 12 ++++++------ src/components/video/types.ts | 4 ++-- src/services/video-conference-manager/index.test.ts | 6 +++--- src/services/video-conference-manager/index.ts | 6 +++--- src/services/video-conference-manager/types.ts | 6 +++--- 7 files changed, 20 insertions(+), 20 deletions(-) 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/components/video/index.test.ts b/src/components/video/index.test.ts index 09af54b4..86ddae59 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -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..a1ddf1cc 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 === false, canShowAudienceList: this.params?.showAudienceList ?? true, canUseChat: !this.params?.chatOff, canUseCams: !this.params?.camsOff, 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/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; From d9520c7c0eac7b42b673b22edfeed7826bbdd514 Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Wed, 29 May 2024 08:26:05 -0300 Subject: [PATCH 4/8] fix: require enableRecording to be truthy to use recording instead of false --- src/components/video/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/video/index.ts b/src/components/video/index.ts index a1ddf1cc..b7dd610a 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -178,7 +178,7 @@ export class VideoConference extends BaseComponent { private startVideo = (): void => { this.videoConfig = { language: this.params?.language, - canUseRecording: this.params?.enableRecording === false, + canUseRecording: !!this.params?.enableRecording, canShowAudienceList: this.params?.showAudienceList ?? true, canUseChat: !this.params?.chatOff, canUseCams: !this.params?.camsOff, From a845cd182e562a4bb4a69a0a960201279cc316f1 Mon Sep 17 00:00:00 2001 From: Carlos Santos Date: Sun, 2 Jun 2024 11:20:19 -0300 Subject: [PATCH 5/8] feat: recieves messages sended by http method --- package.json | 2 +- src/components/realtime/channel.ts | 2 +- src/components/realtime/types.ts | 2 +- src/core/index.ts | 2 +- yarn.lock | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) 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/components/realtime/channel.ts b/src/components/realtime/channel.ts index 038527fd..a60f88d7 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -133,7 +133,7 @@ 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, } as RealtimeMessage); diff --git a/src/components/realtime/types.ts b/src/components/realtime/types.ts index d0ce95a0..e755ff6f 100644 --- a/src/components/realtime/types.ts +++ b/src/components/realtime/types.ts @@ -25,7 +25,7 @@ export type RealtimeData = { export type RealtimeMessage = { name: string; connectionId: string; - participantId: string; + participantId: string | null; data: unknown; timestamp: number; }; 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 Date: Sun, 2 Jun 2024 11:31:07 -0300 Subject: [PATCH 6/8] feat: send connectionId to clients into realtime message --- src/components/realtime/channel.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/realtime/channel.ts b/src/components/realtime/channel.ts index a60f88d7..07e05baf 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -136,6 +136,7 @@ export class Channel extends Observable { participantId: event?.presence?.id || null, name: event.data.name, timestamp: event.timestamp, + connectionId: event.connectionId, } as RealtimeMessage); }); } @@ -174,9 +175,9 @@ export class Channel extends Observable { 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; }, From bd8d7ccdd1b91eda5571440bba4805d650e24578 Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Fri, 7 Jun 2024 07:16:05 -0300 Subject: [PATCH 7/8] fix: unsubscribe from video eventos before destroying video manager --- src/components/video/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/video/index.ts b/src/components/video/index.ts index b7dd610a..f59a5d85 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -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; From 0fbfc8b4db6738122646f9ceec29f11bc04573a7 Mon Sep 17 00:00:00 2001 From: Ian Silva Date: Fri, 7 Jun 2024 07:16:47 -0300 Subject: [PATCH 8/8] fix: do not publish participant as undefined before destroying store --- src/core/launcher/index.ts | 4 +--- src/services/stores/global/index.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/launcher/index.ts b/src/core/launcher/index.ts index e052c7a9..d13e4d57 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -174,11 +174,9 @@ export class Launcher extends Observable implements DefaultLauncher { this.removeComponent(component); }); - const { localParticipant } = useStore(StoreType.GLOBAL); - this.activeComponents = []; this.activeComponentsInstances = []; - localParticipant.publish(undefined); + useGlobalStore().destroy(); this.eventBus.destroy(); diff --git a/src/services/stores/global/index.ts b/src/services/stores/global/index.ts index 39f04ff8..ef167d26 100644 --- a/src/services/stores/global/index.ts +++ b/src/services/stores/global/index.ts @@ -7,7 +7,7 @@ import subject from '../subject'; const instance: Singleton = CreateSingleton(); export class GlobalStore { - public localParticipant = subject(null); + public localParticipant = subject({} as Participant); public participants = subject>({}); public group = subject(null); public isDomainWhitelisted = subject(true);