Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

feat: realtime component presence #739

Merged
merged 9 commits into from
Aug 12, 2024
3 changes: 3 additions & 0 deletions src/components/realtime/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RealtimeChannelSubscribe,
Callback,
} from './types';
import { RealtimePresence } from './presence';

export class Channel extends Observable {
private name: string;
Expand All @@ -26,6 +27,7 @@ export class Channel extends Observable {
event: string;
callback: (data: unknown) => void;
}> = [];
public participant: RealtimePresence;

constructor(
name: string,
Expand All @@ -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<void> {
Expand Down
6 changes: 3 additions & 3 deletions src/components/realtime/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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({
Expand Down
15 changes: 15 additions & 0 deletions src/components/realtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -44,6 +46,19 @@ export class Realtime extends BaseComponent {
* @returns {Channel}
*/
public connect(name: string): Promise<Channel> {
if (!this.channel) {
return new Promise<Channel>((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<Channel>;

Expand Down
59 changes: 59 additions & 0 deletions src/components/realtime/presence.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
42 changes: 42 additions & 0 deletions src/components/realtime/presence.ts
Original file line number Diff line number Diff line change
@@ -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<T = any>(data: T) {
this.logger.log('Realtime Presence @ update presence', data);
this.room.presence.update(data);
}

public subscribe<T = unknown>(event: PresenceEventsArg, callback: Socket.PresenceCallback<T>) {
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;
}
}
2 changes: 2 additions & 0 deletions src/lib/socket/common/types/event.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions src/lib/socket/presence/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -11,12 +15,12 @@ import { Logger } from '../../../common/utils';
export class PresenceRoom {
private logger: Logger;
private presences: Set<PresenceEvent> = new Set();
private observers: Map<PresenceEvents, Subject<PresenceEvent>> = new Map();
private observers: Map<PresenceEventsArg, Subject<PresenceEvent>> = 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();
}

Expand Down Expand Up @@ -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());
Expand All @@ -108,7 +112,7 @@ export class PresenceRoom {
* @returns {void}
*/
public on<T extends unknown>(
event: PresenceEvents,
event: PresenceEventsArg,
callback: PresenceCallback<T>,
error?: ErrorCallback,
): void {
Expand All @@ -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());
}

/**
Expand Down
Loading