Typescript

This commit is contained in:
Mitchell McCaffrey 2021-07-17 17:25:41 +10:00
parent 97734a2f55
commit 2a053f4854
33 changed files with 412 additions and 419 deletions

View File

@ -58,7 +58,7 @@
"react-toast-notifications": "^2.4.3", "react-toast-notifications": "^2.4.3",
"react-use-gesture": "^9.1.3", "react-use-gesture": "^9.1.3",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"simple-peer": "feross/simple-peer#694/head", "simple-peer": "^9.11.0",
"simplebar-react": "^2.1.0", "simplebar-react": "^2.1.0",
"simplify-js": "^1.2.4", "simplify-js": "^1.2.4",
"socket.io-client": "^4.1.2", "socket.io-client": "^4.1.2",
@ -110,7 +110,7 @@
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-select": "^4.0.17", "@types/react-select": "^4.0.17",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"@types/simple-peer": "^9.6.3", "@types/simple-peer": "^9.11.1",
"@types/uuid": "^8.3.1", "@types/uuid": "^8.3.1",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"worker-loader": "^3.0.8" "worker-loader": "^3.0.8"

View File

@ -47,8 +47,8 @@ import { TokenDraggingOptions, TokenMenuOptions } from "../../types/Token";
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note"; import { Note, NoteDraggingOptions, NoteMenuOptions } from "../../types/Note";
type MapProps = { type MapProps = {
map: Map; map: Map | null;
mapState: MapState; mapState: MapState | null;
mapActions: MapActions; mapActions: MapActions;
onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: MapTokenStateRemoveHandler; onMapTokenStateRemove: MapTokenStateRemoveHandler;
@ -66,7 +66,7 @@ type MapProps = {
allowFogDrawing: boolean; allowFogDrawing: boolean;
allowMapChange: boolean; allowMapChange: boolean;
allowNoteEditing: boolean; allowNoteEditing: boolean;
disabledTokens: string[]; disabledTokens: Record<string, boolean>;
session: Session; session: Session;
}; };
@ -244,9 +244,7 @@ function Map({
onRequestClose={() => setIsTokenMenuOpen(false)} onRequestClose={() => setIsTokenMenuOpen(false)}
onTokenStateChange={onMapTokenStateChange} onTokenStateChange={onMapTokenStateChange}
tokenState={ tokenState={
tokenMenuOptions && tokenMenuOptions && mapState?.tokens[tokenMenuOptions.tokenStateId]
mapState &&
mapState.tokens[tokenMenuOptions.tokenStateId]
} }
tokenImage={tokenMenuOptions && tokenMenuOptions.tokenImage} tokenImage={tokenMenuOptions && tokenMenuOptions.tokenImage}
map={map} map={map}
@ -372,9 +370,7 @@ function Map({
isOpen={isNoteMenuOpen} isOpen={isNoteMenuOpen}
onRequestClose={() => setIsNoteMenuOpen(false)} onRequestClose={() => setIsNoteMenuOpen(false)}
onNoteChange={onMapNoteChange} onNoteChange={onMapNoteChange}
note={ note={noteMenuOptions && mapState?.notes[noteMenuOptions.noteId]}
noteMenuOptions && mapState && mapState.notes[noteMenuOptions.noteId]
}
noteNode={noteMenuOptions?.noteNode} noteNode={noteMenuOptions?.noteNode}
map={map} map={map}
/> />

View File

@ -32,8 +32,8 @@ import { Settings } from "../../types/Settings";
type MapControlsProps = { type MapControlsProps = {
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
currentMap?: Map; currentMap: Map | null;
currentMapState?: MapState; currentMapState: MapState | null;
selectedToolId: MapToolId; selectedToolId: MapToolId;
onSelectedToolChange: (toolId: MapToolId) => void; onSelectedToolChange: (toolId: MapToolId) => void;
toolSettings: Settings; toolSettings: Settings;

View File

@ -37,7 +37,7 @@ export type DrawingAddEventHanlder = (drawing: Drawing) => void;
export type DrawingsRemoveEventHandler = (drawingIds: string[]) => void; export type DrawingsRemoveEventHandler = (drawingIds: string[]) => void;
type MapDrawingProps = { type MapDrawingProps = {
map: Map; map: Map | null;
drawings: Drawing[]; drawings: Drawing[];
onDrawingAdd: DrawingAddEventHanlder; onDrawingAdd: DrawingAddEventHanlder;
onDrawingsRemove: DrawingsRemoveEventHandler; onDrawingsRemove: DrawingsRemoveEventHandler;
@ -85,7 +85,7 @@ function MapDrawing({
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
function getBrushPosition() { function getBrushPosition() {
if (!mapStage) { if (!mapStage || !map) {
return; return;
} }
const mapImage = mapStage.findOne("#mapImage"); const mapImage = mapStage.findOne("#mapImage");

View File

@ -20,7 +20,7 @@ import {
} from "../../types/Events"; } from "../../types/Events";
type MapEditBarProps = { type MapEditBarProps = {
currentMap?: Map; currentMap: Map | null;
disabled: boolean; disabled: boolean;
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
@ -79,7 +79,7 @@ function MapEditBar({
await removeMaps(selectedMapIds); await removeMaps(selectedMapIds);
// Removed the map from the map screen if needed // Removed the map from the map screen if needed
if (currentMap && selectedMapIds.includes(currentMap.id)) { if (currentMap && selectedMapIds.includes(currentMap.id)) {
onMapChange(undefined, undefined); onMapChange(null, null);
} }
onLoad(false); onLoad(false);
} }

View File

@ -58,7 +58,7 @@ type FogEditEventHandler = (edit: Partial<Fog>[]) => void;
type FogErrorEventHandler = (message: string) => void; type FogErrorEventHandler = (message: string) => void;
type MapFogProps = { type MapFogProps = {
map: Map; map: Map | null;
shapes: Fog[]; shapes: Fog[];
onShapesAdd: FogAddEventHandler; onShapesAdd: FogAddEventHandler;
onShapesCut: FogCutEventHandler; onShapesCut: FogCutEventHandler;
@ -354,7 +354,7 @@ function MapFog({
x: mapWidth, x: mapWidth,
y: mapHeight, y: mapHeight,
}); });
if (map.snapToGrid) { if (map?.snapToGrid) {
guides.push( guides.push(
...getGuidesFromGridCell( ...getGuidesFromGridCell(
absoluteBrushPosition, absoluteBrushPosition,

View File

@ -25,8 +25,8 @@ import { MapState } from "../../types/MapState";
type SelectedToolChangeEventHanlder = (tool: MapToolId) => void; type SelectedToolChangeEventHanlder = (tool: MapToolId) => void;
type MapInteractionProps = { type MapInteractionProps = {
map: Map; map: Map | null;
mapState: MapState; mapState: MapState | null;
children?: React.ReactNode; children?: React.ReactNode;
controls: React.ReactNode; controls: React.ReactNode;
selectedToolId: MapToolId; selectedToolId: MapToolId;

View File

@ -29,7 +29,7 @@ import { Map } from "../../types/Map";
import { PointsData } from "../../types/Drawing"; import { PointsData } from "../../types/Drawing";
type MapMeasureProps = { type MapMeasureProps = {
map: Map; map: Map | null;
active: boolean; active: boolean;
}; };
@ -74,7 +74,7 @@ function MapMeasure({ map, active }: MapMeasureProps) {
if (!position) { if (!position) {
return; return;
} }
if (map.snapToGrid) { if (map?.snapToGrid) {
position = snapPositionToGrid(position); position = snapPositionToGrid(position);
} }
return Vector2.divide(position, { return Vector2.divide(position, {

View File

@ -26,7 +26,7 @@ import {
const defaultNoteSize = 2; const defaultNoteSize = 2;
type MapNoteProps = { type MapNoteProps = {
map: Map; map: Map | null;
active: boolean; active: boolean;
onNoteAdd: NoteAddEventHander; onNoteAdd: NoteAddEventHander;
onNoteChange: NoteChangeEventHandler; onNoteChange: NoteChangeEventHandler;
@ -75,7 +75,7 @@ function MapNotes({
if (!position) { if (!position) {
return; return;
} }
if (map.snapToGrid) { if (map?.snapToGrid) {
position = snapPositionToGrid(position); position = snapPositionToGrid(position);
} }
return Vector2.divide(position, { return Vector2.divide(position, {

View File

@ -1,5 +1,3 @@
import React from "react";
import MapTile from "./MapTile"; import MapTile from "./MapTile";
import MapTileGroup from "./MapTileGroup"; import MapTileGroup from "./MapTileGroup";

View File

@ -18,7 +18,7 @@ type MapTokensProps = {
onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateChange: TokenStateChangeEventHandler;
onTokenMenuOpen: TokenMenuOpenChangeEventHandler; onTokenMenuOpen: TokenMenuOpenChangeEventHandler;
selectedToolId: MapToolId; selectedToolId: MapToolId;
disabledTokens: string[]; disabledTokens: Record<string, boolean>;
}; };
function MapTokens({ function MapTokens({

View File

@ -16,8 +16,8 @@ import { MapState } from "../../types/MapState";
type SelectMapButtonProps = { type SelectMapButtonProps = {
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
currentMap?: Map; currentMap: Map | null;
currentMapState?: MapState; currentMapState: MapState | null;
disabled: boolean; disabled: boolean;
}; };

View File

@ -28,7 +28,7 @@ const defaultFontSize = 16;
type NoteProps = { type NoteProps = {
note: NoteType; note: NoteType;
map: Map; map: Map | null;
onNoteChange?: NoteChangeEventHandler; onNoteChange?: NoteChangeEventHandler;
onNoteMenuOpen?: NoteMenuOpenEventHandler; onNoteMenuOpen?: NoteMenuOpenEventHandler;
draggable: boolean; draggable: boolean;
@ -72,20 +72,22 @@ function Note({
function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) { function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) {
const noteGroup = event.target; const noteGroup = event.target;
// Snap to corners of grid // Snap to corners of grid
if (map.snapToGrid) { if (map?.snapToGrid) {
noteGroup.position(snapPositionToGrid(noteGroup.position())); noteGroup.position(snapPositionToGrid(noteGroup.position()));
} }
} }
function handleDragEnd(event: Konva.KonvaEventObject<DragEvent>) { function handleDragEnd(event: Konva.KonvaEventObject<DragEvent>) {
const noteGroup = event.target; const noteGroup = event.target;
onNoteChange?.({ if (userId) {
...note, onNoteChange?.({
x: noteGroup.x() / mapWidth, ...note,
y: noteGroup.y() / mapHeight, x: noteGroup.x() / mapWidth,
lastModifiedBy: userId, y: noteGroup.y() / mapHeight,
lastModified: Date.now(), lastModifiedBy: userId,
}); lastModified: Date.now(),
});
}
onNoteDragEnd?.(event, note.id); onNoteDragEnd?.(event, note.id);
setPreventMapInteraction(false); setPreventMapInteraction(false);
} }
@ -103,7 +105,7 @@ function Note({
if (draggable) { if (draggable) {
setPreventMapInteraction(true); setPreventMapInteraction(true);
} }
if (note.locked && map.owner === userId) { if (note.locked && map?.owner === userId) {
notePointerDownTimeRef.current = event.evt.timeStamp; notePointerDownTimeRef.current = event.evt.timeStamp;
} }
} }
@ -114,7 +116,7 @@ function Note({
} }
// Check note click when locked and we are the map owner // Check note click when locked and we are the map owner
// We can't use onClick because that doesn't check pointer distance // 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 // If down and up time is small trigger a click
const delta = event.evt.timeStamp - notePointerDownTimeRef.current; const delta = event.evt.timeStamp - notePointerDownTimeRef.current;
if (delta < 300) { if (delta < 300) {

View File

@ -35,7 +35,7 @@ type NoteMenuProps = {
note?: Note; note?: Note;
noteNode?: Konva.Node; noteNode?: Konva.Node;
onNoteChange: NoteChangeEventHandler; onNoteChange: NoteChangeEventHandler;
map: Map; map: Map | null;
}; };
function NoteMenu({ function NoteMenu({

View File

@ -10,12 +10,6 @@ import SettingsButton from "../SettingsButton";
import StartTimerButton from "./StartTimerButton"; import StartTimerButton from "./StartTimerButton";
import Timer from "./Timer"; import Timer from "./Timer";
import DiceTrayButton from "./DiceTrayButton"; import DiceTrayButton from "./DiceTrayButton";
import {
PartyState,
PlayerDice,
PlayerInfo,
Timer as PartyTimer,
} from "./PartyState";
import useSetting from "../../hooks/useSetting"; import useSetting from "../../hooks/useSetting";
@ -36,10 +30,10 @@ function Party({
onStreamEnd; onStreamEnd;
}) { }) {
const setPlayerState = usePlayerUpdater(); const setPlayerState = usePlayerUpdater();
const playerState: PlayerInfo = usePlayerState(); const playerState = usePlayerState();
const partyState: PartyState = useParty(); const partyState = useParty();
const [fullScreen] = useSetting("map.fullScreen"); const [fullScreen] = useSetting<boolean>("map.fullScreen");
const [shareDice, setShareDice] = useSetting("dice.shareDice"); const [shareDice, setShareDice] = useSetting("dice.shareDice");
function handleTimerStart(newTimer: PartyTimer) { function handleTimerStart(newTimer: PartyTimer) {

View File

@ -6,7 +6,7 @@ import Slider from "../Slider";
import MapMenu from "../map/MapMenu"; import MapMenu from "../map/MapMenu";
import colors, { colorOptions } from "../../helpers/colors"; import colors, { Color, colorOptions } from "../../helpers/colors";
import usePrevious from "../../hooks/usePrevious"; import usePrevious from "../../hooks/usePrevious";
@ -27,10 +27,10 @@ import { Map } from "../../types/Map";
type TokenMenuProps = { type TokenMenuProps = {
isOpen: boolean; isOpen: boolean;
onRequestClose: RequestCloseEventHandler; onRequestClose: RequestCloseEventHandler;
tokenState: TokenState; tokenState?: TokenState;
tokenImage: Konva.Node; tokenImage?: Konva.Node;
onTokenStateChange: TokenStateChangeEventHandler; onTokenStateChange: TokenStateChangeEventHandler;
map: Map; map: Map | null;
}; };
const defaultTokenMaxSize = 6; const defaultTokenMaxSize = 6;
@ -74,7 +74,7 @@ function TokenMenu({
tokenState && onTokenStateChange({ [tokenState.id]: { label: label } }); tokenState && onTokenStateChange({ [tokenState.id]: { label: label } });
} }
function handleStatusChange(status: string) { function handleStatusChange(status: Color) {
if (!tokenState) { if (!tokenState) {
return; return;
} }

View File

@ -209,8 +209,8 @@ export function AssetURLsProvider({ children }: { children: React.ReactNode }) {
* Helper function to load either file or default asset into a URL * Helper function to load either file or default asset into a URL
*/ */
export function useAssetURL( export function useAssetURL(
assetId: string, assetId: string | null,
type: "file" | "default", type: "file" | "default" | null,
defaultSources: Record<string, string>, defaultSources: Record<string, string>,
unknownSource?: string unknownSource?: string
) { ) {
@ -227,9 +227,8 @@ export function useAssetURL(
if (!assetId || type !== "file") { if (!assetId || type !== "file") {
return; return;
} }
const updateAssetURL = () => {
function updateAssetURL() { const increaseReferences = (prevURLs: AssetURLs): AssetURLs => {
function increaseReferences(prevURLs: AssetURLs): AssetURLs {
return { return {
...prevURLs, ...prevURLs,
[assetId]: { [assetId]: {
@ -237,14 +236,14 @@ export function useAssetURL(
references: prevURLs[assetId].references + 1, references: prevURLs[assetId].references + 1,
}, },
}; };
} };
function createReference(prevURLs: AssetURLs): AssetURLs { const createReference = (prevURLs: AssetURLs): AssetURLs => {
return { return {
...prevURLs, ...prevURLs,
[assetId]: { url: null, id: assetId, references: 1 }, [assetId]: { url: null, id: assetId, references: 1 },
}; };
} };
setAssetURLs?.((prevURLs) => { setAssetURLs?.((prevURLs) => {
if (assetId in prevURLs) { if (assetId in prevURLs) {
// Check if the asset url is already added and increase references // Check if the asset url is already added and increase references
@ -253,7 +252,7 @@ export function useAssetURL(
return createReference(prevURLs); return createReference(prevURLs);
} }
}); });
} };
updateAssetURL(); updateAssetURL();
@ -307,22 +306,22 @@ type DefaultData = {
* Load a map or token into a URL taking into account a thumbnail and multiple resolutions * Load a map or token into a URL taking into account a thumbnail and multiple resolutions
*/ */
export function useDataURL( export function useDataURL(
data: FileData | DefaultData, data: FileData | DefaultData | null,
defaultSources: Record<string, string>, defaultSources: Record<string, string>,
unknownSource: string | undefined = undefined, unknownSource: string | undefined = undefined,
thumbnail = false thumbnail = false
) { ) {
const [assetId, setAssetId] = useState<string>(); const [assetId, setAssetId] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (!data) {
return;
}
function loadAssetId() { function loadAssetId() {
if (!data) {
return;
}
if (data.type === "default") { if (data.type === "default") {
setAssetId(data.key); setAssetId(data.key);
} else { } else {
if (thumbnail) { if (thumbnail && data.thumbnail) {
setAssetId(data.thumbnail); setAssetId(data.thumbnail);
} else if ( } else if (
data.resolutions && data.resolutions &&
@ -340,8 +339,8 @@ export function useDataURL(
}, [data, thumbnail]); }, [data, thumbnail]);
const assetURL = useAssetURL( const assetURL = useAssetURL(
assetId || "", assetId || null,
data?.type, data?.type || null,
defaultSources, defaultSources,
unknownSource unknownSource
); );

View File

@ -8,7 +8,7 @@ import { getRandomMonster } from "../helpers/monsters";
import useNetworkedState, { import useNetworkedState, {
SetNetworkedState, SetNetworkedState,
} from "../hooks/useNetworkedState"; } from "../hooks/useNetworkedState";
import Session from "../network/Session"; import Session, { SessionStatus } from "../network/Session";
import { PlayerState } from "../types/PlayerState"; import { PlayerState } from "../types/PlayerState";
export const PlayerStateContext = export const PlayerStateContext =
@ -103,7 +103,7 @@ export function PlayerProvider({ session, children }: PlayerProviderProps) {
updateSessionId(); updateSessionId();
} }
function handleSocketStatus(status: string) { function handleSocketStatus(status: SessionStatus) {
if (status === "joined") { if (status === "joined") {
updateSessionId(); updateSessionId();
} }

View File

@ -245,10 +245,10 @@ export function getGridUpdatedInset(
/** /**
* Get the max zoom for a grid * Get the max zoom for a grid
* @param {Grid} grid * @param {Grid=} grid
* @returns {number} * @returns {number}
*/ */
export function getGridMaxZoom(grid: Grid): number { export function getGridMaxZoom(grid?: Grid): number {
if (!grid) { if (!grid) {
return 10; return 10;
} }

View File

@ -11,7 +11,7 @@ type ImageData = {
}; };
function useImageCenter( function useImageCenter(
data: ImageData, data: ImageData | null,
stageRef: MapStage, stageRef: MapStage,
stageWidth: number, stageWidth: number,
stageHeight: number, stageHeight: number,

View File

@ -8,7 +8,7 @@ import { mapSources as defaultMapSources } from "../maps";
import { Map } from "../types/Map"; import { Map } from "../types/Map";
function useMapImage( function useMapImage(
map: Map map: Map | null
): [HTMLImageElement | undefined, "loaded" | "loading" | "failed"] { ): [HTMLImageElement | undefined, "loaded" | "loading" | "failed"] {
const mapURL = useDataURL(map, defaultMapSources); const mapURL = useDataURL(map, defaultMapSources);
const [mapImage, mapImageStatus] = useImage(mapURL || ""); const [mapImage, mapImageStatus] = useImage(mapURL || "");

View File

@ -42,7 +42,7 @@ type SelectMapProps = {
onDone: RequestCloseEventHandler; onDone: RequestCloseEventHandler;
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
currentMap?: Map; currentMap: Map | null;
}; };
function SelectMapModal({ function SelectMapModal({
@ -175,12 +175,12 @@ function SelectMapModal({
} }
if (mapId) { if (mapId) {
setIsLoading(true); setIsLoading(true);
const map = await getMap(mapId); const map = (await getMap(mapId)) || null;
const mapState = await getMapState(mapId); const mapState = (await getMapState(mapId)) || null;
onMapChange(map, mapState); onMapChange(map, mapState);
setIsLoading(false); setIsLoading(false);
} else { } else {
onMapChange(undefined, undefined); onMapChange(null, null);
} }
onDone(); onDone();
} }

View File

@ -8,21 +8,37 @@ import blobToBuffer from "../helpers/blobToBuffer";
// http://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/ // http://viblast.com/blog/2015/2/5/webrtc-data-channel-message-size/
const MAX_BUFFER_SIZE = 16000; const MAX_BUFFER_SIZE = 16000;
class Connection extends SimplePeer { type NetworkChunk = {
currentChunks; __chunked: boolean;
dataChannels; 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<string, LocalChunk>;
constructor(props: SimplePeer.Options) {
super(props); super(props);
this.currentChunks = {}; this.currentChunks = {};
this.dataChannels = {};
this.on("data", this.handleData); this.on("data", this.handleData);
this.on("datachannel", this.handleDataChannel);
} }
// Intercept the data event with decoding and chunking support // Intercept the data event with decoding and chunking support
handleData(packed) { handleData(packed: Uint8Array) {
const unpacked = decode(packed); const unpacked = decode(packed) as NetworkChunk;
// If the special property __chunked is set and true // If the special property __chunked is set and true
// The data is a partial chunk of the a larger file // The data is a partial chunk of the a larger file
// So wait until all chunks are collected and assembled // So wait until all chunks are collected and assembled
@ -46,7 +62,6 @@ class Connection extends SimplePeer {
// All chunks have been loaded // All chunks have been loaded
if (chunk.count === chunk.total) { if (chunk.count === chunk.total) {
// Merge chunks with a blob // Merge chunks with a blob
// TODO: Look at a more efficient way to recombine buffer data
const merged = new Blob(chunk.data); const merged = new Blob(chunk.data);
blobToBuffer(merged).then((buffer) => { blobToBuffer(merged).then((buffer) => {
this.emit("dataComplete", decode(buffer)); this.emit("dataComplete", decode(buffer));
@ -62,52 +77,23 @@ class Connection extends SimplePeer {
* Custom send function with encoding, chunking and data channel support * Custom send function with encoding, chunking and data channel support
* Uses `write` to send the data to allow for buffer / backpressure handling * Uses `write` to send the data to allow for buffer / backpressure handling
* @param {any} object * @param {any} object
* @param {string=} channel
* @param {string=} chunkId Optional ID to use for chunking * @param {string=} chunkId Optional ID to use for chunking
*/ */
sendObject(object, channel?: string, chunkId?: string) { sendObject(object: any, chunkId?: string) {
try { try {
const packedData = encode(object); const packedData = encode(object);
const chunks = this.chunk(packedData, chunkId); const chunks = this.chunk(packedData, chunkId);
for (let chunk of chunks) { for (let chunk of chunks) {
if (this.dataChannels[channel]) { this.write(encode(chunk));
this.dataChannels[channel].write(encode(chunk));
} else {
this.write(encode(chunk));
}
} }
} catch (error) { } catch (error) {
console.error(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/ // Converted from https://github.com/peers/peerjs/
/** /** Chunk byte array */
* Chunk byte array chunk(data: Uint8Array, chunkId?: string): NetworkChunk[] {
* @param {Uint8Array} data
* @param {string=} chunkId
* @returns {Uint8Array[]}
*/
chunk(data: Uint8Array, chunkId?: string) {
const chunks = []; const chunks = [];
const size = data.byteLength; const size = data.byteLength;
const total = Math.ceil(size / MAX_BUFFER_SIZE); const total = Math.ceil(size / MAX_BUFFER_SIZE);

View File

@ -14,7 +14,7 @@ import useDebounce from "../hooks/useDebounce";
import useNetworkedState from "../hooks/useNetworkedState"; import useNetworkedState from "../hooks/useNetworkedState";
// Load session for auto complete // Load session for auto complete
import Session from "./Session"; import Session, { PeerDataEvent, PeerDataProgressEvent } from "./Session";
import Action from "../actions/Action"; import Action from "../actions/Action";
@ -38,6 +38,7 @@ import {
import { TokenState } from "../types/TokenState"; import { TokenState } from "../types/TokenState";
import { DrawingState } from "../types/Drawing"; import { DrawingState } from "../types/Drawing";
import { FogState } from "../types/Fog"; import { FogState } from "../types/Fog";
import { Note } from "../types/Note";
const defaultMapActions: MapActions = { const defaultMapActions: MapActions = {
mapDrawActions: [], mapDrawActions: [],
@ -204,7 +205,10 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
} }
}, [currentMap, debouncedMapState, userId, database, updateMapState]); }, [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 // Clear map before sending new one
setCurrentMap(null); setCurrentMap(null);
session.socket?.emit("map", null); session.socket?.emit("map", null);
@ -222,7 +226,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
await loadAssetManifestFromMap(newMap, newMapState); await loadAssetManifestFromMap(newMap, newMapState);
} }
function handleMapReset(newMapState) { function handleMapReset(newMapState: MapState) {
setCurrentMapState(newMapState, true, true); setCurrentMapState(newMapState, true, true);
setMapActions(defaultMapActions); setMapActions(defaultMapActions);
} }
@ -264,7 +268,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
} }
function updateActionIndex( function updateActionIndex(
change, change: number,
indexKey: MapActionsIndexKey, indexKey: MapActionsIndexKey,
actionsKey: MapActionsKey, actionsKey: MapActionsKey,
shapesKey: "drawShapes" | "fogShapes" shapesKey: "drawShapes" | "fogShapes"
@ -288,19 +292,21 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
// Redo // Redo
for (let i = prevIndex + 1; i < newIndex + 1; i++) { for (let i = prevIndex + 1; i < newIndex + 1; i++) {
let action = mapActions[actionsKey][i]; let action = mapActions[actionsKey][i];
shapes = action.execute(shapes); shapes = action.execute(shapes as any);
} }
} else { } else {
// Undo // Undo
for (let i = prevIndex; i > newIndex; i--) { for (let i = prevIndex; i > newIndex; i--) {
let action = mapActions[actionsKey][i]; let action = mapActions[actionsKey][i];
shapes = action.undo(shapes); shapes = action.undo(shapes as any);
} }
} }
return { return {
...prevMapState, ...prevMapState,
[shapesKey]: shapes, [shapesKey]: shapes,
}; };
} else {
return prevMapState;
} }
}); });
@ -342,7 +348,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
} }
// If map changes clear map actions // If map changes clear map actions
const previousMapIdRef = useRef(); const previousMapIdRef = useRef<string>();
useEffect(() => { useEffect(() => {
if (currentMap && currentMap?.id !== previousMapIdRef.current) { if (currentMap && currentMap?.id !== previousMapIdRef.current) {
setMapActions(defaultMapActions); setMapActions(defaultMapActions);
@ -350,21 +356,31 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
} }
}, [currentMap]); }, [currentMap]);
function handleNoteChange(note) { function handleNoteChange(note: Note) {
setCurrentMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => {
...prevMapState, if (!prevMapState) {
notes: { return prevMapState;
...prevMapState.notes, }
[note.id]: note, return {
}, ...prevMapState,
})); notes: {
...prevMapState.notes,
[note.id]: note,
},
};
});
} }
function handleNoteRemove(noteId: string) { function handleNoteRemove(noteId: string) {
setCurrentMapState((prevMapState) => ({ setCurrentMapState((prevMapState) => {
...prevMapState, if (!prevMapState) {
notes: omit(prevMapState.notes, [noteId]), return prevMapState;
})); }
return {
...prevMapState,
notes: omit(prevMapState.notes, [noteId]),
};
});
} }
/** /**
@ -387,6 +403,9 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
} }
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let newMapTokens = { ...prevMapState.tokens }; let newMapTokens = { ...prevMapState.tokens };
for (let tokenState of tokenStates) { for (let tokenState of tokenStates) {
newMapTokens[tokenState.id] = tokenState; newMapTokens[tokenState.id] = tokenState;
@ -395,15 +414,20 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
}); });
} }
function handleMapTokenStateChange(change) { function handleMapTokenStateChange(
change: Record<string, Partial<TokenState>>
) {
if (!currentMapState) { if (!currentMapState) {
return; return;
} }
setCurrentMapState((prevMapState) => { setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let tokens = { ...prevMapState.tokens }; let tokens = { ...prevMapState.tokens };
for (let id in change) { for (let id in change) {
if (id in tokens) { 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) => { setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
const { [tokenState.id]: old, ...rest } = prevMapState.tokens; const { [tokenState.id]: old, ...rest } = prevMapState.tokens;
return { ...prevMapState, tokens: rest }; return { ...prevMapState, tokens: rest };
}); });
} }
useEffect(() => { useEffect(() => {
async function handlePeerData({ async function handlePeerData({ id, data, reply }: PeerDataEvent) {
id,
data,
reply,
}: {
id: string;
data;
reply;
}) {
if (id === "assetRequest") { if (id === "assetRequest") {
const asset = await getAsset(data.id); const asset = await getAsset(data.id);
if (asset) { if (asset) {
reply("assetResponseSuccess", asset, undefined, data.id); reply("assetResponseSuccess", asset, data.id);
} else { } else {
reply("assetResponseFail", data.id, undefined, data.id); reply("assetResponseFail", data.id, data.id);
} }
} }
@ -456,15 +475,11 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
id, id,
total, total,
count, count,
}: { }: PeerDataProgressEvent) {
id: string;
total: number;
count: number;
}) {
assetProgressUpdate({ id, total, count }); assetProgressUpdate({ id, total, count });
} }
async function handleSocketMap(map) { async function handleSocketMap(map?: MapType) {
if (map) { if (map) {
setCurrentMap(map); setCurrentMap(map);
} else { } else {
@ -502,7 +517,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
(currentMapState.editFlags.includes("notes") || (currentMapState.editFlags.includes("notes") ||
currentMap?.owner === userId); currentMap?.owner === userId);
const disabledMapTokens = {}; const disabledMapTokens: Record<string, boolean> = {};
// If we have a map and state and have the token permission disabled // If we have a map and state and have the token permission disabled
// and are not the map owner // and are not the map owner
if ( if (
@ -539,10 +554,10 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
onFogDrawRedo={handleFogDrawRedo} onFogDrawRedo={handleFogDrawRedo}
onMapNoteChange={handleNoteChange} onMapNoteChange={handleNoteChange}
onMapNoteRemove={handleNoteRemove} onMapNoteRemove={handleNoteRemove}
allowMapDrawing={canEditMapDrawing} allowMapDrawing={!!canEditMapDrawing}
allowFogDrawing={canEditFogDrawing} allowFogDrawing={!!canEditFogDrawing}
allowMapChange={canChangeMap} allowMapChange={canChangeMap}
allowNoteEditing={canEditNotes} allowNoteEditing={!!canEditNotes}
disabledTokens={disabledMapTokens} disabledTokens={disabledMapTokens}
session={session} session={session}
/> />

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { Group } from "react-konva"; import { Group } from "react-konva";
import { useUserId } from "../contexts/UserIdContext"; import { useUserId } from "../contexts/UserIdContext";
@ -9,20 +9,31 @@ import Vector2 from "../helpers/Vector2";
import useSetting from "../hooks/useSetting"; import useSetting from "../hooks/useSetting";
import Session from "./Session"; import Session from "./Session";
import { Color } from "../helpers/colors";
import { PointerState } from "../types/Pointer";
// Send pointer updates every 50ms (20fps) // Send pointer updates every 50ms (20fps)
const sendTickRate = 50; const sendTickRate = 50;
function NetworkedMapPointer({ type InterpolatedPointerState = PointerState & { time: number };
session,
active, type PointerInterpolation = {
}: { id: string;
from: InterpolatedPointerState | null;
to: InterpolatedPointerState;
};
type NetworkedMapPointerProps = {
session: Session; session: Session;
active: boolean; active: boolean;
}) { };
function NetworkedMapPointer({ session, active }: NetworkedMapPointerProps) {
const userId = useUserId(); const userId = useUserId();
const [localPointerState, setLocalPointerState] = useState({}); const [localPointerState, setLocalPointerState] = useState<
const [pointerColor] = useSetting("pointer.color"); Record<string, PointerState>
>({});
const [pointerColor] = useSetting<Color>("pointer.color");
const sessionRef = useRef(session); const sessionRef = useRef(session);
useEffect(() => { useEffect(() => {
@ -45,14 +56,12 @@ function NetworkedMapPointer({
// Send pointer updates every sendTickRate to peers to save on bandwidth // Send pointer updates every sendTickRate to peers to save on bandwidth
// We use requestAnimationFrame as setInterval was being blocked during // We use requestAnimationFrame as setInterval was being blocked during
// re-renders on Chrome with Windows // re-renders on Chrome with Windows
const ownPointerUpdateRef: React.MutableRefObject< const ownPointerUpdateRef = useRef<PointerState | null>(null);
{ position; visible: boolean; id; color } | undefined | null
> = useRef();
useEffect(() => { useEffect(() => {
let prevTime = performance.now(); let prevTime = performance.now();
let request = requestAnimationFrame(update); let request = requestAnimationFrame(update);
let counter = 0; let counter = 0;
function update(time) { function update(time: number) {
request = requestAnimationFrame(update); request = requestAnimationFrame(update);
const deltaTime = time - prevTime; const deltaTime = time - prevTime;
counter += deltaTime; counter += deltaTime;
@ -79,7 +88,10 @@ function NetworkedMapPointer({
}; };
}, []); }, []);
function updateOwnPointerState(position, visible: boolean) { function updateOwnPointerState(position: Vector2, visible: boolean) {
if (!userId) {
return;
}
setLocalPointerState((prev) => ({ setLocalPointerState((prev) => ({
...prev, ...prev,
[userId]: { position, visible, id: userId, color: pointerColor }, [userId]: { position, visible, id: userId, color: pointerColor },
@ -92,23 +104,23 @@ function NetworkedMapPointer({
}; };
} }
function handleOwnPointerDown(position) { function handleOwnPointerDown(position: Vector2) {
updateOwnPointerState(position, true); updateOwnPointerState(position, true);
} }
function handleOwnPointerMove(position) { function handleOwnPointerMove(position: Vector2) {
updateOwnPointerState(position, true); updateOwnPointerState(position, true);
} }
function handleOwnPointerUp(position) { function handleOwnPointerUp(position: Vector2) {
updateOwnPointerState(position, false); updateOwnPointerState(position, false);
} }
// Handle pointer data receive // Handle pointer data receive
const interpolationsRef: React.MutableRefObject = useRef({}); const interpolationsRef = useRef<Record<string, PointerInterpolation>>({});
useEffect(() => { useEffect(() => {
// TODO: Handle player disconnect while pointer visible // TODO: Handle player disconnect while pointer visible
function handleSocketPlayerPointer(pointer) { function handleSocketPlayerPointer(pointer: InterpolatedPointerState) {
const interpolations = interpolationsRef.current; const interpolations = interpolationsRef.current;
const id = pointer.id; const id = pointer.id;
if (!(id in interpolations)) { if (!(id in interpolations)) {
@ -154,7 +166,7 @@ function NetworkedMapPointer({
function animate() { function animate() {
request = requestAnimationFrame(animate); request = requestAnimationFrame(animate);
const time = performance.now(); const time = performance.now();
let interpolatedPointerState = {}; let interpolatedPointerState: Record<string, PointerState> = {};
for (let interp of Object.values(interpolationsRef.current)) { for (let interp of Object.values(interpolationsRef.current)) {
if (!interp.from || !interp.to) { if (!interp.from || !interp.to) {
continue; continue;
@ -206,9 +218,13 @@ function NetworkedMapPointer({
active={pointer.id === userId ? active : false} active={pointer.id === userId ? active : false}
position={pointer.position} position={pointer.position}
visible={pointer.visible} visible={pointer.visible}
onPointerDown={pointer.id === userId && handleOwnPointerDown} onPointerDown={
onPointerMove={pointer.id === userId && handleOwnPointerMove} pointer.id === userId ? handleOwnPointerDown : undefined
onPointerUp={pointer.id === userId && handleOwnPointerUp} }
onPointerMove={
pointer.id === userId ? handleOwnPointerMove : undefined
}
onPointerUp={pointer.id === userId ? handleOwnPointerUp : undefined}
color={pointer.color} color={pointer.color}
/> />
))} ))}

View File

@ -1,14 +1,12 @@
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { useToasts } from "react-toast-notifications"; import { useToasts } from "react-toast-notifications";
// Load session for auto complete import Session, { PeerTrackAddedEvent, PeerTrackRemovedEvent } from "./Session";
import Session, { SessionPeer } from "./Session";
import { isStreamStopped, omit } from "../helpers/shared"; import { isStreamStopped, omit } from "../helpers/shared";
import { useParty } from "../contexts/PartyContext"; import { useParty } from "../contexts/PartyContext";
import Party from "../components/party/Party"; import Party from "../components/party/Party";
import { PartyState } from "../components/party/PartyState";
/** /**
* @typedef {object} NetworkedPartyProps * @typedef {object} NetworkedPartyProps
@ -16,13 +14,13 @@ import { PartyState } from "../components/party/PartyState";
* @property {Session} session * @property {Session} session
*/ */
type NetworkedPartyProps = { gameId: string, session: Session } type NetworkedPartyProps = { gameId: string; session: Session };
/** /**
* @param {NetworkedPartyProps} props * @param {NetworkedPartyProps} props
*/ */
function NetworkedParty(props: NetworkedPartyProps) { function NetworkedParty({ gameId, session }: NetworkedPartyProps) {
const partyState: PartyState = useParty(); const partyState = useParty();
const [stream, setStream] = useState<MediaStream | null>(null); const [stream, setStream] = useState<MediaStream | null>(null);
const [partyStreams, setPartyStreams] = useState({}); const [partyStreams, setPartyStreams] = useState({});
@ -35,7 +33,9 @@ function NetworkedParty(props: NetworkedPartyProps) {
// Only add the audio track of the stream to the remote peer // Only add the audio track of the stream to the remote peer
if (track.kind === "audio") { if (track.kind === "audio") {
for (let player of Object.values(partyState)) { 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 // Only sending audio so only remove the audio track
if (track.kind === "audio") { if (track.kind === "audio") {
for (let player of Object.values(partyState)) { 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 // 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(); const tracks = stream.getTracks();
for (let track of tracks) { for (let track of tracks) {
if (track.kind === "audio") { 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) => ({ setPartyStreams((prevStreams) => ({
...prevStreams, ...prevStreams,
[peer.id]: remoteStream, [peer.id]: remoteStream,
})); }));
} }
function handlePeerTrackRemoved({ peer, stream: remoteStream }: { peer: SessionPeer, stream: MediaStream }) { function handlePeerTrackRemoved({
peer,
stream: remoteStream,
}: PeerTrackRemovedEvent) {
if (isStreamStopped(remoteStream)) { if (isStreamStopped(remoteStream)) {
setPartyStreams((prevStreams) => omit(prevStreams, [peer.id])); setPartyStreams((prevStreams) => omit(prevStreams, [peer.id]));
} else { } else {
@ -110,16 +118,16 @@ function NetworkedParty(props: NetworkedPartyProps) {
} }
} }
props.session.on("playerJoined", handlePlayerJoined); session.on("playerJoined", handlePlayerJoined);
props.session.on("playerLeft", handlePlayerLeft); session.on("playerLeft", handlePlayerLeft);
props.session.on("peerTrackAdded", handlePeerTrackAdded); session.on("peerTrackAdded", handlePeerTrackAdded);
props.session.on("peerTrackRemoved", handlePeerTrackRemoved); session.on("peerTrackRemoved", handlePeerTrackRemoved);
return () => { return () => {
props.session.off("playerJoined", handlePlayerJoined); session.off("playerJoined", handlePlayerJoined);
props.session.off("playerLeft", handlePlayerLeft); session.off("playerLeft", handlePlayerLeft);
props.session.off("peerTrackAdded", handlePeerTrackAdded); session.off("peerTrackAdded", handlePeerTrackAdded);
props.session.off("peerTrackRemoved", handlePeerTrackRemoved); session.off("peerTrackRemoved", handlePeerTrackRemoved);
}; };
}); });
@ -142,7 +150,7 @@ function NetworkedParty(props: NetworkedPartyProps) {
return ( return (
<> <>
<Party <Party
gameId={props.gameId} gameId={gameId}
onStreamStart={handleStreamStart} onStreamStart={handleStreamStart}
onStreamEnd={handleStreamEnd} onStreamEnd={handleStreamEnd}
stream={stream} stream={stream}

View File

@ -2,14 +2,13 @@ import io, { Socket } from "socket.io-client";
import msgParser from "socket.io-msgpack-parser"; import msgParser from "socket.io-msgpack-parser";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import Connection from "./Connection"; import Connection, { DataProgressEvent } from "./Connection";
import { omit } from "../helpers/shared"; import { omit } from "../helpers/shared";
import { logError } from "../helpers/logging"; import { logError } from "../helpers/logging";
import { SimplePeerData } from "simple-peer"; import { SignalData } from "simple-peer";
/** /**
* @typedef {object} SessionPeer
* @property {string} id - The socket id of the peer * @property {string} id - The socket id of the peer
* @property {Connection} connection - The actual peer connection * @property {Connection} connection - The actual peer connection
* @property {boolean} initiator - Is this peer the initiator of the connection * @property {boolean} initiator - Is this peer the initiator of the connection
@ -22,37 +21,13 @@ export type SessionPeer = {
ready: boolean; ready: boolean;
}; };
/** export type PeerData = any;
* @callback peerReply
* @param {string} id - The id of the event
* @param {object} data - The data to send
* @param {string=} channel - The channel to send to
* @param {string=} chunkId
*/
type peerReply = (id: string, data: SimplePeerData, channel: string) => void; export type PeerReply = (id: string, data: PeerData, chunkId?: string) => void;
/**
* Session Status Event - Status of the session has changed
*
* @event Session#status
* @property {"ready"|"joining"|"joined"|"offline"|"reconnecting"|"auth"|"needs_update"} status
*/
/** /**
* *
* Handles connections to multiple peers * 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 { class Session extends EventEmitter {
/** /**
@ -73,7 +48,7 @@ class Session extends EventEmitter {
return this.socket && this.socket.id; return this.socket && this.socket.id;
} }
_iceServers: string[] = []; _iceServers: RTCIceServer[] = [];
// Store party id and password for reconnect // Store party id and password for reconnect
_gameId: string = ""; _gameId: string = "";
@ -133,19 +108,10 @@ class Session extends EventEmitter {
/** /**
* Send data to a single peer * Send data to a single peer
* *
* @param {string} sessionId - The socket id of the player to send to * @param sessionId - The socket id of the player to send to
* @param {string} eventId - The id of the event to send * @param eventId - The id of the event to send
* @param {object} data
* @param {string=} channel
* @param {string=} chunkId
*/ */
sendTo( sendTo(sessionId: string, eventId: string, data: PeerData, chunkId?: string) {
sessionId: string,
eventId: string,
data,
channel?: string,
chunkId?: string
) {
if (!(sessionId in this.peers)) { if (!(sessionId in this.peers)) {
if (!this._addPeer(sessionId, true)) { if (!this._addPeer(sessionId, true)) {
return; return;
@ -156,14 +122,13 @@ class Session extends EventEmitter {
this.peers[sessionId].connection.once("connect", () => { this.peers[sessionId].connection.once("connect", () => {
this.peers[sessionId].connection.sendObject( this.peers[sessionId].connection.sendObject(
{ id: eventId, data }, { id: eventId, data },
channel,
chunkId chunkId
); );
}); });
} else { } else {
this.peers[sessionId].connection.sendObject( this.peers[sessionId].connection.sendObject(
{ id: eventId, data }, { id: eventId, data },
channel chunkId
); );
} }
} }
@ -241,7 +206,7 @@ class Session extends EventEmitter {
* @param {boolean} initiator * @param {boolean} initiator
* @returns {boolean} True if peer was added successfully * @returns {boolean} True if peer was added successfully
*/ */
_addPeer(id: string, initiator: boolean) { _addPeer(id: string, initiator: boolean): boolean {
try { try {
const connection = new Connection({ const connection = new Connection({
initiator, initiator,
@ -254,11 +219,11 @@ class Session extends EventEmitter {
const peer = { id, connection, initiator, ready: false }; const peer = { id, connection, initiator, ready: false };
function reply(id: string, data, channel?: string, chunkId?: string) { const reply: PeerReply = (id, data, chunkId) => {
peer.connection.sendObject({ id, data }, channel, chunkId); peer.connection.sendObject({ id, data }, chunkId);
} };
const handleSignal = (signal) => { const handleSignal = (signal: SignalData) => {
this.socket.emit("signal", JSON.stringify({ to: peer.id, signal })); this.socket.emit("signal", JSON.stringify({ to: peer.id, signal }));
}; };
@ -266,105 +231,54 @@ class Session extends EventEmitter {
if (peer.id in this.peers) { if (peer.id in this.peers) {
this.peers[peer.id].ready = true; this.peers[peer.id].ready = true;
} }
/** const peerConnectEvent: PeerConnectEvent = {
* Peer Connect Event - A peer has connected peer,
* reply,
* @event Session#peerConnect };
* @type {object} this.emit("peerConnect", peerConnectEvent);
* @property {SessionPeer} peer
* @property {peerReply} reply
*/
this.emit("peerConnect", { peer, reply });
}; };
const handleDataComplete = (data) => { const handleDataComplete = (data: any) => {
/** const peerDataEvent: PeerDataEvent = {
* 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;
} = {
peer, peer,
id: data.id, id: data.id,
data: data.data, data: data.data,
reply: reply, reply: reply,
}; };
console.log(`Data: ${JSON.stringify(data)}`);
this.emit("peerData", peerDataEvent); this.emit("peerData", peerDataEvent);
}; };
const handleDataProgress = ({ const handleDataProgress = ({ id, count, total }: DataProgressEvent) => {
id, const peerDataProgressEvent: PeerDataProgressEvent = {
count,
total,
}: {
id: string;
count: number;
total: number;
}) => {
this.emit("peerDataProgress", {
peer, peer,
id, id,
count, count,
total, total,
reply, reply,
}); };
this.emit("peerDataProgress", peerDataProgressEvent);
}; };
const handleTrack = (track: MediaStreamTrack, stream: MediaStream) => { const handleTrack = (track: MediaStreamTrack, stream: MediaStream) => {
/** const peerTrackAddedEvent: PeerTrackAddedEvent = {
* Peer Track Added Event - A `MediaStreamTrack` was added by a peer peer,
* track,
* @event Session#peerTrackAdded stream,
* @type {object} };
* @property {SessionPeer} peer
* @property {MediaStreamTrack} track
* @property {MediaStream} stream
*/
let peerTrackAddedEvent: {
peer: SessionPeer;
track: MediaStreamTrack;
stream: MediaStream;
} = { peer, track, stream };
this.emit("peerTrackAdded", peerTrackAddedEvent); this.emit("peerTrackAdded", peerTrackAddedEvent);
track.addEventListener("mute", () => { track.addEventListener("mute", () => {
/** const peerTrackRemovedEvent: PeerTrackRemovedEvent = {
* Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer peer,
* track,
* @event Session#peerTrackRemoved stream,
* @type {object} };
* @property {SessionPeer} peer
* @property {MediaStreamTrack} track
* @property {MediaStream} stream
*/
let peerTrackRemovedEvent: {
peer: SessionPeer;
track: MediaStreamTrack;
stream: MediaStream;
} = { peer, track, stream };
this.emit("peerTrackRemoved", peerTrackRemovedEvent); this.emit("peerTrackRemoved", peerTrackRemovedEvent);
}); });
}; };
const handleClose = () => { const handleClose = () => {
/** const peerDisconnectEvent: PeerDisconnectEvent = { peer };
* Peer Disconnect Event - A peer has disconnected
*
* @event Session#peerDisconnect
* @type {object}
* @property {SessionPeer} peer
*/
let peerDisconnectEvent: { peer: SessionPeer } = { peer };
this.emit("peerDisconnect", peerDisconnectEvent); this.emit("peerDisconnect", peerDisconnectEvent);
if (peer.id in this.peers) { if (peer.id in this.peers) {
peer.connection.destroy(); peer.connection.destroy();
@ -372,16 +286,8 @@ class Session extends EventEmitter {
} }
}; };
const handleError = (error: Error) => { const handleError = (error: PeerError) => {
/** const peerErrorEvent: PeerErrorEvent = {
* 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 } = {
peer, peer,
error, error,
}; };
@ -418,31 +324,14 @@ class Session extends EventEmitter {
} }
_handleGameExpired() { _handleGameExpired() {
/**
* Game Expired Event - A joining game has expired
*
* @event Session#gameExpired
*/
this.emit("gameExpired"); this.emit("gameExpired");
} }
_handlePlayerJoined(id: string) { _handlePlayerJoined(id: string) {
/**
* Player Joined Event - A player has joined the game
*
* @event Session#playerJoined
* @property {string} id
*/
this.emit("playerJoined", id); this.emit("playerJoined", id);
} }
_handlePlayerLeft(id: string) { _handlePlayerLeft(id: string) {
/**
* Player Left Event - A player has left the game
*
* @event Session#playerLeft
* @property {string} id
*/
this.emit("playerLeft", id); this.emit("playerLeft", id);
if (id in this.peers) { if (id in this.peers) {
this.peers[id].connection.destroy(); 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; const { from, signal } = data;
if (!(from in this.peers)) { if (!(from in this.peers)) {
if (!this._addPeer(from, false)) { if (!this._addPeer(from, false)) {
@ -484,14 +373,96 @@ class Session extends EventEmitter {
} }
_handleForceUpdate() { _handleForceUpdate() {
/**
* Force Update Event - An update has been released
*
* @event Session#forceUpdate
*/
this.socket.disconnect(); this.socket.disconnect();
this.emit("status", "needs_update"); 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; export default Session;

View File

@ -28,7 +28,7 @@ import { MapLoadingProvider } from "../contexts/MapLoadingContext";
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens"; import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
import NetworkedParty from "../network/NetworkedParty"; import NetworkedParty from "../network/NetworkedParty";
import Session from "../network/Session"; import Session, { PeerErrorEvent, SessionStatus } from "../network/Session";
function Game() { function Game() {
const { id: gameId }: { id: string } = useParams(); const { id: gameId }: { id: string } = useParams();
@ -36,7 +36,7 @@ function Game() {
const { databaseStatus } = useDatabase(); const { databaseStatus } = useDatabase();
const [session] = useState(new Session()); const [session] = useState(new Session());
const [sessionStatus, setSessionStatus] = useState(); const [sessionStatus, setSessionStatus] = useState<SessionStatus>();
useEffect(() => { useEffect(() => {
async function connect() { async function connect() {
@ -50,9 +50,9 @@ function Game() {
}, [session]); }, [session]);
// Handle session errors // Handle session errors
const [peerError, setPeerError] = useState(null); const [peerError, setPeerError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
function handlePeerError({ error }) { function handlePeerError({ error }: PeerErrorEvent) {
if (error.code === "ERR_WEBRTC_SUPPORT") { if (error.code === "ERR_WEBRTC_SUPPORT") {
setPeerError("WebRTC not supported."); setPeerError("WebRTC not supported.");
} else if (error.code === "ERR_CREATE_OFFER") { } else if (error.code === "ERR_CREATE_OFFER") {
@ -66,7 +66,7 @@ function Game() {
}, [session]); }, [session]);
useEffect(() => { useEffect(() => {
function handleStatus(status: any) { function handleStatus(status: SessionStatus) {
setSessionStatus(status); setSessionStatus(status);
} }

View File

@ -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 { Flex, Button, Image, Text, IconButton, Link } from "theme-ui";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
@ -23,9 +23,8 @@ import owlington from "../images/Owlington.png";
function Home() { function Home() {
const [isStartModalOpen, setIsStartModalOpen] = useState(false); const [isStartModalOpen, setIsStartModalOpen] = useState(false);
const [isJoinModalOpen, setIsJoinModalOpen] = useState(false); const [isJoinModalOpen, setIsJoinModalOpen] = useState(false);
const [isGettingStartedModalOpen, setIsGettingStartedModalOpen] = useState( const [isGettingStartedModalOpen, setIsGettingStartedModalOpen] =
false useState(false);
);
// Reset password on visiting home // Reset password on visiting home
const { setPassword } = useAuth(); const { setPassword } = useAuth();
@ -80,6 +79,7 @@ function Home() {
</Text> </Text>
<Button <Button
as="a" as="a"
// @ts-ignore
href="https://patreon.com/owlbearrodeo" href="https://patreon.com/owlbearrodeo"
mt={4} mt={4}
mx={2} mx={2}
@ -94,6 +94,7 @@ function Home() {
</Button> </Button>
<Button <Button
as="a" as="a"
// @ts-ignore
href="/donate" href="/donate"
mt={2} mt={2}
mb={4} mb={4}

View File

@ -6,7 +6,10 @@ import { Note } from "./Note";
import { Token } from "./Token"; import { Token } from "./Token";
import { TokenState } from "./TokenState"; import { TokenState } from "./TokenState";
export type MapChangeEventHandler = (map?: Map, mapState?: MapState) => void; export type MapChangeEventHandler = (
map: Map | null,
mapState: MapState | null
) => void;
export type MapResetEventHandler = (newState: MapState) => void; export type MapResetEventHandler = (newState: MapState) => void;
export type MapSettingsChangeEventHandler = (change: Partial<Map>) => void; export type MapSettingsChangeEventHandler = (change: Partial<Map>) => void;
export type MapStateSettingsChangeEventHandler = ( export type MapStateSettingsChangeEventHandler = (
@ -31,7 +34,7 @@ export type TokenSettingsChangeEventHandler = (change: Partial<Token>) => void;
export type NoteAddEventHander = (note: Note) => void; export type NoteAddEventHander = (note: Note) => void;
export type NoteRemoveEventHander = (noteId: string) => void; export type NoteRemoveEventHander = (noteId: string) => void;
export type NoteChangeEventHandler = (change: Partial<Note>) => void; export type NoteChangeEventHandler = (note: Note) => void;
export type NoteMenuOpenEventHandler = ( export type NoteMenuOpenEventHandler = (
noteId: string, noteId: string,
noteNode: Konva.Node noteNode: Konva.Node

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Action from "../actions/Action"; import Action from "../actions/Action";
import { Drawing } from "./Drawing"; import { DrawingState } from "./Drawing";
import { Fog } from "./Fog"; import { FogState } from "./Fog";
import { Grid } from "./Grid"; import { Grid } from "./Grid";
export type MapToolId = export type MapToolId =
@ -59,9 +59,9 @@ export type FileMap = BaseMap & {
export type Map = DefaultMap | FileMap; export type Map = DefaultMap | FileMap;
export type MapActions = { export type MapActions = {
mapDrawActions: Action<Drawing>[]; mapDrawActions: Action<DrawingState>[];
mapDrawActionIndex: number; mapDrawActionIndex: number;
fogDrawActions: Action<Fog>[]; fogDrawActions: Action<FogState>[];
fogDrawActionIndex: number; fogDrawActionIndex: number;
}; };

View File

@ -1,4 +1,4 @@
import { Drawing } from "./Drawing"; import { DrawingState } from "./Drawing";
import { FogState } from "./Fog"; import { FogState } from "./Fog";
import { Note } from "./Note"; import { Note } from "./Note";
import { TokenState } from "./TokenState"; import { TokenState } from "./TokenState";
@ -7,7 +7,7 @@ export type EditFlag = "drawing" | "tokens" | "notes" | "fog";
export type MapState = { export type MapState = {
tokens: Record<string, TokenState>; tokens: Record<string, TokenState>;
drawShapes: Record<string, Drawing>; drawShapes: DrawingState;
fogShapes: FogState; fogShapes: FogState;
editFlags: Array<EditFlag>; editFlags: Array<EditFlag>;
notes: Record<string, Note>; notes: Record<string, Note>;

View File

@ -3244,10 +3244,10 @@
resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b" resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b"
integrity sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps= integrity sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=
"@types/simple-peer@^9.6.3": "@types/simple-peer@^9.11.1":
version "9.6.3" version "9.11.1"
resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.6.3.tgz#aa118a57e036f4ce2059a7e25367526a4764206d" resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.11.1.tgz#bef6ff1e75178d83438e33aa6a4df2fd98fded1d"
integrity sha512-zrXEBch9tF4NgkZDsGR3c1D0kq99M1bBCjzEyL0PVfEWzCIXrK64TuxRz3XKOx1B0KoEQ9kTs+AhMDuQaHy5RQ== integrity sha512-Pzqbau/WlivSXdRC0He2Wz/ANj2wbi4gzJrtysZz93jvOyI2jo/ibMjUe6AvPllFl/UO6QXT/A0Rcp44bDQB5A==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@ -4219,7 +4219,7 @@ base64-arraybuffer@0.1.4:
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
base64-js@^1.0.2: base64-js@^1.0.2, base64-js@^1.3.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@ -4499,6 +4499,14 @@ buffer@^4.3.0:
ieee754 "^1.1.4" ieee754 "^1.1.4"
isarray "^1.0.0" isarray "^1.0.0"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
builtin-modules@^3.1.0: builtin-modules@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
@ -5535,6 +5543,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
debug@^4.3.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
debug@~4.3.1: debug@~4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
@ -6044,11 +6059,6 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
err-code@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
err-code@^3.0.1: err-code@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920"
@ -6969,7 +6979,7 @@ gensync@^1.0.0-beta.1:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-browser-rtc@^1.0.0, get-browser-rtc@substack/get-browser-rtc#4/head: get-browser-rtc@^1.1.0, get-browser-rtc@substack/get-browser-rtc#4/head:
version "1.1.0" version "1.1.0"
resolved "https://codeload.github.com/substack/get-browser-rtc/tar.gz/8c8b8e086026bc68b24b872ecd8f6c3e83885097" resolved "https://codeload.github.com/substack/get-browser-rtc/tar.gz/8c8b8e086026bc68b24b872ecd8f6c3e83885097"
@ -7492,7 +7502,7 @@ identity-obj-proxy@3.0.0:
dependencies: dependencies:
harmony-reflect "^1.4.6" harmony-reflect "^1.4.6"
ieee754@^1.1.4: ieee754@^1.1.4, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -11090,10 +11100,10 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.1.0: queue-microtask@^1.2.3:
version "1.2.2" version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
raf@^3.4.1: raf@^3.4.1:
version "3.4.1" version "3.4.1"
@ -11107,7 +11117,7 @@ ramda@^0.27.1:
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.3, randombytes@^2.0.5, randombytes@^2.1.0: randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
@ -11509,7 +11519,7 @@ read-pkg@^5.2.0:
string_decoder "~1.1.1" string_decoder "~1.1.1"
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -12043,14 +12053,6 @@ scheduler@^0.20.2:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^1.0.0: schema-utils@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@ -12268,16 +12270,18 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
simple-peer@feross/simple-peer#694/head: simple-peer@^9.11.0:
version "9.7.2" version "9.11.0"
resolved "https://codeload.github.com/feross/simple-peer/tar.gz/0d08d07b83ff3b8c60401688d80642d24dfeffe2" resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.11.0.tgz#e8d27609c7a610c3ddd75767da868e8daab67571"
integrity sha512-qvdNu/dGMHBm2uQ7oLhQBMhYlrOZC1ywXNCH/i8I4etxR1vrjCnU6ZSQBptndB1gcakjo2+w4OHo7Sjza1SHxg==
dependencies: dependencies:
debug "^4.0.1" buffer "^6.0.3"
err-code "^2.0.3" debug "^4.3.1"
get-browser-rtc "^1.0.0" err-code "^3.0.1"
queue-microtask "^1.1.0" get-browser-rtc "^1.1.0"
randombytes "^2.0.3" queue-microtask "^1.2.3"
readable-stream "^3.4.0" randombytes "^2.1.0"
readable-stream "^3.6.0"
simple-swizzle@^0.2.2: simple-swizzle@^0.2.2:
version "0.2.2" version "0.2.2"