From b9e9ae398ebf6b2b8e0d576bd9e67a0b01f3fc4f Mon Sep 17 00:00:00 2001 From: Jeremy Kahn Date: Mon, 15 Aug 2022 21:38:56 -0500 Subject: [PATCH] fix: prevent Room re-renders from throwing an error --- src/components/Room/Room.tsx | 37 ++++++++++++++++++++-------- src/hooks/usePeerRoom/usePeerRoom.ts | 16 ++++++------ src/services/PeerRoom/PeerRoom.ts | 24 ++++++++++++++++++ src/utils.ts | 4 +++ 4 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 src/utils.ts diff --git a/src/components/Room/Room.tsx b/src/components/Room/Room.tsx index bf3d753..983e023 100644 --- a/src/components/Room/Room.tsx +++ b/src/components/Room/Room.tsx @@ -1,25 +1,25 @@ -import { useMemo } from 'react' +import { useState } from 'react' import { useParams } from 'react-router-dom' import Button from '@mui/material/Button' import Typography from '@mui/material/Typography' import { usePeerRoom } from '../../hooks/usePeerRoom' +import { PeerRoom } from '../../services/PeerRoom' enum PeerActions { MESSAGE = 'MESSAGE', } -export function Room() { - const { roomId = '' } = useParams() +interface RoomProps { + peerRoom: PeerRoom + roomId: string +} - const { makeAction } = usePeerRoom({ - appId: `${window.location.origin}_${process.env.REACT_APP_NAME}`, - roomId, - }) +function Room({ peerRoom, roomId }: RoomProps) { + const { makeAction } = peerRoom - const [sendMessage, receiveMessage] = useMemo( - () => makeAction(PeerActions.MESSAGE), - [makeAction] + const [[sendMessage, receiveMessage]] = useState(() => + makeAction(PeerActions.MESSAGE) ) receiveMessage(message => { @@ -43,3 +43,20 @@ export function Room() { ) } + +function RoomLoader() { + const { roomId = '' } = useParams() + + const peerRoom = usePeerRoom({ + appId: `${process.env.REACT_APP_NAME}`, + roomId, + }) + + if (peerRoom) { + return + } else { + return <>Loading... + } +} + +export { RoomLoader as Room } diff --git a/src/hooks/usePeerRoom/usePeerRoom.ts b/src/hooks/usePeerRoom/usePeerRoom.ts index 11124bc..8745e44 100644 --- a/src/hooks/usePeerRoom/usePeerRoom.ts +++ b/src/hooks/usePeerRoom/usePeerRoom.ts @@ -1,6 +1,6 @@ -import { useEffect, useMemo } from 'react' +import { useEffect, useState } from 'react' -import { PeerRoom } from '../../services/PeerRoom' +import { PeerRoom, getPeerRoom } from '../../services/PeerRoom' interface PeerRoomProps { appId: string @@ -8,17 +8,19 @@ interface PeerRoomProps { } export function usePeerRoom({ appId, roomId }: PeerRoomProps) { - const peerRoom = useMemo(() => { - const peerRoom = new PeerRoom({ appId }, roomId) + const [peerRoom, setPeerRoom] = useState(null) - return peerRoom + useEffect(() => { + ;(async () => { + setPeerRoom(await getPeerRoom({ appId }, roomId)) + })() }, [appId, roomId]) useEffect(() => { return () => { - peerRoom.leaveRoom() + peerRoom?.leaveRoom() } - }, [appId, peerRoom, roomId]) + }, [peerRoom]) return peerRoom } diff --git a/src/services/PeerRoom/PeerRoom.ts b/src/services/PeerRoom/PeerRoom.ts index 8767f40..ad38d27 100644 --- a/src/services/PeerRoom/PeerRoom.ts +++ b/src/services/PeerRoom/PeerRoom.ts @@ -1,5 +1,7 @@ import { joinRoom, Room, RoomConfig } from 'trystero' +import { sleep } from '../../utils' + export class PeerRoom { private room: Room @@ -20,3 +22,25 @@ export class PeerRoom { return this.room.makeAction(namespace) } } + +// This abstraction is necessary because it takes some time for a PeerRoom to +// be torn down, and there is no way to detect when that happens. If a new +// PeerRoom is instantiated with the same config and roomId before the previous +// one is torn down, an error is thrown. The workaround is to continually +// trying to instantiate a PeerRoom until it succeeds. +export const getPeerRoom = async (config: RoomConfig, roomId: string) => { + const timeout = 1000 + const epoch = Date.now() + + do { + if (Date.now() - epoch > timeout) { + throw new Error('Could not create PeerRoom') + } + + try { + return new PeerRoom(config, roomId) + } catch (e) {} + + await sleep(100) + } while (true) +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..fb9adf6 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,4 @@ +export const sleep = (milliseconds: number): Promise => + new Promise(res => { + setTimeout(res, milliseconds) + })