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

Commit 07142e3

Browse files
Merge pull request #739 from SuperViz/feat/realtime-component-presence
feat: realtime component presence
2 parents a686521 + 98a97ca commit 07142e3

File tree

7 files changed

+137
-10
lines changed

7 files changed

+137
-10
lines changed

src/components/realtime/channel.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
RealtimeChannelSubscribe,
1515
Callback,
1616
} from './types';
17+
import { RealtimePresence } from './presence';
1718

1819
export class Channel extends Observable {
1920
private name: string;
@@ -26,6 +27,7 @@ export class Channel extends Observable {
2627
event: string;
2728
callback: (data: unknown) => void;
2829
}> = [];
30+
public participant: RealtimePresence;
2931

3032
constructor(
3133
name: string,
@@ -43,6 +45,7 @@ export class Channel extends Observable {
4345

4446
this.subscribeToRealtimeEvents();
4547
this.logger.log('started');
48+
this.participant = new RealtimePresence(this.channel);
4649
}
4750

4851
public async disconnect(): Promise<void> {

src/components/realtime/index.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RealtimeComponentState } from './types';
99

1010
import { Realtime } from '.';
1111
import { LIMITS_MOCK } from '../../../__mocks__/limits.mock';
12-
import { useGlobalStore } from '../../services/stores';
12+
import { StoreType } from '../../common/types/stores.types';
1313

1414
jest.mock('lodash/throttle', () => jest.fn((fn) => fn));
1515
jest.useFakeTimers();
@@ -23,8 +23,8 @@ describe('realtime component', () => {
2323
console.error = jest.fn();
2424
console.debug = jest.fn();
2525

26-
const { hasJoinedRoom } = useGlobalStore();
27-
hasJoinedRoom.value = true;
26+
const { hasJoinedRoom } = useStore(StoreType.GLOBAL);
27+
hasJoinedRoom.publish(true);
2828

2929
RealtimeComponentInstance = new Realtime();
3030
RealtimeComponentInstance.attach({

src/components/realtime/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { ComponentLifeCycleEvent } from '../../common/types/events.types';
22
import { Participant } from '../../common/types/participant.types';
33
import { StoreType } from '../../common/types/stores.types';
44
import { Logger } from '../../common/utils';
5+
import { useGlobalStore } from '../../services/stores';
56
import { BaseComponent } from '../base';
67
import { ComponentNames } from '../types';
78

89
import { Channel } from './channel';
10+
911
import {
1012
Callback,
1113
RealtimeChannelEvent,
@@ -44,6 +46,19 @@ export class Realtime extends BaseComponent {
4446
* @returns {Channel}
4547
*/
4648
public connect(name: string): Promise<Channel> {
49+
if (!this.channel) {
50+
return new Promise<Channel>((resolve) => {
51+
const { localParticipant } = useGlobalStore();
52+
53+
localParticipant.subscribe('connect-after-init', (participant) => {
54+
if (!participant.activeComponents.includes(ComponentNames.REALTIME)) return;
55+
56+
localParticipant.unsubscribe('connect-after-init');
57+
resolve(this.connect(name));
58+
});
59+
});
60+
}
61+
4762
let channel: Channel = this.channels.get(name);
4863
if (channel) return channel as unknown as Promise<Channel>;
4964

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { PresenceEvents, Room } from '../../lib/socket';
2+
3+
import { RealtimePresence } from './presence';
4+
import { MOCK_IO } from '../../../__mocks__/io.mock';
5+
6+
describe('realtime component', () => {
7+
let RealtimePresenceInstance: RealtimePresence;
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks();
11+
12+
const room = new MOCK_IO.Realtime('', '', '');
13+
RealtimePresenceInstance = new RealtimePresence(room.connect() as unknown as Room);
14+
});
15+
16+
afterEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
describe('Realtime Participant Presence', () => {
21+
test('should update presence', () => {
22+
const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'update' as any);
23+
const data = {
24+
id: '123',
25+
name: 'John Doe',
26+
};
27+
28+
RealtimePresenceInstance['update'](data);
29+
30+
expect(spy).toHaveBeenCalledWith(data);
31+
});
32+
33+
test('should subscribe to presence events', () => {
34+
const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'on' as any);
35+
const event = MOCK_IO.PresenceEvents.UPDATE;
36+
const callback = jest.fn();
37+
38+
RealtimePresenceInstance['subscribe'](event as PresenceEvents, callback);
39+
40+
expect(spy).toHaveBeenCalledWith(event, callback);
41+
});
42+
43+
test('should unsubscribe from presence events', () => {
44+
const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'off' as any);
45+
const event = MOCK_IO.PresenceEvents.UPDATE;
46+
47+
RealtimePresenceInstance['unsubscribe'](event as PresenceEvents);
48+
49+
expect(spy).toHaveBeenCalledWith(event);
50+
});
51+
52+
test('should get all presences', () => {
53+
const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'get' as any);
54+
RealtimePresenceInstance['getAll']();
55+
56+
expect(spy).toHaveBeenCalled();
57+
});
58+
});
59+
});

src/components/realtime/presence.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Logger } from '../../common/utils';
2+
import * as Socket from '../../lib/socket';
3+
import { PresenceEventsArg } from '../../lib/socket/common/types/event.types';
4+
5+
export class RealtimePresence {
6+
private logger: Logger;
7+
8+
constructor(private room: Socket.Room) {
9+
this.logger = new Logger('@superviz/sdk/realtime-presence');
10+
}
11+
12+
public update<T = any>(data: T) {
13+
this.logger.log('Realtime Presence @ update presence', data);
14+
this.room.presence.update(data);
15+
}
16+
17+
public subscribe<T = unknown>(event: PresenceEventsArg, callback: Socket.PresenceCallback<T>) {
18+
this.logger.log('Realtime Presence @ subscribe', event);
19+
this.room.presence.on(event, callback);
20+
}
21+
22+
public unsubscribe(event: PresenceEventsArg) {
23+
this.logger.log('Realtime Presence @ unsubscribe', event);
24+
this.room.presence.off(event);
25+
}
26+
27+
public getAll() {
28+
this.logger.log('Realtime Presence @ get all');
29+
let presences: Socket.PresenceEvent[] = [];
30+
this.room.presence.get(
31+
(data) => {
32+
presences = data;
33+
},
34+
(error) => {
35+
const message = `[SuperViz] ${error.name} - ${error.message}`;
36+
this.logger.log(error);
37+
console.error(message);
38+
},
39+
);
40+
return presences;
41+
}
42+
}

src/lib/socket/common/types/event.types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export enum PresenceEvents {
3434
UPDATE = 'presence.update',
3535
}
3636

37+
export type PresenceEventsArg = PresenceEvents | `${PresenceEvents}`;
38+
3739
/**
3840
* @enum InternalPresenceEvents
3941
* @description events that the server listens to in the presence module

src/lib/socket/presence/index.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Subject } from 'rxjs';
22
import type { Socket } from 'socket.io-client';
33

44
import { ErrorCallback } from '../common/types/callbacks.types';
5-
import { InternalPresenceEvents, PresenceEvents } from '../common/types/event.types';
5+
import {
6+
InternalPresenceEvents,
7+
PresenceEvents,
8+
PresenceEventsArg,
9+
} from '../common/types/event.types';
610
import type { Presence } from '../common/types/presence.types';
711

812
import { PresenceCallback, PresenceEvent, PresenceEventFromServer } from './types';
@@ -11,12 +15,12 @@ import { Logger } from '../../../common/utils';
1115
export class PresenceRoom {
1216
private logger: Logger;
1317
private presences: Set<PresenceEvent> = new Set();
14-
private observers: Map<PresenceEvents, Subject<PresenceEvent>> = new Map();
18+
private observers: Map<PresenceEventsArg, Subject<PresenceEvent>> = new Map();
1519

1620
constructor(private io: Socket, private presence: Presence, private roomId: string) {
1721
this.logger = new Logger('@superviz/sdk/socket-client/presence');
1822

19-
this.registerSubsjects();
23+
this.registerSubjects();
2024
this.subscribeToPresenceEvents();
2125
}
2226

@@ -90,11 +94,11 @@ export class PresenceRoom {
9094
}
9195

9296
/**
93-
* @function registerSubsjects
97+
* @function registerSubjects
9498
* @description Register the subjects for the presence events
9599
* @returns {void}
96100
*/
97-
private registerSubsjects(): void {
101+
private registerSubjects(): void {
98102
this.observers.set(PresenceEvents.JOINED_ROOM, new Subject());
99103
this.observers.set(PresenceEvents.LEAVE, new Subject());
100104
this.observers.set(PresenceEvents.UPDATE, new Subject());
@@ -108,7 +112,7 @@ export class PresenceRoom {
108112
* @returns {void}
109113
*/
110114
public on<T extends unknown>(
111-
event: PresenceEvents,
115+
event: PresenceEventsArg,
112116
callback: PresenceCallback<T>,
113117
error?: ErrorCallback,
114118
): void {
@@ -125,8 +129,10 @@ export class PresenceRoom {
125129
* @param callback - The callback to remove from the event
126130
* @returns {void}
127131
*/
128-
public off(event: PresenceEvents): void {
132+
public off(event: PresenceEventsArg): void {
129133
this.observers.get(event).unsubscribe();
134+
this.observers.delete(event);
135+
this.observers.set(event, new Subject());
130136
}
131137

132138
/**

0 commit comments

Comments
 (0)