Added database faker for when indexedb is disabled

Database is now in a context with a status
New FAQ for indexdb errors
This commit is contained in:
Mitchell McCaffrey 2020-05-03 18:22:09 +10:00
parent 05d5c76c86
commit 60059ff447
11 changed files with 264 additions and 129 deletions

View File

@ -9,6 +9,7 @@
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"dexie": "^2.0.4", "dexie": "^2.0.4",
"fake-indexeddb": "^3.0.0",
"interactjs": "^1.9.7", "interactjs": "^1.9.7",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"react": "^16.13.0", "react": "^16.13.0",

View File

@ -9,28 +9,31 @@ import About from "./routes/About";
import FAQ from "./routes/FAQ"; import FAQ from "./routes/FAQ";
import { AuthProvider } from "./contexts/AuthContext"; import { AuthProvider } from "./contexts/AuthContext";
import { DatabaseProvider } from "./contexts/DatabaseContext";
function App() { function App() {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<AuthProvider> <DatabaseProvider>
<Router> <AuthProvider>
<Switch> <Router>
<Route path="/about"> <Switch>
<About /> <Route path="/about">
</Route> <About />
<Route path="/faq"> </Route>
<FAQ /> <Route path="/faq">
</Route> <FAQ />
<Route path="/game/:id"> </Route>
<Game /> <Route path="/game/:id">
</Route> <Game />
<Route path="/"> </Route>
<Home /> <Route path="/">
</Route> <Home />
</Switch> </Route>
</Router> </Switch>
</AuthProvider> </Router>
</AuthProvider>
</DatabaseProvider>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@ -1,10 +1,13 @@
import React from "react"; import React, { useContext } from "react";
import { Flex } from "theme-ui"; import { Flex, Box, Text } from "theme-ui";
import SimpleBar from "simplebar-react"; import SimpleBar from "simplebar-react";
import AddIcon from "../../icons/AddIcon"; import AddIcon from "../../icons/AddIcon";
import MapTile from "./MapTile"; import MapTile from "./MapTile";
import Link from "../Link";
import DatabaseContext from "../../contexts/DatabaseContext";
function MapTiles({ function MapTiles({
maps, maps,
@ -16,59 +19,80 @@ function MapTiles({
onMapReset, onMapReset,
onSubmit, onSubmit,
}) { }) {
const { databaseStatus } = useContext(DatabaseContext);
return ( return (
<SimpleBar style={{ maxHeight: "300px", width: "500px" }}> <Box sx={{ position: "relative" }}>
<Flex <SimpleBar style={{ maxHeight: "300px", width: "500px" }}>
py={2}
bg="muted"
sx={{
flexWrap: "wrap",
width: "500px",
borderRadius: "4px",
}}
>
<Flex <Flex
onClick={onMapAdd} py={2}
sx={{
":hover": {
color: "primary",
},
":focus": {
outline: "none",
},
":active": {
color: "secondary",
},
width: "150px",
height: "150px",
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
}}
m={2}
bg="muted" bg="muted"
aria-label="Add Map" sx={{
title="Add Map" flexWrap: "wrap",
width: "500px",
borderRadius: "4px",
}}
> >
<AddIcon large /> <Flex
onClick={onMapAdd}
sx={{
":hover": {
color: "primary",
},
":focus": {
outline: "none",
},
":active": {
color: "secondary",
},
width: "150px",
height: "150px",
borderRadius: "4px",
justifyContent: "center",
alignItems: "center",
cursor: "pointer",
}}
m={2}
bg="muted"
aria-label="Add Map"
title="Add Map"
>
<AddIcon large />
</Flex>
{maps.map((map) => (
<MapTile
key={map.id}
map={map}
mapState={
selectedMap && map.id === selectedMap.id && selectedMapState
}
isSelected={selectedMap && map.id === selectedMap.id}
onMapSelect={onMapSelect}
onMapRemove={onMapRemove}
onMapReset={onMapReset}
onSubmit={onSubmit}
/>
))}
</Flex> </Flex>
{maps.map((map) => ( </SimpleBar>
<MapTile {databaseStatus === "disabled" && (
key={map.id} <Box
map={map} sx={{
mapState={ position: "absolute",
selectedMap && map.id === selectedMap.id && selectedMapState top: 0,
} left: 0,
isSelected={selectedMap && map.id === selectedMap.id} right: 0,
onMapSelect={onMapSelect} textAlign: "center",
onMapRemove={onMapRemove} }}
onMapReset={onMapReset} bg="highlight"
onSubmit={onSubmit} p={1}
/> >
))} <Text as="p" variant="body2">
</Flex> Map saving is unavailable. See <Link to="/faq">FAQ</Link> for more
</SimpleBar> information.
</Text>
</Box>
)}
</Box>
); );
} }

View File

@ -1,13 +1,15 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from "react";
import shortid from "shortid"; 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(); const AuthContext = React.createContext();
export function AuthProvider({ children }) { export function AuthProvider({ children }) {
const { database } = useContext(DatabaseContext);
const [password, setPassword] = useState( const [password, setPassword] = useState(
sessionStorage.getItem("auth") || "" sessionStorage.getItem("auth") || ""
); );
@ -20,41 +22,47 @@ export function AuthProvider({ children }) {
const [userId, setUserId] = useState(); const [userId, setUserId] = useState();
useEffect(() => { useEffect(() => {
if (!database) {
return;
}
async function loadUserId() { async function loadUserId() {
const storedUserId = await db.table("user").get("userId"); const storedUserId = await database.table("user").get("userId");
if (storedUserId) { if (storedUserId) {
setUserId(storedUserId.value); setUserId(storedUserId.value);
} else { } else {
const id = shortid.generate(); const id = shortid.generate();
setUserId(id); setUserId(id);
db.table("user").add({ key: "userId", value: id }); database.table("user").add({ key: "userId", value: id });
} }
} }
loadUserId(); loadUserId();
}, []); }, [database]);
const [nickname, setNickname] = useState(""); const [nickname, setNickname] = useState("");
useEffect(() => { useEffect(() => {
if (!database) {
return;
}
async function loadNickname() { async function loadNickname() {
const storedNickname = await db.table("user").get("nickname"); const storedNickname = await database.table("user").get("nickname");
if (storedNickname) { if (storedNickname) {
setNickname(storedNickname.value); setNickname(storedNickname.value);
} else { } else {
const name = getRandomMonster(); const name = getRandomMonster();
setNickname(name); setNickname(name);
db.table("user").add({ key: "nickname", value: name }); database.table("user").add({ key: "nickname", value: name });
} }
} }
loadNickname(); loadNickname();
}, []); }, [database]);
useEffect(() => { useEffect(() => {
if (nickname !== undefined) { if (nickname !== undefined && database !== undefined) {
db.table("user").update("nickname", { value: nickname }); database.table("user").update("nickname", { value: nickname });
} }
}, [nickname]); }, [nickname, database]);
const value = { const value = {
userId, userId,

View File

@ -0,0 +1,54 @@
import React, { useState, useEffect } from "react";
import Dexie from "dexie";
const DatabaseContext = React.createContext();
export function DatabaseProvider({ children }) {
const [database, setDatabase] = useState();
const [databaseStatus, setDatabaseStatus] = useState("loading");
function loadVersions(db) {
db.version(1).stores({
maps: "id, owner",
states: "mapId",
tokens: "id, owner",
user: "key",
});
}
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 = new Dexie("OwlbearRodeoDB");
loadVersions(db);
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 = new Dexie("OwlbearRodeoDB", { indexedDB, IDBKeyRange });
loadVersions(db);
setDatabase(db);
setDatabaseStatus("disabled");
window.indexedDB.deleteDatabase("__test");
};
}, []);
const value = {
database,
databaseStatus,
};
return (
<DatabaseContext.Provider value={value}>
{children}
</DatabaseContext.Provider>
);
}
export default DatabaseContext;

View File

@ -1,11 +0,0 @@
import Dexie from "dexie";
const db = new Dexie("OwlbearRodeoDB");
db.version(1).stores({
maps: "id, owner",
states: "mapId",
tokens: "id, owner",
user: "key",
});
export default db;

View File

@ -2,13 +2,12 @@ import React, { useRef, useState, useEffect, useContext } from "react";
import { Box, Button, Flex, Label, Text } from "theme-ui"; import { Box, Button, Flex, Label, Text } from "theme-ui";
import shortid from "shortid"; import shortid from "shortid";
import db from "../database";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import MapTiles from "../components/map/MapTiles"; import MapTiles from "../components/map/MapTiles";
import MapSettings from "../components/map/MapSettings"; import MapSettings from "../components/map/MapSettings";
import AuthContext from "../contexts/AuthContext"; import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext";
import usePrevious from "../helpers/usePrevious"; import usePrevious from "../helpers/usePrevious";
import blobToBuffer from "../helpers/blobToBuffer"; import blobToBuffer from "../helpers/blobToBuffer";
@ -43,6 +42,7 @@ function SelectMapModal({
// The map currently being view in the map screen // The map currently being view in the map screen
currentMap, currentMap,
}) { }) {
const { database } = useContext(DatabaseContext);
const { userId } = useContext(AuthContext); const { userId } = useContext(AuthContext);
const wasOpen = usePrevious(isOpen); const wasOpen = usePrevious(isOpen);
@ -55,7 +55,7 @@ function SelectMapModal({
const [maps, setMaps] = useState([]); const [maps, setMaps] = useState([]);
// Load maps from the database and ensure state is properly setup // Load maps from the database and ensure state is properly setup
useEffect(() => { useEffect(() => {
if (!userId) { if (!userId || !database) {
return; return;
} }
async function getDefaultMaps() { async function getDefaultMaps() {
@ -73,16 +73,16 @@ function SelectMapModal({
...defaultMapProps, ...defaultMapProps,
}); });
// Add a state for the map if there isn't one already // 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) { if (!state) {
await db.table("states").add({ ...defaultMapState, mapId: id }); await database.table("states").add({ ...defaultMapState, mapId: id });
} }
} }
return defaultMapsWithIds; return defaultMapsWithIds;
} }
async function loadMaps() { async function loadMaps() {
let storedMaps = await db let storedMaps = await database
.table("maps") .table("maps")
.where({ owner: userId }) .where({ owner: userId })
.toArray(); .toArray();
@ -93,7 +93,7 @@ function SelectMapModal({
// reload map state as is may have changed while the modal was closed // reload map state as is may have changed while the modal was closed
if (selectedMap) { if (selectedMap) {
const state = await db.table("states").get(selectedMap.id); const state = await database.table("states").get(selectedMap.id);
if (state) { if (state) {
setSelectedMapState(state); setSelectedMapState(state);
} }
@ -103,7 +103,7 @@ function SelectMapModal({
if (!wasOpen && isOpen) { if (!wasOpen && isOpen) {
loadMaps(); loadMaps();
} }
}, [userId, isOpen, wasOpen, selectedMap]); }, [userId, database, isOpen, wasOpen, selectedMap]);
const fileInputRef = useRef(); const fileInputRef = useRef();
@ -180,21 +180,21 @@ function SelectMapModal({
} }
async function handleMapAdd(map) { async function handleMapAdd(map) {
await db.table("maps").add(map); await database.table("maps").add(map);
const state = { ...defaultMapState, mapId: map.id }; const state = { ...defaultMapState, mapId: map.id };
await db.table("states").add(state); await database.table("states").add(state);
setMaps((prevMaps) => [map, ...prevMaps]); setMaps((prevMaps) => [map, ...prevMaps]);
setSelectedMap(map); setSelectedMap(map);
setSelectedMapState(state); setSelectedMapState(state);
} }
async function handleMapRemove(id) { async function handleMapRemove(id) {
await db.table("maps").delete(id); await database.table("maps").delete(id);
await db.table("states").delete(id); await database.table("states").delete(id);
setMaps((prevMaps) => { setMaps((prevMaps) => {
const filtered = prevMaps.filter((map) => map.id !== id); const filtered = prevMaps.filter((map) => map.id !== id);
setSelectedMap(filtered[0]); setSelectedMap(filtered[0]);
db.table("states").get(filtered[0].id).then(setSelectedMapState); database.table("states").get(filtered[0].id).then(setSelectedMapState);
return filtered; return filtered;
}); });
// Removed the map from the map screen if needed // Removed the map from the map screen if needed
@ -204,14 +204,14 @@ function SelectMapModal({
} }
async function handleMapSelect(map) { async function handleMapSelect(map) {
const state = await db.table("states").get(map.id); const state = await database.table("states").get(map.id);
setSelectedMapState(state); setSelectedMapState(state);
setSelectedMap(map); setSelectedMap(map);
} }
async function handleMapReset(id) { async function handleMapReset(id) {
const state = { ...defaultMapState, mapId: id }; const state = { ...defaultMapState, mapId: id };
await db.table("states").put(state); await database.table("states").put(state);
setSelectedMapState(state); setSelectedMapState(state);
// Reset the state of the current map if needed // Reset the state of the current map if needed
if (currentMap && currentMap.id === selectedMap.id) { if (currentMap && currentMap.id === selectedMap.id) {
@ -261,7 +261,7 @@ function SelectMapModal({
async function handleMapSettingsChange(key, value) { async function handleMapSettingsChange(key, value) {
const change = { [key]: value, lastModified: Date.now() }; 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 }; const newMap = { ...selectedMap, ...change };
setMaps((prevMaps) => { setMaps((prevMaps) => {
const newMaps = [...prevMaps]; const newMaps = [...prevMaps];
@ -275,7 +275,7 @@ function SelectMapModal({
} }
async function handleMapStateSettingsChange(key, value) { 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 })); setSelectedMapState((prevState) => ({ ...prevState, [key]: value }));
} }

View File

@ -4,20 +4,20 @@ import { Box, Label, Flex, Button, useColorMode, Checkbox } from "theme-ui";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import AuthContext from "../contexts/AuthContext"; import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext";
import db from "../database";
function SettingsModal({ isOpen, onRequestClose }) { function SettingsModal({ isOpen, onRequestClose }) {
const { database } = useContext(DatabaseContext);
const { userId } = useContext(AuthContext); const { userId } = useContext(AuthContext);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
async function handleEraseAllData() { async function handleEraseAllData() {
await db.delete(); await database.delete();
window.location.reload(); window.location.reload();
} }
async function handleClearCache() { 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 // TODO: With custom tokens look up all tokens that aren't being used in a state
window.location.reload(); window.location.reload();
} }

View File

@ -131,6 +131,20 @@ function FAQ() {
</ExternalLink> </ExternalLink>
. .
</Text> </Text>
<Text my={1} variant="heading" as="h2" sx={{ fontSize: 3 }}>
Saving
</Text>
<Text my={1} variant="heading" as="h3">
Database is disabled.
</Text>
<Text variant="body2" as="p">
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.
</Text>
</Flex> </Flex>
<Footer /> <Footer />
</Flex> </Flex>

View File

@ -2,8 +2,6 @@ import React, { useState, useEffect, useCallback, useContext } from "react";
import { Flex, Box, Text } from "theme-ui"; import { Flex, Box, Text } from "theme-ui";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import db from "../database";
import { omit, isStreamStopped } from "../helpers/shared"; import { omit, isStreamStopped } from "../helpers/shared";
import useSession from "../helpers/useSession"; import useSession from "../helpers/useSession";
import useDebounce from "../helpers/useDebounce"; import useDebounce from "../helpers/useDebounce";
@ -18,10 +16,12 @@ import Link from "../components/Link";
import AuthModal from "../modals/AuthModal"; import AuthModal from "../modals/AuthModal";
import AuthContext from "../contexts/AuthContext"; import AuthContext from "../contexts/AuthContext";
import DatabaseContext from "../contexts/DatabaseContext";
import { tokens as defaultTokens } from "../tokens"; import { tokens as defaultTokens } from "../tokens";
function Game() { function Game() {
const { database } = useContext(DatabaseContext);
const { id: gameId } = useParams(); const { id: gameId } = useParams();
const { authenticationStatus, userId, nickname, setNickname } = useContext( const { authenticationStatus, userId, nickname, setNickname } = useContext(
AuthContext AuthContext
@ -78,11 +78,14 @@ function Game() {
debouncedMapState && debouncedMapState &&
debouncedMapState.mapId && debouncedMapState.mapId &&
map && 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) { function handleMapChange(newMap, newMapState) {
setMapState(newMapState); setMapState(newMapState);
@ -267,7 +270,8 @@ function Game() {
const newMap = data.data; const newMap = data.data;
// If is a file map check cache and request the full file if outdated // If is a file map check cache and request the full file if outdated
if (newMap && newMap.type === "file") { if (newMap && newMap.type === "file") {
db.table("maps") database
.table("maps")
.get(newMap.id) .get(newMap.id)
.then((cachedMap) => { .then((cachedMap) => {
if (cachedMap && cachedMap.lastModified === newMap.lastModified) { if (cachedMap && cachedMap.lastModified === newMap.lastModified) {
@ -291,7 +295,8 @@ function Game() {
if (data.data && data.data.type === "file") { if (data.data && data.data.type === "file") {
const newMap = { ...data.data, file: data.data.file }; const newMap = { ...data.data, file: data.data.file };
// Store in db // Store in db
db.table("maps") database
.table("maps")
.put(newMap) .put(newMap)
.then(() => { .then(() => {
setMap(newMap); setMap(newMap);

View File

@ -2652,6 +2652,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 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: base64-arraybuffer@0.1.5:
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
@ -3493,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" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
core-js@^2.4.0: core-js@^2.4.0, core-js@^2.5.3:
version "2.6.11" version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
@ -4751,6 +4756,14 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= 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: fast-deep-equal@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
@ -9246,6 +9259,16 @@ readdirp@~3.3.0:
dependencies: dependencies:
picomatch "^2.0.7" 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: realpath-native@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
@ -9797,7 +9820,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3" is-plain-object "^2.0.3"
split-string "^3.0.1" split-string "^3.0.1"
setimmediate@^1.0.4: setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@ -10749,6 +10772,20 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 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: unicode-canonical-property-names-ecmascript@^1.0.4:
version "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" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@ -11175,19 +11212,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" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^6.4.1: whatwg-url@7.1.0, whatwg-url@^7.0.0:
version "6.5.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
dependencies: dependencies:
lodash.sortby "^4.7.0" lodash.sortby "^4.7.0"
tr46 "^1.0.1" tr46 "^1.0.1"
webidl-conversions "^4.0.2" webidl-conversions "^4.0.2"
whatwg-url@^7.0.0: whatwg-url@^6.4.1:
version "7.1.0" version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
dependencies: dependencies:
lodash.sortby "^4.7.0" lodash.sortby "^4.7.0"
tr46 "^1.0.1" tr46 "^1.0.1"