grungnet/src/contexts/DatabaseContext.tsx

139 lines
4.3 KiB
TypeScript
Raw Permalink Normal View History

import React, { useState, useEffect, useContext } from "react";
2021-04-22 21:46:52 -04:00
import Dexie from "dexie";
import * as Comlink from "comlink";
2020-11-25 23:24:33 -05:00
import ErrorBanner from "../components/banner/ErrorBanner";
import { getDatabase } from "../database";
2021-06-03 01:31:18 -04:00
//@ts-ignore
import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax
2021-07-16 00:55:33 -04:00
import { DatabaseWorkerService } from "../workers/DatabaseWorker";
export type DatabaseStatus = "loading" | "disabled" | "upgrading" | "loaded";
2021-07-17 04:39:49 -04:00
type DatabaseContextValue = {
2021-07-02 01:54:54 -04:00
database: Dexie | undefined;
2021-07-16 00:55:33 -04:00
databaseStatus: DatabaseStatus;
2021-07-02 01:54:54 -04:00
databaseError: Error | undefined;
2021-07-16 00:55:33 -04:00
worker: Comlink.Remote<DatabaseWorkerService>;
2021-07-02 01:54:54 -04:00
};
2021-06-03 01:31:18 -04:00
2021-07-02 01:54:54 -04:00
const DatabaseContext =
2021-07-17 04:39:49 -04:00
React.createContext<DatabaseContextValue | undefined>(undefined);
2021-07-16 00:55:33 -04:00
const worker: Comlink.Remote<DatabaseWorkerService> = Comlink.wrap(
new DatabaseWorker()
);
2021-07-02 01:54:54 -04:00
export function DatabaseProvider({ children }: { children: React.ReactNode }) {
const [database, setDatabase] = useState<Dexie>();
const [databaseStatus, setDatabaseStatus] =
2021-07-16 00:55:33 -04:00
useState<DatabaseStatus>("loading");
2021-07-02 01:54:54 -04:00
const [databaseError, setDatabaseError] = useState<Error>();
useEffect(() => {
// Create a test database and open it to see if indexedDB is enabled
let testDBRequest = window.indexedDB.open("__test");
2020-10-23 07:16:18 -04:00
testDBRequest.onsuccess = async function () {
testDBRequest.result.close();
let db = getDatabase(
{ autoOpen: false },
undefined,
undefined,
true,
2021-06-25 03:43:43 -04:00
() => {
setDatabaseStatus("upgrading");
}
);
setDatabase(db);
2020-10-23 07:16:18 -04:00
db.on("ready", () => {
setDatabaseStatus("loaded");
});
2021-06-23 23:06:00 -04:00
db.on("versionchange", () => {
// When another tab loads a new version of the database refresh the page
window.location.reload();
});
2020-10-23 07:16:18 -04:00
await db.open();
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");
2020-10-23 07:16:18 -04:00
let db = getDatabase({ indexedDB, IDBKeyRange, autoOpen: false });
setDatabase(db);
2020-10-23 07:16:18 -04:00
db.on("ready", () => {
setDatabaseStatus("disabled");
});
await db.open();
window.indexedDB.deleteDatabase("__test");
};
2020-11-25 23:24:33 -05:00
2021-07-02 01:54:54 -04:00
function handleDatabaseError(event: PromiseRejectionEvent) {
if (event) {
event.preventDefault();
if (event.reason instanceof Dexie.DexieError) {
if (event.reason?.inner?.name === "QuotaExceededError") {
setDatabaseError({
name: event.reason?.name,
message:
"Storage Quota Exceeded Please Clear Space and Try Again.",
});
} else if (event.reason?.inner?.name === "DatabaseClosedError") {
setDatabaseError({
name: event.reason?.name,
message: "Database closed, please refresh your browser.",
});
} else {
setDatabaseError({
name: event.reason?.name,
message: "Something went wrong, please refresh your browser.",
});
}
} else {
setDatabaseError({
name: event.reason?.name,
message: "Something went wrong, please refresh your browser.",
});
}
console.error(event.reason);
2020-11-25 23:24:33 -05:00
}
}
window.addEventListener("unhandledrejection", handleDatabaseError);
return () => {
window.removeEventListener("unhandledrejection", handleDatabaseError);
};
}, []);
const value = {
database,
databaseStatus,
2020-11-25 23:24:33 -05:00
databaseError,
worker,
};
return (
<DatabaseContext.Provider value={value}>
2020-11-25 23:24:33 -05:00
<>
{children}
<ErrorBanner
error={databaseError}
2021-06-03 01:31:18 -04:00
onRequestClose={() => setDatabaseError(undefined)}
/>
2020-11-25 23:24:33 -05:00
</>
</DatabaseContext.Provider>
);
}
2021-07-17 04:39:49 -04:00
export function useDatabase() {
const context = useContext(DatabaseContext);
if (context === undefined) {
throw new Error("useDatabase must be used within a DatabaseProvider");
}
return context;
}
export default DatabaseContext;