UNPKG

wallet-storage-client

Version:
394 lines 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WalletStorageManager = void 0; const index_client_1 = require("../index.client"); /** * The `SignerStorage` class delivers authentication checking storage access to the wallet. * * If manages multiple `StorageBase` derived storage services: one actice, the rest as backups. * * Of the storage services, one is 'active' at any one time. * On startup, and whenever triggered by the wallet, `SignerStorage` runs a syncrhonization sequence: * * 1. While synchronizing, all other access to storage is blocked waiting. * 2. The active service is confirmed, potentially triggering a resolution process if there is disagreement. * 3. Changes are pushed from the active storage service to each inactive, backup service. * * Some storage services do not support multiple writers. `SignerStorage` manages wait-blocking write requests * for these services. */ class WalletStorageManager { constructor(identityKey, active, backups) { this.stores = []; this._userIdentityKeyToId = {}; this._readerCount = 0; this._writerCount = 0; /** * if true, allow only a single writer to proceed at a time. * queue the blocked requests so they get executed in order when released. */ this._isSingleWriter = true; /** * if true, allow no new reader or writers to proceed. * queue the blocked requests so they get executed in order when released. */ this._syncLocked = false; /** * if true, allow no new reader or writers or sync to proceed. * queue the blocked requests so they get executed in order when released. */ this._storageProviderLocked = false; this.stores = []; if (active) this.stores.push(active); if (backups) this.stores = this.stores.concat(backups); this._authId = { identityKey }; } isStorageProvider() { return false; } async getUserId() { let userId = this._authId.userId; if (!userId) { if (!this.isAvailable()) await this.makeAvailable(); const { user, isNew } = await this.getActive().findOrInsertUser(this._authId.identityKey); if (!user) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('identityKey', 'exist on storage.'); userId = user.userId; this._authId.userId = userId; this._authId.isActive = user.activeStorage === undefined || user.activeStorage === this.getSettings().storageIdentityKey; } return userId; } async getAuth(mustBeActive) { if (!this._authId.userId) this._authId.userId = await this.getUserId(); if (mustBeActive && !this._authId.isActive) throw new index_client_1.sdk.WERR_NOT_ACTIVE(); return this._authId; } getActive() { if (this.stores.length < 1) throw new index_client_1.sdk.WERR_INVALID_OPERATION('An active WalletStorageProvider must be added to this WalletStorageManager'); return this.stores[0]; } async getActiveForWriter() { while (this._storageProviderLocked || this._syncLocked || this._isSingleWriter && this._writerCount > 0 || this._readerCount > 0) { await (0, index_client_1.wait)(100); } this._writerCount++; return this.getActive(); } async getActiveForReader() { while (this._storageProviderLocked || this._syncLocked || this._isSingleWriter && this._writerCount > 0) { await (0, index_client_1.wait)(100); } this._readerCount++; return this.getActive(); } async getActiveForSync() { // Wait for a current sync task to complete... while (this._syncLocked) { await (0, index_client_1.wait)(100); } // Set syncLocked which prevents any new storageProvider, readers or writers... this._syncLocked = true; // Wait for any current storageProvider, readers and writers to complete while (this._storageProviderLocked || this._readerCount > 0 || this._writerCount > 0) { await (0, index_client_1.wait)(100); } // Allow the sync to proceed on the active store. return this.getActive(); } async getActiveForStorageProvider() { // Wait for a current storageProvider call to complete... while (this._storageProviderLocked) { await (0, index_client_1.wait)(100); } // Set storageProviderLocked which prevents any new sync, readers or writers... this._storageProviderLocked = true; // Wait for any current sync, readers and writers to complete while (this._syncLocked || this._readerCount > 0 || this._writerCount > 0) { await (0, index_client_1.wait)(100); } // We can finally confirm that active storage is still able to support `StorageProvider` if (!this.getActive().isStorageProvider()) throw new index_client_1.sdk.WERR_INVALID_OPERATION('Active "WalletStorageProvider" does not support "StorageProvider" interface.'); // Allow the sync to proceed on the active store. return this.getActive(); } async runAsWriter(writer) { try { const active = await this.getActiveForWriter(); const r = await writer(active); return r; } finally { this._writerCount--; } } async runAsReader(reader) { try { const active = await this.getActiveForReader(); const r = await reader(active); return r; } finally { this._readerCount--; } } /** * * @param sync the function to run with sync access lock * @param activeSync from chained sync functions, active storage already held under sync access lock. * @returns */ async runAsSync(sync, activeSync) { try { const active = activeSync || await this.getActiveForSync(); const r = await sync(active); return r; } finally { if (!activeSync) this._syncLocked = false; } } async runAsStorageProvider(sync) { try { const active = await this.getActiveForStorageProvider(); const r = await sync(active); return r; } finally { this._storageProviderLocked = false; } } /** * * @returns true if the active `WalletStorageProvider` also implements `StorageProvider` */ isActiveStorageProvider() { return this.getActive().isStorageProvider(); } isAvailable() { return this.getActive().isAvailable(); } async addWalletStorageProvider(provider) { await provider.makeAvailable(); if (this._services) provider.setServices(this._services); this.stores.push(provider); } setServices(v) { this._services = v; for (const store of this.stores) store.setServices(v); } getServices() { if (!this._services) throw new index_client_1.sdk.WERR_INVALID_OPERATION('Must setServices first.'); return this._services; } getSettings() { return this.getActive().getSettings(); } async makeAvailable() { return await this.runAsWriter(async (writer) => { writer.makeAvailable(); return writer.getSettings(); }); } async migrate(storageName, storageIdentityKey) { return await this.runAsWriter(async (writer) => { return writer.migrate(storageName, storageIdentityKey); }); } async destroy() { return await this.runAsWriter(async (writer) => { for (const store of this.stores) await store.destroy(); }); } async findOrInsertUser(identityKey) { const auth = await this.getAuth(); if (identityKey != auth.identityKey) throw new index_client_1.sdk.WERR_UNAUTHORIZED(); return await this.runAsWriter(async (writer) => { const r = await writer.findOrInsertUser(identityKey); if (auth.userId && auth.userId !== r.user.userId) throw new index_client_1.sdk.WERR_INTERNAL('userId may not change for given identityKey'); this._authId.userId = r.user.userId; return r; }); } async abortAction(args) { index_client_1.sdk.validateAbortActionArgs(args); return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.abortAction(auth, args); }); } async createAction(vargs) { return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.createAction(auth, vargs); }); } async internalizeAction(args) { index_client_1.sdk.validateInternalizeActionArgs(args); return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.internalizeAction(auth, args); }); } async relinquishCertificate(args) { index_client_1.sdk.validateRelinquishCertificateArgs(args); return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.relinquishCertificate(auth, args); }); } async relinquishOutput(args) { index_client_1.sdk.validateRelinquishOutputArgs(args); return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.relinquishOutput(auth, args); }); } async processAction(args) { return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.processAction(auth, args); }); } async insertCertificate(certificate) { return await this.runAsWriter(async (writer) => { const auth = await this.getAuth(true); return await writer.insertCertificateAuth(auth, certificate); }); } async listActions(vargs) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.listActions(auth, vargs); }); } async listCertificates(args) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.listCertificates(auth, args); }); } async listOutputs(vargs) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.listOutputs(auth, vargs); }); } async findCertificates(args) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.findCertificatesAuth(auth, args); }); } async findOutputBaskets(args) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.findOutputBasketsAuth(auth, args); }); } async findOutputs(args) { const auth = await this.getAuth(); return await this.runAsReader(async (reader) => { return await reader.findOutputsAuth(auth, args); }); } async findProvenTxReqs(args) { return await this.runAsReader(async (reader) => { return await reader.findProvenTxReqs(args); }); } async syncFromReader(identityKey, reader) { const auth = await this.getAuth(); if (identityKey !== auth.identityKey) throw new index_client_1.sdk.WERR_UNAUTHORIZED(); const readerSettings = await reader.makeAvailable(); return await this.runAsSync(async (sync) => { const writer = sync; const writerSettings = this.getSettings(); let log = ''; let inserts = 0, updates = 0; for (;;) { const ss = await index_client_1.entity.SyncState.fromStorage(writer, identityKey, readerSettings); const args = ss.makeRequestSyncChunkArgs(identityKey, writerSettings.storageIdentityKey); const chunk = await reader.getSyncChunk(args); const r = await writer.processSyncChunk(args, chunk); inserts += r.inserts; updates += r.updates; //log += `${r.maxUpdated_at} inserted ${r.inserts} updated ${r.updates}\n` if (r.done) break; } //console.log(log) console.log(`sync complete: ${inserts} inserts, ${updates} updates`); }); } async updateBackups(activeSync) { const auth = await this.getAuth(); return await this.runAsSync(async (sync) => { for (const backup of this.stores.slice(1)) { await this.syncToWriter(auth, backup, sync); } }, activeSync); } async syncToWriter(auth, writer, activeSync) { const identityKey = auth.identityKey; const writerSettings = await writer.makeAvailable(); return await this.runAsSync(async (sync) => { const reader = sync; const readerSettings = this.getSettings(); let log = ''; let inserts = 0, updates = 0; for (;;) { const ss = await index_client_1.entity.SyncState.fromStorage(writer, identityKey, readerSettings); const args = ss.makeRequestSyncChunkArgs(identityKey, writerSettings.storageIdentityKey); const chunk = await reader.getSyncChunk(args); const r = await writer.processSyncChunk(args, chunk); inserts += r.inserts; updates += r.updates; log += `${r.maxUpdated_at} inserted ${r.inserts} updated ${r.updates}\n`; if (r.done) break; } //console.log(log) //console.log(`sync complete: ${inserts} inserts, ${updates} updates`) return { inserts, updates }; }, activeSync); } /** * Updates backups and switches to new active storage provider from among current backup providers. * * @param storageIdentityKey of current backup storage provider that is to become the new active provider. */ async setActive(storageIdentityKey) { const newActiveIndex = this.stores.findIndex(s => s.getSettings().storageIdentityKey === storageIdentityKey); if (newActiveIndex < 0) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('storageIdentityKey', `registered with this "WalletStorageManager" as a backup data store.`); if (newActiveIndex === 0) /** Setting the current active as the new active is a permitted no-op. */ return; const auth = await this.getAuth(); const newActive = this.stores[newActiveIndex]; const newActiveStorageIdentityKey = (await newActive.makeAvailable()).storageIdentityKey; return await this.runAsSync(async (sync) => { await sync.setActive(auth, newActiveStorageIdentityKey); await this.updateBackups(sync); // swap stores... const oldActive = this.stores[0]; this.stores[0] = this.stores[newActiveIndex]; this.stores[newActiveIndex] = oldActive; this._authId = { ...this._authId, userId: undefined }; }); } } exports.WalletStorageManager = WalletStorageManager; //# sourceMappingURL=WalletStorageManager.js.map