Skip to content
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
23 changes: 17 additions & 6 deletions deep-sea-stories/packages/backend/src/controllers/peers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { RoomId } from '@fishjam-cloud/js-server-sdk';
import { TRPCError } from '@trpc/server';
import { GameRoomFullError } from '../domain/errors.js';
import { GameRoom } from '../game/room.js';
import { createPeerInputSchema } from '../schemas.js';
import { roomService } from '../service/room.js';
Expand All @@ -18,10 +20,19 @@ export const createPeer = publicProcedure
roomService.setGameRoom(room.id, gameRoom);
}

const { peer, peerToken } = await gameRoom.addPlayer(input.name);

return {
peer,
token: peerToken,
};
try {
const { peer, peerToken } = await gameRoom.addPlayer(input.name);
return {
peer,
token: peerToken,
};
} catch (error) {
if (error instanceof GameRoomFullError) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: error.message,
});
}
throw error;
}
});
3 changes: 1 addition & 2 deletions deep-sea-stories/packages/backend/src/controllers/stories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { RoomId } from '@fishjam-cloud/js-server-sdk';
import { stories } from '../config.js';
import { FailedToStartStoryError } from '../domain/errors.js';
import {
selectStoryInputSchema,
startStoryInputSchema,
Expand Down Expand Up @@ -57,7 +56,7 @@ export const startStory = publicProcedure
};
} catch (error) {
console.error(`Failed to start story: %o`, error);
throw new FailedToStartStoryError(0, (error as Error).message);
throw new Error((error as Error).message);
}
});

Expand Down
60 changes: 6 additions & 54 deletions deep-sea-stories/packages/backend/src/domain/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,13 @@ export abstract class DomainError extends Error {
this.statusCode = statusCode;
}
}

export class GameSessionNotFoundError extends DomainError {
constructor(roomId: string) {
super(
'GAME_SESSION_NOT_FOUND',
`No game session found for room ${roomId}`,
404,
);
this.name = 'GameSessionNotFoundError';
}
}

export class StoryNotFoundError extends DomainError {
constructor(roomId: string) {
super('STORY_NOT_FOUND', `No story available for room ${roomId}`, 400);
this.name = 'StoryNotFoundError';
}
}

export class NoPeersConnectedError extends DomainError {
constructor(roomId: string) {
super('NO_PEERS_CONNECTED', `No connected peers in room ${roomId}`, 400);
this.name = 'NoPeersConnectedError';
}
}

export class NoVoiceSessionManagerError extends DomainError {
constructor(roomId: string) {
super(
'NO_VOICE_SESSION_MANAGER',
`No voice session manager configured for room ${roomId}`,
500,
);
this.name = 'NoVoiceSessionManagerError';
}
}

export class AudioConnectionError extends DomainError {
constructor(peerId: string, reason: string) {
super(
'AUDIO_CONNECTION_ERROR',
`Failed to establish audio connection for peer ${peerId}: ${reason}`,
500,
);
this.name = 'AudioConnectionError';
}
}

