diff --git a/src/client.js b/src/client.js index 7ce1253..6ff26d4 100644 --- a/src/client.js +++ b/src/client.js @@ -36,7 +36,7 @@ export class Saturn { * @param {number} [config.fallbackLimit] * @param {boolean} [config.experimental] * @param {string} [config.format] - * @param {import('./storage/index.js').Storage} [config.storage] + * @param {function():Promise} [config.storage] */ constructor (config = {}) { this.config = Object.assign({}, { @@ -60,7 +60,7 @@ export class Saturn { if (this.reportingLogs && this.hasPerformanceAPI) { this._monitorPerformanceBuffer() } - this.storage = this.config.storage || memoryStorage() + this._setStorage() this.loadNodesPromise = this.config.experimental ? this._loadNodes(this.config) : null this.authLimiter = pLimit(1) } @@ -226,6 +226,20 @@ export class Saturn { return { res, controller, log } } + async _setStorage () { + if (!this.config.storage) { + this.storage = await memoryStorage() + return + } + + try { + const getStorage = this.config.storage + this.storage = await getStorage() + } catch (error) { + this.storage = await memoryStorage() + } + } + /** * @param {Response} res * @param {object} log @@ -607,10 +621,10 @@ export class Saturn { async _loadNodes (opts) { let origin = opts.orchURL - let cacheNodesListPromise - if (this.storage) { - cacheNodesListPromise = this.storage.get(Saturn.nodesListKey) + if (!this.storage) { + await this._setStorage() } + const cacheNodesListPromise = this.storage?.get(Saturn.nodesListKey) origin = addHttpPrefix(origin) @@ -645,7 +659,7 @@ export class Saturn { nodes = await orchNodesListPromise nodes = this._sortNodes(nodes) this.nodes = nodes - this.storage.set(Saturn.nodesListKey, nodes) + this.storage?.set(Saturn.nodesListKey, nodes) this.hashring = this.createHashring(nodes) } } diff --git a/src/storage/index.js b/src/storage/index.js index a8b52b8..aaf5397 100644 --- a/src/storage/index.js +++ b/src/storage/index.js @@ -6,7 +6,7 @@ import { memoryStorage } from './memory-storage.js' /** * @typedef {object} Storage * @property {function(string):Promise} get - Retrieves the value associated with the key. - * @property {function(string,any):Promise} set - Sets a new value for the key. + * @property {function(string,any):Promise} set - Sets a new value for the key. * @property {function(string):Promise} delete - Deletes the value associated with the key. */ diff --git a/src/storage/indexed-db-storage.js b/src/storage/indexed-db-storage.js index fa2437b..7d3a570 100644 --- a/src/storage/indexed-db-storage.js +++ b/src/storage/indexed-db-storage.js @@ -8,22 +8,28 @@ const DEFAULT_SATURN_STORAGE_NAME = 'saturn-client' /** * @function indexedDbStorage - * @returns {import('./index.js').Storage} + * @returns {Promise} */ -export function indexedDbStorage () { - const indexedDbExists = (typeof self !== 'undefined') && self?.indexedDB - let dbPromise - if (indexedDbExists) { - dbPromise = openDB(DEFAULT_IDB_STORAGE_NAME, DEFAULT_IDB_VERSION, { - upgrade (db) { +export async function indexedDbStorage () { + const indexedDbExists = typeof self !== 'undefined' && self?.indexedDB + + if (!indexedDbExists) { + throw Error('Indexed DB is not supported in this environment') + } + + const dbPromise = await openDB(DEFAULT_IDB_STORAGE_NAME, DEFAULT_IDB_VERSION, { + upgrade (db) { + try { db.createObjectStore(DEFAULT_SATURN_STORAGE_NAME) + } catch (error) { + throw Error(`Cannot initialize indexed DB Object store, error: ${error}`) } - }) - } + } + }) return { - get: async (key) => indexedDbExists && (await dbPromise).get(DEFAULT_SATURN_STORAGE_NAME, key), - set: async (key, value) => indexedDbExists && (await dbPromise).put(DEFAULT_SATURN_STORAGE_NAME, value, key), - delete: async (key) => indexedDbExists && (await dbPromise).delete(DEFAULT_SATURN_STORAGE_NAME, key) + get: async (key) => (await dbPromise).get(DEFAULT_SATURN_STORAGE_NAME, key), + set: async (key, value) => (await dbPromise).put(DEFAULT_SATURN_STORAGE_NAME, value, key), + delete: async (key) => (await dbPromise).delete(DEFAULT_SATURN_STORAGE_NAME, key) } } diff --git a/src/storage/memory-storage.js b/src/storage/memory-storage.js index 7a11c8e..6455fae 100644 --- a/src/storage/memory-storage.js +++ b/src/storage/memory-storage.js @@ -2,9 +2,9 @@ /** * @function memoryStorage - * @returns {import('./index.js').Storage} + * @returns {Promise} */ -export function memoryStorage () { +export async function memoryStorage () { const storageObject = {} return { diff --git a/test/fallback.spec.js b/test/fallback.spec.js index 3799f67..c4b9cf5 100644 --- a/test/fallback.spec.js +++ b/test/fallback.spec.js @@ -52,20 +52,19 @@ describe('Client Fallback', () => { const expectedNodes = generateNodes(2, TEST_ORIGIN_DOMAIN) - // Mocking storage object const mockStorage = { - get: async (key) => null, - set: async (key, value) => null, - delete: async (key) => null + get: async (key) => expectedNodes, + set: async (key, value) => { return null } } + // Mocking storage object + const storage = async () => mockStorage t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ storage: mockStorage, ...options }) + const saturn = new Saturn({ storage, ...options }) // Mocking options const mockOpts = { orchURL: TEST_DEFAULT_ORCH } - await saturn._loadNodes(mockOpts) // Assert that all the storage methods were called twice. @@ -98,7 +97,8 @@ describe('Client Fallback', () => { t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ storage: mockStorage, ...options }) + const storage = async () => mockStorage + const saturn = new Saturn({ storage, ...options }) // Mocking options const mockOpts = { orchURL: TEST_DEFAULT_ORCH } @@ -137,7 +137,8 @@ describe('Client Fallback', () => { t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ storage: mockStorage, ...options }) + const storage = async () => mockStorage + const saturn = new Saturn({ storage, ...options }) const cid = saturn.fetchContentWithFallback('bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4') @@ -167,10 +168,13 @@ describe('Client Fallback', () => { get: async (key) => expectedNodes, set: async (key, value) => { return null } } + t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ ...options }) + const storage = async () => mockStorage + + const saturn = new Saturn({ storage, ...options }) const cid = saturn.fetchContentWithFallback('bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4', { raceNodes: true }) @@ -203,7 +207,9 @@ describe('Client Fallback', () => { t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ ...options }) + const storage = async () => mockStorage + + const saturn = new Saturn({ storage, ...options }) await saturn.loadNodesPromise @@ -242,7 +248,8 @@ describe('Client Fallback', () => { t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ storage: mockStorage, ...options }) + const storage = async () => mockStorage + const saturn = new Saturn({ storage, ...options }) const cid = saturn.fetchContentWithFallback('bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4', { raceNodes: true }) @@ -336,7 +343,9 @@ describe('Client Fallback', () => { t.mock.method(mockStorage, 'get') t.mock.method(mockStorage, 'set') - const saturn = new Saturn({ storage: mockStorage, customerFallbackURL: TEST_CUSTOMER_ORIGIN, ...options }) + const storage = async () => mockStorage + + const saturn = new Saturn({ storage, customerFallbackURL: TEST_CUSTOMER_ORIGIN, ...options }) const cid = saturn.fetchContentWithFallback(TEST_CUSTOMER_ORIGIN, { raceNodes: true })