Skip to content

Commit

Permalink
Merge pull request #195 from SuperViz/feat/new-room-oackage-params
Browse files Browse the repository at this point in the history
Handling room events and IO connections
  • Loading branch information
carlossantos74 authored Jan 3, 2025
2 parents 94f1323 + b84dba5 commit 5c64f18
Show file tree
Hide file tree
Showing 11 changed files with 641 additions and 20 deletions.
55 changes: 49 additions & 6 deletions apps/playground/src/pages/superviz-room.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRoom } from '@superviz/room'
import { createRoom, type Room, ParticipantEvent, RoomEvent } from '@superviz/room'
import { v4 as generateId } from "uuid";

import { useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { getConfig } from "../config";

const SUPERVIZ_KEY = getConfig<string>("keys.superviz");
Expand All @@ -10,8 +10,9 @@ const SUPERVIZ_ROOM_PREFIX = getConfig<string>("roomPrefix");
const componentName = "new-room";

export function SuperVizRoom() {
const room = useRef<any>();
const room = useRef<Room | null>(null);
const loaded = useRef<boolean>(false);
const [subscribed, setSubscribed] = useState<boolean>(false);

const initializeSuperViz = useCallback(async () => {
const uuid = generateId();
Expand All @@ -32,15 +33,57 @@ export function SuperVizRoom() {
});

room.current = newRoom;
subscribeToEvents();
}, []);

useEffect(() => {
if (loaded.current) return;
loaded.current = true;


initializeSuperViz();
}, []);
}, [initializeSuperViz]);

const subscribeToEvents = () => {
if (!room.current) return;

Object.values(ParticipantEvent).forEach(event => {
room.current?.subscribe(event, (data) => {
console.log('New event from room, eventName:', event, 'data:', data);
})
});

Object.values(RoomEvent).forEach(event => {
room.current?.subscribe(event, (data) => {
console.log('New event from room, eventName:', event, 'data:', data);
})
});

setSubscribed(true);
}

const unsubscribeFromEvents = () => {
if (!room.current) return;

Object.values(ParticipantEvent).forEach(event => {
room.current?.unsubscribe(event);
});

Object.values(RoomEvent).forEach(event => {
room.current?.unsubscribe(event)
});

setSubscribed(false);
}

const leaveRoom = () => {
room.current?.leave();
}

