@gakr-gakr/matrix 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +285 -0
- package/SPEC-SUPPORT.md +116 -0
- package/api.ts +38 -0
- package/auth-presence.ts +56 -0
- package/autobot.plugin.json +28 -0
- package/channel-plugin-api.ts +3 -0
- package/cli-metadata.ts +11 -0
- package/contract-api.ts +17 -0
- package/doctor-contract-api.ts +1 -0
- package/helper-api.ts +3 -0
- package/index.ts +55 -0
- package/package.json +101 -0
- package/plugin-entry.handlers.runtime.ts +1 -0
- package/runtime-api.ts +72 -0
- package/runtime-heavy-api.ts +1 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +17 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-selection.ts +223 -0
- package/src/actions.ts +346 -0
- package/src/approval-auth.ts +25 -0
- package/src/approval-handler.runtime.ts +595 -0
- package/src/approval-ids.ts +6 -0
- package/src/approval-native.ts +348 -0
- package/src/approval-reaction-auth.ts +45 -0
- package/src/approval-reactions.ts +313 -0
- package/src/auth-precedence.ts +61 -0
- package/src/channel-account-paths.ts +97 -0
- package/src/channel.runtime.ts +17 -0
- package/src/channel.setup.ts +48 -0
- package/src/channel.ts +667 -0
- package/src/cli-metadata.ts +19 -0
- package/src/cli.ts +2298 -0
- package/src/config-adapter.ts +41 -0
- package/src/config-schema.ts +159 -0
- package/src/config-ui-hints.ts +56 -0
- package/src/directory-live.ts +238 -0
- package/src/doctor-contract.ts +287 -0
- package/src/doctor.ts +262 -0
- package/src/env-vars.ts +92 -0
- package/src/exec-approval-resolver.ts +23 -0
- package/src/exec-approvals.ts +293 -0
- package/src/group-mentions.ts +41 -0
- package/src/legacy-crypto-inspector-availability.ts +60 -0
- package/src/legacy-crypto.ts +531 -0
- package/src/legacy-state.ts +156 -0
- package/src/matrix/account-config.ts +175 -0
- package/src/matrix/accounts.ts +194 -0
- package/src/matrix/actions/client.ts +31 -0
- package/src/matrix/actions/devices.ts +34 -0
- package/src/matrix/actions/limits.ts +6 -0
- package/src/matrix/actions/messages.ts +129 -0
- package/src/matrix/actions/pins.ts +63 -0
- package/src/matrix/actions/polls.ts +109 -0
- package/src/matrix/actions/profile.ts +37 -0
- package/src/matrix/actions/reactions.ts +59 -0
- package/src/matrix/actions/room.ts +71 -0
- package/src/matrix/actions/summary.ts +88 -0
- package/src/matrix/actions/types.ts +63 -0
- package/src/matrix/actions/verification.ts +589 -0
- package/src/matrix/actions.ts +37 -0
- package/src/matrix/active-client.ts +26 -0
- package/src/matrix/async-lock.ts +18 -0
- package/src/matrix/backup-health.ts +124 -0
- package/src/matrix/client/config-runtime-api.ts +9 -0
- package/src/matrix/client/config-secret-input.runtime.ts +1 -0
- package/src/matrix/client/config.ts +853 -0
- package/src/matrix/client/create-client.ts +105 -0
- package/src/matrix/client/env-auth.ts +95 -0
- package/src/matrix/client/file-sync-store.ts +289 -0
- package/src/matrix/client/logging.ts +140 -0
- package/src/matrix/client/migration-snapshot.runtime.ts +1 -0
- package/src/matrix/client/private-network-host.ts +1 -0
- package/src/matrix/client/runtime.ts +4 -0
- package/src/matrix/client/shared.ts +316 -0
- package/src/matrix/client/storage.ts +543 -0
- package/src/matrix/client/types.ts +50 -0
- package/src/matrix/client/url-validation.ts +76 -0
- package/src/matrix/client-bootstrap.ts +173 -0
- package/src/matrix/client.ts +23 -0
- package/src/matrix/config-paths.ts +31 -0
- package/src/matrix/config-update.ts +292 -0
- package/src/matrix/credentials-read.ts +207 -0
- package/src/matrix/credentials-write.runtime.ts +35 -0
- package/src/matrix/credentials.ts +95 -0
- package/src/matrix/deps.ts +309 -0
- package/src/matrix/device-health.ts +31 -0
- package/src/matrix/direct-management.ts +349 -0
- package/src/matrix/direct-room.ts +128 -0
- package/src/matrix/draft-stream.ts +225 -0
- package/src/matrix/encryption-guidance.ts +24 -0
- package/src/matrix/errors.ts +21 -0
- package/src/matrix/format.ts +426 -0
- package/src/matrix/legacy-crypto-inspector.ts +95 -0
- package/src/matrix/media-errors.ts +20 -0
- package/src/matrix/media-text.ts +162 -0
- package/src/matrix/monitor/access-state.ts +145 -0
- package/src/matrix/monitor/ack-config.ts +27 -0
- package/src/matrix/monitor/allowlist.ts +92 -0
- package/src/matrix/monitor/auto-join.ts +86 -0
- package/src/matrix/monitor/config.ts +569 -0
- package/src/matrix/monitor/context-summary.ts +43 -0
- package/src/matrix/monitor/direct.ts +296 -0
- package/src/matrix/monitor/events.ts +397 -0
- package/src/matrix/monitor/handler.ts +2271 -0
- package/src/matrix/monitor/inbound-dedupe.ts +267 -0
- package/src/matrix/monitor/index.ts +540 -0
- package/src/matrix/monitor/legacy-crypto-restore.ts +139 -0
- package/src/matrix/monitor/location.ts +108 -0
- package/src/matrix/monitor/media.ts +119 -0
- package/src/matrix/monitor/mentions.ts +256 -0
- package/src/matrix/monitor/reaction-events.ts +197 -0
- package/src/matrix/monitor/recent-invite.ts +30 -0
- package/src/matrix/monitor/replies.ts +136 -0
- package/src/matrix/monitor/reply-context.ts +92 -0
- package/src/matrix/monitor/room-history.ts +301 -0
- package/src/matrix/monitor/room-info.ts +126 -0
- package/src/matrix/monitor/rooms.ts +52 -0
- package/src/matrix/monitor/route.ts +179 -0
- package/src/matrix/monitor/runtime-api.ts +28 -0
- package/src/matrix/monitor/startup-verification.ts +237 -0
- package/src/matrix/monitor/startup.ts +218 -0
- package/src/matrix/monitor/status.ts +120 -0
- package/src/matrix/monitor/sync-lifecycle.ts +91 -0
- package/src/matrix/monitor/task-runner.ts +38 -0
- package/src/matrix/monitor/test-events.ts +21 -0
- package/src/matrix/monitor/thread-context.ts +108 -0
- package/src/matrix/monitor/threads.ts +85 -0
- package/src/matrix/monitor/types.ts +30 -0
- package/src/matrix/monitor/verification-events.ts +643 -0
- package/src/matrix/monitor/verification-utils.ts +46 -0
- package/src/matrix/outbound-media-runtime.ts +1 -0
- package/src/matrix/poll-summary.ts +110 -0
- package/src/matrix/poll-types.ts +429 -0
- package/src/matrix/probe.runtime.ts +4 -0
- package/src/matrix/probe.ts +97 -0
- package/src/matrix/profile.ts +184 -0
- package/src/matrix/reaction-common.ts +147 -0
- package/src/matrix/sdk/crypto-bootstrap.ts +438 -0
- package/src/matrix/sdk/crypto-facade.ts +242 -0
- package/src/matrix/sdk/crypto-node.runtime.ts +17 -0
- package/src/matrix/sdk/crypto-runtime.ts +14 -0
- package/src/matrix/sdk/decrypt-bridge.ts +410 -0
- package/src/matrix/sdk/event-helpers.ts +83 -0
- package/src/matrix/sdk/http-client.ts +87 -0
- package/src/matrix/sdk/idb-persistence-lock.ts +51 -0
- package/src/matrix/sdk/idb-persistence.ts +286 -0
- package/src/matrix/sdk/logger.ts +108 -0
- package/src/matrix/sdk/read-response-with-limit.ts +19 -0
- package/src/matrix/sdk/recovery-key-store.ts +453 -0
- package/src/matrix/sdk/timeout-abort-signal.ts +1 -0
- package/src/matrix/sdk/transport-runtime-api.ts +18 -0
- package/src/matrix/sdk/transport.ts +352 -0
- package/src/matrix/sdk/types.ts +245 -0
- package/src/matrix/sdk/verification-manager.ts +795 -0
- package/src/matrix/sdk/verification-status.ts +23 -0
- package/src/matrix/sdk.ts +2152 -0
- package/src/matrix/send/client.ts +93 -0
- package/src/matrix/send/formatting.ts +189 -0
- package/src/matrix/send/media.ts +244 -0
- package/src/matrix/send/targets.ts +104 -0
- package/src/matrix/send/types.ts +131 -0
- package/src/matrix/send.ts +660 -0
- package/src/matrix/session-store-metadata.ts +108 -0
- package/src/matrix/startup-abort.ts +44 -0
- package/src/matrix/subagent-hooks.ts +308 -0
- package/src/matrix/sync-state.ts +27 -0
- package/src/matrix/target-ids.ts +79 -0
- package/src/matrix/thread-bindings-shared.ts +206 -0
- package/src/matrix/thread-bindings.ts +580 -0
- package/src/matrix-migration.runtime.ts +9 -0
- package/src/migration-config.ts +243 -0
- package/src/migration-snapshot-backup.ts +116 -0
- package/src/migration-snapshot.ts +53 -0
- package/src/onboarding.ts +775 -0
- package/src/outbound.ts +248 -0
- package/src/plugin-entry.runtime.js +115 -0
- package/src/plugin-entry.runtime.ts +70 -0
- package/src/profile-update.ts +71 -0
- package/src/record-shared.ts +3 -0
- package/src/resolve-targets.ts +175 -0
- package/src/resolver.runtime.ts +5 -0
- package/src/resolver.ts +21 -0
- package/src/runtime-api.ts +106 -0
- package/src/runtime.ts +13 -0
- package/src/secret-contract.ts +174 -0
- package/src/session-route.ts +126 -0
- package/src/setup-bootstrap.ts +102 -0
- package/src/setup-config.ts +222 -0
- package/src/setup-contract.ts +90 -0
- package/src/setup-core.ts +146 -0
- package/src/setup-dm-policy.ts +15 -0
- package/src/setup-surface.ts +4 -0
- package/src/startup-maintenance.ts +114 -0
- package/src/storage-paths.ts +92 -0
- package/src/thread-binding-api.ts +23 -0
- package/src/tool-actions.runtime.ts +1 -0
- package/src/tool-actions.ts +498 -0
- package/src/types.ts +257 -0
- package/subagent-hooks-api.ts +31 -0
- package/test-api.ts +21 -0
- package/thread-binding-api.ts +4 -0
- package/thread-bindings-runtime.ts +4 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { requireRuntimeConfig } from "autobot/plugin-sdk/plugin-config-runtime";
|
|
3
|
+
import { normalizeOptionalString } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
4
|
+
import type { CoreConfig } from "../../types.js";
|
|
5
|
+
import { formatMatrixEncryptionUnavailableError } from "../encryption-guidance.js";
|
|
6
|
+
import type { MatrixDeviceVerificationStatus, MatrixOwnDeviceVerificationStatus } from "../sdk.js";
|
|
7
|
+
import type { MatrixVerificationSummary } from "../sdk/verification-manager.js";
|
|
8
|
+
import { withResolvedActionClient, withStartedActionClient } from "./client.js";
|
|
9
|
+
import type { MatrixActionClientOpts } from "./types.js";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MATRIX_SELF_VERIFICATION_TIMEOUT_MS = 180_000;
|
|
12
|
+
|
|
13
|
+
type MatrixCryptoActionFacade = NonNullable<import("../sdk.js").MatrixClient["crypto"]>;
|
|
14
|
+
type MatrixActionClient = import("../sdk.js").MatrixClient;
|
|
15
|
+
type MatrixVerificationDmLookupOpts = {
|
|
16
|
+
verificationDmRoomId?: string;
|
|
17
|
+
verificationDmUserId?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type MatrixSelfVerificationResult = MatrixVerificationSummary & {
|
|
21
|
+
deviceOwnerVerified: boolean;
|
|
22
|
+
ownerVerification: MatrixOwnDeviceVerificationStatus;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function requireCrypto(
|
|
26
|
+
client: import("../sdk.js").MatrixClient,
|
|
27
|
+
opts: MatrixActionClientOpts,
|
|
28
|
+
): NonNullable<import("../sdk.js").MatrixClient["crypto"]> {
|
|
29
|
+
if (!client.crypto) {
|
|
30
|
+
if (!opts.cfg) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Matrix verification actions requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const cfg = requireRuntimeConfig(opts.cfg, "Matrix verification actions") as CoreConfig;
|
|
36
|
+
throw new Error(formatMatrixEncryptionUnavailableError(cfg, opts.accountId));
|
|
37
|
+
}
|
|
38
|
+
return client.crypto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveVerificationId(input: string): string {
|
|
42
|
+
const normalized = input.trim();
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
throw new Error("Matrix verification request id is required");
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function ensureMatrixVerificationDmTracked(
|
|
50
|
+
crypto: MatrixCryptoActionFacade,
|
|
51
|
+
opts: MatrixVerificationDmLookupOpts,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const roomId = normalizeOptionalString(opts.verificationDmRoomId);
|
|
54
|
+
const userId = normalizeOptionalString(opts.verificationDmUserId);
|
|
55
|
+
if (Boolean(roomId) !== Boolean(userId)) {
|
|
56
|
+
throw new Error("--user-id and --room-id must be provided together for Matrix DM verification");
|
|
57
|
+
}
|
|
58
|
+
if (!roomId || !userId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const tracked = await crypto.ensureVerificationDmTracked({ roomId, userId });
|
|
62
|
+
if (!tracked) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Matrix DM verification request not found for room ${roomId} and user ${userId}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isSameMatrixVerification(
|
|
70
|
+
left: MatrixVerificationSummary,
|
|
71
|
+
right: MatrixVerificationSummary,
|
|
72
|
+
): boolean {
|
|
73
|
+
return (
|
|
74
|
+
left.id === right.id ||
|
|
75
|
+
Boolean(left.transactionId && left.transactionId === right.transactionId)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isMatrixVerificationReadyForSas(summary: MatrixVerificationSummary): boolean {
|
|
80
|
+
return (
|
|
81
|
+
summary.completed ||
|
|
82
|
+
summary.hasSas ||
|
|
83
|
+
summary.phaseName === "ready" ||
|
|
84
|
+
summary.phaseName === "started"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function shouldStartMatrixSasVerification(summary: MatrixVerificationSummary): boolean {
|
|
89
|
+
return !summary.hasSas && summary.phaseName !== "started" && !summary.completed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isMatrixVerificationCancelled(summary: MatrixVerificationSummary): boolean {
|
|
93
|
+
return summary.phaseName === "cancelled";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isMatrixSasMethod(method: string | null | undefined): boolean {
|
|
97
|
+
return method === "m.sas.v1" || method === "sas";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getMatrixVerificationSasWaitFailure(
|
|
101
|
+
summary: MatrixVerificationSummary,
|
|
102
|
+
label: string,
|
|
103
|
+
): string | null {
|
|
104
|
+
if (summary.hasSas || summary.phaseName === "cancelled") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const method = summary.chosenMethod ? ` (method: ${summary.chosenMethod})` : "";
|
|
108
|
+
if (summary.completed) {
|
|
109
|
+
return `Matrix self-verification completed without SAS while waiting to ${label}${method}`;
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
summary.phaseName === "started" &&
|
|
113
|
+
summary.chosenMethod &&
|
|
114
|
+
!isMatrixSasMethod(summary.chosenMethod)
|
|
115
|
+
) {
|
|
116
|
+
return `Matrix self-verification started without SAS while waiting to ${label}${method}`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function waitForMatrixVerificationSummary(params: {
|
|
122
|
+
crypto: MatrixCryptoActionFacade;
|
|
123
|
+
label: string;
|
|
124
|
+
request: MatrixVerificationSummary;
|
|
125
|
+
timeoutMs: number;
|
|
126
|
+
predicate: (summary: MatrixVerificationSummary) => boolean;
|
|
127
|
+
reject?: (summary: MatrixVerificationSummary) => string | null;
|
|
128
|
+
}): Promise<MatrixVerificationSummary> {
|
|
129
|
+
const startedAt = Date.now();
|
|
130
|
+
let last: MatrixVerificationSummary | undefined;
|
|
131
|
+
while (Date.now() - startedAt < params.timeoutMs) {
|
|
132
|
+
const summaries = await params.crypto.listVerifications();
|
|
133
|
+
const found = summaries.find((summary) => isSameMatrixVerification(summary, params.request));
|
|
134
|
+
if (found) {
|
|
135
|
+
last = found;
|
|
136
|
+
if (params.predicate(found)) {
|
|
137
|
+
return found;
|
|
138
|
+
}
|
|
139
|
+
if (isMatrixVerificationCancelled(found)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Matrix self-verification was cancelled${
|
|
142
|
+
found.error ? `: ${found.error}` : ` while waiting to ${params.label}`
|
|
143
|
+
}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const rejection = params.reject?.(found);
|
|
147
|
+
if (rejection) {
|
|
148
|
+
throw new Error(rejection);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
await sleep(Math.min(250, Math.max(25, params.timeoutMs - (Date.now() - startedAt))));
|
|
152
|
+
}
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Timed out waiting for Matrix self-verification to ${params.label}${
|
|
155
|
+
last ? ` (last phase: ${last.phaseName})` : ""
|
|
156
|
+
}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatMatrixOwnerVerificationDiagnostics(
|
|
161
|
+
status: MatrixDeviceVerificationStatus | MatrixOwnDeviceVerificationStatus | undefined,
|
|
162
|
+
): string {
|
|
163
|
+
if (!status) {
|
|
164
|
+
return "Matrix identity trust status was unavailable";
|
|
165
|
+
}
|
|
166
|
+
return `cross-signing verified: ${status.crossSigningVerified ? "yes" : "no"}, signed by owner: ${
|
|
167
|
+
status.signedByOwner ? "yes" : "no"
|
|
168
|
+
}, locally trusted: ${status.localVerified ? "yes" : "no"}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function waitForMatrixSelfVerificationTrustStatus(params: {
|
|
172
|
+
client: MatrixActionClient;
|
|
173
|
+
timeoutMs: number;
|
|
174
|
+
}): Promise<MatrixOwnDeviceVerificationStatus> {
|
|
175
|
+
const startedAt = Date.now();
|
|
176
|
+
let last: MatrixOwnDeviceVerificationStatus | undefined;
|
|
177
|
+
let crossSigningPublished = false;
|
|
178
|
+
while (Date.now() - startedAt < params.timeoutMs) {
|
|
179
|
+
const [status, crossSigning] = await Promise.all([
|
|
180
|
+
params.client.getOwnDeviceVerificationStatus(),
|
|
181
|
+
params.client.getOwnCrossSigningPublicationStatus(),
|
|
182
|
+
]);
|
|
183
|
+
last = status;
|
|
184
|
+
crossSigningPublished = crossSigning.published;
|
|
185
|
+
if (status.verified && crossSigningPublished) {
|
|
186
|
+
return status;
|
|
187
|
+
}
|
|
188
|
+
await sleep(Math.min(250, Math.max(25, params.timeoutMs - (Date.now() - startedAt))));
|
|
189
|
+
}
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Timed out waiting for Matrix self-verification to establish full Matrix identity trust for this device (${formatMatrixOwnerVerificationDiagnostics(
|
|
192
|
+
last,
|
|
193
|
+
)}, cross-signing keys published: ${crossSigningPublished ? "yes" : "no"}). Complete self-verification from another Matrix client, then check Matrix verification status for details.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function cancelMatrixSelfVerificationOnFailure(params: {
|
|
198
|
+
crypto: MatrixCryptoActionFacade;
|
|
199
|
+
request: MatrixVerificationSummary | undefined;
|
|
200
|
+
}): Promise<void> {
|
|
201
|
+
if (!params.request || typeof params.crypto.cancelVerification !== "function") {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
await params.crypto
|
|
205
|
+
.cancelVerification(params.request.id, {
|
|
206
|
+
reason: "AutoBot self-verification did not complete",
|
|
207
|
+
code: "m.user",
|
|
208
|
+
})
|
|
209
|
+
.catch(() => undefined);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function completeMatrixSelfVerification(params: {
|
|
213
|
+
client: MatrixActionClient;
|
|
214
|
+
completed: MatrixVerificationSummary;
|
|
215
|
+
timeoutMs: number;
|
|
216
|
+
}): Promise<MatrixSelfVerificationResult> {
|
|
217
|
+
const initial = await Promise.all([
|
|
218
|
+
params.client.getOwnDeviceVerificationStatus(),
|
|
219
|
+
params.client.getOwnCrossSigningPublicationStatus(),
|
|
220
|
+
]);
|
|
221
|
+
let ownerVerification = initial[0];
|
|
222
|
+
if (!ownerVerification.verified || !initial[1].published) {
|
|
223
|
+
if (!ownerVerification.verified) {
|
|
224
|
+
await params.client.trustOwnIdentityAfterSelfVerification?.();
|
|
225
|
+
}
|
|
226
|
+
ownerVerification = await waitForMatrixSelfVerificationTrustStatus({
|
|
227
|
+
client: params.client,
|
|
228
|
+
timeoutMs: params.timeoutMs,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
...params.completed,
|
|
233
|
+
deviceOwnerVerified: ownerVerification.verified,
|
|
234
|
+
ownerVerification,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function listMatrixVerifications(opts: MatrixActionClientOpts = {}) {
|
|
239
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
240
|
+
const crypto = requireCrypto(client, opts);
|
|
241
|
+
return await crypto.listVerifications();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function requestMatrixVerification(
|
|
246
|
+
params: MatrixActionClientOpts & {
|
|
247
|
+
ownUser?: boolean;
|
|
248
|
+
userId?: string;
|
|
249
|
+
deviceId?: string;
|
|
250
|
+
roomId?: string;
|
|
251
|
+
} = {},
|
|
252
|
+
) {
|
|
253
|
+
return await withStartedActionClient(params, async (client) => {
|
|
254
|
+
const crypto = requireCrypto(client, params);
|
|
255
|
+
const ownUser = params.ownUser ?? (!params.userId && !params.deviceId && !params.roomId);
|
|
256
|
+
return await crypto.requestVerification({
|
|
257
|
+
ownUser,
|
|
258
|
+
userId: normalizeOptionalString(params.userId),
|
|
259
|
+
deviceId: normalizeOptionalString(params.deviceId),
|
|
260
|
+
roomId: normalizeOptionalString(params.roomId),
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function runMatrixSelfVerification(
|
|
266
|
+
params: MatrixActionClientOpts & {
|
|
267
|
+
confirmSas: (
|
|
268
|
+
sas: NonNullable<MatrixVerificationSummary["sas"]>,
|
|
269
|
+
summary: MatrixVerificationSummary,
|
|
270
|
+
) => Promise<boolean>;
|
|
271
|
+
onReady?: (summary: MatrixVerificationSummary) => void | Promise<void>;
|
|
272
|
+
onRequested?: (summary: MatrixVerificationSummary) => void | Promise<void>;
|
|
273
|
+
onSas?: (summary: MatrixVerificationSummary) => void | Promise<void>;
|
|
274
|
+
timeoutMs?: number;
|
|
275
|
+
},
|
|
276
|
+
): Promise<MatrixSelfVerificationResult> {
|
|
277
|
+
return await withStartedActionClient(params, async (client) => {
|
|
278
|
+
const crypto = requireCrypto(client, params);
|
|
279
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_MATRIX_SELF_VERIFICATION_TIMEOUT_MS;
|
|
280
|
+
let requested: MatrixVerificationSummary | undefined;
|
|
281
|
+
let requestCompleted = false;
|
|
282
|
+
let handledByMismatch = false;
|
|
283
|
+
try {
|
|
284
|
+
requested = await crypto.requestVerification({ ownUser: true });
|
|
285
|
+
await params.onRequested?.(requested);
|
|
286
|
+
|
|
287
|
+
const ready = isMatrixVerificationReadyForSas(requested)
|
|
288
|
+
? requested
|
|
289
|
+
: await waitForMatrixVerificationSummary({
|
|
290
|
+
crypto,
|
|
291
|
+
label: "be accepted in another Matrix client",
|
|
292
|
+
request: requested,
|
|
293
|
+
timeoutMs,
|
|
294
|
+
predicate: isMatrixVerificationReadyForSas,
|
|
295
|
+
});
|
|
296
|
+
await params.onReady?.(ready);
|
|
297
|
+
|
|
298
|
+
if (ready.completed) {
|
|
299
|
+
requestCompleted = true;
|
|
300
|
+
return await completeMatrixSelfVerification({ client, completed: ready, timeoutMs });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const started = shouldStartMatrixSasVerification(ready)
|
|
304
|
+
? await crypto.startVerification(ready.id, "sas")
|
|
305
|
+
: ready;
|
|
306
|
+
let sasSummary = started;
|
|
307
|
+
if (!sasSummary.hasSas) {
|
|
308
|
+
const sasFailure = getMatrixVerificationSasWaitFailure(
|
|
309
|
+
sasSummary,
|
|
310
|
+
"show SAS emoji or decimals",
|
|
311
|
+
);
|
|
312
|
+
if (sasFailure) {
|
|
313
|
+
throw new Error(sasFailure);
|
|
314
|
+
}
|
|
315
|
+
sasSummary = await waitForMatrixVerificationSummary({
|
|
316
|
+
crypto,
|
|
317
|
+
label: "show SAS emoji or decimals",
|
|
318
|
+
request: started,
|
|
319
|
+
timeoutMs,
|
|
320
|
+
predicate: (summary) => summary.hasSas,
|
|
321
|
+
reject: (summary) =>
|
|
322
|
+
getMatrixVerificationSasWaitFailure(summary, "show SAS emoji or decimals"),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (!sasSummary.sas) {
|
|
326
|
+
throw new Error("Matrix SAS data is not available for this verification request");
|
|
327
|
+
}
|
|
328
|
+
await params.onSas?.(sasSummary);
|
|
329
|
+
|
|
330
|
+
const matched = await params.confirmSas(sasSummary.sas, sasSummary);
|
|
331
|
+
if (!matched) {
|
|
332
|
+
await crypto.mismatchVerificationSas(sasSummary.id);
|
|
333
|
+
handledByMismatch = true;
|
|
334
|
+
throw new Error("Matrix SAS verification was not confirmed.");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const confirmed = await crypto.confirmVerificationSas(sasSummary.id);
|
|
338
|
+
const completed = confirmed.completed
|
|
339
|
+
? confirmed
|
|
340
|
+
: await waitForMatrixVerificationSummary({
|
|
341
|
+
crypto,
|
|
342
|
+
label: "complete",
|
|
343
|
+
request: confirmed,
|
|
344
|
+
timeoutMs,
|
|
345
|
+
predicate: (summary) => summary.completed,
|
|
346
|
+
});
|
|
347
|
+
requestCompleted = true;
|
|
348
|
+
return await completeMatrixSelfVerification({ client, completed, timeoutMs });
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (!requestCompleted && !handledByMismatch) {
|
|
351
|
+
await cancelMatrixSelfVerificationOnFailure({ crypto, request: requested });
|
|
352
|
+
}
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export async function acceptMatrixVerification(
|
|
359
|
+
requestId: string,
|
|
360
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
361
|
+
) {
|
|
362
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
363
|
+
const crypto = requireCrypto(client, opts);
|
|
364
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
365
|
+
return await crypto.acceptVerification(resolveVerificationId(requestId));
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export async function cancelMatrixVerification(
|
|
370
|
+
requestId: string,
|
|
371
|
+
opts: MatrixActionClientOpts &
|
|
372
|
+
MatrixVerificationDmLookupOpts & { reason?: string; code?: string } = {},
|
|
373
|
+
) {
|
|
374
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
375
|
+
const crypto = requireCrypto(client, opts);
|
|
376
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
377
|
+
return await crypto.cancelVerification(resolveVerificationId(requestId), {
|
|
378
|
+
reason: normalizeOptionalString(opts.reason),
|
|
379
|
+
code: normalizeOptionalString(opts.code),
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function startMatrixVerification(
|
|
385
|
+
requestId: string,
|
|
386
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts & { method?: "sas" } = {},
|
|
387
|
+
) {
|
|
388
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
389
|
+
const crypto = requireCrypto(client, opts);
|
|
390
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
391
|
+
return await crypto.startVerification(resolveVerificationId(requestId), opts.method ?? "sas");
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function generateMatrixVerificationQr(
|
|
396
|
+
requestId: string,
|
|
397
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
398
|
+
) {
|
|
399
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
400
|
+
const crypto = requireCrypto(client, opts);
|
|
401
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
402
|
+
return await crypto.generateVerificationQr(resolveVerificationId(requestId));
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function scanMatrixVerificationQr(
|
|
407
|
+
requestId: string,
|
|
408
|
+
qrDataBase64: string,
|
|
409
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
410
|
+
) {
|
|
411
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
412
|
+
const crypto = requireCrypto(client, opts);
|
|
413
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
414
|
+
const payload = qrDataBase64.trim();
|
|
415
|
+
if (!payload) {
|
|
416
|
+
throw new Error("Matrix QR data is required");
|
|
417
|
+
}
|
|
418
|
+
return await crypto.scanVerificationQr(resolveVerificationId(requestId), payload);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function getMatrixVerificationSas(
|
|
423
|
+
requestId: string,
|
|
424
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
425
|
+
) {
|
|
426
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
427
|
+
const crypto = requireCrypto(client, opts);
|
|
428
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
429
|
+
return await crypto.getVerificationSas(resolveVerificationId(requestId));
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export async function confirmMatrixVerificationSas(
|
|
434
|
+
requestId: string,
|
|
435
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
436
|
+
) {
|
|
437
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
438
|
+
const crypto = requireCrypto(client, opts);
|
|
439
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
440
|
+
const summary = await crypto.confirmVerificationSas(resolveVerificationId(requestId));
|
|
441
|
+
// For self-verifications, mirror the trust-own-identity step that the
|
|
442
|
+
// higher-level runMatrixSelfVerification path already performs at
|
|
443
|
+
// completeMatrixSelfVerification: cross-sign the operator's master key
|
|
444
|
+
// from the bot side so Element X clears the "Verify" prompt without
|
|
445
|
+
// waiting for a passive sync tick. Non-self verifications are a no-op.
|
|
446
|
+
if (summary.isSelfVerification && summary.completed && !summary.error) {
|
|
447
|
+
await client.trustOwnIdentityAfterSelfVerification?.();
|
|
448
|
+
}
|
|
449
|
+
return summary;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function mismatchMatrixVerificationSas(
|
|
454
|
+
requestId: string,
|
|
455
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
456
|
+
) {
|
|
457
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
458
|
+
const crypto = requireCrypto(client, opts);
|
|
459
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
460
|
+
return await crypto.mismatchVerificationSas(resolveVerificationId(requestId));
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function confirmMatrixVerificationReciprocateQr(
|
|
465
|
+
requestId: string,
|
|
466
|
+
opts: MatrixActionClientOpts & MatrixVerificationDmLookupOpts = {},
|
|
467
|
+
) {
|
|
468
|
+
return await withStartedActionClient(opts, async (client) => {
|
|
469
|
+
const crypto = requireCrypto(client, opts);
|
|
470
|
+
await ensureMatrixVerificationDmTracked(crypto, opts);
|
|
471
|
+
return await crypto.confirmVerificationReciprocateQr(resolveVerificationId(requestId));
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export async function getMatrixEncryptionStatus(
|
|
476
|
+
opts: MatrixActionClientOpts & { includeRecoveryKey?: boolean } = {},
|
|
477
|
+
) {
|
|
478
|
+
return await withResolvedActionClient(opts, async (client) => {
|
|
479
|
+
const crypto = requireCrypto(client, opts);
|
|
480
|
+
const recoveryKey = await crypto.getRecoveryKey();
|
|
481
|
+
return {
|
|
482
|
+
encryptionEnabled: true,
|
|
483
|
+
recoveryKeyStored: Boolean(recoveryKey),
|
|
484
|
+
recoveryKeyCreatedAt: recoveryKey?.createdAt ?? null,
|
|
485
|
+
...(opts.includeRecoveryKey ? { recoveryKey: recoveryKey?.encodedPrivateKey ?? null } : {}),
|
|
486
|
+
pendingVerifications: (await crypto.listVerifications()).length,
|
|
487
|
+
};
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function getMatrixVerificationStatus(
|
|
492
|
+
opts: MatrixActionClientOpts & { includeRecoveryKey?: boolean } = {},
|
|
493
|
+
) {
|
|
494
|
+
const readiness = opts.readiness ?? "prepared";
|
|
495
|
+
return await withResolvedActionClient(
|
|
496
|
+
{ ...opts, readiness: "none" },
|
|
497
|
+
async (client) => {
|
|
498
|
+
const preflight = await readMatrixVerificationStatus(client, opts);
|
|
499
|
+
if (readiness === "none" || preflight.serverDeviceKnown === false) {
|
|
500
|
+
return preflight;
|
|
501
|
+
}
|
|
502
|
+
if (readiness === "started") {
|
|
503
|
+
await client.start();
|
|
504
|
+
} else {
|
|
505
|
+
await client.prepareForOneOff();
|
|
506
|
+
}
|
|
507
|
+
return await readMatrixVerificationStatus(client, opts);
|
|
508
|
+
},
|
|
509
|
+
"discard",
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function readMatrixVerificationStatus(
|
|
514
|
+
client: MatrixActionClient,
|
|
515
|
+
opts: MatrixActionClientOpts & { includeRecoveryKey?: boolean },
|
|
516
|
+
) {
|
|
517
|
+
const status = await client.getOwnDeviceVerificationStatus();
|
|
518
|
+
const payload = {
|
|
519
|
+
...status,
|
|
520
|
+
pendingVerifications: client.crypto ? (await client.crypto.listVerifications()).length : 0,
|
|
521
|
+
};
|
|
522
|
+
if (!opts.includeRecoveryKey) {
|
|
523
|
+
return payload;
|
|
524
|
+
}
|
|
525
|
+
const recoveryKey = client.crypto ? await client.crypto.getRecoveryKey() : null;
|
|
526
|
+
return {
|
|
527
|
+
...payload,
|
|
528
|
+
recoveryKey: recoveryKey?.encodedPrivateKey ?? null,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export async function getMatrixRoomKeyBackupStatus(opts: MatrixActionClientOpts = {}) {
|
|
533
|
+
return await withResolvedActionClient(
|
|
534
|
+
opts,
|
|
535
|
+
async (client) => await client.getRoomKeyBackupStatus(),
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export async function verifyMatrixRecoveryKey(
|
|
540
|
+
recoveryKey: string,
|
|
541
|
+
opts: MatrixActionClientOpts = {},
|
|
542
|
+
) {
|
|
543
|
+
return await withStartedActionClient(
|
|
544
|
+
opts,
|
|
545
|
+
async (client) => await client.verifyWithRecoveryKey(recoveryKey),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export async function restoreMatrixRoomKeyBackup(
|
|
550
|
+
opts: MatrixActionClientOpts & {
|
|
551
|
+
recoveryKey?: string;
|
|
552
|
+
} = {},
|
|
553
|
+
) {
|
|
554
|
+
return await withResolvedActionClient(
|
|
555
|
+
opts,
|
|
556
|
+
async (client) =>
|
|
557
|
+
await client.restoreRoomKeyBackup({
|
|
558
|
+
recoveryKey: normalizeOptionalString(opts.recoveryKey),
|
|
559
|
+
}),
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function resetMatrixRoomKeyBackup(
|
|
564
|
+
opts: MatrixActionClientOpts & { rotateRecoveryKey?: boolean } = {},
|
|
565
|
+
) {
|
|
566
|
+
return await withStartedActionClient(
|
|
567
|
+
opts,
|
|
568
|
+
async (client) =>
|
|
569
|
+
await client.resetRoomKeyBackup({
|
|
570
|
+
rotateRecoveryKey: opts.rotateRecoveryKey,
|
|
571
|
+
}),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export async function bootstrapMatrixVerification(
|
|
576
|
+
opts: MatrixActionClientOpts & {
|
|
577
|
+
recoveryKey?: string;
|
|
578
|
+
forceResetCrossSigning?: boolean;
|
|
579
|
+
} = {},
|
|
580
|
+
) {
|
|
581
|
+
return await withStartedActionClient(
|
|
582
|
+
opts,
|
|
583
|
+
async (client) =>
|
|
584
|
+
await client.bootstrapOwnDeviceVerification({
|
|
585
|
+
recoveryKey: normalizeOptionalString(opts.recoveryKey),
|
|
586
|
+
forceResetCrossSigning: opts.forceResetCrossSigning === true,
|
|
587
|
+
}),
|
|
588
|
+
);
|
|
589
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
MatrixActionClientOpts,
|
|
3
|
+
MatrixMessageSummary,
|
|
4
|
+
MatrixReactionSummary,
|
|
5
|
+
} from "./actions/types.js";
|
|
6
|
+
export {
|
|
7
|
+
sendMatrixMessage,
|
|
8
|
+
editMatrixMessage,
|
|
9
|
+
deleteMatrixMessage,
|
|
10
|
+
readMatrixMessages,
|
|
11
|
+
} from "./actions/messages.js";
|
|
12
|
+
export { voteMatrixPoll } from "./actions/polls.js";
|
|
13
|
+
export { listMatrixReactions, removeMatrixReactions } from "./actions/reactions.js";
|
|
14
|
+
export { pinMatrixMessage, unpinMatrixMessage, listMatrixPins } from "./actions/pins.js";
|
|
15
|
+
export { getMatrixMemberInfo, getMatrixRoomInfo } from "./actions/room.js";
|
|
16
|
+
export { updateMatrixOwnProfile } from "./actions/profile.js";
|
|
17
|
+
export {
|
|
18
|
+
bootstrapMatrixVerification,
|
|
19
|
+
acceptMatrixVerification,
|
|
20
|
+
cancelMatrixVerification,
|
|
21
|
+
confirmMatrixVerificationReciprocateQr,
|
|
22
|
+
confirmMatrixVerificationSas,
|
|
23
|
+
generateMatrixVerificationQr,
|
|
24
|
+
getMatrixEncryptionStatus,
|
|
25
|
+
getMatrixRoomKeyBackupStatus,
|
|
26
|
+
getMatrixVerificationStatus,
|
|
27
|
+
getMatrixVerificationSas,
|
|
28
|
+
listMatrixVerifications,
|
|
29
|
+
mismatchMatrixVerificationSas,
|
|
30
|
+
requestMatrixVerification,
|
|
31
|
+
resetMatrixRoomKeyBackup,
|
|
32
|
+
restoreMatrixRoomKeyBackup,
|
|
33
|
+
scanMatrixVerificationQr,
|
|
34
|
+
startMatrixVerification,
|
|
35
|
+
verifyMatrixRecoveryKey,
|
|
36
|
+
} from "./actions/verification.js";
|
|
37
|
+
export { reactMatrixMessage } from "./send.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "autobot/plugin-sdk/account-id";
|
|
2
|
+
import type { MatrixClient } from "./sdk.js";
|
|
3
|
+
|
|
4
|
+
const activeClients = new Map<string, MatrixClient>();
|
|
5
|
+
|
|
6
|
+
function resolveAccountKey(accountId?: string | null): string {
|
|
7
|
+
const normalized = normalizeAccountId(accountId);
|
|
8
|
+
return normalized || DEFAULT_ACCOUNT_ID;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function setActiveMatrixClient(
|
|
12
|
+
client: MatrixClient | null,
|
|
13
|
+
accountId?: string | null,
|
|
14
|
+
): void {
|
|
15
|
+
const key = resolveAccountKey(accountId);
|
|
16
|
+
if (!client) {
|
|
17
|
+
activeClients.delete(key);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
activeClients.set(key, client);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getActiveMatrixClient(accountId?: string | null): MatrixClient | null {
|
|
24
|
+
const key = resolveAccountKey(accountId);
|
|
25
|
+
return activeClients.get(key) ?? null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AsyncLock = <T>(fn: () => Promise<T>) => Promise<T>;
|
|
2
|
+
|
|
3
|
+
export function createAsyncLock(): AsyncLock {
|
|
4
|
+
let lock: Promise<void> = Promise.resolve();
|
|
5
|
+
return async function withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
6
|
+
const previous = lock;
|
|
7
|
+
let release: (() => void) | undefined;
|
|
8
|
+
lock = new Promise<void>((resolve) => {
|
|
9
|
+
release = resolve;
|
|
10
|
+
});
|
|
11
|
+
await previous;
|
|
12
|
+
try {
|
|
13
|
+
return await fn();
|
|
14
|
+
} finally {
|
|
15
|
+
release?.();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|