From d922c74b6824205c872d110f5e44cb5606f15c9d Mon Sep 17 00:00:00 2001 From: "Dr. Vortex" Date: Sun, 24 Sep 2023 16:10:59 -0500 Subject: [PATCH] Updated Storage backend to support all `Storage`s --- src/backends/LocalStorage.ts | 100 ----------------------------------- src/backends/Storage.ts | 93 ++++++++++++++++++++++++++++++++ src/backends/index.ts | 6 +-- src/index.ts | 4 +- test/common.ts | 2 +- 5 files changed, 99 insertions(+), 106 deletions(-) delete mode 100644 src/backends/LocalStorage.ts create mode 100644 src/backends/Storage.ts diff --git a/src/backends/LocalStorage.ts b/src/backends/LocalStorage.ts deleted file mode 100644 index 91138a54..00000000 --- a/src/backends/LocalStorage.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { SyncKeyValueStore, SimpleSyncStore, SyncKeyValueFileSystem, SimpleSyncRWTransaction, SyncKeyValueRWTransaction } from '../generic/key_value_filesystem'; -import { ApiError, ErrorCode } from '../ApiError'; -import { Buffer } from 'buffer'; -import { CreateBackend, type BackendOptions } from './backend'; - -/** - * Some versions of FF and all versions of IE do not support the full range of - * 16-bit numbers encoded as characters, as they enforce UTF-16 restrictions. - * @url http://stackoverflow.com/questions/11170716/are-there-any-characters-that-are-not-allowed-in-localstorage/11173673#11173673 - * @hidden - */ -let supportsBinaryString: boolean = false, - binaryEncoding: BufferEncoding; -try { - globalThis.localStorage.setItem('__test__', String.fromCharCode(0xd800)); - supportsBinaryString = globalThis.localStorage.getItem('__test__') === String.fromCharCode(0xd800); -} catch (e) { - // IE throws an exception. - supportsBinaryString = false; -} -binaryEncoding = (supportsBinaryString ? 'binary_string' : 'binary_string_ie') as BufferEncoding; -if (!Buffer.isEncoding(binaryEncoding)) { - // Fallback for non BrowserFS implementations of buffer that lack a - // binary_string format. - binaryEncoding = 'base64'; -} - -/** - * A synchronous key-value store backed by localStorage. - */ -export class LocalStorageStore implements SyncKeyValueStore, SimpleSyncStore { - public name(): string { - return LocalStorageFileSystem.Name; - } - - public clear(): void { - globalThis.localStorage.clear(); - } - - public beginTransaction(type: string): SyncKeyValueRWTransaction { - // No need to differentiate. - return new SimpleSyncRWTransaction(this); - } - - public get(key: string): Buffer | undefined { - try { - const data = globalThis.localStorage.getItem(key); - if (data !== null) { - return Buffer.from(data, binaryEncoding); - } - } catch (e) { - // Do nothing. - } - // Key doesn't exist, or a failure occurred. - return undefined; - } - - public put(key: string, data: Buffer, overwrite: boolean): boolean { - try { - if (!overwrite && globalThis.localStorage.getItem(key) !== null) { - // Don't want to overwrite the key! - return false; - } - globalThis.localStorage.setItem(key, data.toString(binaryEncoding)); - return true; - } catch (e) { - throw new ApiError(ErrorCode.ENOSPC, 'LocalStorage is full.'); - } - } - - public del(key: string): void { - try { - globalThis.localStorage.removeItem(key); - } catch (e) { - throw new ApiError(ErrorCode.EIO, 'Unable to delete key ' + key + ': ' + e); - } - } -} - -/** - * A synchronous file system backed by localStorage. Connects our - * LocalStorageStore to our SyncKeyValueFileSystem. - */ -export class LocalStorageFileSystem extends SyncKeyValueFileSystem { - public static readonly Name = 'LocalStorage'; - - public static Create = CreateBackend.bind(this); - - public static readonly Options: BackendOptions = {}; - - public static isAvailable(): boolean { - return typeof globalThis.localStorage !== 'undefined'; - } - /** - * Creates a new LocalStorage file system using the contents of `localStorage`. - */ - constructor() { - super({ store: new LocalStorageStore() }); - } -} diff --git a/src/backends/Storage.ts b/src/backends/Storage.ts new file mode 100644 index 00000000..ed0a67e4 --- /dev/null +++ b/src/backends/Storage.ts @@ -0,0 +1,93 @@ +import { SyncKeyValueStore, SimpleSyncStore, SyncKeyValueFileSystem, SimpleSyncRWTransaction, SyncKeyValueRWTransaction } from '../generic/key_value_filesystem'; +import { ApiError, ErrorCode } from '../ApiError'; +import { Buffer } from 'buffer'; +import { CreateBackend, type BackendOptions } from './backend'; + +/** + * A synchronous key-value store backed by Storage. + */ +export class StorageStore implements SyncKeyValueStore, SimpleSyncStore { + public name(): string { + return StorageFileSystem.Name; + } + + constructor(protected _storage) {} + + public clear(): void { + this._storage.clear(); + } + + public beginTransaction(type: string): SyncKeyValueRWTransaction { + // No need to differentiate. + return new SimpleSyncRWTransaction(this); + } + + public get(key: string): Buffer | undefined { + const data = this._storage.getItem(key); + if (typeof data != 'string') { + return; + } + + return Buffer.from(data); + } + + public put(key: string, data: Buffer, overwrite: boolean): boolean { + try { + if (!overwrite && this._storage.getItem(key) !== null) { + // Don't want to overwrite the key! + return false; + } + this._storage.setItem(key, data.toString()); + return true; + } catch (e) { + throw new ApiError(ErrorCode.ENOSPC, 'Storage is full.'); + } + } + + public del(key: string): void { + try { + this._storage.removeItem(key); + } catch (e) { + throw new ApiError(ErrorCode.EIO, 'Unable to delete key ' + key + ': ' + e); + } + } +} + +export namespace StorageFileSystem { + /** + * Options to pass to the StorageFileSystem + */ + export interface Options { + /** + * The Storage to use. Defaults to globalThis.localStorage. + */ + storage: Storage; + } +} + +/** + * A synchronous file system backed by a `Storage` (e.g. localStorage). + */ +export class StorageFileSystem extends SyncKeyValueFileSystem { + public static readonly Name = 'Storage'; + + public static Create = CreateBackend.bind(this); + + public static readonly Options: BackendOptions = { + storage: { + type: 'object', + optional: true, + description: 'The Storage to use. Defaults to globalThis.localStorage.', + }, + }; + + public static isAvailable(storage: Storage = globalThis.localStorage): boolean { + return storage instanceof Storage; + } + /** + * Creates a new Storage file system using the contents of `Storage`. + */ + constructor({ storage = globalThis.localStorage }: StorageFileSystem.Options) { + super({ store: new StorageStore(storage) }); + } +} diff --git a/src/backends/index.ts b/src/backends/index.ts index 9082fa9e..78c59868 100644 --- a/src/backends/index.ts +++ b/src/backends/index.ts @@ -5,7 +5,7 @@ import { FileSystemAccessFileSystem as FileSystemAccess } from './FileSystemAcce import { FolderAdapter } from './FolderAdapter'; import { InMemoryFileSystem as InMemory } from './InMemory'; import { IndexedDBFileSystem as IndexedDB } from './IndexedDB'; -import { LocalStorageFileSystem as LocalStorage } from './LocalStorage'; +import { StorageFileSystem as Storage } from './Storage'; import { OverlayFS } from './OverlayFS'; import { WorkerFS } from './WorkerFS'; import { HTTPRequest } from './HTTPRequest'; @@ -22,7 +22,7 @@ export const backends: { [backend: string]: BackendConstructor } = { InMemory, IndexedDB, IsoFS, - LocalStorage, + Storage, OverlayFS, WorkerFS, HTTPRequest, @@ -39,7 +39,7 @@ export { InMemory, IndexedDB, IsoFS, - LocalStorage, + Storage, OverlayFS, WorkerFS, HTTPRequest, diff --git a/src/index.ts b/src/index.ts index f229ab0b..9f9bc87b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -99,14 +99,14 @@ export function configure(config: Configuration, cb?: BFSOneArgCallback): Promis * Individual options can recursively contain FileSystemConfiguration objects for * option values that require file systems. * - * For example, to mirror Dropbox to LocalStorage with AsyncMirror, use the following + * For example, to mirror Dropbox to Storage with AsyncMirror, use the following * object: * * ```javascript * var config = { * fs: "AsyncMirror", * options: { - * sync: {fs: "LocalStorage"}, + * sync: {fs: "Storage"}, * async: {fs: "Dropbox", options: {client: anAuthenticatedDropboxSDKClient }} * } * }; diff --git a/test/common.ts b/test/common.ts index 88fc0764..d92e62a4 100644 --- a/test/common.ts +++ b/test/common.ts @@ -42,7 +42,7 @@ const tests /*: { [B in keyof typeof Backends]: Parameters