diff --git a/src/components/realtime/channel.ts b/src/components/realtime/channel.ts index 2d9768ac..1939d930 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -14,6 +14,7 @@ import { RealtimeChannelSubscribe, Callback, } from './types'; +import { RealtimePresence } from './presence'; export class Channel extends Observable { private name: string; @@ -26,6 +27,7 @@ export class Channel extends Observable { event: string; callback: (data: unknown) => void; }> = []; + public participant: RealtimePresence; constructor( name: string, @@ -43,6 +45,7 @@ export class Channel extends Observable { this.subscribeToRealtimeEvents(); this.logger.log('started'); + this.participant = new RealtimePresence(this.channel); } public async disconnect(): Promise { diff --git a/src/components/realtime/index.test.ts b/src/components/realtime/index.test.ts index e25bf43a..6449e1a8 100644 --- a/src/components/realtime/index.test.ts +++ b/src/components/realtime/index.test.ts @@ -9,7 +9,7 @@ import { RealtimeComponentState } from './types'; import { Realtime } from '.'; import { LIMITS_MOCK } from '../../../__mocks__/limits.mock'; -import { useGlobalStore } from '../../services/stores'; +import { StoreType } from '../../common/types/stores.types'; jest.mock('lodash/throttle', () => jest.fn((fn) => fn)); jest.useFakeTimers(); @@ -23,8 +23,8 @@ describe('realtime component', () => { console.error = jest.fn(); console.debug = jest.fn(); - const { hasJoinedRoom } = useGlobalStore(); - hasJoinedRoom.value = true; + const { hasJoinedRoom } = useStore(StoreType.GLOBAL); + hasJoinedRoom.publish(true); RealtimeComponentInstance = new Realtime(); RealtimeComponentInstance.attach({ diff --git a/src/components/realtime/index.ts b/src/components/realtime/index.ts index 0e15b1d5..85a18493 100644 --- a/src/components/realtime/index.ts +++ b/src/components/realtime/index.ts @@ -2,10 +2,12 @@ import { ComponentLifeCycleEvent } from '../../common/types/events.types'; import { Participant } from '../../common/types/participant.types'; import { StoreType } from '../../common/types/stores.types'; import { Logger } from '../../common/utils'; +import { useGlobalStore } from '../../services/stores'; import { BaseComponent } from '../base'; import { ComponentNames } from '../types'; import { Channel } from './channel'; + import { Callback, RealtimeChannelEvent, @@ -44,6 +46,19 @@ export class Realtime extends BaseComponent { * @returns {Channel} */ public connect(name: string): Promise { + if (!this.channel) { + return new Promise((resolve) => { + const { localParticipant } = useGlobalStore(); + + localParticipant.subscribe('connect-after-init', (participant) => { + if (!participant.activeComponents.includes(ComponentNames.REALTIME)) return; + + localParticipant.unsubscribe('connect-after-init'); + resolve(this.connect(name)); + }); + }); + } + let channel: Channel = this.channels.get(name); if (channel) return channel as unknown as Promise; diff --git a/src/components/realtime/presence.test.ts b/src/components/realtime/presence.test.ts new file mode 100644 index 00000000..cd2d9197 --- /dev/null +++ b/src/components/realtime/presence.test.ts @@ -0,0 +1,59 @@ +import { PresenceEvents, Room } from '../../lib/socket'; + +import { RealtimePresence } from './presence'; +import { MOCK_IO } from '../../../__mocks__/io.mock'; + +describe('realtime component', () => { + let RealtimePresenceInstance: RealtimePresence; + + beforeEach(() => { + jest.clearAllMocks(); + + const room = new MOCK_IO.Realtime('', '', ''); + RealtimePresenceInstance = new RealtimePresence(room.connect() as unknown as Room); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Realtime Participant Presence', () => { + test('should update presence', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'update' as any); + const data = { + id: '123', + name: 'John Doe', + }; + + RealtimePresenceInstance['update'](data); + + expect(spy).toHaveBeenCalledWith(data); + }); + + test('should subscribe to presence events', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'on' as any); + const event = MOCK_IO.PresenceEvents.UPDATE; + const callback = jest.fn(); + + RealtimePresenceInstance['subscribe'](event as PresenceEvents, callback); + + expect(spy).toHaveBeenCalledWith(event, callback); + }); + + test('should unsubscribe from presence events', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'off' as any); + const event = MOCK_IO.PresenceEvents.UPDATE; + + RealtimePresenceInstance['unsubscribe'](event as PresenceEvents); + + expect(spy).toHaveBeenCalledWith(event); + }); + + test('should get all presences', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'get' as any); + RealtimePresenceInstance['getAll'](); + + expect(spy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/realtime/presence.ts b/src/components/realtime/presence.ts new file mode 100644 index 00000000..60f6d1a6 --- /dev/null +++ b/src/components/realtime/presence.ts @@ -0,0 +1,42 @@ +import { Logger } from '../../common/utils'; +import * as Socket from '../../lib/socket'; +import { PresenceEventsArg } from '../../lib/socket/common/types/event.types'; + +export class RealtimePresence { + private logger: Logger; + + constructor(private room: Socket.Room) { + this.logger = new Logger('@superviz/sdk/realtime-presence'); + } + + public update(data: T) { + this.logger.log('Realtime Presence @ update presence', data); + this.room.presence.update(data); + } + + public subscribe(event: PresenceEventsArg, callback: Socket.PresenceCallback) { + this.logger.log('Realtime Presence @ subscribe', event); + this.room.presence.on(event, callback); + } + + public unsubscribe(event: PresenceEventsArg) { + this.logger.log('Realtime Presence @ unsubscribe', event); + this.room.presence.off(event); + } + + public getAll() { + this.logger.log('Realtime Presence @ get all'); + let presences: Socket.PresenceEvent[] = []; + this.room.presence.get( + (data) => { + presences = data; + }, + (error) => { + const message = `[SuperViz] ${error.name} - ${error.message}`; + this.logger.log(error); + console.error(message); + }, + ); + return presences; + } +} diff --git a/src/lib/socket/common/types/event.types.ts b/src/lib/socket/common/types/event.types.ts index 96dbc206..2db2c012 100644 --- a/src/lib/socket/common/types/event.types.ts +++ b/src/lib/socket/common/types/event.types.ts @@ -34,6 +34,8 @@ export enum PresenceEvents { UPDATE = 'presence.update', } +export type PresenceEventsArg = PresenceEvents | `${PresenceEvents}`; + /** * @enum InternalPresenceEvents * @description events that the server listens to in the presence module diff --git a/src/lib/socket/presence/index.ts b/src/lib/socket/presence/index.ts index d633afcc..d82fc66f 100644 --- a/src/lib/socket/presence/index.ts +++ b/src/lib/socket/presence/index.ts @@ -2,7 +2,11 @@ import { Subject } from 'rxjs'; import type { Socket } from 'socket.io-client'; import { ErrorCallback } from '../common/types/callbacks.types'; -import { InternalPresenceEvents, PresenceEvents } from '../common/types/event.types'; +import { + InternalPresenceEvents, + PresenceEvents, + PresenceEventsArg, +} from '../common/types/event.types'; import type { Presence } from '../common/types/presence.types'; import { PresenceCallback, PresenceEvent, PresenceEventFromServer } from './types'; @@ -11,12 +15,12 @@ import { Logger } from '../../../common/utils'; export class PresenceRoom { private logger: Logger; private presences: Set = new Set(); - private observers: Map> = new Map(); + private observers: Map> = new Map(); constructor(private io: Socket, private presence: Presence, private roomId: string) { this.logger = new Logger('@superviz/sdk/socket-client/presence'); - this.registerSubsjects(); + this.registerSubjects(); this.subscribeToPresenceEvents(); } @@ -90,11 +94,11 @@ export class PresenceRoom { } /** - * @function registerSubsjects + * @function registerSubjects * @description Register the subjects for the presence events * @returns {void} */ - private registerSubsjects(): void { + private registerSubjects(): void { this.observers.set(PresenceEvents.JOINED_ROOM, new Subject()); this.observers.set(PresenceEvents.LEAVE, new Subject()); this.observers.set(PresenceEvents.UPDATE, new Subject()); @@ -108,7 +112,7 @@ export class PresenceRoom { * @returns {void} */ public on( - event: PresenceEvents, + event: PresenceEventsArg, callback: PresenceCallback, error?: ErrorCallback, ): void { @@ -125,8 +129,10 @@ export class PresenceRoom { * @param callback - The callback to remove from the event * @returns {void} */ - public off(event: PresenceEvents): void { + public off(event: PresenceEventsArg): void { this.observers.get(event).unsubscribe(); + this.observers.delete(event); + this.observers.set(event, new Subject()); } /**