UNPKG

@bsv/wallet-toolbox-client

Version:
477 lines 20.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StorageClient = void 0; const sdk_1 = require("@bsv/sdk"); const WERR_errors_1 = require("../../sdk/WERR_errors"); /** * `StorageClient` implements the `WalletStorageProvider` interface which allows it to * serve as a BRC-100 wallet's active storage. * * Internally, it uses JSON-RPC over HTTPS to make requests of a remote server. * Typically this server uses the `StorageServer` class to implement the service. * * The `AuthFetch` component is used to secure and authenticate the requests to the remote server. * * `AuthFetch` is initialized with a BRC-100 wallet which establishes the identity of * the party making requests of the remote service. * * For details of the API implemented, follow the "See also" link for the `WalletStorageProvider` interface. */ class StorageClient { constructor(wallet, endpointUrl) { this.nextId = 1; this.authClient = new sdk_1.AuthFetch(wallet); this.endpointUrl = endpointUrl; } /** * The `StorageClient` implements the `WalletStorageProvider` interface. * It does not implement the lower level `StorageProvider` interface. * * @returns false */ isStorageProvider() { return false; } ////////////////////////////////////////////////////////////////////////////// // JSON-RPC helper ////////////////////////////////////////////////////////////////////////////// /** * Make a JSON-RPC call to the remote server. * @param method The WalletStorage method name to call. * @param params The array of parameters to pass to the method in order. */ async rpcCall(method, params) { try { const id = this.nextId++; const body = { jsonrpc: '2.0', method, params, id }; let response; try { response = await this.authClient.fetch(this.endpointUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } catch (eu) { throw eu; } if (!response.ok) { throw new Error(`WalletStorageClient rpcCall: network error ${response.status} ${response.statusText}`); } const json = await response.json(); if (json.error) { const { code, message, data } = json.error; const err = new Error(`RPC Error: ${message}`); err.code = code; err.data = data; throw err; } return json.result; } catch (eu) { throw eu; } } /** * @returns true once storage `TableSettings` have been retreived from remote storage. */ isAvailable() { // We'll just say "yes" if we have settings return !!this.settings; } /** * @returns remote storage `TableSettings` if they have been retreived by `makeAvailable`. * @throws WERR_INVALID_OPERATION if `makeAvailable` has not yet been called. */ getSettings() { if (!this.settings) { throw new WERR_errors_1.WERR_INVALID_OPERATION('call makeAvailable at least once before getSettings'); } return this.settings; } /** * Must be called prior to making use of storage. * Retreives `TableSettings` from remote storage provider. * @returns remote storage `TableSettings` */ async makeAvailable() { if (!this.settings) { this.settings = await this.rpcCall('makeAvailable', []); } return this.settings; } ////////////////////////////////////////////////////////////////////////////// // // Implementation of all WalletStorage interface methods // They are simple pass-thrus to rpcCall // // IMPORTANT: The parameter ordering must match exactly as in your interface. ////////////////////////////////////////////////////////////////////////////// /** * Called to cleanup resources when no further use of this object will occur. */ async destroy() { return this.rpcCall('destroy', []); } /** * Requests schema migration to latest. * Typically remote storage will ignore this request. * @param storageName Unique human readable name for remote storage if it does not yet exist. * @param storageIdentityKey Unique identity key for remote storage if it does not yet exist. * @returns current schema migration identifier */ async migrate(storageName, storageIdentityKey) { return this.rpcCall('migrate', [storageName]); } /** * Remote storage does not offer `Services` to remote clients. * @throws WERR_INVALID_OPERATION */ getServices() { // Typically, the client would not store or retrieve "Services" from a remote server. // The "services" in local in-memory usage is a no-op or your own approach: throw new WERR_errors_1.WERR_INVALID_OPERATION('getServices() not implemented in remote client. This method typically is not used remotely.'); } /** * Ignored. Remote storage cannot share `Services` with remote clients. */ setServices(v) { // Typically no-op for remote client // Because "services" are usually local definitions to the Storage. } /** * Storage level processing for wallet `internalizeAction`. * Updates internalized outputs in remote storage. * Triggers proof validation of containing transaction. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args Original wallet `internalizeAction` arguments. * @returns `internalizeAction` results */ async internalizeAction(auth, args) { return this.rpcCall('internalizeAction', [auth, args]); } /** * Storage level processing for wallet `createAction`. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args Validated extension of original wallet `createAction` arguments. * @returns `StorageCreateActionResults` supporting additional wallet processing to yield `createAction` results. */ async createAction(auth, args) { return this.rpcCall('createAction', [auth, args]); } /** * Storage level processing for wallet `createAction` and `signAction`. * * Handles remaining storage tasks once a fully signed transaction has been completed. This is common to both `createAction` and `signAction`. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args `StorageProcessActionArgs` convey completed signed transaction to storage. * @returns `StorageProcessActionResults` supporting final wallet processing to yield `createAction` or `signAction` results. */ async processAction(auth, args) { return this.rpcCall('processAction', [auth, args]); } /** * Aborts an action by `reference` string. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args original wallet `abortAction` args. * @returns `abortAction` result. */ async abortAction(auth, args) { return this.rpcCall('abortAction', [auth, args]); } /** * Used to both find and initialize a new user by identity key. * It is up to the remote storage whether to allow creation of new users by this method. * @param identityKey of the user. * @returns `TableUser` for the user and whether a new user was created. */ async findOrInsertUser(identityKey) { return this.rpcCall('findOrInsertUser', [identityKey]); } /** * Used to both find and insert a `TableSyncState` record for the user to track wallet data replication across storage providers. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param storageName the name of the remote storage being sync'd * @param storageIdentityKey the identity key of the remote storage being sync'd * @returns `TableSyncState` and whether a new record was created. */ async findOrInsertSyncStateAuth(auth, storageIdentityKey, storageName) { const r = await this.rpcCall('findOrInsertSyncStateAuth', [ auth, storageIdentityKey, storageName ]); r.syncState = this.validateEntity(r.syncState, ['when']); return r; } /** * Inserts a new certificate with fields and keyring into remote storage. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param certificate the certificate to insert. * @returns record Id of the inserted `TableCertificate` record. */ async insertCertificateAuth(auth, certificate) { const r = await this.rpcCall('insertCertificateAuth', [auth, certificate]); return r; } /** * Storage level processing for wallet `listActions`. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args Validated extension of original wallet `listActions` arguments. * @returns `listActions` results. */ async listActions(auth, vargs) { const r = await this.rpcCall('listActions', [auth, vargs]); return r; } /** * Storage level processing for wallet `listOutputs`. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args Validated extension of original wallet `listOutputs` arguments. * @returns `listOutputs` results. */ async listOutputs(auth, vargs) { const r = await this.rpcCall('listOutputs', [auth, vargs]); return r; } /** * Storage level processing for wallet `listCertificates`. * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args Validated extension of original wallet `listCertificates` arguments. * @returns `listCertificates` results. */ async listCertificates(auth, vargs) { const r = await this.rpcCall('listCertificates', [auth, vargs]); return r; } /** * Find user certificates, optionally with fields. * * This certificate retrieval method supports internal wallet operations. * Field values are stored and retrieved encrypted. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args `FindCertificatesArgs` determines which certificates to retrieve and whether to include fields. * @returns array of certificates matching args. */ async findCertificatesAuth(auth, args) { const r = await this.rpcCall('findCertificatesAuth', [auth, args]); this.validateEntities(r); if (args.includeFields) { for (const c of r) { if (c.fields) this.validateEntities(c.fields); } } return r; } /** * Find output baskets. * * This retrieval method supports internal wallet operations. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args `FindOutputBasketsArgs` determines which baskets to retrieve. * @returns array of output baskets matching args. */ async findOutputBasketsAuth(auth, args) { const r = await this.rpcCall('findOutputBaskets', [auth, args]); this.validateEntities(r); return r; } /** * Find outputs. * * This retrieval method supports internal wallet operations. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args `FindOutputsArgs` determines which outputs to retrieve. * @returns array of outputs matching args. */ async findOutputsAuth(auth, args) { const r = await this.rpcCall('findOutputsAuth', [auth, args]); this.validateEntities(r); return r; } /** * Find requests for transaction proofs. * * This retrieval method supports internal wallet operations. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args `FindProvenTxReqsArgs` determines which proof requests to retrieve. * @returns array of proof requests matching args. */ async findProvenTxReqs(args) { const r = await this.rpcCall('findProvenTxReqs', [args]); this.validateEntities(r); return r; } /** * Relinquish a certificate. * * For storage supporting replication records must be kept of deletions. Therefore certificates are marked as deleted * when relinquished, and no longer returned by `listCertificates`, but are still retained by storage. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args original wallet `relinquishCertificate` args. */ async relinquishCertificate(auth, args) { return this.rpcCall('relinquishCertificate', [auth, args]); } /** * Relinquish an output. * * Relinquishing an output removes the output from whatever basket was tracking it. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param args original wallet `relinquishOutput` args. */ async relinquishOutput(auth, args) { return this.rpcCall('relinquishOutput', [auth, args]); } /** * Process a "chunk" of replication data for the user. * * The normal data flow is for the active storage to push backups as a sequence of data chunks to backup storage providers. * * @param args a copy of the replication request args that initiated the sequence of data chunks. * @param chunk the current data chunk to process. * @returns whether processing is done, counts of inserts and udpates, and related progress tracking properties. */ async processSyncChunk(args, chunk) { const r = await this.rpcCall('processSyncChunk', [args, chunk]); return r; } /** * Request a "chunk" of replication data for a specific user and storage provider. * * The normal data flow is for the active storage to push backups as a sequence of data chunks to backup storage providers. * Also supports recovery where non-active storage can attempt to merge available data prior to becoming active. * * @param args that identify the non-active storage which will receive replication data and constrains the replication process. * @returns the next "chunk" of replication data */ async getSyncChunk(args) { const r = await this.rpcCall('getSyncChunk', [args]); if (r.certificateFields) r.certificateFields = this.validateEntities(r.certificateFields); if (r.certificates) r.certificates = this.validateEntities(r.certificates); if (r.commissions) r.commissions = this.validateEntities(r.commissions); if (r.outputBaskets) r.outputBaskets = this.validateEntities(r.outputBaskets); if (r.outputTagMaps) r.outputTagMaps = this.validateEntities(r.outputTagMaps); if (r.outputTags) r.outputTags = this.validateEntities(r.outputTags); if (r.outputs) r.outputs = this.validateEntities(r.outputs); if (r.provenTxReqs) r.provenTxReqs = this.validateEntities(r.provenTxReqs); if (r.provenTxs) r.provenTxs = this.validateEntities(r.provenTxs); if (r.transactions) r.transactions = this.validateEntities(r.transactions); if (r.txLabelMaps) r.txLabelMaps = this.validateEntities(r.txLabelMaps); if (r.txLabels) r.txLabels = this.validateEntities(r.txLabels); if (r.user) r.user = this.validateEntity(r.user); return r; } /** * Handles the data received when a new transaction proof is found in response to an outstanding request for proof data: * * - Creates a new `TableProvenTx` record. * - Notifies all user transaction records of the new status. * - Updates the proof request record to 'completed' status which enables delayed deletion. * * @param args proof request and new transaction proof data * @returns results of updates */ async updateProvenTxReqWithNewProvenTx(args) { const r = await this.rpcCall('updateProvenTxReqWithNewProvenTx', [args]); return r; } /** * Ensures up-to-date wallet data replication to all configured backup storage providers, * then promotes one of the configured backups to active, * demoting the current active to new backup. * * @param auth Identifies client by identity key and the storage identity key of their currently active storage. * This must match the `AuthFetch` identity securing the remote conneciton. * @param newActiveStorageIdentityKey which must be a currently configured backup storage provider. */ async setActive(auth, newActiveStorageIdentityKey) { return this.rpcCall('setActive', [auth, newActiveStorageIdentityKey]); } validateDate(date) { let r; if (date instanceof Date) r = date; else r = new Date(date); return r; } /** * Helper to force uniform behavior across database engines. * Use to process all individual records with time stamps retreived from database. */ validateEntity(entity, dateFields) { entity.created_at = this.validateDate(entity.created_at); entity.updated_at = this.validateDate(entity.updated_at); if (dateFields) { for (const df of dateFields) { if (entity[df]) entity[df] = this.validateDate(entity[df]); } } for (const key of Object.keys(entity)) { const val = entity[key]; if (val === null) { entity[key] = undefined; } else if (val instanceof Uint8Array) { entity[key] = Array.from(val); } } return entity; } /** * Helper to force uniform behavior across database engines. * Use to process all arrays of records with time stamps retreived from database. * @returns input `entities` array with contained values validated. */ validateEntities(entities, dateFields) { for (let i = 0; i < entities.length; i++) { entities[i] = this.validateEntity(entities[i], dateFields); } return entities; } } exports.StorageClient = StorageClient; //# sourceMappingURL=StorageClient.js.map