export class FailedToStartStoryError extends DomainError {
constructor(storyId: number, reason: string) {
export class GameRoomFullError extends DomainError {
constructor() {
super(
'FAILED_TO_START_STORY',
`Failed to start story ${storyId}: ${reason}`,
500,
'GAME_ROOM_FULL',
'Room is full. Please wait for a spot or create a new room.',
400,
);
this.name = 'FailedToStartStoryError';
this.name = 'GameRoomFullError';
}
}
9 changes: 8 additions & 1 deletion deep-sea-stories/packages/backend/src/game/room.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { GAME_TIME_LIMIT_SECONDS } from '@deep-sea-stories/common';
import {
GAME_TIME_LIMIT_SECONDS,
ROOM_PLAYERS_LIMIT,
} from '@deep-sea-stories/common';
import {
type FishjamClient,
type Peer,
Expand All @@ -13,6 +16,7 @@ import { AudioStreamingOrchestrator } from '../service/audio-streaming-orchestra
import type { NotifierService } from '../service/notifier.js';
import type { Story } from '../types.js';
import { GameSession } from './session.js';
import { GameRoomFullError } from '../domain/errors.js';

type Player = {
name: string;
Expand Down Expand Up @@ -61,6 +65,9 @@ export class GameRoom {
}

async addPlayer(name: string): Promise<{ peer: Peer; peerToken: string }> {
if (this.players.size >= ROOM_PLAYERS_LIMIT) {
throw new GameRoomFullError();
}
const { peer, peerToken } = await this.fishjamClient.createPeer(
this.roomId,
);
Expand Down
2 changes: 2 additions & 0 deletions deep-sea-stories/packages/common/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const GAME_TIME_LIMIT_MINUTES = 30;

export const GAME_TIME_LIMIT_SECONDS = GAME_TIME_LIMIT_MINUTES * 60;

export const ROOM_PLAYERS_LIMIT = 4;
1 change: 1 addition & 0 deletions deep-sea-stories/packages/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export {
GAME_TIME_LIMIT_MINUTES,
GAME_TIME_LIMIT_SECONDS,
ROOM_PLAYERS_LIMIT,
} from './constants.js';
export type { AgentEvent } from './events.js';
export type { StoryData } from './types.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ROOM_PLAYERS_LIMIT } from '@deep-sea-stories/common';
import { Users } from 'lucide-react';
import type { FC } from 'react';

type PlayerCountIndicatorProps = {
count: number;
};

export const PlayerCountIndicator: FC<PlayerCountIndicatorProps> = ({
count,
}) => {
return (
<div className="rounded-full border border-white/15 bg-white/10 px-3 py-1 text-xs md:text-sm font-display text-white backdrop-blur flex items-center gap-1.5">
<Users className="w-3 h-3 md:w-4 md:h-4" />
{count}/{ROOM_PLAYERS_LIMIT}
</div>
);
};
18 changes: 13 additions & 5 deletions deep-sea-stories/packages/web/src/views/GameView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FC } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import GameControlPanel from '@/components/GameControlPanel';
import PeerGrid from '@/components/PeerGrid';
import { PlayerCountIndicator } from '@/components/PlayerCountIndicator';

export type GameViewProps = {
roomId: string;
Expand All @@ -29,6 +30,8 @@ const GameView: FC<GameViewProps> = ({ roomId }) => {
}, [agentPeer?.tracks[0]?.stream]);

const userName = localPeer?.metadata?.peer?.name ?? 'Unknown';
const playerCount = (localPeer ? 1 : 0) + displayedPeers.length;

return (
<div className="h-full w-full flex flex-col">
<GameControlPanel
Expand All @@ -37,11 +40,16 @@ const GameView: FC<GameViewProps> = ({ roomId }) => {
agentStream={agentPeer?.tracks[0]?.stream}
/>

<PeerGrid
roomId={roomId}
localPeer={localPeer}
displayedPeers={displayedPeers}
/>
<div className="relative flex-1 flex flex-col">
<div className="absolute right-2 top-2 md:right-6 md:top-6 z-10">
<PlayerCountIndicator count={playerCount} />
</div>
<PeerGrid
roomId={roomId}
localPeer={localPeer}
displayedPeers={displayedPeers}
/>
</div>

{/* biome-ignore lint/a11y/useMediaCaption: Peer audio feed from WebRTC doesn't have captions */}
<audio ref={agentAudioRef} autoPlay playsInline title={'Agent audio'} />
Expand Down
9 changes: 8 additions & 1 deletion deep-sea-stories/packages/web/src/views/JoinView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { toast } from '@/components/ui/sonner';
import { useTRPCClient } from '@/contexts/trpc';
import { TRPCClientError } from '@trpc/client';

interface JoinViewProps {
roomId: string;
Expand Down Expand Up @@ -64,7 +65,13 @@ const JoinView: FC<JoinViewProps> = ({ roomId }) => {
peerMetadata: { name },
});
} catch (error) {
console.error('Failed to join room:', error);
if (
error instanceof TRPCClientError &&
error.data?.code === 'BAD_REQUEST'
) {
toast(error.message, User);
}
console.error(error);
}
}, [trpcClient, roomId, joinRoom, name]);

Expand Down