2020-08-05 22:08:55 +05:30
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-09-29 15:19:58 +05:30
|
|
|
import {IDOMStorage} from "./types";
|
2021-08-13 01:36:09 +05:30
|
|
|
import {Storage} from "./Storage";
|
2021-08-10 02:26:20 +05:30
|
|
|
import { openDatabase, reqAsPromise } from "./utils";
|
2021-09-01 04:20:57 +05:30
|
|
|
import { exportSession, importSession, Export } from "./export";
|
2021-08-13 01:58:36 +05:30
|
|
|
import { schema } from "./schema";
|
2021-08-13 22:34:03 +05:30
|
|
|
import { detectWebkitEarlyCloseTxnBug } from "./quirks";
|
2021-09-01 04:31:13 +05:30
|
|
|
import { BaseLogger } from "../../../logging/BaseLogger.js";
|
2021-09-17 21:49:26 +05:30
|
|
|
import { LogItem } from "../../../logging/LogItem.js";
|
2021-09-01 03:56:26 +05:30
|
|
|
|
2021-08-13 22:43:40 +05:30
|
|
|
const sessionName = (sessionId: string) => `hydrogen_session_${sessionId}`;
|
2021-09-29 15:19:58 +05:30
|
|
|
const openDatabaseWithSessionId = function(sessionId: string, idbFactory: IDBFactory, localStorage: IDOMStorage, log: LogItem) {
|
|
|
|
const create = (db, txn, oldVersion, version) => createStores(db, txn, oldVersion, version, localStorage, log);
|
2021-08-27 23:04:53 +05:30
|
|
|
return openDatabase(sessionName(sessionId), create, schema.length, idbFactory);
|
2021-06-02 16:01:13 +05:30
|
|
|
}
|
2019-02-07 03:34:39 +05:30
|
|
|
|
2021-08-13 22:43:40 +05:30
|
|
|
interface ServiceWorkerHandler {
|
|
|
|
preventConcurrentSessionAccess: (sessionId: string) => Promise<void>;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function requestPersistedStorage(): Promise<boolean> {
|
2021-06-02 16:01:13 +05:30
|
|
|
// don't assume browser so we can run in node with fake-idb
|
|
|
|
const glob = this;
|
|
|
|
if (glob?.navigator?.storage?.persist) {
|
|
|
|
return await glob.navigator.storage.persist();
|
|
|
|
} else if (glob?.document.requestStorageAccess) {
|
2020-10-20 20:32:34 +05:30
|
|
|
try {
|
2021-06-02 16:01:13 +05:30
|
|
|
await glob.document.requestStorageAccess();
|
2020-10-20 20:32:34 +05:30
|
|
|
return true;
|
|
|
|
} catch (err) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 00:56:39 +05:30
|
|
|
export class StorageFactory {
|
2021-08-13 22:43:40 +05:30
|
|
|
private _serviceWorkerHandler: ServiceWorkerHandler;
|
|
|
|
private _idbFactory: IDBFactory;
|
2021-09-29 15:19:58 +05:30
|
|
|
private _IDBKeyRange: typeof IDBKeyRange;
|
|
|
|
private _localStorage: IDOMStorage;
|
2021-08-13 22:43:40 +05:30
|
|
|
|
2021-09-29 15:19:58 +05:30
|
|
|
constructor(serviceWorkerHandler: ServiceWorkerHandler, idbFactory: IDBFactory = window.indexedDB, _IDBKeyRange = window.IDBKeyRange, localStorage: IDOMStorage = window.localStorage) {
|
2020-10-16 16:20:37 +05:30
|
|
|
this._serviceWorkerHandler = serviceWorkerHandler;
|
2021-06-02 16:01:13 +05:30
|
|
|
this._idbFactory = idbFactory;
|
2021-09-01 03:56:26 +05:30
|
|
|
this._IDBKeyRange = _IDBKeyRange;
|
2021-09-29 15:19:58 +05:30
|
|
|
this._localStorage = localStorage;
|
2020-10-16 16:20:37 +05:30
|
|
|
}
|
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
async create(sessionId: string, log: LogItem): Promise<Storage> {
|
2020-10-16 16:20:37 +05:30
|
|
|
await this._serviceWorkerHandler?.preventConcurrentSessionAccess(sessionId);
|
2020-10-20 20:32:34 +05:30
|
|
|
requestPersistedStorage().then(persisted => {
|
|
|
|
// Firefox lies here though, and returns true even if the user denied the request
|
|
|
|
if (!persisted) {
|
|
|
|
console.warn("no persisted storage, database can be evicted by browser");
|
|
|
|
}
|
|
|
|
});
|
2021-03-05 00:19:13 +05:30
|
|
|
|
2021-06-02 16:01:13 +05:30
|
|
|
const hasWebkitEarlyCloseTxnBug = await detectWebkitEarlyCloseTxnBug(this._idbFactory);
|
2021-09-29 15:19:58 +05:30
|
|
|
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
|
|
|
|
return new Storage(db, this._idbFactory, this._IDBKeyRange, hasWebkitEarlyCloseTxnBug, this._localStorage, log.logger);
|
2019-10-13 00:46:48 +05:30
|
|
|
}
|
|
|
|
|
2021-08-13 22:43:40 +05:30
|
|
|
delete(sessionId: string): Promise<IDBDatabase> {
|
2019-12-14 22:59:35 +05:30
|
|
|
const databaseName = sessionName(sessionId);
|
2021-06-02 16:01:13 +05:30
|
|
|
const req = this._idbFactory.deleteDatabase(databaseName);
|
2019-10-13 00:46:48 +05:30
|
|
|
return reqAsPromise(req);
|
|
|
|
}
|
2019-12-14 22:59:35 +05:30
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
async export(sessionId: string, log: LogItem): Promise<Export> {
|
2021-09-29 15:19:58 +05:30
|
|
|
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
|
2019-12-14 22:59:35 +05:30
|
|
|
return await exportSession(db);
|
|
|
|
}
|
|
|
|
|
2021-09-17 21:49:26 +05:30
|
|
|
async import(sessionId: string, data: Export, log: LogItem): Promise<void> {
|
2021-09-29 15:19:58 +05:30
|
|
|
const db = await openDatabaseWithSessionId(sessionId, this._idbFactory, this._localStorage, log);
|
2019-12-14 22:59:35 +05:30
|
|
|
return await importSession(db, data);
|
|
|
|
}
|
2019-02-05 04:51:50 +05:30
|
|
|
}
|
|
|
|
|
2021-09-29 15:19:58 +05:30
|
|
|
async function createStores(db: IDBDatabase, txn: IDBTransaction, oldVersion: number | null, version: number, localStorage: IDOMStorage, log: LogItem): Promise<void> {
|
2020-06-27 02:56:24 +05:30
|
|
|
const startIdx = oldVersion || 0;
|
2021-08-27 23:04:53 +05:30
|
|
|
return log.wrap({l: "storage migration", oldVersion, version}, async log => {
|
|
|
|
for(let i = startIdx; i < version; ++i) {
|
2021-09-29 15:19:58 +05:30
|
|
|
const migrationFunc = schema[i];
|
|
|
|
await log.wrap(`v${i + 1}`, log => migrationFunc(db, txn, localStorage, log));
|
2021-08-27 23:04:53 +05:30
|
|
|
}
|
2021-11-12 23:12:15 +05:30
|
|
|
}) as Promise<void>;
|
2019-04-18 23:49:43 +05:30
|
|
|
}
|