diff --git a/src/common/types/participant.types.ts b/src/common/types/participant.types.ts index 05804f73..99404444 100644 --- a/src/common/types/participant.types.ts +++ b/src/common/types/participant.types.ts @@ -17,7 +17,7 @@ export type Slot = { export interface Participant { id: string; name?: string; - type?: ParticipantType; + type?: ParticipantType | `${ParticipantType}`; slot?: Slot; avatar?: Avatar; isHost?: boolean; diff --git a/src/common/types/stores.types.ts b/src/common/types/stores.types.ts index 92d17b30..e78fc22a 100644 --- a/src/common/types/stores.types.ts +++ b/src/common/types/stores.types.ts @@ -1,4 +1,5 @@ import { useGlobalStore } from '../../services/stores'; +import { useCoreStore } from '../../services/stores/core'; import { usePresence3DStore } from '../../services/stores/presence3D'; import { useVideoStore } from '../../services/stores/video'; import { useWhoIsOnlineStore } from '../../services/stores/who-is-online/index'; @@ -9,6 +10,7 @@ export enum StoreType { WHO_IS_ONLINE = 'who-is-online-store', VIDEO = 'video-store', PRESENCE_3D = 'presence-3d-store', + CORE = 'core-store', } type Subject any, K extends keyof ReturnType> = ReturnType[K]; @@ -24,13 +26,13 @@ type IncompleteStoreApi any> = { type StoreApi any> = IncompleteStoreApi & { destroy(): void; - restart(): void; }; type GlobalStore = StoreType.GLOBAL | `${StoreType.GLOBAL}`; type WhoIsOnlineStore = StoreType.WHO_IS_ONLINE | 'who-is-online-store'; type VideoStore = StoreType.VIDEO | 'video-store'; type Presence3DStore = StoreType.PRESENCE_3D | 'presence-3d-store'; +type CoreStore = StoreType.CORE | 'core-store'; export type Store = T extends GlobalStore ? StoreApi @@ -40,5 +42,7 @@ export type Store = T extends GlobalStore ? StoreApi : T extends Presence3DStore ? StoreApi + : T extends CoreStore + ? StoreApi : never; export type StoresTypes = typeof StoreType; diff --git a/src/common/utils/use-store.ts b/src/common/utils/use-store.ts index fd8ef63e..3396d4d8 100644 --- a/src/common/utils/use-store.ts +++ b/src/common/utils/use-store.ts @@ -1,4 +1,5 @@ import { PublicSubject } from '../../services/stores/common/types'; +import { useCoreStore } from '../../services/stores/core'; import { useGlobalStore } from '../../services/stores/global'; import { usePresence3DStore } from '../../services/stores/presence3D'; import { useVideoStore } from '../../services/stores/video'; @@ -10,6 +11,7 @@ const stores = { [StoreType.WHO_IS_ONLINE]: useWhoIsOnlineStore, [StoreType.VIDEO]: useVideoStore, [StoreType.PRESENCE_3D]: usePresence3DStore, + [StoreType.CORE]: useCoreStore, }; /** diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index 1e51df9c..345d3986 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -520,7 +520,7 @@ describe('VideoConference', () => { VideoConferenceInstance['onParticipantLeft'](MOCK_LOCAL_PARTICIPANT); expect(VideoConferenceInstance['publish']).toHaveBeenCalledWith( MeetingEvent.MY_PARTICIPANT_LEFT, - MOCK_LOCAL_PARTICIPANT, + { ...MOCK_LOCAL_PARTICIPANT, type: ParticipantType.HOST }, ); }); diff --git a/src/components/video/index.ts b/src/components/video/index.ts index e93241a9..76943b1e 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -38,6 +38,7 @@ import { ComponentNames } from '../types'; import { ParticipantToFrame, VideoComponentOptions } from './types'; import { MEETING_COLORS } from '../../common/types/meeting-colors.types'; +import { coreBridge } from '../../services/core-bridge'; const KICK_PARTICIPANTS_TIME = 1000 * 60; let KICK_PARTICIPANTS_TIMEOUT: ReturnType | null = null; @@ -331,6 +332,7 @@ export class VideoConference extends BaseComponent { this.localParticipant = { ...this.localParticipant, ...participant, + type: this.params.userType, }; }); @@ -542,13 +544,13 @@ export class VideoConference extends BaseComponent { joinedMeeting: true, }); - localParticipant.publish({ + coreBridge.updateLocalParticipant({ ...localParticipant.value, avatar: participant.avatar, name: participant.name, }); - participants.publish({ + coreBridge.updateParticipantsList({ ...participants.value, [participant.id]: { ...participants.value[participant.id], @@ -560,12 +562,12 @@ export class VideoConference extends BaseComponent { return; } - localParticipant.publish({ + coreBridge.updateLocalParticipant({ ...localParticipant.value, name: newParticipantName, }); - participants.publish({ + coreBridge.updateParticipantsList({ ...participants.value, [participant.id]: { ...participants.value[participant.id], diff --git a/src/components/video/types.ts b/src/components/video/types.ts index 040407ab..81283986 100644 --- a/src/components/video/types.ts +++ b/src/components/video/types.ts @@ -54,6 +54,6 @@ export type ParticipantToFrame = { name: string; isHost: boolean; avatar?: Avatar; - type: ParticipantType; + type: ParticipantType | `${ParticipantType}`; slot: Slot; }; diff --git a/src/core/launcher/index.ts b/src/core/launcher/index.ts index 95ee1099..486e24cd 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -41,25 +41,33 @@ export class Launcher extends Observable implements DefaultLauncher { super(); this.logger = new Logger('@superviz/sdk/launcher'); - const { localParticipant, group, isDomainWhitelisted } = this.useStore(StoreType.GLOBAL); - - localParticipant.publish({ ...participant }); + const { + localParticipant: globalParticipant, + group, + isDomainWhitelisted, + } = this.useStore(StoreType.GLOBAL); + const { localParticipant, participants } = this.useStore(StoreType.CORE); + + globalParticipant.publish({ ...participant }); isDomainWhitelisted.subscribe(this.onAuthentication); - localParticipant.subscribe(this.onLocalParticipantUpdateOnStore); + globalParticipant.subscribe(this.onLocalParticipantUpdateOnStore); + + localParticipant.subscribe(this.onLocalParticipantUpdateOnCore); + participants.subscribe(this.onParticipantsListUpdateOnCore); group.publish(participantGroup); - this.ioc = new IOC(localParticipant.value); + this.ioc = new IOC(globalParticipant.value); this.room = this.ioc.createRoom('launcher', 'unlimited'); // Assign a slot to the participant this.slotService = new SlotService(this.room, this.useStore); - localParticipant.publish({ - ...localParticipant.value, + globalParticipant.publish({ + ...globalParticipant.value, slot: this.slotService.slot, activeComponents: [], }); - this.participant = localParticipant.value; + this.participant = globalParticipant.value; // internal events without realtime this.eventBus = new EventBus(); @@ -272,6 +280,16 @@ export class Launcher extends Observable implements DefaultLauncher { this.activeComponents = participant.activeComponents || []; }; + private onLocalParticipantUpdateOnCore = (participant: Participant): void => { + if (!this.room) return; + this.room.presence.update(participant); + }; + + private onParticipantsListUpdateOnCore = (list: Record): void => { + const { participants } = this.useStore(StoreType.GLOBAL); + participants.publish(list); + }; + private onSameAccount = (): void => { this.publish(ParticipantEvent.SAME_ACCOUNT_ERROR); this.destroy(); diff --git a/src/services/core-bridge/index.test.ts b/src/services/core-bridge/index.test.ts new file mode 100644 index 00000000..8ad74dce --- /dev/null +++ b/src/services/core-bridge/index.test.ts @@ -0,0 +1,40 @@ +import { coreBridge } from '.'; + +const localParticipantSpy = jest.fn(); +const participantsSpy = jest.fn(); +jest.mock('../../common/utils/use-store', () => ({ + useStore: () => ({ + localParticipant: { + publish: localParticipantSpy, + }, + participants: { + publish: participantsSpy, + }, + }), +})); + +describe('coreBridge', () => { + describe('updateLocalParticipant', () => { + test('should log "updateLocalParticipant" and call localParticipant.publish', () => { + const data = { id: '1' }; + const logSpy = jest.spyOn(coreBridge['logger'], 'log'); + + coreBridge.updateLocalParticipant(data); + + expect(logSpy).toHaveBeenCalledWith('updateLocalParticipant', data); + expect(localParticipantSpy).toHaveBeenCalledWith(data); + }); + }); + + describe('updateParticipantsList', () => { + test('should log "updateParticipantsList" and call participants.publish', () => { + const data = { 1: { id: '1' } }; + const logSpy = jest.spyOn(coreBridge['logger'], 'log'); + + coreBridge.updateParticipantsList(data); + + expect(logSpy).toHaveBeenCalledWith('updateParticipantsList', data); + expect(participantsSpy).toHaveBeenCalledWith(data); + }); + }); +}); diff --git a/src/services/core-bridge/index.ts b/src/services/core-bridge/index.ts new file mode 100644 index 00000000..fcf9367f --- /dev/null +++ b/src/services/core-bridge/index.ts @@ -0,0 +1,26 @@ +import { Participant } from '../../common/types/participant.types'; +import { StoreType } from '../../common/types/stores.types'; +import { Logger } from '../../common/utils'; +import { useStore } from '../../common/utils/use-store'; + +class CoreBridge { + private logger: Logger; + constructor() { + this.logger = new Logger('@superviz/sdk/core-bridge'); + this.logger.log('CoreBridge initialized'); + } + + public updateLocalParticipant(data: Participant) { + this.logger.log('updateLocalParticipant', data); + const { localParticipant } = useStore(StoreType.CORE); + localParticipant.publish(data); + } + + public updateParticipantsList(data: Record) { + this.logger.log('updateParticipantsList', data); + const { participants } = useStore(StoreType.CORE); + participants.publish(data); + } +} + +export const coreBridge = new CoreBridge(); diff --git a/src/services/presence-3d-manager/index.ts b/src/services/presence-3d-manager/index.ts index bf04814e..7fea1aa1 100644 --- a/src/services/presence-3d-manager/index.ts +++ b/src/services/presence-3d-manager/index.ts @@ -142,7 +142,7 @@ export class Presence3DManager { private onJoinedPresence = (event: PresenceEvent): void => { if (event.id !== this.localParticipant.id) return; - this.logger.log('participant joined 3D room', event.id); + this.logger.log('participant joined 3D room', event.id, this.localParticipant); this.onLocalParticipantJoined(this.localParticipant); }; diff --git a/src/services/stores/core/index.ts b/src/services/stores/core/index.ts new file mode 100644 index 00000000..fd807a4f --- /dev/null +++ b/src/services/stores/core/index.ts @@ -0,0 +1,38 @@ +import { Participant } from '../../../common/types/participant.types'; +import { Singleton } from '../common/types'; +import { CreateSingleton } from '../common/utils'; +import subject from '../subject'; + +const instance: Singleton = CreateSingleton(); + +class CoreStore { + public localParticipant = subject({} as Participant); + public participants = subject>({}); + + constructor() { + if (instance.value) { + throw new Error('CoreStore is a singleton. There can only be one instance of it.'); + } + + instance.value = this; + } + + public destroy() { + this.localParticipant.destroy(); + this.participants.destroy(); + } +} + +const store = new CoreStore(); +const destroy = store.destroy.bind(store); + +const localParticipant = store.localParticipant.expose(); +const participants = store.participants.expose(); + +export function useCoreStore() { + return { + localParticipant, + participants, + destroy, + }; +}