diff --git a/src/client/handlers/AgentStatus.ts b/src/client/handlers/AgentStatus.ts index 249af6e58..4ee6017a2 100644 --- a/src/client/handlers/AgentStatus.ts +++ b/src/client/handlers/AgentStatus.ts @@ -5,8 +5,8 @@ import type { } from '../types'; import type PolykeyAgent from '../../PolykeyAgent'; import { UnaryHandler } from '@matrixai/rpc'; -import * as nodesUtils from '../../nodes/utils'; import config from '../../config'; +import * as nodesUtils from '../../nodes/utils'; class AgentStatus extends UnaryHandler< { diff --git a/src/client/handlers/AuditEventsGet.ts b/src/client/handlers/AuditEventsGet.ts index 8abeefcf5..308bb05c7 100644 --- a/src/client/handlers/AuditEventsGet.ts +++ b/src/client/handlers/AuditEventsGet.ts @@ -1,4 +1,5 @@ import type { ContextTimed } from '@matrixai/contexts'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; import type { AuditEvent, @@ -42,8 +43,8 @@ class AuditEventsGet extends ServerHandler< }> & { paths: Array; }, - _cancel, - _meta, + _cancel: (reason?: any) => void, + _meta: Record, ctx: ContextTimed, ): AsyncGenerator> { const { audit }: { audit: Audit } = this.container; diff --git a/src/client/handlers/GestaltsActionsGetByIdentity.ts b/src/client/handlers/GestaltsActionsGetByIdentity.ts index 5b5544e4d..33d118e04 100644 --- a/src/client/handlers/GestaltsActionsGetByIdentity.ts +++ b/src/client/handlers/GestaltsActionsGetByIdentity.ts @@ -8,9 +8,9 @@ import type GestaltGraph from '../../gestalts/GestaltGraph'; import type { GestaltAction } from '../../gestalts/types'; import type { IdentityId, ProviderId } from '../../ids'; import { UnaryHandler } from '@matrixai/rpc'; -import * as ids from '../../ids'; import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; +import * as ids from '../../ids'; class GestaltsActionsGetByIdentity extends UnaryHandler< { diff --git a/src/client/handlers/VaultsClone.ts b/src/client/handlers/VaultsClone.ts index ff2b99376..1a5386dde 100644 --- a/src/client/handlers/VaultsClone.ts +++ b/src/client/handlers/VaultsClone.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -6,11 +8,8 @@ import type { SuccessMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; -import type { NodeId } from '../../ids'; import { UnaryHandler } from '@matrixai/rpc'; import * as ids from '../../ids'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; class VaultsClone extends UnaryHandler< { @@ -22,27 +21,15 @@ class VaultsClone extends UnaryHandler< > { public handle = async ( input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): Promise> => { const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => ids.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: input.nodeIdEncoded, - }, - ); - // Vault id + const nodeId = ids.parseNodeId(input.nodeIdEncoded); await db.withTransactionF(async (tran) => { - await vaultManager.cloneVault(nodeId, input.nameOrId, tran); + await vaultManager.cloneVault(nodeId, input.nameOrId, ctx, tran); }); return { type: 'success', success: true }; }; diff --git a/src/client/handlers/VaultsCreate.ts b/src/client/handlers/VaultsCreate.ts index cd7f503d0..cc5d3a60f 100644 --- a/src/client/handlers/VaultsCreate.ts +++ b/src/client/handlers/VaultsCreate.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -19,17 +21,16 @@ class VaultsCreate extends UnaryHandler< > { public handle = async ( input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): Promise> => { const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; - const vaultId = await db.withTransactionF((tran) => - vaultManager.createVault(input.vaultName, tran), + vaultManager.createVault(input.vaultName, ctx, tran), ); - - return { - vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), - }; + return { vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId) }; }; } diff --git a/src/client/handlers/VaultsDelete.ts b/src/client/handlers/VaultsDelete.ts index d341d4f66..f1d050a5e 100644 --- a/src/client/handlers/VaultsDelete.ts +++ b/src/client/handlers/VaultsDelete.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -6,10 +8,9 @@ import type { VaultIdentifierMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName } from '../../vaults/types'; import { UnaryHandler } from '@matrixai/rpc'; -import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; +import * as vaultsUtils from '../../vaults/utils'; class VaultsDelete extends UnaryHandler< { @@ -21,20 +22,25 @@ class VaultsDelete extends UnaryHandler< > { public handle = async ( input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): Promise> => { const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; await db.withTransactionF(async (tran) => { const vaultIdFromName = await vaultManager.getVaultId( - input.nameOrId as VaultName, + input.nameOrId, tran, ); const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } - await vaultManager.destroyVault(vaultId, tran); + await vaultManager.destroyVault(vaultId, ctx, tran); }); return { type: 'success', success: true }; }; diff --git a/src/client/handlers/VaultsList.ts b/src/client/handlers/VaultsList.ts index fb0c0aa3d..724f46a2a 100644 --- a/src/client/handlers/VaultsList.ts +++ b/src/client/handlers/VaultsList.ts @@ -1,3 +1,4 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; import type { ClientRPCRequestParams, @@ -5,6 +6,7 @@ import type { VaultListMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; +import type { JSONValue } from '@matrixai/rpc'; import { ServerHandler } from '@matrixai/rpc'; import * as vaultsUtils from '../../vaults/utils'; @@ -17,21 +19,21 @@ class VaultsList extends ServerHandler< ClientRPCResponseResult > { public handle = async function* ( - _input, - _cancel, - _meta, - ctx, + _input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): AsyncGenerator> { if (ctx.signal.aborted) throw ctx.signal.reason; const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; const vaults = await db.withTransactionF((tran) => - vaultManager.listVaults(tran), + vaultManager.listVaults(ctx, tran), ); for await (const [vaultName, vaultId] of vaults) { if (ctx.signal.aborted) throw ctx.signal.reason; yield { - vaultName, + vaultName: vaultName, vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), }; } diff --git a/src/client/handlers/VaultsLog.ts b/src/client/handlers/VaultsLog.ts index 591b6ca9c..6b47ae98c 100644 --- a/src/client/handlers/VaultsLog.ts +++ b/src/client/handlers/VaultsLog.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -6,7 +8,6 @@ import type { VaultsLogMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName } from '../../vaults/types'; import { ServerHandler } from '@matrixai/rpc'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; @@ -21,22 +22,24 @@ class VaultsLog extends ServerHandler< > { public handle = async function* ( input: ClientRPCRequestParams, - _cancel, - _meta, - ctx, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): AsyncGenerator> { if (ctx.signal.aborted) throw ctx.signal.reason; const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; const log = await db.withTransactionF(async (tran) => { const vaultIdFromName = await vaultManager.getVaultId( - input.nameOrId as VaultName, + input.nameOrId, tran, ); const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } // Getting the log return await vaultManager.withVaults( @@ -44,6 +47,7 @@ class VaultsLog extends ServerHandler< async (vault) => { return await vault.log(input.commitId, input.depth); }, + ctx, tran, ); }); diff --git a/src/client/handlers/VaultsPermissionGet.ts b/src/client/handlers/VaultsPermissionGet.ts index 9e08d6bff..0ee38d8f0 100644 --- a/src/client/handlers/VaultsPermissionGet.ts +++ b/src/client/handlers/VaultsPermissionGet.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -26,9 +28,9 @@ class VaultsPermissionGet extends ServerHandler< > { public handle = async function* ( input: ClientRPCRequestParams, - _cancel, - _meta, - ctx, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): AsyncGenerator> { if (ctx.signal.aborted) throw ctx.signal.reason; const { @@ -45,7 +47,9 @@ class VaultsPermissionGet extends ServerHandler< const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } // Getting permissions return [await acl.getVaultPerm(vaultId, tran), vaultId]; diff --git a/src/client/handlers/VaultsPermissionSet.ts b/src/client/handlers/VaultsPermissionSet.ts index 358fd8340..3e7ee34ec 100644 --- a/src/client/handlers/VaultsPermissionSet.ts +++ b/src/client/handlers/VaultsPermissionSet.ts @@ -6,8 +6,7 @@ import type { SuccessMessage, } from '../types'; import type ACL from '../../acl/ACL'; -import type { VaultAction, VaultActions } from '../../vaults/types'; -import type { NodeId } from '../../ids'; +import type { VaultActions } from '../../vaults/types'; import type VaultManager from '../../vaults/VaultManager'; import type NotificationsManager from '../../notifications/NotificationsManager'; import type GestaltGraph from '../../gestalts/GestaltGraph'; @@ -15,8 +14,6 @@ import { UnaryHandler } from '@matrixai/rpc'; import * as ids from '../../ids'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; class VaultsPermissionSet extends UnaryHandler< { @@ -53,30 +50,21 @@ class VaultsPermissionSet extends UnaryHandler< const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } - const { - nodeId, - actions, - }: { - nodeId: NodeId; - actions: Array; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => ids.parseNodeId(value)], - [['actions'], () => value.map(vaultsUtils.parseVaultAction)], - () => value, - ); - }, - { - nodeId: input.nodeIdEncoded, - actions: input.vaultPermissionList, - }, + const nodeId = ids.parseNodeId(input.nodeIdEncoded); + const actions = input.vaultPermissionList.map( + vaultsUtils.parseVaultAction, ); // Checking if vault exists const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + if (!vaultMeta) { + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); + } // Setting permissions const actionsSet: VaultActions = {}; await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); diff --git a/src/client/handlers/VaultsPull.ts b/src/client/handlers/VaultsPull.ts index 328ea8a78..bf2eb9b39 100644 --- a/src/client/handlers/VaultsPull.ts +++ b/src/client/handlers/VaultsPull.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -6,14 +8,11 @@ import type { VaultsPullMessage, } from '../types'; import type { VaultName } from '../../vaults/types'; -import type { NodeId } from '../../ids'; import type VaultManager from '../../vaults/VaultManager'; import { UnaryHandler } from '@matrixai/rpc'; import * as ids from '../../ids'; import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; class VaultsPull extends UnaryHandler< { @@ -25,12 +24,14 @@ class VaultsPull extends UnaryHandler< > { public handle = async ( input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): Promise> => { const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; - let pullVaultId; - pullVaultId = vaultsUtils.decodeVaultId(input.pullVault); - pullVaultId = pullVaultId ?? input.pullVault; + const pullVaultId = + vaultsUtils.decodeVaultId(input.pullVault) ?? input.pullVault; await db.withTransactionF(async (tran) => { const vaultIdFromName = await vaultManager.getVaultId( input.nameOrId as VaultName, @@ -39,28 +40,19 @@ class VaultsPull extends UnaryHandler< const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } - const { - nodeId, - }: { - nodeId: NodeId | undefined; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => (value ? ids.parseNodeId(value) : undefined)], - () => value, - ); - }, - { - nodeId: input.nodeIdEncoded, - }, - ); + const nodeId = input.nodeIdEncoded + ? ids.parseNodeId(input.nodeIdEncoded) + : undefined; await vaultManager.pullVault({ - vaultId, + vaultId: vaultId, pullNodeId: nodeId, pullVaultNameOrId: pullVaultId, - tran, + ctx: ctx, + tran: tran, }); }); return { type: 'success', success: true }; diff --git a/src/client/handlers/VaultsRename.ts b/src/client/handlers/VaultsRename.ts index 3720e792e..555f7361e 100644 --- a/src/client/handlers/VaultsRename.ts +++ b/src/client/handlers/VaultsRename.ts @@ -1,4 +1,6 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { DB } from '@matrixai/db'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -20,6 +22,9 @@ class VaultsRename extends UnaryHandler< > { public handle = async ( input: ClientRPCRequestParams, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): Promise> => { const { db, vaultManager }: { db: DB; vaultManager: VaultManager } = this.container; @@ -31,12 +36,12 @@ class VaultsRename extends UnaryHandler< const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); + throw new vaultsErrors.ErrorVaultsVaultUndefined( + `Vault "${input.nameOrId}" does not exist`, + ); } - await vaultManager.renameVault(vaultId, input.newName, tran); - return { - vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), - }; + await vaultManager.renameVault(vaultId, input.newName, ctx, tran); + return { vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId) }; }); }; } diff --git a/src/client/handlers/VaultsScan.ts b/src/client/handlers/VaultsScan.ts index 7d0880399..87b432f51 100644 --- a/src/client/handlers/VaultsScan.ts +++ b/src/client/handlers/VaultsScan.ts @@ -1,3 +1,5 @@ +import type { ContextTimed } from '@matrixai/contexts'; +import type { JSONValue } from '@matrixai/rpc'; import type { ClientRPCRequestParams, ClientRPCResponseResult, @@ -5,11 +7,8 @@ import type { VaultsScanMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; -import type { NodeId } from '../../ids'; import { ServerHandler } from '@matrixai/rpc'; import * as ids from '../../ids'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; class VaultsScan extends ServerHandler< { @@ -20,36 +19,22 @@ class VaultsScan extends ServerHandler< > { public handle = async function* ( input: ClientRPCRequestParams, - _cancel, - _meta, - ctx, + _cancel: (reason?: any) => void, + _meta: Record, + ctx: ContextTimed, ): AsyncGenerator> { if (ctx.signal.aborted) throw ctx.signal.reason; const { vaultManager }: { vaultManager: VaultManager } = this.container; - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => ids.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: input.nodeIdEncoded, - }, - ); + const nodeId = ids.parseNodeId(input.nodeIdEncoded); for await (const { vaultIdEncoded, vaultName, vaultPermissions, - } of vaultManager.scanVaults(nodeId)) { + } of vaultManager.scanVaults(nodeId, ctx)) { if (ctx.signal.aborted) throw ctx.signal.reason; yield { - vaultName, - vaultIdEncoded, + vaultName: vaultName, + vaultIdEncoded: vaultIdEncoded, permissions: vaultPermissions, }; } diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index 30a1aa068..efe129876 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -1149,7 +1149,7 @@ class GestaltGraph { return; } default: - never(`type must be either "node" or "identity" got "${type}"`); + never(`type must be either "node" or "identity", got "${type}"`); } } diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index 33163be1c..201230cb4 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -1,6 +1,6 @@ import type { ReadCommitResult } from 'isomorphic-git'; import type { EncryptedFS } from 'encryptedfs'; -import type { ContextCancellable } from '@matrixai/contexts'; +import type { ContextTimed } from '@matrixai/contexts'; import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { RPCClient } from '@matrixai/rpc'; import type { ResourceAcquire, ResourceRelease } from '@matrixai/resources'; @@ -28,7 +28,7 @@ import { ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { withF, withG } from '@matrixai/resources'; -import { context, cancellable } from '@matrixai/contexts/dist/decorators'; +import { context, timedCancellable } from '@matrixai/contexts/dist/decorators'; import { RWLockWriter } from '@matrixai/async-locks'; import { tagLast } from './types'; import * as vaultsUtils from './utils'; @@ -70,8 +70,9 @@ class VaultInternal { vaultsDbPath, keyRing, efs, - logger = new Logger(this.name), fresh = false, + ctx, + logger = new Logger(this.name), tran, }: { vaultId: VaultId; @@ -80,8 +81,9 @@ class VaultInternal { vaultsDbPath: LevelPath; keyRing: KeyRing; efs: EncryptedFS; - logger?: Logger; fresh?: boolean; + ctx: ContextTimed; + logger?: Logger; tran?: DBTransaction; }): Promise { if (tran == null) { @@ -93,8 +95,9 @@ class VaultInternal { vaultsDbPath, keyRing, efs, - logger, fresh, + ctx, + logger, tran, }), ); @@ -110,7 +113,7 @@ class VaultInternal { efs, logger, }); - await vault.start({ fresh, vaultName, tran }); + await vault.start({ fresh, vaultName, ctx, tran }); logger.info(`Created ${this.name} - ${vaultIdEncoded}`); return vault; } @@ -127,6 +130,7 @@ class VaultInternal { keyRing, nodeManager, efs, + ctx, logger = new Logger(this.name), tran, }: { @@ -138,6 +142,7 @@ class VaultInternal { efs: EncryptedFS; keyRing: KeyRing; nodeManager: NodeManager; + ctx: ContextTimed; logger?: Logger; tran?: DBTransaction; }): Promise { @@ -152,6 +157,7 @@ class VaultInternal { keyRing, nodeManager, efs, + ctx, logger, tran, }), @@ -173,12 +179,12 @@ class VaultInternal { const [vaultName, remoteVaultId]: [VaultName, VaultId] = await nodeManager.withConnF(targetNodeId, async (connection) => { const client = connection.getClient(); - const [request, vaultName, remoteVaultId] = await vault.request( client, targetVaultNameOrId, 'clone', ); + // TODO: ability to cancel git clone await git.clone({ fs: efs, http: { request }, @@ -195,7 +201,7 @@ class VaultInternal { remoteVault: vaultsUtils.encodeVaultId(remoteVaultId), }; - await vault.start({ vaultName, tran }); + await vault.start({ vaultName, ctx, tran }); // Setting the remote in the metadata await tran.put( [...vault.vaultMetadataDbPath, VaultInternal.remoteKey], @@ -256,26 +262,28 @@ class VaultInternal { } /** - * * @param fresh Clears all state before starting * @param vaultName Name of the vault, Only used when creating a new vault + * @param ctx * @param tran */ public async start({ fresh = false, vaultName, + ctx, tran, }: { fresh?: boolean; vaultName?: VaultName; + ctx?: ContextTimed; tran?: DBTransaction; } = {}): Promise { if (tran == null) { return await this.db.withTransactionF((tran) => - this.start_(fresh, tran, vaultName), + this.start_(fresh, tran, ctx, vaultName), ); } - return await this.start_(fresh, tran, vaultName); + return await this.start_(fresh, tran, ctx, vaultName); } /** @@ -285,8 +293,16 @@ class VaultInternal { protected async start_( fresh: boolean, tran: DBTransaction, + ctx?: Partial, vaultName?: VaultName, - ) { + ): Promise; + @timedCancellable(true) + protected async start_( + fresh: boolean, + tran: DBTransaction, + @context ctx: ContextTimed, + vaultName?: VaultName, + ): Promise { this.logger.info( `Starting ${this.constructor.name} - ${this.vaultIdEncoded}`, ); @@ -308,7 +324,7 @@ class VaultInternal { await vaultsUtils.mkdirExists(this.efs, this.vaultDataDir); await vaultsUtils.mkdirExists(this.efs, this.vaultGitDir); await this.setupMeta({ vaultName, tran }); - await this.setupGit(tran); + await this.setupGit(ctx, tran); this.efsVault = await this.efs.chroot(this.vaultDataDir); this.logger.info( `Started ${this.constructor.name} - ${this.vaultIdEncoded}`, @@ -445,14 +461,14 @@ class VaultInternal { */ public async writeF( f: (fs: FileSystemWritable) => Promise, - ctx?: Partial, + ctx?: Partial, tran?: DBTransaction, ): Promise; @ready(new vaultsErrors.ErrorVaultNotRunning()) - @cancellable(true) + @timedCancellable(true) public async writeF( f: (fs: FileSystemWritable) => Promise, - @context ctx: ContextCancellable, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { @@ -503,7 +519,7 @@ class VaultInternal { @ready(new vaultsErrors.ErrorVaultNotRunning()) public writeG( g: (fs: FileSystemWritable) => AsyncGenerator, - ctx: ContextCancellable, + ctx: ContextTimed, tran?: DBTransaction, ): AsyncGenerator { if (tran == null) { @@ -569,7 +585,7 @@ class VaultInternal { */ @ready(new vaultsErrors.ErrorVaultNotRunning()) public acquireWrite( - ctx: ContextCancellable, + ctx: ContextTimed, tran?: DBTransaction, ): ResourceAcquire { return async () => { @@ -786,7 +802,10 @@ class VaultInternal { * If the vault is in a dirty state then we clean up the working directory * or any history not part of the canonicalBranch. */ - protected async setupGit(tran: DBTransaction): Promise { + protected async setupGit( + ctx: ContextTimed, + tran: DBTransaction, + ): Promise { // Initialization is idempotent // It works even with an existing git repository await git.init({ @@ -843,7 +862,7 @@ class VaultInternal { // This ensures that any uncommitted state is dropped await this.cleanWorkingDirectory(); // Do global GC operation - await this.garbageCollectGitObjectsGlobal(); + await this.garbageCollectGitObjectsGlobal(ctx); // Setting dirty back to false await tran.put( @@ -979,7 +998,7 @@ class VaultInternal { * and the old history is removed from the old canonical head to the branch point. This is to maintain the strict * non-branching linear history. */ - protected async createCommit(ctx: ContextCancellable) { + protected async createCommit(ctx: ContextTimed) { // Forced wait for 1 ms to allow difference in mTime between file changes await utils.sleep(1); // Checking if commit is appending or branching @@ -1143,12 +1162,10 @@ class VaultInternal { * This is costly since it will compare the walked tree with all existing objects. */ protected async garbageCollectGitObjectsGlobal( - ctx?: Partial, + ctx?: Partial, ): Promise; - @cancellable(true) - protected async garbageCollectGitObjectsGlobal( - @context ctx: ContextCancellable, - ) { + @timedCancellable(true) + protected async garbageCollectGitObjectsGlobal(@context ctx: ContextTimed) { const objectIdsAll = await gitUtils.listObjectsAll({ fs: this.efs, gitDir: this.vaultGitDir, @@ -1192,7 +1209,7 @@ class VaultInternal { protected async garbageCollectGitObjectsLocal( startId: string, stopId: string, - ctx: ContextCancellable, + ctx: ContextTimed, ) { const objects = await gitUtils.listObjects( { diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index 32e7d2b02..59870a438 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -1,6 +1,6 @@ -import type { DBTransaction, LevelPath } from '@matrixai/db'; import type { LockRequest } from '@matrixai/async-locks'; -import type { ContextCancellable, ContextTimed } from '@matrixai/contexts'; +import type { ContextTimed } from '@matrixai/contexts'; +import type { DBTransaction, LevelPath } from '@matrixai/db'; import type { VaultId, VaultName, @@ -30,6 +30,7 @@ import { import { IdInternal } from '@matrixai/id'; import { withF, withG } from '@matrixai/resources'; import { LockBox, RWLockWriter } from '@matrixai/async-locks'; +import { context, timedCancellable } from '@matrixai/contexts/dist/decorators'; import Logger from '@matrixai/logger'; import VaultInternal from './VaultInternal'; import * as vaultsEvents from './events'; @@ -314,14 +315,21 @@ class VaultManager { * Constructs a new vault instance with a given name and * stores it in memory */ + public async createVault( + vaultName: VaultName, + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) public async createVault( vaultName: VaultName, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.createVault(vaultName, tran), + this.createVault(vaultName, ctx, tran), ); } // Adding vault to name map @@ -342,7 +350,7 @@ class VaultManager { ); const vaultIdString = vaultId.toString() as VaultIdString; return await this.vaultLocks.withF( - [vaultId.toString(), RWLockWriter, 'write'], + [vaultId.toString(), RWLockWriter, 'write', ctx], async () => { // Creating vault const vault = await VaultInternal.createVaultInternal({ @@ -354,6 +362,7 @@ class VaultManager { db: this.db, vaultsDbPath: this.vaultsDbPath, fresh: true, + ctx, tran, }); // Adding vault to object map @@ -407,19 +416,26 @@ class VaultManager { * Removes the metadata and EFS state of a vault using a * given VaultId */ + public async destroyVault( + vaultId: VaultId, + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) public async destroyVault( vaultId: VaultId, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.destroyVault(vaultId, tran), + this.destroyVault(vaultId, ctx, tran), ); } await this.vaultLocks.withF( - [vaultId.toString(), RWLockWriter, 'write'], + [vaultId.toString(), RWLockWriter, 'write', ctx], async () => { await tran.lock([...this.vaultsDbPath, vaultId].join('')); // Ensure protection from write skew @@ -479,10 +495,18 @@ class VaultManager { * Lists the vault name and associated VaultId of all * the vaults stored */ + public async listVaults( + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) - public async listVaults(tran?: DBTransaction): Promise { + @timedCancellable(true) + public async listVaults( + @context ctx: ContextTimed, + tran?: DBTransaction, + ): Promise { if (tran == null) { - return this.db.withTransactionF((tran) => this.listVaults(tran)); + return this.db.withTransactionF((tran) => this.listVaults(ctx, tran)); } const vaults: VaultList = new Map(); @@ -490,6 +514,7 @@ class VaultManager { for await (const [vaultNameBuffer, vaultIdBuffer] of tran.iterator( this.vaultsNamesDbPath, )) { + if (ctx.signal.aborted) throw ctx.signal.reason; const vaultName = vaultNameBuffer.toString() as VaultName; const vaultId = IdInternal.fromBuffer(vaultIdBuffer); vaults.set(vaultName, vaultId); @@ -500,20 +525,28 @@ class VaultManager { /** * Changes the vault name metadata of a VaultId */ + public async renameVault( + vaultId: VaultId, + newVaultName: VaultName, + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) public async renameVault( vaultId: VaultId, newVaultName: VaultName, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.renameVault(vaultId, newVaultName, tran), + this.renameVault(vaultId, newVaultName, ctx, tran), ); } await this.vaultLocks.withF( - [vaultId.toString(), RWLockWriter, 'write'], + [vaultId.toString(), RWLockWriter, 'write', ctx], async () => { await tran.lock( [...this.vaultsNamesDbPath, newVaultName] @@ -684,15 +717,23 @@ class VaultManager { * Clones the contents of a remote vault into a new local * vault instance */ + public async cloneVault( + nodeId: NodeId, + vaultNameOrId: VaultId | VaultName, + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) public async cloneVault( nodeId: NodeId, vaultNameOrId: VaultId | VaultName, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.cloneVault(nodeId, vaultNameOrId, tran), + this.cloneVault(nodeId, vaultNameOrId, ctx, tran), ); } @@ -707,14 +748,15 @@ class VaultManager { const vault = await VaultInternal.cloneVaultInternal({ targetNodeId: nodeId, targetVaultNameOrId: vaultNameOrId, - vaultId, + vaultId: vaultId, db: this.db, nodeManager: this.nodeManager, vaultsDbPath: this.vaultsDbPath, keyRing: this.keyRing, efs: this.efs, + ctx: ctx, logger: this.logger.getChild(VaultInternal.name), - tran, + tran: tran, }); this.vaultMap.set(vaultIdString, vault); const vaultMetadata = (await this.getVaultMeta(vaultId, tran))!; @@ -765,20 +807,38 @@ class VaultManager { * Pulls the contents of a remote vault into an existing vault * instance */ - public async pullVault({ - vaultId, - pullNodeId, - pullVaultNameOrId, - tran, - }: { - vaultId: VaultId; - pullNodeId?: NodeId; - pullVaultNameOrId?: VaultId | VaultName; - tran?: DBTransaction; - }): Promise { + public async pullVault( + { + vaultId, + pullNodeId, + pullVaultNameOrId, + tran, + }: { + vaultId: VaultId; + pullNodeId?: NodeId; + pullVaultNameOrId?: VaultId | VaultName; + tran?: DBTransaction; + }, + ctx?: Partial, + ): Promise; + @timedCancellable(true) + public async pullVault( + { + vaultId, + pullNodeId, + pullVaultNameOrId, + tran, + }: { + vaultId: VaultId; + pullNodeId?: NodeId; + pullVaultNameOrId?: VaultId | VaultName; + tran?: DBTransaction; + }, + @context ctx: ContextTimed, + ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.pullVault({ vaultId, pullNodeId, pullVaultNameOrId, tran }), + this.pullVault({ vaultId, pullNodeId, pullVaultNameOrId, tran }, ctx), ); } @@ -787,7 +847,7 @@ class VaultManager { [vaultId.toString(), RWLockWriter, 'write'], async () => { await tran.lock([...this.vaultsDbPath, vaultId].join('')); - const vault = await this.getVault(vaultId, tran); + const vault = await this.getVault(vaultId, tran, ctx); await vault.pullVault({ nodeManager: this.nodeManager, pullNodeId, @@ -844,7 +904,7 @@ class VaultManager { public async *handlePackRequest( vaultId: VaultId, body: Array, - ctx: ContextCancellable, + ctx: ContextTimed, tran?: DBTransaction, ): AsyncGenerator { if (tran == null) { @@ -880,7 +940,10 @@ class VaultManager { /** * Retrieves all the vaults for a peers node */ - public async *scanVaults(targetNodeId: NodeId): AsyncGenerator<{ + public async *scanVaults( + targetNodeId: NodeId, + ctx: ContextTimed, + ): AsyncGenerator<{ vaultName: VaultName; vaultIdEncoded: VaultIdEncoded; vaultPermissions: VaultAction[]; @@ -894,12 +957,14 @@ class VaultManager { vaultPermissions: VaultAction[]; }> { const client = connection.getClient(); - const genReadable = await client.methods.vaultsScan({}); + const genReadable = await client.methods.vaultsScan({}, ctx); for await (const vault of genReadable) { - const vaultName = vault.vaultName; - const vaultIdEncoded = vault.vaultIdEncoded; - const vaultPermissions = vault.vaultPermissions; - yield { vaultName, vaultIdEncoded, vaultPermissions }; + ctx.signal.throwIfAborted(); + yield { + vaultName: vault.vaultName, + vaultIdEncoded: vault.vaultIdEncoded, + vaultPermissions: vault.vaultPermissions, + }; } }, ); @@ -910,7 +975,7 @@ class VaultManager { */ public async *handleScanVaults( nodeId: NodeId, - ctx: ContextCancellable, + ctx: ContextTimed, tran?: DBTransaction, ): AsyncGenerator<{ vaultId: VaultId; @@ -977,10 +1042,17 @@ class VaultManager { return vaultId; } + protected async getVault( + vaultId: VaultId, + tran: DBTransaction, + ctx?: Partial, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) protected async getVault( vaultId: VaultId, tran: DBTransaction, + @context ctx: ContextTimed, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => this.getVault(vaultId, tran)); @@ -997,13 +1069,14 @@ class VaultManager { } // 2. if the state exists then create, add to map and return that const newVault = await VaultInternal.createVaultInternal({ - vaultId, + vaultId: vaultId, keyRing: this.keyRing, efs: this.efs, logger: this.logger.getChild(VaultInternal.name), db: this.db, vaultsDbPath: this.vaultsDbPath, - tran, + ctx: ctx, + tran: tran, }); this.vaultMap.set(vaultIdString, newVault); return newVault; @@ -1013,24 +1086,33 @@ class VaultManager { * Takes a function and runs it with the listed vaults. locking is handled automatically * @param vaultIds List of vault ID for vaults you wish to use * @param f Function you wish to run with the provided vaults + * @param ctx * @param tran */ + public async withVaults( + vaultIds: VaultId[], + f: (...args: Vault[]) => Promise, + ctx?: Partial, + tran?: DBTransaction, + ): Promise; @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) + @timedCancellable(true) public async withVaults( vaultIds: VaultId[], f: (...args: Vault[]) => Promise, + @context ctx: ContextTimed, tran?: DBTransaction, ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.withVaults(vaultIds, f, tran), + this.withVaults(vaultIds, f, ctx, tran), ); } // Obtaining locks const vaultLocks: Array> = vaultIds.map( (vaultId) => { - return [vaultId.toString(), RWLockWriter, 'read']; + return [vaultId.toString(), RWLockWriter, 'read', ctx]; }, ); // Running the function with locking diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 5b8809417..92d4801ae 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -1,3 +1,4 @@ +import type { ContextTimed } from '@matrixai/contexts'; import type { NodeId } from '@/ids/types'; import type { VaultAction, @@ -353,7 +354,7 @@ describe('VaultManager', () => { // Scanning vaults const abortController = new AbortController(); - const ctx = { signal: abortController.signal }; + const ctx = { signal: abortController.signal } as ContextTimed; const gen = vaultManager.handleScanVaults(nodeId1, ctx); const vaults: Record = {}; for await (const vault of gen) { @@ -366,7 +367,7 @@ describe('VaultManager', () => { // Should throw due to no permission await expect(async () => { const abortController = new AbortController(); - const ctx = { signal: abortController.signal }; + const ctx = { signal: abortController.signal } as ContextTimed; for await (const _ of vaultManager.handleScanVaults(nodeId2, ctx)) { // Should throw } @@ -375,7 +376,7 @@ describe('VaultManager', () => { await gestaltGraph.setGestaltAction(['node', nodeId2], 'notify'); await expect(async () => { const abortController = new AbortController(); - const ctx = { signal: abortController.signal }; + const ctx = { signal: abortController.signal } as ContextTimed; for await (const _ of vaultManager.handleScanVaults(nodeId2, ctx)) { // Should throw } @@ -1498,7 +1499,9 @@ describe('VaultManager', () => { // Should throw due to no permission const testFun = async () => { - for await (const _ of vaultManager.scanVaults(targetNodeId)) { + const abortController = new AbortController(); + const ctx = { signal: abortController.signal } as ContextTimed; + for await (const _ of vaultManager.scanVaults(targetNodeId, ctx)) { // Should throw } }; @@ -1526,7 +1529,9 @@ describe('VaultManager', () => { await remoteKeynode1.acl.setVaultAction(vault2, nodeId1, 'clone'); // No permissions for vault3 - const gen = vaultManager.scanVaults(targetNodeId); + const abortController = new AbortController(); + const ctx = { signal: abortController.signal } as ContextTimed; + const gen = vaultManager.scanVaults(targetNodeId, ctx); const vaults: Record = {}; for await (const vault of gen) { vaults[vault.vaultIdEncoded] = [