From 2a053f4854b19633660876d5631c2afdbf97509c Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Sat, 17 Jul 2021 17:25:41 +1000 Subject: [PATCH] Typescript --- package.json | 4 +- src/components/map/Map.tsx | 14 +- src/components/map/MapControls.tsx | 4 +- src/components/map/MapDrawing.tsx | 4 +- src/components/map/MapEditBar.tsx | 4 +- src/components/map/MapFog.tsx | 4 +- src/components/map/MapInteraction.tsx | 4 +- src/components/map/MapMeasure.tsx | 4 +- src/components/map/MapNotes.tsx | 4 +- src/components/map/MapTiles.tsx | 2 - src/components/map/MapTokens.tsx | 2 +- src/components/map/SelectMapButton.tsx | 4 +- src/components/note/Note.tsx | 24 ++- src/components/note/NoteMenu.tsx | 2 +- src/components/party/Party.tsx | 12 +- src/components/token/TokenMenu.tsx | 10 +- src/contexts/AssetsContext.tsx | 33 ++- src/contexts/PlayerContext.tsx | 4 +- src/helpers/grid.ts | 4 +- src/hooks/useImageCenter.ts | 2 +- src/hooks/useMapImage.ts | 2 +- src/modals/SelectMapModal.tsx | 8 +- src/network/Connection.ts | 70 +++--- src/network/NetworkedMapAndTokens.tsx | 101 +++++---- src/network/NetworkedMapPointer.tsx | 60 ++++-- src/network/NetworkedParty.tsx | 50 +++-- src/network/Session.ts | 283 +++++++++++-------------- src/routes/Game.tsx | 10 +- src/routes/{Home.js => Home.tsx} | 9 +- src/types/Events.ts | 7 +- src/types/Map.ts | 8 +- src/types/MapState.ts | 4 +- yarn.lock | 74 ++++--- 33 files changed, 412 insertions(+), 419 deletions(-) rename src/routes/{Home.js => Home.tsx} (97%) diff --git a/package.json b/package.json index d544a96..d5b61d7 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "react-toast-notifications": "^2.4.3", "react-use-gesture": "^9.1.3", "shortid": "^2.2.15", - "simple-peer": "feross/simple-peer#694/head", + "simple-peer": "^9.11.0", "simplebar-react": "^2.1.0", "simplify-js": "^1.2.4", "socket.io-client": "^4.1.2", @@ -110,7 +110,7 @@ "@types/react-router-dom": "^5.1.7", "@types/react-select": "^4.0.17", "@types/shortid": "^0.0.29", - "@types/simple-peer": "^9.6.3", + "@types/simple-peer": "^9.11.1", "@types/uuid": "^8.3.1", "typescript": "^4.2.4", "worker-loader": "^3.0.8" diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index e970f24..065e17d 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -47,8 +47,8 @@ import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token"; import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note"; type MapProps = { - map: Map; - mapState: MapState; + map: Map | null; + mapState: MapState | null; mapActions: MapActions; onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateRemove: MapTokenStateRemoveHandler; @@ -66,7 +66,7 @@ type MapProps = { allowFogDrawing: boolean; allowMapChange: boolean; allowNoteEditing: boolean; - disabledTokens: string[]; + disabledTokens: Record; session: Session; }; @@ -244,9 +244,7 @@ function Map({ onRequestClose={() => setIsTokenMenuOpen(false)} onTokenStateChange={onMapTokenStateChange} tokenState={ - tokenMenuOptions && - mapState && - mapState.tokens[tokenMenuOptions.tokenStateId] + tokenMenuOptions && mapState?.tokens[tokenMenuOptions.tokenStateId] } tokenImage={tokenMenuOptions && tokenMenuOptions.tokenImage} map={map} @@ -372,9 +370,7 @@ function Map({ isOpen={isNoteMenuOpen} onRequestClose={() => setIsNoteMenuOpen(false)} onNoteChange={onMapNoteChange} - note={ - noteMenuOptions && mapState && mapState.notes[noteMenuOptions.noteId] - } + note={noteMenuOptions && mapState?.notes[noteMenuOptions.noteId]} noteNode={noteMenuOptions?.noteNode} map={map} /> diff --git a/src/components/map/MapControls.tsx b/src/components/map/MapControls.tsx index fdd72ff..7aef397 100644 --- a/src/components/map/MapControls.tsx +++ b/src/components/map/MapControls.tsx @@ -32,8 +32,8 @@ import { Settings } from "../../types/Settings"; type MapControlsProps = { onMapChange: MapChangeEventHandler; onMapReset: MapResetEventHandler; - currentMap?: Map; - currentMapState?: MapState; + currentMap: Map | null; + currentMapState: MapState | null; selectedToolId: MapToolId; onSelectedToolChange: (toolId: MapToolId) => void; toolSettings: Settings; diff --git a/src/components/map/MapDrawing.tsx b/src/components/map/MapDrawing.tsx index 118f5e2..778a1a6 100644 --- a/src/components/map/MapDrawing.tsx +++ b/src/components/map/MapDrawing.tsx @@ -37,7 +37,7 @@ export type DrawingAddEventHanlder = (drawing: Drawing) => void; export type DrawingsRemoveEventHandler = (drawingIds: string[]) => void; type MapDrawingProps = { - map: Map; + map: Map | null; drawings: Drawing[]; onDrawingAdd: DrawingAddEventHanlder; onDrawingsRemove: DrawingsRemoveEventHandler; @@ -85,7 +85,7 @@ function MapDrawing({ const mapStage = mapStageRef.current; function getBrushPosition() { - if (!mapStage) { + if (!mapStage || !map) { return; } const mapImage = mapStage.findOne("#mapImage"); diff --git a/src/components/map/MapEditBar.tsx b/src/components/map/MapEditBar.tsx index eef70ee..201ae51 100644 --- a/src/components/map/MapEditBar.tsx +++ b/src/components/map/MapEditBar.tsx @@ -20,7 +20,7 @@ import { } from "../../types/Events"; type MapEditBarProps = { - currentMap?: Map; + currentMap: Map | null; disabled: boolean; onMapChange: MapChangeEventHandler; onMapReset: MapResetEventHandler; @@ -79,7 +79,7 @@ function MapEditBar({ await removeMaps(selectedMapIds); // Removed the map from the map screen if needed if (currentMap && selectedMapIds.includes(currentMap.id)) { - onMapChange(undefined, undefined); + onMapChange(null, null); } onLoad(false); } diff --git a/src/components/map/MapFog.tsx b/src/components/map/MapFog.tsx index b70dce5..a2598c7 100644 --- a/src/components/map/MapFog.tsx +++ b/src/components/map/MapFog.tsx @@ -58,7 +58,7 @@ type FogEditEventHandler = (edit: Partial[]) => void; type FogErrorEventHandler = (message: string) => void; type MapFogProps = { - map: Map; + map: Map | null; shapes: Fog[]; onShapesAdd: FogAddEventHandler; onShapesCut: FogCutEventHandler; @@ -354,7 +354,7 @@ function MapFog({ x: mapWidth, y: mapHeight, }); - if (map.snapToGrid) { + if (map?.snapToGrid) { guides.push( ...getGuidesFromGridCell( absoluteBrushPosition, diff --git a/src/components/map/MapInteraction.tsx b/src/components/map/MapInteraction.tsx index 1a778dd..e9b48e4 100644 --- a/src/components/map/MapInteraction.tsx +++ b/src/components/map/MapInteraction.tsx @@ -25,8 +25,8 @@ import { MapState } from "../../types/MapState"; type SelectedToolChangeEventHanlder = (tool: MapToolId) => void; type MapInteractionProps = { - map: Map; - mapState: MapState; + map: Map | null; + mapState: MapState | null; children?: React.ReactNode; controls: React.ReactNode; selectedToolId: MapToolId; diff --git a/src/components/map/MapMeasure.tsx b/src/components/map/MapMeasure.tsx index cd093b5..b984cd9 100644 --- a/src/components/map/MapMeasure.tsx +++ b/src/components/map/MapMeasure.tsx @@ -29,7 +29,7 @@ import { Map } from "../../types/Map"; import { PointsData } from "../../types/Drawing"; type MapMeasureProps = { - map: Map; + map: Map | null; active: boolean; }; @@ -74,7 +74,7 @@ function MapMeasure({ map, active }: MapMeasureProps) { if (!position) { return; } - if (map.snapToGrid) { + if (map?.snapToGrid) { position = snapPositionToGrid(position); } return Vector2.divide(position, { diff --git a/src/components/map/MapNotes.tsx b/src/components/map/MapNotes.tsx index b84dc55..24f6918 100644 --- a/src/components/map/MapNotes.tsx +++ b/src/components/map/MapNotes.tsx @@ -26,7 +26,7 @@ import { const defaultNoteSize = 2; type MapNoteProps = { - map: Map; + map: Map | null; active: boolean; onNoteAdd: NoteAddEventHander; onNoteChange: NoteChangeEventHandler; @@ -75,7 +75,7 @@ function MapNotes({ if (!position) { return; } - if (map.snapToGrid) { + if (map?.snapToGrid) { position = snapPositionToGrid(position); } return Vector2.divide(position, { diff --git a/src/components/map/MapTiles.tsx b/src/components/map/MapTiles.tsx index 3b5c096..a8a7ff3 100644 --- a/src/components/map/MapTiles.tsx +++ b/src/components/map/MapTiles.tsx @@ -1,5 +1,3 @@ -import React from "react"; - import MapTile from "./MapTile"; import MapTileGroup from "./MapTileGroup"; diff --git a/src/components/map/MapTokens.tsx b/src/components/map/MapTokens.tsx index 2e18b69..c37b069 100644 --- a/src/components/map/MapTokens.tsx +++ b/src/components/map/MapTokens.tsx @@ -18,7 +18,7 @@ type MapTokensProps = { onMapTokenStateChange: TokenStateChangeEventHandler; onTokenMenuOpen: TokenMenuOpenChangeEventHandler; selectedToolId: MapToolId; - disabledTokens: string[]; + disabledTokens: Record; }; function MapTokens({ diff --git a/src/components/map/SelectMapButton.tsx b/src/components/map/SelectMapButton.tsx index aae8164..f5f3ea9 100644 --- a/src/components/map/SelectMapButton.tsx +++ b/src/components/map/SelectMapButton.tsx @@ -16,8 +16,8 @@ import { MapState } from "../../types/MapState"; type SelectMapButtonProps = { onMapChange: MapChangeEventHandler; onMapReset: MapResetEventHandler; - currentMap?: Map; - currentMapState?: MapState; + currentMap: Map | null; + currentMapState: MapState | null; disabled: boolean; }; diff --git a/src/components/note/Note.tsx b/src/components/note/Note.tsx index 9e09deb..4274801 100644 --- a/src/components/note/Note.tsx +++ b/src/components/note/Note.tsx @@ -28,7 +28,7 @@ const defaultFontSize = 16; type NoteProps = { note: NoteType; - map: Map; + map: Map | null; onNoteChange?: NoteChangeEventHandler; onNoteMenuOpen?: NoteMenuOpenEventHandler; draggable: boolean; @@ -72,20 +72,22 @@ function Note({ function handleDragMove(event: Konva.KonvaEventObject) { const noteGroup = event.target; // Snap to corners of grid - if (map.snapToGrid) { + if (map?.snapToGrid) { noteGroup.position(snapPositionToGrid(noteGroup.position())); } } function handleDragEnd(event: Konva.KonvaEventObject) { const noteGroup = event.target; - onNoteChange?.({ - ...note, - x: noteGroup.x() / mapWidth, - y: noteGroup.y() / mapHeight, - lastModifiedBy: userId, - lastModified: Date.now(), - }); + if (userId) { + onNoteChange?.({ + ...note, + x: noteGroup.x() / mapWidth, + y: noteGroup.y() / mapHeight, + lastModifiedBy: userId, + lastModified: Date.now(), + }); + } onNoteDragEnd?.(event, note.id); setPreventMapInteraction(false); } @@ -103,7 +105,7 @@ function Note({ if (draggable) { setPreventMapInteraction(true); } - if (note.locked && map.owner === userId) { + if (note.locked && map?.owner === userId) { notePointerDownTimeRef.current = event.evt.timeStamp; } } @@ -114,7 +116,7 @@ function Note({ } // Check note click when locked and we are the map owner // We can't use onClick because that doesn't check pointer distance - if (note.locked && map.owner === userId) { + if (note.locked && map?.owner === userId) { // If down and up time is small trigger a click const delta = event.evt.timeStamp - notePointerDownTimeRef.current; if (delta < 300) { diff --git a/src/components/note/NoteMenu.tsx b/src/components/note/NoteMenu.tsx index 0d81bbb..4096ec7 100644 --- a/src/components/note/NoteMenu.tsx +++ b/src/components/note/NoteMenu.tsx @@ -35,7 +35,7 @@ type NoteMenuProps = { note?: Note; noteNode?: Konva.Node; onNoteChange: NoteChangeEventHandler; - map: Map; + map: Map | null; }; function NoteMenu({ diff --git a/src/components/party/Party.tsx b/src/components/party/Party.tsx index 3197749..5b35722 100644 --- a/src/components/party/Party.tsx +++ b/src/components/party/Party.tsx @@ -10,12 +10,6 @@ import SettingsButton from "../SettingsButton"; import StartTimerButton from "./StartTimerButton"; import Timer from "./Timer"; import DiceTrayButton from "./DiceTrayButton"; -import { - PartyState, - PlayerDice, - PlayerInfo, - Timer as PartyTimer, -} from "./PartyState"; import useSetting from "../../hooks/useSetting"; @@ -36,10 +30,10 @@ function Party({ onStreamEnd; }) { const setPlayerState = usePlayerUpdater(); - const playerState: PlayerInfo = usePlayerState(); - const partyState: PartyState = useParty(); + const playerState = usePlayerState(); + const partyState = useParty(); - const [fullScreen] = useSetting("map.fullScreen"); + const [fullScreen] = useSetting("map.fullScreen"); const [shareDice, setShareDice] = useSetting("dice.shareDice"); function handleTimerStart(newTimer: PartyTimer) { diff --git a/src/components/token/TokenMenu.tsx b/src/components/token/TokenMenu.tsx index df6a24a..438c7ce 100644 --- a/src/components/token/TokenMenu.tsx +++ b/src/components/token/TokenMenu.tsx @@ -6,7 +6,7 @@ import Slider from "../Slider"; import MapMenu from "../map/MapMenu"; -import colors, { colorOptions } from "../../helpers/colors"; +import colors, { Color, colorOptions } from "../../helpers/colors"; import usePrevious from "../../hooks/usePrevious"; @@ -27,10 +27,10 @@ import { Map } from "../../types/Map"; type TokenMenuProps = { isOpen: boolean; onRequestClose: RequestCloseEventHandler; - tokenState: TokenState; - tokenImage: Konva.Node; + tokenState?: TokenState; + tokenImage?: Konva.Node; onTokenStateChange: TokenStateChangeEventHandler; - map: Map; + map: Map | null; }; const defaultTokenMaxSize = 6; @@ -74,7 +74,7 @@ function TokenMenu({ tokenState && onTokenStateChange({ [tokenState.id]: { label: label } }); } - function handleStatusChange(status: string) { + function handleStatusChange(status: Color) { if (!tokenState) { return; } diff --git a/src/contexts/AssetsContext.tsx b/src/contexts/AssetsContext.tsx index bd0c6d9..cd7906d 100644 --- a/src/contexts/AssetsContext.tsx +++ b/src/contexts/AssetsContext.tsx @@ -209,8 +209,8 @@ export function AssetURLsProvider({ children }: { children: React.ReactNode }) { * Helper function to load either file or default asset into a URL */ export function useAssetURL( - assetId: string, - type: "file" | "default", + assetId: string | null, + type: "file" | "default" | null, defaultSources: Record, unknownSource?: string ) { @@ -227,9 +227,8 @@ export function useAssetURL( if (!assetId || type !== "file") { return; } - - function updateAssetURL() { - function increaseReferences(prevURLs: AssetURLs): AssetURLs { + const updateAssetURL = () => { + const increaseReferences = (prevURLs: AssetURLs): AssetURLs => { return { ...prevURLs, [assetId]: { @@ -237,14 +236,14 @@ export function useAssetURL( references: prevURLs[assetId].references + 1, }, }; - } + }; - function createReference(prevURLs: AssetURLs): AssetURLs { + const createReference = (prevURLs: AssetURLs): AssetURLs => { return { ...prevURLs, [assetId]: { url: null, id: assetId, references: 1 }, }; - } + }; setAssetURLs?.((prevURLs) => { if (assetId in prevURLs) { // Check if the asset url is already added and increase references @@ -253,7 +252,7 @@ export function useAssetURL( return createReference(prevURLs); } }); - } + }; updateAssetURL(); @@ -307,22 +306,22 @@ type DefaultData = { * Load a map or token into a URL taking into account a thumbnail and multiple resolutions */ export function useDataURL( - data: FileData | DefaultData, + data: FileData | DefaultData | null, defaultSources: Record, unknownSource: string | undefined = undefined, thumbnail = false ) { - const [assetId, setAssetId] = useState(); + const [assetId, setAssetId] = useState(null); useEffect(() => { - if (!data) { - return; - } function loadAssetId() { + if (!data) { + return; + } if (data.type === "default") { setAssetId(data.key); } else { - if (thumbnail) { + if (thumbnail && data.thumbnail) { setAssetId(data.thumbnail); } else if ( data.resolutions && @@ -340,8 +339,8 @@ export function useDataURL( }, [data, thumbnail]); const assetURL = useAssetURL( - assetId || "", - data?.type, + assetId || null, + data?.type || null, defaultSources, unknownSource ); diff --git a/src/contexts/PlayerContext.tsx b/src/contexts/PlayerContext.tsx index e7d7003..0307585 100644 --- a/src/contexts/PlayerContext.tsx +++ b/src/contexts/PlayerContext.tsx @@ -8,7 +8,7 @@ import { getRandomMonster } from "../helpers/monsters"; import useNetworkedState, { SetNetworkedState, } from "../hooks/useNetworkedState"; -import Session from "../network/Session"; +import Session, { SessionStatus } from "../network/Session"; import { PlayerState } from "../types/PlayerState"; export const PlayerStateContext = @@ -103,7 +103,7 @@ export function PlayerProvider({ session, children }: PlayerProviderProps) { updateSessionId(); } - function handleSocketStatus(status: string) { + function handleSocketStatus(status: SessionStatus) { if (status === "joined") { updateSessionId(); } diff --git a/src/helpers/grid.ts b/src/helpers/grid.ts index f6eeea5..04844ce 100644 --- a/src/helpers/grid.ts +++ b/src/helpers/grid.ts @@ -245,10 +245,10 @@ export function getGridUpdatedInset( /** * Get the max zoom for a grid - * @param {Grid} grid + * @param {Grid=} grid * @returns {number} */ -export function getGridMaxZoom(grid: Grid): number { +export function getGridMaxZoom(grid?: Grid): number { if (!grid) { return 10; } diff --git a/src/hooks/useImageCenter.ts b/src/hooks/useImageCenter.ts index acc1a58..00f3423 100644 --- a/src/hooks/useImageCenter.ts +++ b/src/hooks/useImageCenter.ts @@ -11,7 +11,7 @@ type ImageData = { }; function useImageCenter( - data: ImageData, + data: ImageData | null, stageRef: MapStage, stageWidth: number, stageHeight: number, diff --git a/src/hooks/useMapImage.ts b/src/hooks/useMapImage.ts index de2309e..c9bbfa5 100644 --- a/src/hooks/useMapImage.ts +++ b/src/hooks/useMapImage.ts @@ -8,7 +8,7 @@ import { mapSources as defaultMapSources } from "../maps"; import { Map } from "../types/Map"; function useMapImage( - map: Map + map: Map | null ): [HTMLImageElement | undefined, "loaded" | "loading" | "failed"] { const mapURL = useDataURL(map, defaultMapSources); const [mapImage, mapImageStatus] = useImage(mapURL || ""); diff --git a/src/modals/SelectMapModal.tsx b/src/modals/SelectMapModal.tsx index 730cd2e..1f5189b 100644 --- a/src/modals/SelectMapModal.tsx +++ b/src/modals/SelectMapModal.tsx @@ -42,7 +42,7 @@ type SelectMapProps = { onDone: RequestCloseEventHandler; onMapChange: MapChangeEventHandler; onMapReset: MapResetEventHandler; - currentMap?: Map; + currentMap: Map | null; }; function SelectMapModal({ @@ -175,12 +175,12 @@ function SelectMapModal({ } if (mapId) { setIsLoading(true); - const map = await getMap(mapId); - const mapState = await getMapState(mapId); + const map = (await getMap(mapId)) || null; + const mapState = (await getMapState(mapId)) || null; onMapChange(map, mapState); setIsLoading(false); } else { - onMapChange(undefined, undefined); + onMapChange(null, null); } onDone(); } diff --git a/src/network/Connection.ts b/src/network/Connection.ts index 995ad9d..fbc0c55 100644 --- a/src/network/Connection.ts +++ b/src/network/Connection.ts @@ -8,21 +8,37 @@ import blobToBuffer from "../helpers/blobToBuffer"; // http://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/ const MAX_BUFFER_SIZE = 16000; -class Connection extends SimplePeer { - currentChunks; - dataChannels; +type NetworkChunk = { + __chunked: boolean; + data: Uint8Array; + id: string; + index: number; + total: number; +}; - constructor(props) { +type LocalChunk = { + data: Uint8Array[]; + count: number; + total: number; +}; + +export type DataProgressEvent = { + id: string; + count: number; + total: number; +}; + +class Connection extends SimplePeer { + currentChunks: Record; + constructor(props: SimplePeer.Options) { super(props); this.currentChunks = {}; - this.dataChannels = {}; this.on("data", this.handleData); - this.on("datachannel", this.handleDataChannel); } // Intercept the data event with decoding and chunking support - handleData(packed) { - const unpacked = decode(packed); + handleData(packed: Uint8Array) { + const unpacked = decode(packed) as NetworkChunk; // If the special property __chunked is set and true // The data is a partial chunk of the a larger file // So wait until all chunks are collected and assembled @@ -46,7 +62,6 @@ class Connection extends SimplePeer { // All chunks have been loaded if (chunk.count === chunk.total) { // Merge chunks with a blob - // TODO: Look at a more efficient way to recombine buffer data const merged = new Blob(chunk.data); blobToBuffer(merged).then((buffer) => { this.emit("dataComplete", decode(buffer)); @@ -62,52 +77,23 @@ class Connection extends SimplePeer { * Custom send function with encoding, chunking and data channel support * Uses `write` to send the data to allow for buffer / backpressure handling * @param {any} object - * @param {string=} channel * @param {string=} chunkId Optional ID to use for chunking */ - sendObject(object, channel?: string, chunkId?: string) { + sendObject(object: any, chunkId?: string) { try { const packedData = encode(object); const chunks = this.chunk(packedData, chunkId); for (let chunk of chunks) { - if (this.dataChannels[channel]) { - this.dataChannels[channel].write(encode(chunk)); - } else { - this.write(encode(chunk)); - } + this.write(encode(chunk)); } } catch (error) { console.error(error); } } - // Override the create data channel function to store our own named reference to it - // and to use our custom data handler - createDataChannel(channelName: string, channelConfig, opts) { - // TODO: resolve createDataChannel - // @ts-ignore - const channel = super.createDataChannel(channelName, channelConfig, opts); - this.handleDataChannel(channel); - return channel; - } - - handleDataChannel(channel) { - const channelName = channel.channelName; - this.dataChannels[channelName] = channel; - channel.on("data", this.handleData.bind(this)); - channel.on("error", (error) => { - this.emit("error", error); - }); - } - // Converted from https://github.com/peers/peerjs/ - /** - * Chunk byte array - * @param {Uint8Array} data - * @param {string=} chunkId - * @returns {Uint8Array[]} - */ - chunk(data: Uint8Array, chunkId?: string) { + /** Chunk byte array */ + chunk(data: Uint8Array, chunkId?: string): NetworkChunk[] { const chunks = []; const size = data.byteLength; const total = Math.ceil(size / MAX_BUFFER_SIZE); diff --git a/src/network/NetworkedMapAndTokens.tsx b/src/network/NetworkedMapAndTokens.tsx index 7dad668..6a67cfa 100644 --- a/src/network/NetworkedMapAndTokens.tsx +++ b/src/network/NetworkedMapAndTokens.tsx @@ -14,7 +14,7 @@ import useDebounce from "../hooks/useDebounce"; import useNetworkedState from "../hooks/useNetworkedState"; // Load session for auto complete -import Session from "./Session"; +import Session, { PeerDataEvent, PeerDataProgressEvent } from "./Session"; import Action from "../actions/Action"; @@ -38,6 +38,7 @@ import { import { TokenState } from "../types/TokenState"; import { DrawingState } from "../types/Drawing"; import { FogState } from "../types/Fog"; +import { Note } from "../types/Note"; const defaultMapActions: MapActions = { mapDrawActions: [], @@ -204,7 +205,10 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { } }, [currentMap, debouncedMapState, userId, database, updateMapState]); - async function handleMapChange(newMap, newMapState) { + async function handleMapChange( + newMap: MapType | null, + newMapState: MapState | null + ) { // Clear map before sending new one setCurrentMap(null); session.socket?.emit("map", null); @@ -222,7 +226,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { await loadAssetManifestFromMap(newMap, newMapState); } - function handleMapReset(newMapState) { + function handleMapReset(newMapState: MapState) { setCurrentMapState(newMapState, true, true); setMapActions(defaultMapActions); } @@ -264,7 +268,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { } function updateActionIndex( - change, + change: number, indexKey: MapActionsIndexKey, actionsKey: MapActionsKey, shapesKey: "drawShapes" | "fogShapes" @@ -288,19 +292,21 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { // Redo for (let i = prevIndex + 1; i < newIndex + 1; i++) { let action = mapActions[actionsKey][i]; - shapes = action.execute(shapes); + shapes = action.execute(shapes as any); } } else { // Undo for (let i = prevIndex; i > newIndex; i--) { let action = mapActions[actionsKey][i]; - shapes = action.undo(shapes); + shapes = action.undo(shapes as any); } } return { ...prevMapState, [shapesKey]: shapes, }; + } else { + return prevMapState; } }); @@ -342,7 +348,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { } // If map changes clear map actions - const previousMapIdRef = useRef(); + const previousMapIdRef = useRef(); useEffect(() => { if (currentMap && currentMap?.id !== previousMapIdRef.current) { setMapActions(defaultMapActions); @@ -350,21 +356,31 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { } }, [currentMap]); - function handleNoteChange(note) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - notes: { - ...prevMapState.notes, - [note.id]: note, - }, - })); + function handleNoteChange(note: Note) { + setCurrentMapState((prevMapState) => { + if (!prevMapState) { + return prevMapState; + } + return { + ...prevMapState, + notes: { + ...prevMapState.notes, + [note.id]: note, + }, + }; + }); } function handleNoteRemove(noteId: string) { - setCurrentMapState((prevMapState) => ({ - ...prevMapState, - notes: omit(prevMapState.notes, [noteId]), - })); + setCurrentMapState((prevMapState) => { + if (!prevMapState) { + return prevMapState; + } + return { + ...prevMapState, + notes: omit(prevMapState.notes, [noteId]), + }; + }); } /** @@ -387,6 +403,9 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { } setCurrentMapState((prevMapState) => { + if (!prevMapState) { + return prevMapState; + } let newMapTokens = { ...prevMapState.tokens }; for (let tokenState of tokenStates) { newMapTokens[tokenState.id] = tokenState; @@ -395,15 +414,20 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { }); } - function handleMapTokenStateChange(change) { + function handleMapTokenStateChange( + change: Record> + ) { if (!currentMapState) { return; } setCurrentMapState((prevMapState) => { + if (!prevMapState) { + return prevMapState; + } let tokens = { ...prevMapState.tokens }; for (let id in change) { if (id in tokens) { - tokens[id] = { ...tokens[id], ...change[id] }; + tokens[id] = { ...tokens[id], ...change[id] } as TokenState; } } @@ -414,29 +438,24 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { }); } - function handleMapTokenStateRemove(tokenState) { + function handleMapTokenStateRemove(tokenState: TokenState) { setCurrentMapState((prevMapState) => { + if (!prevMapState) { + return prevMapState; + } const { [tokenState.id]: old, ...rest } = prevMapState.tokens; return { ...prevMapState, tokens: rest }; }); } useEffect(() => { - async function handlePeerData({ - id, - data, - reply, - }: { - id: string; - data; - reply; - }) { + async function handlePeerData({ id, data, reply }: PeerDataEvent) { if (id === "assetRequest") { const asset = await getAsset(data.id); if (asset) { - reply("assetResponseSuccess", asset, undefined, data.id); + reply("assetResponseSuccess", asset, data.id); } else { - reply("assetResponseFail", data.id, undefined, data.id); + reply("assetResponseFail", data.id, data.id); } } @@ -456,15 +475,11 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { id, total, count, - }: { - id: string; - total: number; - count: number; - }) { + }: PeerDataProgressEvent) { assetProgressUpdate({ id, total, count }); } - async function handleSocketMap(map) { + async function handleSocketMap(map?: MapType) { if (map) { setCurrentMap(map); } else { @@ -502,7 +517,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { (currentMapState.editFlags.includes("notes") || currentMap?.owner === userId); - const disabledMapTokens = {}; + const disabledMapTokens: Record = {}; // If we have a map and state and have the token permission disabled // and are not the map owner if ( @@ -539,10 +554,10 @@ function NetworkedMapAndTokens({ session }: { session: Session }) { onFogDrawRedo={handleFogDrawRedo} onMapNoteChange={handleNoteChange} onMapNoteRemove={handleNoteRemove} - allowMapDrawing={canEditMapDrawing} - allowFogDrawing={canEditFogDrawing} + allowMapDrawing={!!canEditMapDrawing} + allowFogDrawing={!!canEditFogDrawing} allowMapChange={canChangeMap} - allowNoteEditing={canEditNotes} + allowNoteEditing={!!canEditNotes} disabledTokens={disabledMapTokens} session={session} /> diff --git a/src/network/NetworkedMapPointer.tsx b/src/network/NetworkedMapPointer.tsx index b28ab12..9fe38b8 100644 --- a/src/network/NetworkedMapPointer.tsx +++ b/src/network/NetworkedMapPointer.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import { Group } from "react-konva"; import { useUserId } from "../contexts/UserIdContext"; @@ -9,20 +9,31 @@ import Vector2 from "../helpers/Vector2"; import useSetting from "../hooks/useSetting"; import Session from "./Session"; +import { Color } from "../helpers/colors"; +import { PointerState } from "../types/Pointer"; // Send pointer updates every 50ms (20fps) const sendTickRate = 50; -function NetworkedMapPointer({ - session, - active, -}: { +type InterpolatedPointerState = PointerState & { time: number }; + +type PointerInterpolation = { + id: string; + from: InterpolatedPointerState | null; + to: InterpolatedPointerState; +}; + +type NetworkedMapPointerProps = { session: Session; active: boolean; -}) { +}; + +function NetworkedMapPointer({ session, active }: NetworkedMapPointerProps) { const userId = useUserId(); - const [localPointerState, setLocalPointerState] = useState({}); - const [pointerColor] = useSetting("pointer.color"); + const [localPointerState, setLocalPointerState] = useState< + Record + >({}); + const [pointerColor] = useSetting("pointer.color"); const sessionRef = useRef(session); useEffect(() => { @@ -45,14 +56,12 @@ function NetworkedMapPointer({ // Send pointer updates every sendTickRate to peers to save on bandwidth // We use requestAnimationFrame as setInterval was being blocked during // re-renders on Chrome with Windows - const ownPointerUpdateRef: React.MutableRefObject< - { position; visible: boolean; id; color } | undefined | null - > = useRef(); + const ownPointerUpdateRef = useRef(null); useEffect(() => { let prevTime = performance.now(); let request = requestAnimationFrame(update); let counter = 0; - function update(time) { + function update(time: number) { request = requestAnimationFrame(update); const deltaTime = time - prevTime; counter += deltaTime; @@ -79,7 +88,10 @@ function NetworkedMapPointer({ }; }, []); - function updateOwnPointerState(position, visible: boolean) { + function updateOwnPointerState(position: Vector2, visible: boolean) { + if (!userId) { + return; + } setLocalPointerState((prev) => ({ ...prev, [userId]: { position, visible, id: userId, color: pointerColor }, @@ -92,23 +104,23 @@ function NetworkedMapPointer({ }; } - function handleOwnPointerDown(position) { + function handleOwnPointerDown(position: Vector2) { updateOwnPointerState(position, true); } - function handleOwnPointerMove(position) { + function handleOwnPointerMove(position: Vector2) { updateOwnPointerState(position, true); } - function handleOwnPointerUp(position) { + function handleOwnPointerUp(position: Vector2) { updateOwnPointerState(position, false); } // Handle pointer data receive - const interpolationsRef: React.MutableRefObject = useRef({}); + const interpolationsRef = useRef>({}); useEffect(() => { // TODO: Handle player disconnect while pointer visible - function handleSocketPlayerPointer(pointer) { + function handleSocketPlayerPointer(pointer: InterpolatedPointerState) { const interpolations = interpolationsRef.current; const id = pointer.id; if (!(id in interpolations)) { @@ -154,7 +166,7 @@ function NetworkedMapPointer({ function animate() { request = requestAnimationFrame(animate); const time = performance.now(); - let interpolatedPointerState = {}; + let interpolatedPointerState: Record = {}; for (let interp of Object.values(interpolationsRef.current)) { if (!interp.from || !interp.to) { continue; @@ -206,9 +218,13 @@ function NetworkedMapPointer({ active={pointer.id === userId ? active : false} position={pointer.position} visible={pointer.visible} - onPointerDown={pointer.id === userId && handleOwnPointerDown} - onPointerMove={pointer.id === userId && handleOwnPointerMove} - onPointerUp={pointer.id === userId && handleOwnPointerUp} + onPointerDown={ + pointer.id === userId ? handleOwnPointerDown : undefined + } + onPointerMove={ + pointer.id === userId ? handleOwnPointerMove : undefined + } + onPointerUp={pointer.id === userId ? handleOwnPointerUp : undefined} color={pointer.color} /> ))} diff --git a/src/network/NetworkedParty.tsx b/src/network/NetworkedParty.tsx index 79d47c6..7b3d068 100644 --- a/src/network/NetworkedParty.tsx +++ b/src/network/NetworkedParty.tsx @@ -1,14 +1,12 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { useToasts } from "react-toast-notifications"; -// Load session for auto complete -import Session, { SessionPeer } from "./Session"; +import Session, { PeerTrackAddedEvent, PeerTrackRemovedEvent } from "./Session"; import { isStreamStopped, omit } from "../helpers/shared"; import { useParty } from "../contexts/PartyContext"; import Party from "../components/party/Party"; -import { PartyState } from "../components/party/PartyState"; /** * @typedef {object} NetworkedPartyProps @@ -16,13 +14,13 @@ import { PartyState } from "../components/party/PartyState"; * @property {Session} session */ -type NetworkedPartyProps = { gameId: string, session: Session } +type NetworkedPartyProps = { gameId: string; session: Session }; /** * @param {NetworkedPartyProps} props */ -function NetworkedParty(props: NetworkedPartyProps) { - const partyState: PartyState = useParty(); +function NetworkedParty({ gameId, session }: NetworkedPartyProps) { + const partyState = useParty(); const [stream, setStream] = useState(null); const [partyStreams, setPartyStreams] = useState({}); @@ -35,7 +33,9 @@ function NetworkedParty(props: NetworkedPartyProps) { // Only add the audio track of the stream to the remote peer if (track.kind === "audio") { for (let player of Object.values(partyState)) { - props.session.startStreamTo(player.sessionId, track, localStream); + if (player.sessionId) { + session.startStreamTo(player.sessionId, track, localStream); + } } } } @@ -50,12 +50,14 @@ function NetworkedParty(props: NetworkedPartyProps) { // Only sending audio so only remove the audio track if (track.kind === "audio") { for (let player of Object.values(partyState)) { - props.session.endStreamTo(player.sessionId, track, localStream); + if (player.sessionId) { + session.endStreamTo(player.sessionId, track, localStream); + } } } } }, - [props.session, partyState] + [session, partyState] ); // Keep a reference to players who have just joined to show the joined notification @@ -77,7 +79,7 @@ function NetworkedParty(props: NetworkedPartyProps) { const tracks = stream.getTracks(); for (let track of tracks) { if (track.kind === "audio") { - props.session.startStreamTo(sessionId, track, stream); + session.startStreamTo(sessionId, track, stream); } } } @@ -92,14 +94,20 @@ function NetworkedParty(props: NetworkedPartyProps) { } } - function handlePeerTrackAdded({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream}) { + function handlePeerTrackAdded({ + peer, + stream: remoteStream, + }: PeerTrackAddedEvent) { setPartyStreams((prevStreams) => ({ ...prevStreams, [peer.id]: remoteStream, })); } - function handlePeerTrackRemoved({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream }) { + function handlePeerTrackRemoved({ + peer, + stream: remoteStream, + }: PeerTrackRemovedEvent) { if (isStreamStopped(remoteStream)) { setPartyStreams((prevStreams) => omit(prevStreams, [peer.id])); } else { @@ -110,16 +118,16 @@ function NetworkedParty(props: NetworkedPartyProps) { } } - props.session.on("playerJoined", handlePlayerJoined); - props.session.on("playerLeft", handlePlayerLeft); - props.session.on("peerTrackAdded", handlePeerTrackAdded); - props.session.on("peerTrackRemoved", handlePeerTrackRemoved); + session.on("playerJoined", handlePlayerJoined); + session.on("playerLeft", handlePlayerLeft); + session.on("peerTrackAdded", handlePeerTrackAdded); + session.on("peerTrackRemoved", handlePeerTrackRemoved); return () => { - props.session.off("playerJoined", handlePlayerJoined); - props.session.off("playerLeft", handlePlayerLeft); - props.session.off("peerTrackAdded", handlePeerTrackAdded); - props.session.off("peerTrackRemoved", handlePeerTrackRemoved); + session.off("playerJoined", handlePlayerJoined); + session.off("playerLeft", handlePlayerLeft); + session.off("peerTrackAdded", handlePeerTrackAdded); + session.off("peerTrackRemoved", handlePeerTrackRemoved); }; }); @@ -142,7 +150,7 @@ function NetworkedParty(props: NetworkedPartyProps) { return ( <> void; - -/** - * Session Status Event - Status of the session has changed - * - * @event Session#status - * @property {"ready"|"joining"|"joined"|"offline"|"reconnecting"|"auth"|"needs_update"} status - */ +export type PeerReply = (id: string, data: PeerData, chunkId?: string) => void; /** * * Handles connections to multiple peers - * - * @fires Session#peerConnect - * @fires Session#peerData - * @fires Session#peerTrackAdded - * @fires Session#peerTrackRemoved - * @fires Session#peerDisconnect - * @fires Session#peerError - * @fires Session#status - * @fires Session#playerJoined - * @fires Session#playerLeft - * @fires Session#gameExpired */ class Session extends EventEmitter { /** @@ -73,7 +48,7 @@ class Session extends EventEmitter { return this.socket && this.socket.id; } - _iceServers: string[] = []; + _iceServers: RTCIceServer[] = []; // Store party id and password for reconnect _gameId: string = ""; @@ -133,19 +108,10 @@ class Session extends EventEmitter { /** * Send data to a single peer * - * @param {string} sessionId - The socket id of the player to send to - * @param {string} eventId - The id of the event to send - * @param {object} data - * @param {string=} channel - * @param {string=} chunkId + * @param sessionId - The socket id of the player to send to + * @param eventId - The id of the event to send */ - sendTo( - sessionId: string, - eventId: string, - data, - channel?: string, - chunkId?: string - ) { + sendTo(sessionId: string, eventId: string, data: PeerData, chunkId?: string) { if (!(sessionId in this.peers)) { if (!this._addPeer(sessionId, true)) { return; @@ -156,14 +122,13 @@ class Session extends EventEmitter { this.peers[sessionId].connection.once("connect", () => { this.peers[sessionId].connection.sendObject( { id: eventId, data }, - channel, chunkId ); }); } else { this.peers[sessionId].connection.sendObject( { id: eventId, data }, - channel + chunkId ); } } @@ -241,7 +206,7 @@ class Session extends EventEmitter { * @param {boolean} initiator * @returns {boolean} True if peer was added successfully */ - _addPeer(id: string, initiator: boolean) { + _addPeer(id: string, initiator: boolean): boolean { try { const connection = new Connection({ initiator, @@ -254,11 +219,11 @@ class Session extends EventEmitter { const peer = { id, connection, initiator, ready: false }; - function reply(id: string, data, channel?: string, chunkId?: string) { - peer.connection.sendObject({ id, data }, channel, chunkId); - } + const reply: PeerReply = (id, data, chunkId) => { + peer.connection.sendObject({ id, data }, chunkId); + }; - const handleSignal = (signal) => { + const handleSignal = (signal: SignalData) => { this.socket.emit("signal", JSON.stringify({ to: peer.id, signal })); }; @@ -266,105 +231,54 @@ class Session extends EventEmitter { if (peer.id in this.peers) { this.peers[peer.id].ready = true; } - /** - * Peer Connect Event - A peer has connected - * - * @event Session#peerConnect - * @type {object} - * @property {SessionPeer} peer - * @property {peerReply} reply - */ - this.emit("peerConnect", { peer, reply }); + const peerConnectEvent: PeerConnectEvent = { + peer, + reply, + }; + this.emit("peerConnect", peerConnectEvent); }; - const handleDataComplete = (data) => { - /** - * Peer Data Event - Data received by a peer - * - * @event Session#peerData - * @type {object} - * @property {SessionPeer} peer - * @property {string} id - * @property {object} data - * @property {peerReply} reply - */ - let peerDataEvent: { - peer: SessionPeer; - id: string; - data; - reply: peerReply; - } = { + const handleDataComplete = (data: any) => { + const peerDataEvent: PeerDataEvent = { peer, id: data.id, data: data.data, reply: reply, }; - console.log(`Data: ${JSON.stringify(data)}`); this.emit("peerData", peerDataEvent); }; - const handleDataProgress = ({ - id, - count, - total, - }: { - id: string; - count: number; - total: number; - }) => { - this.emit("peerDataProgress", { + const handleDataProgress = ({ id, count, total }: DataProgressEvent) => { + const peerDataProgressEvent: PeerDataProgressEvent = { peer, id, count, total, reply, - }); + }; + + this.emit("peerDataProgress", peerDataProgressEvent); }; const handleTrack = (track: MediaStreamTrack, stream: MediaStream) => { - /** - * Peer Track Added Event - A `MediaStreamTrack` was added by a peer - * - * @event Session#peerTrackAdded - * @type {object} - * @property {SessionPeer} peer - * @property {MediaStreamTrack} track - * @property {MediaStream} stream - */ - let peerTrackAddedEvent: { - peer: SessionPeer; - track: MediaStreamTrack; - stream: MediaStream; - } = { peer, track, stream }; + const peerTrackAddedEvent: PeerTrackAddedEvent = { + peer, + track, + stream, + }; this.emit("peerTrackAdded", peerTrackAddedEvent); track.addEventListener("mute", () => { - /** - * Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer - * - * @event Session#peerTrackRemoved - * @type {object} - * @property {SessionPeer} peer - * @property {MediaStreamTrack} track - * @property {MediaStream} stream - */ - let peerTrackRemovedEvent: { - peer: SessionPeer; - track: MediaStreamTrack; - stream: MediaStream; - } = { peer, track, stream }; + const peerTrackRemovedEvent: PeerTrackRemovedEvent = { + peer, + track, + stream, + }; this.emit("peerTrackRemoved", peerTrackRemovedEvent); }); }; const handleClose = () => { - /** - * Peer Disconnect Event - A peer has disconnected - * - * @event Session#peerDisconnect - * @type {object} - * @property {SessionPeer} peer - */ - let peerDisconnectEvent: { peer: SessionPeer } = { peer }; + const peerDisconnectEvent: PeerDisconnectEvent = { peer }; this.emit("peerDisconnect", peerDisconnectEvent); if (peer.id in this.peers) { peer.connection.destroy(); @@ -372,16 +286,8 @@ class Session extends EventEmitter { } }; - const handleError = (error: Error) => { - /** - * Peer Error Event - An error occured with a peer connection - * - * @event Session#peerError - * @type {object} - * @property {SessionPeer} peer - * @property {Error} error - */ - let peerErrorEvent: { peer: SessionPeer; error: Error } = { + const handleError = (error: PeerError) => { + const peerErrorEvent: PeerErrorEvent = { peer, error, }; @@ -418,31 +324,14 @@ class Session extends EventEmitter { } _handleGameExpired() { - /** - * Game Expired Event - A joining game has expired - * - * @event Session#gameExpired - */ this.emit("gameExpired"); } _handlePlayerJoined(id: string) { - /** - * Player Joined Event - A player has joined the game - * - * @event Session#playerJoined - * @property {string} id - */ this.emit("playerJoined", id); } _handlePlayerLeft(id: string) { - /** - * Player Left Event - A player has left the game - * - * @event Session#playerLeft - * @property {string} id - */ this.emit("playerLeft", id); if (id in this.peers) { this.peers[id].connection.destroy(); @@ -450,7 +339,7 @@ class Session extends EventEmitter { } } - _handleSignal(data) { + _handleSignal(data: { from: string; signal: SignalData }) { const { from, signal } = data; if (!(from in this.peers)) { if (!this._addPeer(from, false)) { @@ -484,14 +373,96 @@ class Session extends EventEmitter { } _handleForceUpdate() { - /** - * Force Update Event - An update has been released - * - * @event Session#forceUpdate - */ this.socket.disconnect(); this.emit("status", "needs_update"); } } +export type PeerConnectEvent = { + peer: SessionPeer; + reply: PeerReply; +}; +export type PeerConnectEventHandler = (event: PeerConnectEvent) => void; + +export type PeerDataEvent = { + peer: SessionPeer; + id: string; + data: PeerData; + reply: PeerReply; +}; +export type PeerDataEventHandler = (event: PeerDataEvent) => void; + +export type PeerDataProgressEvent = { + peer: SessionPeer; + id: string; + count: number; + total: number; + reply: PeerReply; +}; +export type PeerDataProgressEventHandler = ( + event: PeerDataProgressEvent +) => void; + +export type PeerTrackAddedEvent = { + peer: SessionPeer; + track: MediaStreamTrack; + stream: MediaStream; +}; +export type PeerTrackAddedEventHandler = (event: PeerTrackAddedEvent) => void; + +export type PeerTrackRemovedEvent = { + peer: SessionPeer; + track: MediaStreamTrack; + stream: MediaStream; +}; +export type PeerTrackRemovedEventHandler = ( + event: PeerTrackRemovedEvent +) => void; + +export type PeerDisconnectEvent = { peer: SessionPeer }; +export type PeerDisconnectEventHandler = (event: PeerDisconnectEvent) => void; + +export type PeerError = Error & { code: string }; +export type PeerErrorEvent = { peer: SessionPeer; error: PeerError }; +export type PeerErrorEventHandler = (event: PeerErrorEvent) => void; + +export type SessionStatus = + | "ready" + | "joining" + | "joined" + | "offline" + | "reconnecting" + | "auth" + | "needs_update"; +export type SessionStatusHandler = (status: SessionStatus) => void; + +export type PlayerJoinedHandler = (id: string) => void; +export type PlayerLeftHandler = (id: string) => void; +export type GameExpiredHandler = () => void; + +declare interface Session { + /** Peer Connect Event - A peer has connected */ + on(event: "peerConnect", listener: PeerConnectEventHandler): this; + /** Peer Data Event - Data received by a peer */ + on(event: "peerData", listener: PeerDataEventHandler): this; + /** Peer Data Progress Event - Part of some data received by a peer */ + on(event: "peerDataProgress", listener: PeerDataProgressEventHandler): this; + /** Peer Track Added Event - A `MediaStreamTrack` was added by a peer */ + on(event: "peerTrackAdded", listener: PeerTrackAddedEventHandler): this; + /** Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer */ + on(event: "peerTrackRemoved", listener: PeerTrackRemovedEventHandler): this; + /** Peer Disconnect Event - A peer has disconnected */ + on(event: "peerDisconnect", listener: PeerDisconnectEventHandler): this; + /** Peer Error Event - An error occured with a peer connection */ + on(event: "peerError", listener: PeerErrorEventHandler): this; + /** Session Status Event - Status of the session has changed */ + on(event: "status", listener: SessionStatusHandler): this; + /** Player Joined Event - A player has joined the game */ + on(event: "playerJoined", listener: PlayerJoinedHandler): this; + /** Player Left Event - A player has left the game */ + on(event: "playerLeft", listener: PlayerLeftHandler): this; + /** Game Expired Event - A joining game has expired */ + on(event: "gameExpired", listener: GameExpiredHandler): this; +} + export default Session; diff --git a/src/routes/Game.tsx b/src/routes/Game.tsx index 09115e9..9d9e20e 100644 --- a/src/routes/Game.tsx +++ b/src/routes/Game.tsx @@ -28,7 +28,7 @@ import { MapLoadingProvider } from "../contexts/MapLoadingContext"; import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens"; import NetworkedParty from "../network/NetworkedParty"; -import Session from "../network/Session"; +import Session, { PeerErrorEvent, SessionStatus } from "../network/Session"; function Game() { const { id: gameId }: { id: string } = useParams(); @@ -36,7 +36,7 @@ function Game() { const { databaseStatus } = useDatabase(); const [session] = useState(new Session()); - const [sessionStatus, setSessionStatus] = useState(); + const [sessionStatus, setSessionStatus] = useState(); useEffect(() => { async function connect() { @@ -50,9 +50,9 @@ function Game() { }, [session]); // Handle session errors - const [peerError, setPeerError] = useState(null); + const [peerError, setPeerError] = useState(null); useEffect(() => { - function handlePeerError({ error }) { + function handlePeerError({ error }: PeerErrorEvent) { if (error.code === "ERR_WEBRTC_SUPPORT") { setPeerError("WebRTC not supported."); } else if (error.code === "ERR_CREATE_OFFER") { @@ -66,7 +66,7 @@ function Game() { }, [session]); useEffect(() => { - function handleStatus(status: any) { + function handleStatus(status: SessionStatus) { setSessionStatus(status); } diff --git a/src/routes/Home.js b/src/routes/Home.tsx similarity index 97% rename from src/routes/Home.js rename to src/routes/Home.tsx index 17c93f5..4747936 100644 --- a/src/routes/Home.js +++ b/src/routes/Home.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { Flex, Button, Image, Text, IconButton, Link } from "theme-ui"; import { useHistory } from "react-router-dom"; @@ -23,9 +23,8 @@ import owlington from "../images/Owlington.png"; function Home() { const [isStartModalOpen, setIsStartModalOpen] = useState(false); const [isJoinModalOpen, setIsJoinModalOpen] = useState(false); - const [isGettingStartedModalOpen, setIsGettingStartedModalOpen] = useState( - false - ); + const [isGettingStartedModalOpen, setIsGettingStartedModalOpen] = + useState(false); // Reset password on visiting home const { setPassword } = useAuth(); @@ -80,6 +79,7 @@ function Home() {