diff --git a/.env b/.env
new file mode 100644
index 0000000..f2f811d
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+REACT_APP_BROKER_URL=http://localhost:9000
+REACT_APP_ICE_SERVERS_URL=http://localhost:9000/iceservers
\ No newline at end of file
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..3277f06
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,2 @@
+REACT_APP_BROKER_URL=https://agent.owlbear.rodeo
+REACT_APP_ICE_SERVERS_URL=https://agent.owlbear.rodeo/iceservers
\ No newline at end of file
diff --git a/package.json b/package.json
index 353b2ff..0808450 100644
--- a/package.json
+++ b/package.json
@@ -3,18 +3,20 @@
"version": "1.2.0",
"private": true,
"dependencies": {
+ "@msgpack/msgpack": "^1.12.1",
"@stripe/stripe-js": "^1.3.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"dexie": "^2.0.4",
+ "fake-indexeddb": "^3.0.0",
"interactjs": "^1.9.7",
- "js-binarypack": "^0.0.9",
"normalize-wheel": "^1.0.1",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-modal": "^3.11.2",
"react-router-dom": "^5.1.2",
+ "react-router-hash-link": "^1.2.2",
"react-scripts": "3.4.0",
"shortid": "^2.2.15",
"simple-peer": "^9.6.2",
diff --git a/src/App.js b/src/App.js
index 09fc5e3..470a417 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,28 +9,31 @@ import About from "./routes/About";
import FAQ from "./routes/FAQ";
import { AuthProvider } from "./contexts/AuthContext";
+import { DatabaseProvider } from "./contexts/DatabaseContext";
function App() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/components/Link.js b/src/components/Link.js
index f5264cc..5f29f97 100644
--- a/src/components/Link.js
+++ b/src/components/Link.js
@@ -1,6 +1,6 @@
import React from "react";
import { Link as ThemeLink } from "theme-ui";
-import { Link as RouterLink } from "react-router-dom";
+import { HashLink as RouterLink } from "react-router-hash-link";
function Link({ to, ...rest }) {
return (
diff --git a/src/components/map/MapTiles.js b/src/components/map/MapTiles.js
index 5babba3..4ebc275 100644
--- a/src/components/map/MapTiles.js
+++ b/src/components/map/MapTiles.js
@@ -1,10 +1,13 @@
-import React from "react";
-import { Flex } from "theme-ui";
+import React, { useContext } from "react";
+import { Flex, Box, Text } from "theme-ui";
import SimpleBar from "simplebar-react";
import AddIcon from "../../icons/AddIcon";
import MapTile from "./MapTile";
+import Link from "../Link";
+
+import DatabaseContext from "../../contexts/DatabaseContext";
function MapTiles({
maps,
@@ -16,59 +19,80 @@ function MapTiles({
onMapReset,
onSubmit,
}) {
+ const { databaseStatus } = useContext(DatabaseContext);
return (
-
-
+
+
-
+
+
+
+ {maps.map((map) => (
+
+ ))}
- {maps.map((map) => (
-
- ))}
-
-
+
+ {databaseStatus === "disabled" && (
+
+
+ Map saving is unavailable. See FAQ for
+ more information.
+
+
+ )}
+
);
}
diff --git a/src/components/party/StartStreamButton.js b/src/components/party/StartStreamButton.js
index b55020f..749368c 100644
--- a/src/components/party/StartStreamButton.js
+++ b/src/components/party/StartStreamButton.js
@@ -22,7 +22,7 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) {
Browser not supported for audio sharing.
- See FAQ for more information.
+ See FAQ for more information.
);
@@ -35,7 +35,7 @@ function StartStreamButton({ onStreamStart, onStreamEnd, stream }) {
Ensure "Share audio" is selected when sharing.
- See FAQ for more information.
+ See FAQ for more information.
);
diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js
index 0d71b2c..e9aced1 100644
--- a/src/contexts/AuthContext.js
+++ b/src/contexts/AuthContext.js
@@ -1,13 +1,15 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useContext } from "react";
import shortid from "shortid";
-import { getRandomMonster } from "../helpers/monsters";
+import DatabaseContext from "./DatabaseContext";
-import db from "../database";
+import { getRandomMonster } from "../helpers/monsters";
const AuthContext = React.createContext();
export function AuthProvider({ children }) {
+ const { database } = useContext(DatabaseContext);
+
const [password, setPassword] = useState(
sessionStorage.getItem("auth") || ""
);
@@ -20,41 +22,47 @@ export function AuthProvider({ children }) {
const [userId, setUserId] = useState();
useEffect(() => {
+ if (!database) {
+ return;
+ }
async function loadUserId() {
- const storedUserId = await db.table("user").get("userId");
+ const storedUserId = await database.table("user").get("userId");
if (storedUserId) {
setUserId(storedUserId.value);
} else {
const id = shortid.generate();
setUserId(id);
- db.table("user").add({ key: "userId", value: id });
+ database.table("user").add({ key: "userId", value: id });
}
}
loadUserId();
- }, []);
+ }, [database]);
const [nickname, setNickname] = useState("");
useEffect(() => {
+ if (!database) {
+ return;
+ }
async function loadNickname() {
- const storedNickname = await db.table("user").get("nickname");
+ const storedNickname = await database.table("user").get("nickname");
if (storedNickname) {
setNickname(storedNickname.value);
} else {
const name = getRandomMonster();
setNickname(name);
- db.table("user").add({ key: "nickname", value: name });
+ database.table("user").add({ key: "nickname", value: name });
}
}
loadNickname();
- }, []);
+ }, [database]);
useEffect(() => {
- if (nickname !== undefined) {
- db.table("user").update("nickname", { value: nickname });
+ if (nickname !== undefined && database !== undefined) {
+ database.table("user").update("nickname", { value: nickname });
}
- }, [nickname]);
+ }, [nickname, database]);
const value = {
userId,
diff --git a/src/contexts/DatabaseContext.js b/src/contexts/DatabaseContext.js
new file mode 100644
index 0000000..7492682
--- /dev/null
+++ b/src/contexts/DatabaseContext.js
@@ -0,0 +1,44 @@
+import React, { useState, useEffect } from "react";
+
+import { getDatabase } from "../database";
+
+const DatabaseContext = React.createContext();
+
+export function DatabaseProvider({ children }) {
+ const [database, setDatabase] = useState();
+ const [databaseStatus, setDatabaseStatus] = useState("loading");
+
+ useEffect(() => {
+ // Create a test database and open it to see if indexedDB is enabled
+ let testDBRequest = window.indexedDB.open("__test");
+ testDBRequest.onsuccess = function () {
+ testDBRequest.result.close();
+ let db = getDatabase();
+ setDatabase(db);
+ setDatabaseStatus("loaded");
+ window.indexedDB.deleteDatabase("__test");
+ };
+ // If indexedb disabled create an in memory database
+ testDBRequest.onerror = async function () {
+ console.warn("Database is disabled, no state will be saved");
+ const indexedDB = await import("fake-indexeddb");
+ const IDBKeyRange = await import("fake-indexeddb/lib/FDBKeyRange");
+ let db = getDatabase({ indexedDB, IDBKeyRange });
+ setDatabase(db);
+ setDatabaseStatus("disabled");
+ window.indexedDB.deleteDatabase("__test");
+ };
+ }, []);
+
+ const value = {
+ database,
+ databaseStatus,
+ };
+ return (
+
+ {children}
+
+ );
+}
+
+export default DatabaseContext;
diff --git a/src/database.js b/src/database.js
index c1ad236..71851cb 100644
--- a/src/database.js
+++ b/src/database.js
@@ -1,11 +1,36 @@
import Dexie from "dexie";
-const db = new Dexie("OwlbearRodeoDB");
-db.version(1).stores({
- maps: "id, owner",
- states: "mapId",
- tokens: "id, owner",
- user: "key",
-});
+import blobToBuffer from "./helpers/blobToBuffer";
-export default db;
+function loadVersions(db) {
+ // v1.2.0
+ db.version(1).stores({
+ maps: "id, owner",
+ states: "mapId",
+ tokens: "id, owner",
+ user: "key",
+ });
+ // v1.2.1 - Move from blob files to array buffers
+ db.version(2)
+ .stores({})
+ .upgrade(async (tx) => {
+ const maps = await Dexie.waitFor(tx.table("maps").toArray());
+ let mapBuffers = {};
+ for (let map of maps) {
+ mapBuffers[map.id] = await Dexie.waitFor(blobToBuffer(map.file));
+ }
+ return tx
+ .table("maps")
+ .toCollection()
+ .modify((map) => {
+ map.file = mapBuffers[map.id];
+ });
+ });
+}
+
+// Get the dexie database used in DatabaseContext
+export function getDatabase(options) {
+ let db = new Dexie("OwlbearRodeoDB", options);
+ loadVersions(db);
+ return db;
+}
diff --git a/src/helpers/Peer.js b/src/helpers/Peer.js
index 04d8893..680506b 100644
--- a/src/helpers/Peer.js
+++ b/src/helpers/Peer.js
@@ -1,5 +1,5 @@
import SimplePeer from "simple-peer";
-import BinaryPack from "js-binarypack";
+import { encode, decode } from "@msgpack/msgpack";
import shortid from "shortid";
import blobToBuffer from "./blobToBuffer";
@@ -14,7 +14,7 @@ class Peer extends SimplePeer {
this.currentChunks = {};
this.on("data", (packed) => {
- const unpacked = BinaryPack.unpack(packed);
+ const unpacked = decode(packed);
// 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
@@ -31,9 +31,13 @@ class Peer extends SimplePeer {
// All chunks have been loaded
if (chunk.count === chunk.total) {
- const merged = BinaryPack.unpack(Buffer.concat(chunk.data));
- this.emit("dataComplete", merged);
- delete this.currentChunks[unpacked.id];
+ // 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));
+ delete this.currentChunks[unpacked.id];
+ });
}
} else {
this.emit("dataComplete", unpacked);
@@ -41,29 +45,24 @@ class Peer extends SimplePeer {
});
}
- async sendPackedData(packedData) {
- const buffer = await blobToBuffer(packedData);
- super.send(buffer);
- }
-
send(data) {
- const packedData = BinaryPack.pack(data);
+ const packedData = encode(data);
- if (packedData.size > MAX_BUFFER_SIZE) {
+ if (packedData.byteLength > MAX_BUFFER_SIZE) {
const chunks = this.chunk(packedData);
for (let chunk of chunks) {
- this.sendPackedData(BinaryPack.pack(chunk));
+ super.send(encode(chunk));
}
return;
} else {
- this.sendPackedData(packedData);
+ super.send(packedData);
}
}
// Converted from https://github.com/peers/peerjs/
- chunk(blob) {
+ chunk(data) {
const chunks = [];
- const size = blob.size;
+ const size = data.byteLength;
const total = Math.ceil(size / MAX_BUFFER_SIZE);
const id = shortid.generate();
@@ -72,7 +71,7 @@ class Peer extends SimplePeer {
while (start < size) {
const end = Math.min(size, start + MAX_BUFFER_SIZE);
- const slice = blob.slice(start, end);
+ const slice = data.slice(start, end);
const chunk = {
__chunked: true,
diff --git a/src/helpers/blobToBuffer.js b/src/helpers/blobToBuffer.js
index 55eb6d7..c816ced 100644
--- a/src/helpers/blobToBuffer.js
+++ b/src/helpers/blobToBuffer.js
@@ -1,12 +1,10 @@
async function blobToBuffer(blob) {
if (blob.arrayBuffer) {
const arrayBuffer = await blob.arrayBuffer();
- const buffer = Buffer.from(arrayBuffer);
- return buffer;
+ return new Uint8Array(arrayBuffer);
} else {
- const arrayBuffer = await new Response(blob).arrayBuffer();
- const buffer = Buffer.from(arrayBuffer);
- return buffer;
+ const arrayBuffer = new Response(blob).arrayBuffer();
+ return new Uint8Array(arrayBuffer);
}
}
diff --git a/src/helpers/useDataSource.js b/src/helpers/useDataSource.js
index 9eed094..4af57a1 100644
--- a/src/helpers/useDataSource.js
+++ b/src/helpers/useDataSource.js
@@ -11,7 +11,7 @@ function useDataSource(data, defaultSources) {
}
let url = null;
if (data.type === "file") {
- url = URL.createObjectURL(data.file);
+ url = URL.createObjectURL(new Blob([data.file]));
} else if (data.type === "default") {
url = defaultSources[data.key];
}
diff --git a/src/helpers/useSession.js b/src/helpers/useSession.js
index 164a829..1de6308 100644
--- a/src/helpers/useSession.js
+++ b/src/helpers/useSession.js
@@ -6,7 +6,7 @@ import Peer from "../helpers/Peer";
import AuthContext from "../contexts/AuthContext";
-const socket = io("https://agent.owlbear.rodeo");
+const socket = io(process.env.REACT_APP_BROKER_URL);
function useSession(
partyId,
@@ -18,9 +18,16 @@ function useSession(
onPeerError
) {
const { password, setAuthenticationStatus } = useContext(AuthContext);
+ const [iceServers, setIceServers] = useState([]);
useEffect(() => {
- socket.emit("join party", partyId, password);
+ async function joinParty() {
+ const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
+ const data = await response.json();
+ setIceServers(data.iceServers);
+ socket.emit("join party", partyId, password);
+ }
+ joinParty();
}, [partyId, password]);
const [peers, setPeers] = useState({});
@@ -127,7 +134,11 @@ function useSession(
// Setup event listeners for the socket
useEffect(() => {
function addPeer(id, initiator, sync) {
- const connection = new Peer({ initiator, trickle: false });
+ const connection = new Peer({
+ initiator,
+ trickle: false,
+ config: { iceServers },
+ });
setPeers((prevPeers) => ({
...prevPeers,
@@ -178,7 +189,7 @@ function useSession(
socket.removeListener("signal", handleSignal);
socket.removeListener("auth error", handleAuthError);
};
- }, [peers, setAuthenticationStatus]);
+ }, [peers, setAuthenticationStatus, iceServers]);
return { peers, socket };
}
diff --git a/src/modals/SelectMapModal.js b/src/modals/SelectMapModal.js
index 89083e2..cb0cbcb 100644
--- a/src/modals/SelectMapModal.js
+++ b/src/modals/SelectMapModal.js
@@ -2,15 +2,15 @@ import React, { useRef, useState, useEffect, useContext } from "react";
import { Box, Button, Flex, Label, Text } from "theme-ui";
import shortid from "shortid";
-import db from "../database";
-
import Modal from "../components/Modal";
import MapTiles from "../components/map/MapTiles";
import MapSettings from "../components/map/MapSettings";
import AuthContext from "../contexts/AuthContext";
+import DatabaseContext from "../contexts/DatabaseContext";
import usePrevious from "../helpers/usePrevious";
+import blobToBuffer from "../helpers/blobToBuffer";
import { maps as defaultMaps } from "../maps";
@@ -42,6 +42,7 @@ function SelectMapModal({
// The map currently being view in the map screen
currentMap,
}) {
+ const { database } = useContext(DatabaseContext);
const { userId } = useContext(AuthContext);
const wasOpen = usePrevious(isOpen);
@@ -54,7 +55,7 @@ function SelectMapModal({
const [maps, setMaps] = useState([]);
// Load maps from the database and ensure state is properly setup
useEffect(() => {
- if (!userId) {
+ if (!userId || !database) {
return;
}
async function getDefaultMaps() {
@@ -72,16 +73,16 @@ function SelectMapModal({
...defaultMapProps,
});
// Add a state for the map if there isn't one already
- const state = await db.table("states").get(id);
+ const state = await database.table("states").get(id);
if (!state) {
- await db.table("states").add({ ...defaultMapState, mapId: id });
+ await database.table("states").add({ ...defaultMapState, mapId: id });
}
}
return defaultMapsWithIds;
}
async function loadMaps() {
- let storedMaps = await db
+ let storedMaps = await database
.table("maps")
.where({ owner: userId })
.toArray();
@@ -92,7 +93,7 @@ function SelectMapModal({
// reload map state as is may have changed while the modal was closed
if (selectedMap) {
- const state = await db.table("states").get(selectedMap.id);
+ const state = await database.table("states").get(selectedMap.id);
if (state) {
setSelectedMapState(state);
}
@@ -102,7 +103,7 @@ function SelectMapModal({
if (!wasOpen && isOpen) {
loadMaps();
}
- }, [userId, isOpen, wasOpen, selectedMap]);
+ }, [userId, database, isOpen, wasOpen, selectedMap]);
const fileInputRef = useRef();
@@ -141,31 +142,35 @@ function SelectMapModal({
let image = new Image();
setImageLoading(true);
- // Copy file to avoid permissions issues
- const copy = new Blob([file], { type: file.type });
- // Create and load the image temporarily to get its dimensions
- const url = URL.createObjectURL(copy);
- image.onload = function () {
- handleMapAdd({
- file: copy,
- name,
- type: "file",
- gridX: fileGridX,
- gridY: fileGridY,
- width: image.width,
- height: image.height,
- id: shortid.generate(),
- created: Date.now(),
- lastModified: Date.now(),
- owner: userId,
- ...defaultMapProps,
- });
- setImageLoading(false);
- URL.revokeObjectURL(url);
- };
- image.src = url;
- // Set file input to null to allow adding the same image 2 times in a row
- fileInputRef.current.value = null;
+ blobToBuffer(file).then((buffer) => {
+ // Copy file to avoid permissions issues
+ const blob = new Blob([buffer]);
+ // Create and load the image temporarily to get its dimensions
+ const url = URL.createObjectURL(blob);
+ image.onload = function () {
+ handleMapAdd({
+ // Save as a buffer to send with msgpack
+ file: buffer,
+ name,
+ type: "file",
+ gridX: fileGridX,
+ gridY: fileGridY,
+ width: image.width,
+ height: image.height,
+ id: shortid.generate(),
+ created: Date.now(),
+ lastModified: Date.now(),
+ owner: userId,
+ ...defaultMapProps,
+ });
+ setImageLoading(false);
+ URL.revokeObjectURL(url);
+ };
+ image.src = url;
+
+ // Set file input to null to allow adding the same image 2 times in a row
+ fileInputRef.current.value = null;
+ });
}
function openImageDialog() {
@@ -175,21 +180,21 @@ function SelectMapModal({
}
async function handleMapAdd(map) {
- await db.table("maps").add(map);
+ await database.table("maps").add(map);
const state = { ...defaultMapState, mapId: map.id };
- await db.table("states").add(state);
+ await database.table("states").add(state);
setMaps((prevMaps) => [map, ...prevMaps]);
setSelectedMap(map);
setSelectedMapState(state);
}
async function handleMapRemove(id) {
- await db.table("maps").delete(id);
- await db.table("states").delete(id);
+ await database.table("maps").delete(id);
+ await database.table("states").delete(id);
setMaps((prevMaps) => {
const filtered = prevMaps.filter((map) => map.id !== id);
setSelectedMap(filtered[0]);
- db.table("states").get(filtered[0].id).then(setSelectedMapState);
+ database.table("states").get(filtered[0].id).then(setSelectedMapState);
return filtered;
});
// Removed the map from the map screen if needed
@@ -199,14 +204,14 @@ function SelectMapModal({
}
async function handleMapSelect(map) {
- const state = await db.table("states").get(map.id);
+ const state = await database.table("states").get(map.id);
setSelectedMapState(state);
setSelectedMap(map);
}
async function handleMapReset(id) {
const state = { ...defaultMapState, mapId: id };
- await db.table("states").put(state);
+ await database.table("states").put(state);
setSelectedMapState(state);
// Reset the state of the current map if needed
if (currentMap && currentMap.id === selectedMap.id) {
@@ -256,7 +261,7 @@ function SelectMapModal({
async function handleMapSettingsChange(key, value) {
const change = { [key]: value, lastModified: Date.now() };
- db.table("maps").update(selectedMap.id, change);
+ database.table("maps").update(selectedMap.id, change);
const newMap = { ...selectedMap, ...change };
setMaps((prevMaps) => {
const newMaps = [...prevMaps];
@@ -270,7 +275,7 @@ function SelectMapModal({
}
async function handleMapStateSettingsChange(key, value) {
- db.table("states").update(selectedMap.id, { [key]: value });
+ database.table("states").update(selectedMap.id, { [key]: value });
setSelectedMapState((prevState) => ({ ...prevState, [key]: value }));
}
diff --git a/src/modals/SettingsModal.js b/src/modals/SettingsModal.js
index c60cb20..43fbe55 100644
--- a/src/modals/SettingsModal.js
+++ b/src/modals/SettingsModal.js
@@ -4,20 +4,20 @@ import { Box, Label, Flex, Button, useColorMode, Checkbox } from "theme-ui";
import Modal from "../components/Modal";
import AuthContext from "../contexts/AuthContext";
-
-import db from "../database";
+import DatabaseContext from "../contexts/DatabaseContext";
function SettingsModal({ isOpen, onRequestClose }) {
+ const { database } = useContext(DatabaseContext);
const { userId } = useContext(AuthContext);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
async function handleEraseAllData() {
- await db.delete();
+ await database.delete();
window.location.reload();
}
async function handleClearCache() {
- await db.table("maps").where("owner").notEqual(userId).delete();
+ await database.table("maps").where("owner").notEqual(userId).delete();
// TODO: With custom tokens look up all tokens that aren't being used in a state
window.location.reload();
}
diff --git a/src/routes/FAQ.js b/src/routes/FAQ.js
index 03a548a..8a3afef 100644
--- a/src/routes/FAQ.js
+++ b/src/routes/FAQ.js
@@ -26,7 +26,7 @@ function FAQ() {
Frequently Asked Questions
-
+
Using Radio (experimental)
@@ -61,7 +61,13 @@ function FAQ() {
{" "}
on the Mozilla Developer Network.
-
+
Connection
@@ -131,6 +137,20 @@ function FAQ() {
.
+
+ Saving
+
+
+ Database is disabled.
+
+
+ Owlbear Rodeo uses a local database to store saved data. If you are
+ seeing a database is disabled message this usually means you have data
+ storage disabled. The most common occurances of this is if you are
+ using Private Browsing modes or in Firefox have the Never Remember
+ History option enabled. The site will still function in these cases
+ however all data will be lost when the page closes or reloads.
+
diff --git a/src/routes/Game.js b/src/routes/Game.js
index d83c7ca..f5d2c96 100644
--- a/src/routes/Game.js
+++ b/src/routes/Game.js
@@ -2,8 +2,6 @@ import React, { useState, useEffect, useCallback, useContext } from "react";
import { Flex, Box, Text } from "theme-ui";
import { useParams } from "react-router-dom";
-import db from "../database";
-
import { omit, isStreamStopped } from "../helpers/shared";
import useSession from "../helpers/useSession";
import useDebounce from "../helpers/useDebounce";
@@ -18,10 +16,12 @@ import Link from "../components/Link";
import AuthModal from "../modals/AuthModal";
import AuthContext from "../contexts/AuthContext";
+import DatabaseContext from "../contexts/DatabaseContext";
import { tokens as defaultTokens } from "../tokens";
function Game() {
+ const { database } = useContext(DatabaseContext);
const { id: gameId } = useParams();
const { authenticationStatus, userId, nickname, setNickname } = useContext(
AuthContext
@@ -78,11 +78,14 @@ function Game() {
debouncedMapState &&
debouncedMapState.mapId &&
map &&
- map.owner === userId
+ map.owner === userId &&
+ database
) {
- db.table("states").update(debouncedMapState.mapId, debouncedMapState);
+ database
+ .table("states")
+ .update(debouncedMapState.mapId, debouncedMapState);
}
- }, [map, debouncedMapState, userId]);
+ }, [map, debouncedMapState, userId, database]);
function handleMapChange(newMap, newMapState) {
setMapState(newMapState);
@@ -267,7 +270,8 @@ function Game() {
const newMap = data.data;
// If is a file map check cache and request the full file if outdated
if (newMap && newMap.type === "file") {
- db.table("maps")
+ database
+ .table("maps")
.get(newMap.id)
.then((cachedMap) => {
if (cachedMap && cachedMap.lastModified === newMap.lastModified) {
@@ -289,11 +293,10 @@ function Game() {
if (data.id === "mapResponse") {
setMapLoading(false);
if (data.data && data.data.type === "file") {
- // Convert file back to blob after peer transfer
- const file = new Blob([data.data.file]);
- const newMap = { ...data.data, file };
+ const newMap = { ...data.data, file: data.data.file };
// Store in db
- db.table("maps")
+ database
+ .table("maps")
.put(newMap)
.then(() => {
setMap(newMap);
@@ -498,7 +501,8 @@ function Game() {
setPeerError(null)}>
- {peerError} See FAQ for more information.
+ {peerError} See FAQ for more
+ information.
diff --git a/yarn.lock b/yarn.lock
index 3dce1d6..0645779 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1398,6 +1398,11 @@
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
+"@msgpack/msgpack@^1.12.1":
+ version "1.12.1"
+ resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-1.12.1.tgz#aab1084bc33c955501bc0f202099e38304143e0b"
+ integrity sha512-nGwwmkdm3tuLdEkWMIwLBgFBfMFILILxcZIQY0dfqsdboN2iZdKfOYKUOKoa0wXw1FL1PL3yEYGPCXhwodQDTA==
+
"@nodelib/fs.stat@^1.1.2":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
@@ -2647,6 +2652,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-arraybuffer-es6@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.5.0.tgz#27877d01148bcfb3919c17ecf64ea163d9bdba62"
+ integrity sha512-UCIPaDJrNNj5jG2ZL+nzJ7czvZV/ZYX6LaIRgfVU1k1edJOQg7dkbiSKzwHkNp6aHEHER/PhlFBrMYnlvJJQEw==
+
base64-arraybuffer@0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
@@ -3488,7 +3498,7 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
-core-js@^2.4.0:
+core-js@^2.4.0, core-js@^2.5.3:
version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
@@ -4746,6 +4756,14 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
+fake-indexeddb@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-3.0.0.tgz#1bd0ffce41b0f433409df301d334d8fd7d77da27"
+ integrity sha512-VrnV9dJWlVWvd8hp9MMR+JS4RLC4ZmToSkuCg91ZwpYE5mSODb3n5VEaV62Hf3AusnbrPfwQhukU+rGZm5W8PQ==
+ dependencies:
+ realistic-structured-clone "^2.0.1"
+ setimmediate "^1.0.5"
+
fast-deep-equal@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
@@ -6537,11 +6555,6 @@ jest@24.9.0:
import-local "^2.0.0"
jest-cli "^24.9.0"
-js-binarypack@^0.0.9:
- version "0.0.9"
- resolved "https://registry.yarnpkg.com/js-binarypack/-/js-binarypack-0.0.9.tgz#454243d3de212961cc1514a2f119dec2faf64035"
- integrity sha1-RUJD094hKWHMFRSi8Rnewvr2QDU=
-
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -8830,7 +8843,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.3"
-prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9089,6 +9102,13 @@ react-router-dom@^5.1.2:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
+react-router-hash-link@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-1.2.2.tgz#7a0ad5e925d49596d19554de8bc6c554ce4f8099"
+ integrity sha512-LBthLVHdqPeKDVt3+cFRhy15Z7veikOvdKRZRfyBR2vjqIE7rxn+tKLjb6DOmLm6JpoQVemVDnxQ35RVnEHdQA==
+ dependencies:
+ prop-types "^15.6.0"
+
react-router@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418"
@@ -9246,6 +9266,16 @@ readdirp@~3.3.0:
dependencies:
picomatch "^2.0.7"
+realistic-structured-clone@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz#2f8ec225b1f9af20efc79ac96a09043704414959"
+ integrity sha512-5IEvyfuMJ4tjQOuKKTFNvd+H9GSbE87IcendSBannE28PTrbolgaVg5DdEApRKhtze794iXqVUFKV60GLCNKEg==
+ dependencies:
+ core-js "^2.5.3"
+ domexception "^1.0.1"
+ typeson "^5.8.2"
+ typeson-registry "^1.0.0-alpha.20"
+
realpath-native@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
@@ -9797,7 +9827,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -10749,6 +10779,20 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typeson-registry@^1.0.0-alpha.20:
+ version "1.0.0-alpha.35"
+ resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.35.tgz#b86abfe440e6ee69102eebb0e8c5a916dd182ff9"
+ integrity sha512-a/NffrpFswBTyU6w2d6vjk62K1TZ45H64af9AfRbn7LXqNEfL+h+gw3OV2IaG+enfwqgLB5WmbkrNBaQuc/97A==
+ dependencies:
+ base64-arraybuffer-es6 "0.5.0"
+ typeson "5.18.2"
+ whatwg-url "7.1.0"
+
+typeson@5.18.2, typeson@^5.8.2:
+ version "5.18.2"
+ resolved "https://registry.yarnpkg.com/typeson/-/typeson-5.18.2.tgz#0d217fc0e11184a66aa7ca0076d9aa7707eb7bc2"
+ integrity sha512-Vetd+OGX05P4qHyHiSLdHZ5Z5GuQDrHHwSdjkqho9NSCYVSLSfRMjklD/unpHH8tXBR9Z/R05rwJSuMpMFrdsw==
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -11175,19 +11219,19 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
-whatwg-url@^6.4.1:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
- integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
+whatwg-url@7.1.0, whatwg-url@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
+ integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
-whatwg-url@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
- integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
+whatwg-url@^6.4.1:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
+ integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^1.0.1"