return <></>
return (
<div className='w-full h-full flex items-center justify-center gap-2'>
<button onClick={leaveRoom}> Leave </button>
<button onClick={subscribeToEvents} disabled={subscribed}> Subscribe to Events </button>
<button onClick={unsubscribeFromEvents} disabled={!subscribed}> Unsubscribe from Events </button>
</div>
)
}
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@superviz/react-sdk",
"private": false,
"version": "1.15.0-lab.2",
"version": "1.15.0",
"type": "module",
"scripts": {
"watch": "./node_modules/typescript/bin/tsc && vite build --watch",
Expand Down
2 changes: 1 addition & 1 deletion packages/realtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superviz/realtime",
"version": "1.3.0-lab.3",
"version": "1.3.0",
"description": "SuperViz Real-Time",
"main": "./dist/node/index.cjs.js",
"module": "./dist/browser/index.js",
Expand Down
15 changes: 14 additions & 1 deletion packages/room/src/common/types/participant.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export type Participant = {
export type InitialParticipant = {
id: string
name: string
}

export type Participant = InitialParticipant & {
slot: Slot
activeComponents: string[]
}

export type Slot = {
index: number;
color: string;
textColor: string;
colorName: string;
timestamp: number;
}
184 changes: 184 additions & 0 deletions packages/room/src/core/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { Subject } from 'rxjs';

import { Logger } from '../common/utils/logger';
import { IOC } from '../services/io';
import { IOCState } from '../services/io/types';

import { ParticipantEvent, RoomParams } from './types';

import { Room } from './index';

jest.mock('../services/io', () => ({
IOC: jest.fn().mockImplementation(() => ({
stateSubject: new Subject(),
destroy: jest.fn(),
createRoom: jest.fn(() => ({
disconnect: jest.fn(),
presence: {
off: jest.fn(),
on: jest.fn(),
update: jest.fn(),
},
})),
})),
}));
jest.mock('../common/utils/logger');

describe('Room', () => {
let room: Room;
let params: RoomParams;

beforeEach(() => {
params = {
participant: { id: '123', name: 'Test Participant' },
};
room = new Room(params);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should create a room and initialize it', () => {
expect(IOC).toHaveBeenCalledWith(params.participant);
expect(Logger).toHaveBeenCalledWith('@superviz/room/room');
});

it('should leave the room and destroy the socket connection', () => {
room.leave();

expect(room['room'].disconnect).toHaveBeenCalled();
expect(room['io'].destroy).toHaveBeenCalled();
});

it('should remove all subscriptions and observers from the room when it\'s destroyed', () => {
room.subscribe('my-participant.joined', () => {});

room.leave();

expect(room['subscriptions'].size).toBe(0);
});

it('should subscribe to an event', () => {
const callback = jest.fn();
const event = 'participant.joined';

room.subscribe(event, callback);

expect(room['observers'].get(event)).toBeInstanceOf(Subject);
expect(room['subscriptions'].get(callback)).toBeDefined();
});

it('should unsubscribe from an event', () => {
const callback = jest.fn();
const event = 'participant.joined';

room.subscribe(event, callback);
room.unsubscribe(event, callback);

expect(room['subscriptions'].get(callback)).toBeUndefined();
});

it('should unsubscribe from all callbacks of an event', () => {
const event = 'participant.joined';

room.subscribe(event, jest.fn());
room.unsubscribe(event);

expect(room['observers'].get(event)).toBeUndefined();
});

it('should handle participant joined room event', () => {
const data = { id: '123' } as any;
const emitSpy = jest.spyOn(room as any, 'emit');
const expected = room['transfromSocketMesssageToParticipant'](data);

room['onParticipantJoinedRoom'](data);

expect(emitSpy).toHaveBeenCalledWith(ParticipantEvent.PARTICIPANT_JOINED, expected);
});

it('should handle local participant joined room event', () => {
const data = { id: '123' } as any;
const emitSpy = jest.spyOn(room as any, 'emit');
const updateSpy = jest.spyOn(room['room'].presence, 'update');
const emitExpected = room['transfromSocketMesssageToParticipant'](data);
const updateExpcted = room['createParticipant'](params.participant);

room['onLocalParticipantJoinedRoom'](data);

expect(updateSpy).toHaveBeenCalledWith(updateExpcted);
expect(emitSpy).toHaveBeenCalledWith(ParticipantEvent.MY_PARTICIPANT_JOINED, emitExpected);
});

it('should handle participant leaves room event', () => {
const data = { id: '123' } as any;
const emitSpy = jest.spyOn(room as any, 'emit');
const expected = room['transfromSocketMesssageToParticipant'](data);

room['onParticipantLeavesRoom'](data);

expect(emitSpy).toHaveBeenCalledWith(ParticipantEvent.PARTICIPANT_LEFT, expected);
});

it('should handle participant updates event', () => {
const data = { data: { id: '123' } } as any;
const emitSpy = jest.spyOn(room as any, 'emit');

room['onParticipantUpdates'](data);

expect(emitSpy).toHaveBeenCalledWith(ParticipantEvent.PARTICIPANT_UPDATED, data.data);
});

it('should handle local participant updates event', () => {
const data = { data: { id: '123' } } as any;
const emitSpy = jest.spyOn(room as any, 'emit');

room['onLocalParticipantUpdates'](data);

expect(emitSpy).toHaveBeenCalledWith(ParticipantEvent.MY_PARTICIPANT_UPDATED, data.data);
});

it('should handle the same account error', () => {
const emitSpy = jest.spyOn(room as any, 'emit');
const leaveSpy = jest.spyOn(room, 'leave');

room['onConnectionStateChange'](IOCState.SAME_ACCOUNT_ERROR);

expect(leaveSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(
'room.error',
{
code: 'same_account_error',
message: '[SuperViz] Room initialization failed: the user is already connected to the room. Please verify if the user is connected with the same account and try again.',
},
);
});

it('should handle the authentication error', () => {
const emitSpy = jest.spyOn(room as any, 'emit');
const leaveSpy = jest.spyOn(room, 'leave');

room['onConnectionStateChange'](IOCState.AUTH_ERROR);

expect(emitSpy).toHaveBeenCalledWith(
'room.error',
{
code: 'auth_error',
message: "[SuperViz] Room initialization failed: this website's domain is not whitelisted. If you are the developer, please add your domain in https://dashboard.superviz.com/developer",
},
);

expect(leaveSpy).toHaveBeenCalled();
expect(room['room'].disconnect).toHaveBeenCalled();
});

it('should update the room state', () => {
const state = IOCState.CONNECTED;
const emitSpy = jest.spyOn(room as any, 'emit');

room['onConnectionStateChange'](state);

expect(emitSpy).toHaveBeenCalledWith('room.update', { status: state });
});
});
Loading

0 comments on commit 5c64f18

Please sign in to comment.