Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: exports to the CDN and make sure that can't create to more than one room at a time. #197

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions apps/playground/src/pages/superviz-room.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRoom, type Room, ParticipantEvent, RoomEvent, Participant } from '@superviz/room'
import { v4 as generateId } from "uuid";

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

const SUPERVIZ_KEY = getConfig<string>("keys.superviz");
Expand All @@ -11,7 +11,6 @@ const componentName = "new-room";

export function SuperVizRoom() {
const room = useRef<Room | null>(null);
const loaded = useRef<boolean>(false);
const [subscribed, setSubscribed] = useState<boolean>(false);
const [participants, setParticipants] = useState<Participant[]>([]);
const [roomState, setRoomState] = useState<string>("Not connected");
Expand Down Expand Up @@ -41,13 +40,6 @@ export function SuperVizRoom() {
subscribeToEvents();
}, []);

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

initializeSuperViz();
}, [initializeSuperViz]);

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

Expand Down Expand Up @@ -95,7 +87,7 @@ export function SuperVizRoom() {
setParticipants(participants);
console.log('Participants:', participants);
});
}
}

return (
<div className='w-full h-full flex justify-between gap-2 p-10 overflow-hidden'>
Expand All @@ -104,11 +96,17 @@ export function SuperVizRoom() {
<h2>Room State: {roomState}</h2>
<h2>Observer State: {observerState}</h2>
</div>
<button
className='px-4 py-2 bg-green-500 text-white rounded hover:bg-green-700'
onClick={initializeSuperViz}
>
Initialize Room
</button>
<button
onClick={leaveRoom}
className='px-4 py-2 bg-red-500 text-white rounded hover:bg-red-700'
>
Leave
Leave Room
</button>
<button
onClick={subscribeToEvents}
Expand Down
1 change: 1 addition & 0 deletions packages/room/src/common/types/participant.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type InitialParticipant = {
id: string
name: string
email?: string
}

export type Participant = InitialParticipant & {
Expand Down
1 change: 1 addition & 0 deletions packages/room/src/core/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe('Room', () => {

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

it('should create a room and initialize it', () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/room/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class Room {
this.io.destroy();

this.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
subscription?.unsubscribe();
});

this.observers.forEach((observer) => {
Expand All @@ -54,6 +54,10 @@ export class Room {
this.subscriptions.clear();
this.observers.clear();
this.participants.clear();

if (typeof window !== 'undefined') {
delete window.SUPERVIZ_ROOM;
}
}

/**
Expand Down
140 changes: 134 additions & 6 deletions packages/room/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Room } from './core';
import { ApiService } from './services/api';
import config from './services/config';

import { createRoom } from '.';
Expand All @@ -8,9 +9,20 @@ jest.mock('./services/api', () => ({
validateApiKey: jest.fn(() => Promise.resolve(true)),
fetchWaterMark: jest.fn(() => Promise.resolve({})),
fetchLimits: jest.fn(() => Promise.resolve({})),
createParticipant: jest.fn(() => Promise.resolve()),
fetchParticipant: jest.fn(() => Promise.resolve(null)),
},
}));

let room: Room | null = null;

afterEach(() => {
if (room) {
room.leave();
room = null;
}
});

describe('createRoom', () => {
test('creates a room with valid params', async () => {
const params = {
Expand All @@ -26,7 +38,7 @@ describe('createRoom', () => {
},
};

const room = await createRoom(params);
room = await createRoom(params);

expect(room).toBeInstanceOf(Room);
});
Expand Down Expand Up @@ -135,7 +147,7 @@ describe('createRoom', () => {
},
};

await createRoom(params);
room = await createRoom(params);

expect(config.get('apiKey')).toBe('abc123');
expect(config.get('roomId')).toBe('abc123');
Expand All @@ -157,7 +169,7 @@ describe('createRoom', () => {
environment: 'dev' as 'dev',
};

await createRoom(paramsWithOptionalFields);
room = await createRoom(paramsWithOptionalFields);

expect(config.get('apiKey')).toBe('abc123');
expect(config.get('roomId')).toBe('abc123');
Expand All @@ -179,11 +191,11 @@ describe('createRoom', () => {
},
};

await createRoom(params);
room = await createRoom(params);

expect(config.get('apiUrl')).toBe('https://api.superviz.com');

const paramsWithProdEnvironment = {
const paramsWithDevEnvironment = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
Expand All @@ -197,8 +209,124 @@ describe('createRoom', () => {
environment: 'dev' as 'dev',
};

await createRoom(paramsWithProdEnvironment);
room = await createRoom(paramsWithDevEnvironment);

expect(config.get('apiUrl')).toBe('https://dev.nodeapi.superviz.com');
});

test('warn if a room already exists in the window object', async () => {
const params = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
id: 'abc123',
name: 'John Doe',
},
group: {
id: 'abc123',
name: 'Group',
},
environment: 'dev' as 'dev',
};

room = await createRoom(params);
const roomPromise = createRoom(params);

await expect(roomPromise).resolves.toBe(room);
});

test('should return a new room instance if leave and initialize again', async () => {
const params = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
id: 'abc123',
name: 'John Doe',
},
group: {
id: 'abc123',
name: 'Group',
},
environment: 'dev' as 'dev',
};

room = await createRoom(params);
room.leave();

const newRoom = await createRoom(params);

expect(newRoom).not.toBe(room);
});

test('should throw an error if the participant name is missing and the participant does not exist in the API', async () => {
const params = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
id: 'abc123',
},
group: {
id: 'abc123',
name: 'Group',
},
environment: 'dev' as 'dev',
};

const createRoomPromise = createRoom(params);

await expect(createRoomPromise).rejects.toThrow(
'[SuperViz | Room] - Participant does not exist, create the user in the API or add the name in the initialization to initialize the SuperViz room.',
);
});

test('should ignore the participant name if the participant exists in the API', async () => {
const params = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
id: 'abc123',
},
group: {
id: 'abc123',
name: 'Group',
},
environment: 'dev' as 'dev',
};

jest.spyOn(ApiService, 'fetchParticipant').mockResolvedValueOnce({
id: 'abc123',
name: 'John Doe',
email: '[email protected]',
});

room = await createRoom(params);

expect(room).toBeInstanceOf(Room);
});

test('should create the participant if the participant does not exist in the API', async () => {
const params = {
developerToken: 'abc123',
roomId: 'abc123',
participant: {
id: 'abc123',
name: 'John Doe',
},
group: {
id: 'abc123',
name: 'Group',
},
environment: 'dev' as 'dev',
};

jest.spyOn(ApiService, 'fetchParticipant').mockResolvedValueOnce(null);

room = await createRoom(params);

expect(ApiService.createParticipant).toHaveBeenCalledWith({
participantId: 'abc123',
name: 'John Doe',
email: undefined,
});
});
});
67 changes: 65 additions & 2 deletions packages/room/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import debug from 'debug';
import { z } from 'zod';

import { Participant } from './common/types/participant.types';
import { InitialParticipant, Participant } from './common/types/participant.types';
import { Room } from './core';
import { Callback, ParticipantEvent, RoomEvent } from './core/types';
import { ApiService } from './services/api';
Expand Down Expand Up @@ -57,6 +57,38 @@ async function setUpEnvironment({
config.set('waterMark', waterMark);
}

/**
* Sets up a participant by fetching their details from the API or creating
a new participant if they do not exist.
*
* @param {InitialParticipant} participant - The initial participant data.
* @returns {Promise<InitialParticipant>} - A promise that resolves to the participant data.
* @throws {Error} - Throws an error if the participant does not exist and no name is provided.
*/
async function setUpParticipant(participant: InitialParticipant): Promise<InitialParticipant> {
const apiParticipant = await ApiService.fetchParticipant(participant.id).catch(() => null);

if (!apiParticipant && !participant.name) {
throw new Error(
'[SuperViz | Room] - Participant does not exist, create the user in the API or add the name in the initialization to initialize the SuperViz room.',
);
}

if (!apiParticipant) {
await ApiService.createParticipant({
participantId: participant.id,
name: participant?.name,
email: participant?.email,
});
}

return {
id: participant.id,
name: participant.name ?? apiParticipant?.name,
email: participant.email ?? apiParticipant?.email,
};
}

/**
* @description Creates a new room with the given parameters.
* @param {InitializeRoomParams} params - The parameters required to initialize the room.
Expand All @@ -70,8 +102,29 @@ export async function createRoom(params: InitializeRoomParams): Promise<Room> {
const { participant } = InitializeRoomSchema.parse(params);

await setUpEnvironment(params);
await setUpParticipant(participant as InitialParticipant);

return new Room({ participant: participant as Participant });
if (typeof window !== 'undefined' && window.SUPERVIZ_ROOM) {
console.warn(`[SuperViz | Room] An existing room instance was found in the window object.
To prevent conflicts, please call the 'leave' method on the existing room before creating a new one.
Returning the previously created room instance.
`);

return window.SUPERVIZ_ROOM;
}

const room = new Room({
participant: {
id: participant.id,
name: participant.name,
},
});

if (typeof window !== 'undefined') {
window.SUPERVIZ_ROOM = room;
}

return room;
} catch (error) {
if (error instanceof z.ZodError) {
const message = error.errors.map((err) => err.message).join('\n');
Expand All @@ -83,6 +136,8 @@ export async function createRoom(params: InitializeRoomParams): Promise<Room> {
}
}

// Exports

export type {
Room,
Participant,
Expand All @@ -93,3 +148,11 @@ export {
ParticipantEvent,
Callback,
};

if (typeof window !== 'undefined') {
window.SuperVizRoom = {
createRoom,
RoomEvent,
ParticipantEvent,
};
}
Loading
Loading