@opengeni/sdk 3.1.2 → 3.2.2
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/dist/artifacts.js +3 -3
- package/dist/browser.js +1 -1
- package/dist/chat/index.js +4 -4
- package/dist/{chunk-NEM5N6C6.js → chunk-JKG75TTU.js} +35 -1
- package/dist/chunk-JKG75TTU.js.map +1 -0
- package/dist/{chunk-YDVPI7WT.js → chunk-JP62FZSI.js} +2 -2
- package/dist/chunk-KGEABAID.js +71 -0
- package/dist/chunk-KGEABAID.js.map +1 -0
- package/dist/{chunk-S3D75WSQ.js → chunk-N2I422X4.js} +2 -2
- package/dist/{chunk-JXXCTW5W.js → chunk-PBVUAXSU.js} +2 -2
- package/dist/{chunk-OAKHWFSV.js → chunk-PDU6EU2M.js} +2 -2
- package/dist/{chunk-RJHCBZF7.js → chunk-VT4YM4XQ.js} +8 -68
- package/dist/{chunk-RJHCBZF7.js.map → chunk-VT4YM4XQ.js.map} +1 -1
- package/dist/core.js +2 -2
- package/dist/document-authority.js +2 -2
- package/dist/external-vault-receiver.d.ts +203 -0
- package/dist/external-vault-receiver.js +564 -0
- package/dist/external-vault-receiver.js.map +1 -0
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/site-bridge-host.js +2 -2
- package/dist/site-browser-runtime.gen.d.ts +1 -1
- package/dist/site-document.js +1 -1
- package/dist/site-document.js.map +1 -1
- package/dist/site.js +2 -2
- package/dist/vault-types.d.ts +91 -3
- package/dist/vaults-api.d.ts +9 -1
- package/dist/workspace-integrations.js +11 -9
- package/package.json +7 -3
- package/src/external-vault-receiver.ts +914 -0
- package/src/site-browser-runtime.gen.ts +1 -1
- package/src/vault-types.ts +88 -2
- package/src/vaults-api.ts +51 -0
- package/dist/chunk-NEM5N6C6.js.map +0 -1
- /package/dist/{chunk-YDVPI7WT.js.map → chunk-JP62FZSI.js.map} +0 -0
- /package/dist/{chunk-S3D75WSQ.js.map → chunk-N2I422X4.js.map} +0 -0
- /package/dist/{chunk-JXXCTW5W.js.map → chunk-PBVUAXSU.js.map} +0 -0
- /package/dist/{chunk-OAKHWFSV.js.map → chunk-PDU6EU2M.js.map} +0 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OPENGENI_SIGNATURE_HEADER,
|
|
3
|
+
signOpenGeniPayload,
|
|
4
|
+
verifyOpenGeniSignature
|
|
5
|
+
} from "./chunk-KGEABAID.js";
|
|
6
|
+
|
|
7
|
+
// src/external-vault-receiver.ts
|
|
8
|
+
import {
|
|
9
|
+
VAULT_PROVIDER_WIRE_LIMITS,
|
|
10
|
+
VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER,
|
|
11
|
+
VAULT_PROVIDER_WIRE_VERSION,
|
|
12
|
+
VaultProviderWireFreshness,
|
|
13
|
+
VaultProviderWireRequest,
|
|
14
|
+
VaultProviderWireResults,
|
|
15
|
+
isVaultProviderWireMutation,
|
|
16
|
+
vaultProviderWireNameKey,
|
|
17
|
+
vaultProviderWireResponseKey,
|
|
18
|
+
vaultProviderWireUtf8Bytes
|
|
19
|
+
} from "@opengeni/contracts/vault-provider-wire";
|
|
20
|
+
import {
|
|
21
|
+
VAULT_PROVIDER_WIRE_ERROR_CODES,
|
|
22
|
+
VAULT_PROVIDER_WIRE_LIMITS as VAULT_PROVIDER_WIRE_LIMITS2,
|
|
23
|
+
VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER as VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER2,
|
|
24
|
+
VAULT_PROVIDER_WIRE_VERSION as VAULT_PROVIDER_WIRE_VERSION2,
|
|
25
|
+
vaultProviderWireResponseKey as vaultProviderWireResponseKey2
|
|
26
|
+
} from "@opengeni/contracts/vault-provider-wire";
|
|
27
|
+
var ExternalVaultReceiverError = class extends Error {
|
|
28
|
+
code;
|
|
29
|
+
retryAfterMs;
|
|
30
|
+
freshness;
|
|
31
|
+
constructor(code, options = {}) {
|
|
32
|
+
super(options.message ?? code);
|
|
33
|
+
this.name = "ExternalVaultReceiverError";
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.retryAfterMs = options.retryAfterMs === void 0 ? null : Math.min(Math.max(Math.trunc(options.retryAfterMs), 0), 864e5);
|
|
36
|
+
this.freshness = options.freshness ?? null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var JSON_HEADERS = { "content-type": "application/json", "cache-control": "no-store" };
|
|
40
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41
|
+
function plain(status, error) {
|
|
42
|
+
return { status, headers: { ...JSON_HEADERS }, body: JSON.stringify({ error }) };
|
|
43
|
+
}
|
|
44
|
+
function headerValue(headers, name) {
|
|
45
|
+
if (headers instanceof Headers) return headers.get(name);
|
|
46
|
+
const wanted = name.toLowerCase();
|
|
47
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
48
|
+
if (key.toLowerCase() === wanted) {
|
|
49
|
+
return Array.isArray(value) ? value.length === 1 ? value[0] ?? null : null : value ?? null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function canonicalJson(value) {
|
|
55
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
56
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
57
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
58
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
59
|
+
}
|
|
60
|
+
async function hmacHex(key, message) {
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
63
|
+
"raw",
|
|
64
|
+
encoder.encode(key),
|
|
65
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
66
|
+
false,
|
|
67
|
+
["sign"]
|
|
68
|
+
);
|
|
69
|
+
const mac = new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message)));
|
|
70
|
+
return Array.from(mac, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
71
|
+
}
|
|
72
|
+
async function externalVaultMutationDigest(input) {
|
|
73
|
+
const { request } = input;
|
|
74
|
+
const { idempotencyKey: _ignored, ...operation } = request.operation;
|
|
75
|
+
return hmacHex(
|
|
76
|
+
`${input.signingSecret}\0${VAULT_PROVIDER_WIRE_VERSION}.idempotency`,
|
|
77
|
+
canonicalJson({
|
|
78
|
+
connectionId: request.connectionId.toLowerCase(),
|
|
79
|
+
provider: request.provider,
|
|
80
|
+
accountId: request.authority.accountId,
|
|
81
|
+
workspaceId: request.authority.workspaceId,
|
|
82
|
+
subjectId: request.authority.subjectId,
|
|
83
|
+
connectionWorkspaceId: request.authority.connectionWorkspaceId,
|
|
84
|
+
operation
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function pick(value, keys) {
|
|
89
|
+
const out = {};
|
|
90
|
+
for (const key of keys) if (value[key] !== void 0) out[key] = value[key];
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function project(op, raw) {
|
|
94
|
+
if (!raw || typeof raw !== "object") return raw;
|
|
95
|
+
const result = raw;
|
|
96
|
+
const field = (f) => f && typeof f === "object" ? pick(f, ["externalFieldId", "name", "kind"]) : f;
|
|
97
|
+
const item = (i) => i && typeof i === "object" ? {
|
|
98
|
+
...pick(i, ["externalItemId", "name", "type", "websites", "version", "updatedAt"]),
|
|
99
|
+
fields: Array.isArray(i.fields) ? i.fields.map(field) : i.fields
|
|
100
|
+
} : i;
|
|
101
|
+
const vault = (v) => v && typeof v === "object" ? pick(v, [
|
|
102
|
+
"externalVaultId",
|
|
103
|
+
"name",
|
|
104
|
+
"contentVersion",
|
|
105
|
+
"itemCount",
|
|
106
|
+
"writeAccess",
|
|
107
|
+
"operations"
|
|
108
|
+
]) : v;
|
|
109
|
+
switch (op) {
|
|
110
|
+
case "describe":
|
|
111
|
+
return pick(result, [
|
|
112
|
+
"accountId",
|
|
113
|
+
"accountName",
|
|
114
|
+
"operations",
|
|
115
|
+
"capabilities",
|
|
116
|
+
"inventoryScope"
|
|
117
|
+
]);
|
|
118
|
+
case "refresh":
|
|
119
|
+
return pick(result, ["refreshed"]);
|
|
120
|
+
case "list_vaults":
|
|
121
|
+
case "list_items":
|
|
122
|
+
return {
|
|
123
|
+
...pick(result, ["nextCursor", "snapshot"]),
|
|
124
|
+
items: Array.isArray(result.items) ? result.items.map(op === "list_vaults" ? vault : item) : result.items
|
|
125
|
+
};
|
|
126
|
+
case "read_fields":
|
|
127
|
+
return {
|
|
128
|
+
fields: Array.isArray(result.fields) ? result.fields.map(
|
|
129
|
+
(f) => f && typeof f === "object" ? pick(f, ["externalFieldId", "value"]) : f
|
|
130
|
+
) : result.fields
|
|
131
|
+
};
|
|
132
|
+
case "write_item":
|
|
133
|
+
return { item: item(result.item) };
|
|
134
|
+
case "archive_item":
|
|
135
|
+
return pick(result, ["archived"]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function safeExternalVaultName(raw, fallback = "Untitled") {
|
|
139
|
+
const forbidden = /[/{}#\\\u0000-\u001f\u007f-\u009f]/g;
|
|
140
|
+
const clean = (value) => [...value.replace(forbidden, "-").trim()].slice(0, 120).join("").trim();
|
|
141
|
+
let name = clean(typeof raw === "string" ? raw : "");
|
|
142
|
+
if (!name) name = clean(fallback) || "Untitled";
|
|
143
|
+
if (/^id:/i.test(name)) name = `id-${name.slice(3)}`;
|
|
144
|
+
return name;
|
|
145
|
+
}
|
|
146
|
+
function uniqueExternalVaultFieldName(raw, taken, fallback = "field") {
|
|
147
|
+
let base = safeExternalVaultName(raw, fallback);
|
|
148
|
+
if (vaultProviderWireNameKey(base) === "otp") base = "otp-code";
|
|
149
|
+
let candidate = base;
|
|
150
|
+
for (let n = 2; taken.has(vaultProviderWireNameKey(candidate)); n += 1) {
|
|
151
|
+
const suffix = ` (${n})`;
|
|
152
|
+
candidate = `${[...base].slice(0, 120 - suffix.length).join("")}${suffix}`;
|
|
153
|
+
}
|
|
154
|
+
taken.add(vaultProviderWireNameKey(candidate));
|
|
155
|
+
return candidate;
|
|
156
|
+
}
|
|
157
|
+
function createInMemoryExternalVaultReceiverStore(options = {}) {
|
|
158
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
159
|
+
const now = options.now ?? (() => Date.now());
|
|
160
|
+
const requests = /* @__PURE__ */ new Map();
|
|
161
|
+
const mutations = /* @__PURE__ */ new Map();
|
|
162
|
+
const prune = () => {
|
|
163
|
+
const t = now();
|
|
164
|
+
for (const [key, expiresAt] of requests) if (expiresAt <= t) requests.delete(key);
|
|
165
|
+
for (const [key, entry] of mutations) if (entry.expiresAt <= t) mutations.delete(key);
|
|
166
|
+
};
|
|
167
|
+
const full = () => requests.size + mutations.size >= maxEntries;
|
|
168
|
+
return {
|
|
169
|
+
size: () => requests.size + mutations.size,
|
|
170
|
+
async rememberRequest({ connectionId, requestId, expiresAt }) {
|
|
171
|
+
prune();
|
|
172
|
+
const key = `${connectionId}\0${requestId}`;
|
|
173
|
+
if (requests.has(key)) return false;
|
|
174
|
+
if (full()) throw new ExternalVaultReceiverError("provider_rate_limited");
|
|
175
|
+
requests.set(key, expiresAt.getTime());
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
async claimMutation({ connectionId, idempotencyKey, digest, expiresAt }) {
|
|
179
|
+
prune();
|
|
180
|
+
const key = `${connectionId}\0${idempotencyKey}`;
|
|
181
|
+
const existing = mutations.get(key);
|
|
182
|
+
if (existing) {
|
|
183
|
+
if (existing.digest !== digest) return { state: "mismatch" };
|
|
184
|
+
return existing.state === "completed" ? { state: "completed", result: structuredClone(existing.result) } : { state: "pending" };
|
|
185
|
+
}
|
|
186
|
+
if (full()) throw new ExternalVaultReceiverError("provider_rate_limited");
|
|
187
|
+
mutations.set(key, { digest, expiresAt: expiresAt.getTime(), state: "pending" });
|
|
188
|
+
return { state: "claimed" };
|
|
189
|
+
},
|
|
190
|
+
async completeMutation({ connectionId, idempotencyKey, digest, result }) {
|
|
191
|
+
const entry = mutations.get(`${connectionId}\0${idempotencyKey}`);
|
|
192
|
+
if (!entry || entry.digest !== digest) throw new Error("mutation claim missing");
|
|
193
|
+
entry.state = "completed";
|
|
194
|
+
entry.result = structuredClone(result);
|
|
195
|
+
},
|
|
196
|
+
async releaseMutation({ connectionId, idempotencyKey, digest }) {
|
|
197
|
+
const key = `${connectionId}\0${idempotencyKey}`;
|
|
198
|
+
if (mutations.get(key)?.digest === digest) mutations.delete(key);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function operationTarget(operation) {
|
|
203
|
+
return {
|
|
204
|
+
op: operation.op,
|
|
205
|
+
..."externalVaultId" in operation ? { externalVaultId: operation.externalVaultId } : {},
|
|
206
|
+
..."externalItemId" in operation ? { externalItemId: operation.externalItemId } : {},
|
|
207
|
+
...operation.op === "write_item" && operation.item.externalItemId ? { externalItemId: operation.item.externalItemId } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createExternalVaultReceiver(options) {
|
|
211
|
+
const now = options.now ?? (() => Date.now());
|
|
212
|
+
const tolerance = VAULT_PROVIDER_WIRE_LIMITS.signatureToleranceSeconds;
|
|
213
|
+
const retentionMs = Math.max(options.mutationRetentionMs ?? 864e5, tolerance * 2e3);
|
|
214
|
+
const report = (stage, op, connectionId, error) => {
|
|
215
|
+
try {
|
|
216
|
+
options.onError?.({ stage, op, connectionId, error });
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
async function signed(secret, response) {
|
|
221
|
+
const body = JSON.stringify(response);
|
|
222
|
+
const signature = await signOpenGeniPayload(
|
|
223
|
+
vaultProviderWireResponseKey(secret),
|
|
224
|
+
body,
|
|
225
|
+
Math.floor(now() / 1e3)
|
|
226
|
+
);
|
|
227
|
+
return {
|
|
228
|
+
status: 200,
|
|
229
|
+
headers: { ...JSON_HEADERS, [VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER]: signature },
|
|
230
|
+
body
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async function handle(request) {
|
|
234
|
+
if (typeof request.body !== "string") return plain(400, "invalid_request");
|
|
235
|
+
if (vaultProviderWireUtf8Bytes(request.body) > VAULT_PROVIDER_WIRE_LIMITS.maxRequestBytes) {
|
|
236
|
+
return plain(413, "request_too_large");
|
|
237
|
+
}
|
|
238
|
+
const signature = headerValue(request.headers, OPENGENI_SIGNATURE_HEADER);
|
|
239
|
+
if (!signature) return plain(401, "unauthorized");
|
|
240
|
+
let raw;
|
|
241
|
+
try {
|
|
242
|
+
raw = JSON.parse(request.body);
|
|
243
|
+
} catch {
|
|
244
|
+
return plain(400, "invalid_request");
|
|
245
|
+
}
|
|
246
|
+
const claimedConnectionId = raw && typeof raw === "object" ? raw.connectionId : void 0;
|
|
247
|
+
if (typeof claimedConnectionId !== "string" || !UUID.test(claimedConnectionId)) {
|
|
248
|
+
return plain(401, "unauthorized");
|
|
249
|
+
}
|
|
250
|
+
const connectionId = claimedConnectionId.toLowerCase();
|
|
251
|
+
let connection;
|
|
252
|
+
try {
|
|
253
|
+
connection = await options.resolveConnection(connectionId);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
report("store", "describe", connectionId, error);
|
|
256
|
+
return plain(503, "unavailable");
|
|
257
|
+
}
|
|
258
|
+
if (!connection || connection.signingSecrets.length === 0) return plain(401, "unauthorized");
|
|
259
|
+
let secret = null;
|
|
260
|
+
for (const candidate of connection.signingSecrets.slice(0, 4)) {
|
|
261
|
+
if (await verifyOpenGeniSignature({
|
|
262
|
+
secret: candidate,
|
|
263
|
+
body: request.body,
|
|
264
|
+
signature,
|
|
265
|
+
toleranceSeconds: tolerance,
|
|
266
|
+
nowSeconds: Math.floor(now() / 1e3)
|
|
267
|
+
})) {
|
|
268
|
+
secret = candidate;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!secret) return plain(401, "unauthorized");
|
|
273
|
+
const parsed = VaultProviderWireRequest.safeParse(raw);
|
|
274
|
+
if (!parsed.success) return plain(400, "invalid_request");
|
|
275
|
+
const wire = parsed.data;
|
|
276
|
+
const op = wire.operation.op;
|
|
277
|
+
const sentAt = Date.parse(wire.sentAt);
|
|
278
|
+
if (!Number.isFinite(sentAt) || Math.abs(now() - sentAt) > tolerance * 1e3) {
|
|
279
|
+
return plain(401, "unauthorized");
|
|
280
|
+
}
|
|
281
|
+
const failure = (code, extra = {}) => signed(secret, {
|
|
282
|
+
version: VAULT_PROVIDER_WIRE_VERSION,
|
|
283
|
+
type: "response",
|
|
284
|
+
requestId: wire.requestId,
|
|
285
|
+
ok: false,
|
|
286
|
+
op,
|
|
287
|
+
error: { code, retryAfterMs: extra.retryAfterMs ?? null },
|
|
288
|
+
// Host-supplied freshness is validated like handler output; invalid is dropped.
|
|
289
|
+
freshness: extra.freshness == null ? null : VaultProviderWireFreshness.safeParse(extra.freshness).data ?? null
|
|
290
|
+
});
|
|
291
|
+
try {
|
|
292
|
+
const fresh = await options.store.rememberRequest({
|
|
293
|
+
connectionId,
|
|
294
|
+
requestId: wire.requestId,
|
|
295
|
+
expiresAt: new Date(now() + tolerance * 2e3)
|
|
296
|
+
});
|
|
297
|
+
if (!fresh) return plain(409, "replayed_request");
|
|
298
|
+
} catch (error) {
|
|
299
|
+
report("store", op, connectionId, error);
|
|
300
|
+
if (error instanceof ExternalVaultReceiverError) return failure(error.code);
|
|
301
|
+
return failure("provider_unavailable");
|
|
302
|
+
}
|
|
303
|
+
if (wire.provider !== connection.provider || connection.accountId !== void 0 && wire.authority.accountId !== connection.accountId || connection.connectionWorkspaceId !== void 0 && wire.authority.connectionWorkspaceId !== connection.connectionWorkspaceId) {
|
|
304
|
+
return failure("provider_permission_denied");
|
|
305
|
+
}
|
|
306
|
+
let principal;
|
|
307
|
+
try {
|
|
308
|
+
principal = await options.authorize({
|
|
309
|
+
connectionId,
|
|
310
|
+
connection: connection.context,
|
|
311
|
+
authority: wire.authority,
|
|
312
|
+
operation: operationTarget(wire.operation),
|
|
313
|
+
requestId: wire.requestId
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
report("authorize", op, connectionId, error);
|
|
317
|
+
if (error instanceof ExternalVaultReceiverError) return failure(error.code);
|
|
318
|
+
return failure("provider_unavailable");
|
|
319
|
+
}
|
|
320
|
+
if (principal === null || principal === void 0) {
|
|
321
|
+
return failure("provider_permission_denied");
|
|
322
|
+
}
|
|
323
|
+
const inventory = {
|
|
324
|
+
connectionId,
|
|
325
|
+
connection: connection.context,
|
|
326
|
+
requestId: wire.requestId,
|
|
327
|
+
...request.signal ? { signal: request.signal } : {}
|
|
328
|
+
};
|
|
329
|
+
const call = {
|
|
330
|
+
...inventory,
|
|
331
|
+
principal,
|
|
332
|
+
authority: wire.authority
|
|
333
|
+
};
|
|
334
|
+
const respond = async (output2) => {
|
|
335
|
+
const projected = project(op, output2?.result);
|
|
336
|
+
const result = VaultProviderWireResults[op].safeParse(projected);
|
|
337
|
+
const freshness = output2?.freshness == null ? null : VaultProviderWireFreshness.safeParse(output2.freshness);
|
|
338
|
+
if (!result.success || freshness && !freshness.success) {
|
|
339
|
+
report("output", op, connectionId, result.error ?? freshness?.error);
|
|
340
|
+
return { ok: false, code: "provider_unavailable" };
|
|
341
|
+
}
|
|
342
|
+
const consistency = checkConsistency(wire.operation, result.data);
|
|
343
|
+
if (consistency) return { ok: false, code: consistency };
|
|
344
|
+
return {
|
|
345
|
+
ok: true,
|
|
346
|
+
result: result.data,
|
|
347
|
+
response: await signed(secret, {
|
|
348
|
+
version: VAULT_PROVIDER_WIRE_VERSION,
|
|
349
|
+
type: "response",
|
|
350
|
+
requestId: wire.requestId,
|
|
351
|
+
ok: true,
|
|
352
|
+
op,
|
|
353
|
+
result: result.data,
|
|
354
|
+
freshness: freshness ? freshness.data : null
|
|
355
|
+
})
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
if (!isVaultProviderWireMutation(op)) {
|
|
359
|
+
try {
|
|
360
|
+
const output2 = await runRead(wire.operation, inventory, call);
|
|
361
|
+
if (!output2) return failure("provider_permission_denied");
|
|
362
|
+
const outcome2 = await respond(output2);
|
|
363
|
+
return outcome2.ok ? outcome2.response : failure(outcome2.code);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (error instanceof ExternalVaultReceiverError) {
|
|
366
|
+
return failure(error.code, {
|
|
367
|
+
retryAfterMs: error.retryAfterMs,
|
|
368
|
+
freshness: error.freshness
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
report("handler", op, connectionId, error);
|
|
372
|
+
return failure("provider_unavailable");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const mutation = wire.operation;
|
|
376
|
+
const handler = mutation.op === "write_item" ? options.handlers.writeItem : options.handlers.archiveItem;
|
|
377
|
+
if (!handler) return failure("provider_permission_denied");
|
|
378
|
+
const digest = await externalVaultMutationDigest({ signingSecret: secret, request: wire });
|
|
379
|
+
const key = { connectionId, idempotencyKey: mutation.idempotencyKey, digest };
|
|
380
|
+
let claim;
|
|
381
|
+
try {
|
|
382
|
+
claim = await options.store.claimMutation({
|
|
383
|
+
...key,
|
|
384
|
+
expiresAt: new Date(now() + retentionMs)
|
|
385
|
+
});
|
|
386
|
+
} catch (error) {
|
|
387
|
+
report("store", op, connectionId, error);
|
|
388
|
+
return failure(
|
|
389
|
+
error instanceof ExternalVaultReceiverError ? error.code : "provider_unavailable"
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (claim.state === "mismatch") return failure("provider_conflict");
|
|
393
|
+
if (claim.state === "pending") return failure("provider_outcome_unknown");
|
|
394
|
+
if (claim.state === "completed") {
|
|
395
|
+
const outcome2 = await respond({ result: claim.result });
|
|
396
|
+
return outcome2.ok ? outcome2.response : failure("provider_outcome_unknown");
|
|
397
|
+
}
|
|
398
|
+
let output;
|
|
399
|
+
try {
|
|
400
|
+
output = mutation.op === "write_item" ? await options.handlers.writeItem(call, {
|
|
401
|
+
externalVaultId: mutation.externalVaultId,
|
|
402
|
+
item: mutation.item,
|
|
403
|
+
idempotencyKey: mutation.idempotencyKey
|
|
404
|
+
}) : await options.handlers.archiveItem(call, {
|
|
405
|
+
externalVaultId: mutation.externalVaultId,
|
|
406
|
+
externalItemId: mutation.externalItemId,
|
|
407
|
+
idempotencyKey: mutation.idempotencyKey
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (error instanceof ExternalVaultReceiverError && error.code !== "provider_outcome_unknown") {
|
|
411
|
+
try {
|
|
412
|
+
await options.store.releaseMutation(key);
|
|
413
|
+
} catch (storeError) {
|
|
414
|
+
report("store", op, connectionId, storeError);
|
|
415
|
+
}
|
|
416
|
+
return failure(error.code, {
|
|
417
|
+
retryAfterMs: error.retryAfterMs,
|
|
418
|
+
freshness: error.freshness
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
report("handler", op, connectionId, error);
|
|
422
|
+
return failure("provider_outcome_unknown");
|
|
423
|
+
}
|
|
424
|
+
const outcome = await respond(output);
|
|
425
|
+
if (!outcome.ok) return failure("provider_outcome_unknown");
|
|
426
|
+
try {
|
|
427
|
+
await options.store.completeMutation({ ...key, result: outcome.result });
|
|
428
|
+
} catch (error) {
|
|
429
|
+
report("store", op, connectionId, error);
|
|
430
|
+
}
|
|
431
|
+
return outcome.response;
|
|
432
|
+
}
|
|
433
|
+
async function runRead(operation, inventory, call) {
|
|
434
|
+
const handlers = options.handlers;
|
|
435
|
+
switch (operation.op) {
|
|
436
|
+
case "describe":
|
|
437
|
+
return handlers.describe(inventory);
|
|
438
|
+
case "refresh":
|
|
439
|
+
return handlers.refresh ? handlers.refresh(inventory) : null;
|
|
440
|
+
case "list_vaults":
|
|
441
|
+
return handlers.listVaults(inventory, {
|
|
442
|
+
page: operation.page,
|
|
443
|
+
forceRefresh: operation.forceRefresh,
|
|
444
|
+
cacheEpoch: operation.cacheEpoch
|
|
445
|
+
});
|
|
446
|
+
case "list_items":
|
|
447
|
+
return handlers.listItems(inventory, {
|
|
448
|
+
externalVaultId: operation.externalVaultId,
|
|
449
|
+
page: operation.page,
|
|
450
|
+
forceRefresh: operation.forceRefresh,
|
|
451
|
+
cacheEpoch: operation.cacheEpoch
|
|
452
|
+
});
|
|
453
|
+
case "read_fields":
|
|
454
|
+
return handlers.readFields(call, {
|
|
455
|
+
externalVaultId: operation.externalVaultId,
|
|
456
|
+
externalItemId: operation.externalItemId,
|
|
457
|
+
externalFieldIds: operation.externalFieldIds,
|
|
458
|
+
forceRefresh: operation.forceRefresh,
|
|
459
|
+
cacheEpoch: operation.cacheEpoch
|
|
460
|
+
});
|
|
461
|
+
default:
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
handle,
|
|
467
|
+
async fetch(request) {
|
|
468
|
+
if (request.method !== "POST") {
|
|
469
|
+
return new Response(JSON.stringify({ error: "method_not_allowed" }), {
|
|
470
|
+
status: 405,
|
|
471
|
+
headers: { ...JSON_HEADERS, allow: "POST" }
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const max = VAULT_PROVIDER_WIRE_LIMITS.maxRequestBytes;
|
|
475
|
+
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
476
|
+
if (Number.isFinite(declared) && declared > max) {
|
|
477
|
+
await request.body?.cancel().catch(() => {
|
|
478
|
+
});
|
|
479
|
+
const response2 = plain(413, "request_too_large");
|
|
480
|
+
return new Response(response2.body, { status: response2.status, headers: response2.headers });
|
|
481
|
+
}
|
|
482
|
+
const chunks = [];
|
|
483
|
+
let total = 0;
|
|
484
|
+
const reader = request.body?.getReader();
|
|
485
|
+
if (reader) {
|
|
486
|
+
for (; ; ) {
|
|
487
|
+
const { done, value } = await reader.read();
|
|
488
|
+
if (done) break;
|
|
489
|
+
total += value.byteLength;
|
|
490
|
+
if (total > max) {
|
|
491
|
+
await reader.cancel().catch(() => {
|
|
492
|
+
});
|
|
493
|
+
const response2 = plain(413, "request_too_large");
|
|
494
|
+
return new Response(response2.body, {
|
|
495
|
+
status: response2.status,
|
|
496
|
+
headers: response2.headers
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
chunks.push(value);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const bytes = new Uint8Array(total);
|
|
503
|
+
let offset = 0;
|
|
504
|
+
for (const chunk of chunks) {
|
|
505
|
+
bytes.set(chunk, offset);
|
|
506
|
+
offset += chunk.byteLength;
|
|
507
|
+
}
|
|
508
|
+
let body;
|
|
509
|
+
try {
|
|
510
|
+
body = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
511
|
+
} catch {
|
|
512
|
+
const response2 = plain(400, "invalid_request");
|
|
513
|
+
return new Response(response2.body, { status: response2.status, headers: response2.headers });
|
|
514
|
+
}
|
|
515
|
+
const response = await handle({ body, headers: request.headers, signal: request.signal });
|
|
516
|
+
return new Response(response.body, { status: response.status, headers: response.headers });
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function checkConsistency(operation, result) {
|
|
521
|
+
switch (operation.op) {
|
|
522
|
+
case "list_vaults":
|
|
523
|
+
case "list_items": {
|
|
524
|
+
const page = result;
|
|
525
|
+
if (page.items.length > operation.page.limit) return "provider_unavailable";
|
|
526
|
+
const ids = /* @__PURE__ */ new Set();
|
|
527
|
+
for (const entry of page.items) {
|
|
528
|
+
const id = entry.externalVaultId ?? entry.externalItemId ?? "";
|
|
529
|
+
if (ids.has(id)) return "provider_unavailable";
|
|
530
|
+
ids.add(id);
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
case "read_fields": {
|
|
535
|
+
const fields = result.fields;
|
|
536
|
+
const requested = new Set(operation.externalFieldIds);
|
|
537
|
+
if (fields.some((field) => !requested.has(field.externalFieldId))) {
|
|
538
|
+
return "provider_unavailable";
|
|
539
|
+
}
|
|
540
|
+
return fields.length === requested.size ? null : "provider_not_found";
|
|
541
|
+
}
|
|
542
|
+
case "write_item": {
|
|
543
|
+
const item = result.item;
|
|
544
|
+
const expected = operation.item.externalItemId;
|
|
545
|
+
return expected !== void 0 && item.externalItemId !== expected ? "provider_outcome_unknown" : null;
|
|
546
|
+
}
|
|
547
|
+
default:
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
export {
|
|
552
|
+
ExternalVaultReceiverError,
|
|
553
|
+
VAULT_PROVIDER_WIRE_ERROR_CODES,
|
|
554
|
+
VAULT_PROVIDER_WIRE_LIMITS2 as VAULT_PROVIDER_WIRE_LIMITS,
|
|
555
|
+
VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER2 as VAULT_PROVIDER_WIRE_RESPONSE_SIGNATURE_HEADER,
|
|
556
|
+
VAULT_PROVIDER_WIRE_VERSION2 as VAULT_PROVIDER_WIRE_VERSION,
|
|
557
|
+
createExternalVaultReceiver,
|
|
558
|
+
createInMemoryExternalVaultReceiverStore,
|
|
559
|
+
externalVaultMutationDigest,
|
|
560
|
+
safeExternalVaultName,
|
|
561
|
+
uniqueExternalVaultFieldName,
|
|
562
|
+
vaultProviderWireResponseKey2 as vaultProviderWireResponseKey
|
|
563
|
+
};
|
|
564
|
+
//# sourceMappingURL=external-vault-receiver.js.map
|