@tinycloud/node-sdk 3.8.0-beta.2 → 3.8.0-beta.21

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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/dist/{TinyCloudNode-B6VOBrCb.d.ts → TinyCloudNode-DaD5vJhL.d.cts} +49 -6
  3. package/dist/{TinyCloudNode-CVO41O12.d.cts → TinyCloudNode-k3TbBwwj.d.ts} +49 -6
  4. package/dist/{WasmKeyProvider-5Ee7fcAw.d.cts → WasmKeyProvider-BKCPRC-c.d.cts} +21 -1
  5. package/dist/{WasmKeyProvider-5Ee7fcAw.d.ts → WasmKeyProvider-BKCPRC-c.d.ts} +21 -1
  6. package/dist/{authority-DGi1hIMK.d.ts → authority-C_fWaxAu.d.ts} +1 -1
  7. package/dist/{authority-DgVV80bI.d.cts → authority-DJdbZM6k.d.cts} +1 -1
  8. package/dist/core.cjs +375 -40
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +5 -4
  11. package/dist/core.d.ts +5 -4
  12. package/dist/core.js +370 -35
  13. package/dist/core.js.map +1 -1
  14. package/dist/credentials-core.d.cts +1 -1
  15. package/dist/credentials-core.d.ts +1 -1
  16. package/dist/credentials.d.cts +1 -1
  17. package/dist/credentials.d.ts +1 -1
  18. package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
  19. package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
  20. package/dist/encryption-keys.d.cts +2 -2
  21. package/dist/encryption-keys.d.ts +2 -2
  22. package/dist/index.cjs +343 -51
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +333 -38
  27. package/dist/index.js.map +1 -1
  28. package/dist/replication/authority.cjs +3 -0
  29. package/dist/replication/authority.cjs.map +1 -1
  30. package/dist/replication/authority.d.cts +4 -3
  31. package/dist/replication/authority.d.ts +4 -3
  32. package/dist/replication/authority.js +3 -0
  33. package/dist/replication/authority.js.map +1 -1
  34. package/dist/replication/runtime.cjs +1 -0
  35. package/dist/replication/runtime.cjs.map +1 -1
  36. package/dist/replication/runtime.js +1 -0
  37. package/dist/replication/runtime.js.map +1 -1
  38. package/dist/share-receive.cjs +775 -0
  39. package/dist/share-receive.cjs.map +1 -0
  40. package/dist/share-receive.d.cts +32 -0
  41. package/dist/share-receive.d.ts +32 -0
  42. package/dist/share-receive.js +777 -0
  43. package/dist/share-receive.js.map +1 -0
  44. package/package.json +11 -4
@@ -0,0 +1,777 @@
1
+ // src/share-receive.ts
2
+ import {
3
+ credentialRequirementDigest as credentialRequirementDigest2,
4
+ createEmailCredentialRequirement as createEmailCredentialRequirement2
5
+ } from "@tinycloud/sdk-core";
6
+ import { inspectShare, ShareReceiveError as ShareReceiveError2 } from "@tinycloud/share-sdk";
7
+
8
+ // src/credentials/email-proof.ts
9
+ import {
10
+ canonicalDigest as canonicalDigest2,
11
+ createEmailCredentialRequirement,
12
+ credentialError,
13
+ credentialRequirementDigest,
14
+ CredentialError as CredentialError3,
15
+ descriptorSatisfiesRequirement,
16
+ encodeBase64Url as encodeBase64Url3,
17
+ sha256Base64Url as sha256Base64Url2,
18
+ validateCredentialFlowDescriptor,
19
+ verifyIssuedCredential
20
+ } from "@tinycloud/sdk-core";
21
+
22
+ // src/credentials/interpreter.ts
23
+ import {
24
+ CredentialError,
25
+ encodeBase64Url,
26
+ holderBindingSigningBytes,
27
+ validateCredentialHolderBinding
28
+ } from "@tinycloud/sdk-core";
29
+ var delay = (ms, signal) => new Promise((resolve, reject) => {
30
+ const timer = setTimeout(resolve, ms);
31
+ signal?.addEventListener("abort", () => {
32
+ clearTimeout(timer);
33
+ reject(new DOMException("Aborted", "AbortError"));
34
+ }, { once: true });
35
+ });
36
+ async function interpretCredentialFlow(input) {
37
+ const steps = new Map(input.descriptor.steps.map((step) => [step.type, step]));
38
+ const completed = /* @__PURE__ */ new Set();
39
+ let lastTransition = "";
40
+ for (; ; ) {
41
+ input.signal?.throwIfAborted();
42
+ const state = await input.transport.state(input.requestId, input.verifier, input.signal);
43
+ if (state.requestId !== input.requestId) throw new CredentialError("REQUEST_SUBSTITUTED", "Credential request identity changed");
44
+ if (state.state === "expired") throw new CredentialError("REQUEST_EXPIRED", "Credential request expired", { state: state.state, correlationId: state.correlationId });
45
+ if (state.state === "canceled") throw new CredentialError("CANCELED", "Credential request was canceled", { state: state.state, correlationId: state.correlationId });
46
+ if (state.state === "issuer_unready") throw new CredentialError("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: state.retryAfterMs, state: state.state, correlationId: state.correlationId });
47
+ if (state.state === "complete") return;
48
+ if (state.state === "ready_to_issue" || state.state === "issued") {
49
+ if (state.state === "ready_to_issue" && !completed.has("issue")) {
50
+ await input.transport.issue(input.requestId, input.verifier, input.signal);
51
+ completed.add("issue");
52
+ } else await (input.onWait?.() ?? delay(state.retryAfterMs ?? 50, input.signal));
53
+ continue;
54
+ }
55
+ const next = state.nextStep;
56
+ if (!next) {
57
+ await (input.onWait?.() ?? delay(state.retryAfterMs ?? 50, input.signal));
58
+ continue;
59
+ }
60
+ const declared = steps.get(next.type);
61
+ if (next.id !== next.type || !declared || declared.type !== next.type || declared.version !== next.version) throw new CredentialError("REQUEST_SUBSTITUTED", "Server selected an undeclared credential step");
62
+ if (completed.has(next.id) || state.transitionId === lastTransition) {
63
+ await (input.onWait?.() ?? delay(state.retryAfterMs ?? 50, input.signal));
64
+ continue;
65
+ }
66
+ lastTransition = state.transitionId;
67
+ if (next.type === "holder_signature") {
68
+ input.onProgress?.({ state: "signing", stepId: next.id, correlationId: state.correlationId });
69
+ const binding = validateCredentialHolderBinding(await input.transport.holderBinding(input.requestId, input.verifier, input.signal));
70
+ if (binding.requestId !== input.requestId || binding.descriptorDigest !== input.descriptorDigest || binding.requirementDigest !== input.requirementDigest || binding.holderDid !== input.holderDid || binding.issuer !== input.descriptor.issuer.did || binding.issuerKid !== input.descriptor.issuer.kid || binding.profile !== input.descriptor.profile || binding.openerOrigin !== input.openerOrigin || binding.completionOrigin !== input.openerOrigin || binding.audience !== "tinycloud://credentials") throw new CredentialError("REQUEST_SUBSTITUTED", "Holder binding was substituted");
71
+ const bytes = holderBindingSigningBytes(binding);
72
+ let signature = await input.signing.autoSign?.(binding, bytes, input.signal);
73
+ if (signature === void 0) {
74
+ if (!input.signing.requestApproval) throw new CredentialError("SIGNATURE_REJECTED", "OpenKey approval is required");
75
+ try {
76
+ signature = await input.signing.requestApproval(binding, bytes, input.signal);
77
+ } catch (cause) {
78
+ if (cause instanceof CredentialError) throw cause;
79
+ throw new CredentialError("SIGNATURE_REJECTED", "OpenKey approval was rejected", { cause });
80
+ }
81
+ }
82
+ if (!(signature instanceof Uint8Array) || signature.length === 0) throw new CredentialError("SIGNATURE_REJECTED", "OpenKey signature is invalid");
83
+ await input.transport.submitHolderSignature(input.requestId, input.verifier, encodeBase64Url(signature), input.signal, input.signing.holderKey);
84
+ } else {
85
+ input.onProgress?.({ state: next.type === "collect_input" ? "collecting" : "proving", stepId: next.id, correlationId: state.correlationId });
86
+ const handler = input.handlers?.[next.type];
87
+ const inlineHandler = input.proofHandler;
88
+ if (!handler && !inlineHandler) {
89
+ await (input.onWait?.() ?? delay(state.retryAfterMs ?? 50, input.signal));
90
+ continue;
91
+ }
92
+ if (next.constraints.challengeRequired === true) await input.transport.beginStep(input.requestId, input.verifier, next.type, input.signal);
93
+ if (handler) {
94
+ const proof = await handler({ descriptor: input.descriptor, requirement: input.requirement, stepId: next.id, constraints: next.constraints, signal: input.signal });
95
+ await input.transport.submitStep(input.requestId, input.verifier, next.id, proof, input.signal);
96
+ } else {
97
+ let feedback;
98
+ for (let attempt = 1; ; attempt += 1) {
99
+ const proof = await inlineHandler({
100
+ stepId: next.id,
101
+ constraints: next.constraints,
102
+ display: {
103
+ title: input.descriptor.display.title,
104
+ description: input.descriptor.display.description,
105
+ consent: input.descriptor.display.consent,
106
+ progressLabel: input.descriptor.accessibility.progressLabel,
107
+ errorLiveRegion: input.descriptor.accessibility.errorLiveRegion
108
+ },
109
+ inputs: input.descriptor.inputs.map(({ id, label, schema }) => ({ id, label, schema })),
110
+ ...feedback === void 0 ? {} : { feedback },
111
+ signal: input.signal
112
+ });
113
+ try {
114
+ await input.transport.submitStep(input.requestId, input.verifier, next.id, proof, input.signal);
115
+ break;
116
+ } catch (cause) {
117
+ if (!(cause instanceof CredentialError) || cause.code !== "PROOF_REJECTED") throw cause;
118
+ const attemptsRemaining = input.descriptor.lifecycle.maxProofAttempts - attempt;
119
+ if (next.type !== "mailbox_otp" || attemptsRemaining <= 0) throw new CredentialError("VERIFICATION_FAILED", "The proof was not accepted", { state: attemptsRemaining <= 0 ? "proof_attempts_exhausted" : "proof_rejected", cause });
120
+ feedback = { kind: "rejected", attemptsRemaining };
121
+ }
122
+ }
123
+ }
124
+ }
125
+ completed.add(next.id);
126
+ }
127
+ }
128
+
129
+ // src/credentials/transport.ts
130
+ import {
131
+ CredentialError as CredentialError2,
132
+ canonicalDigest,
133
+ credentialEndpointPath,
134
+ decodeBase64Url,
135
+ encodeBase64Url as encodeBase64Url2,
136
+ holderBindingCanonicalBytes,
137
+ sha256Base64Url,
138
+ validateCredentialHolderBinding as validateCredentialHolderBinding2
139
+ } from "@tinycloud/sdk-core";
140
+ function object(value, label) {
141
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError2("VERIFICATION_FAILED", `${label} is invalid`);
142
+ return value;
143
+ }
144
+ function opaque(value, label) {
145
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]{16,128}$/.test(value)) throw new CredentialError2("REQUEST_SUBSTITUTED", `${label} is invalid`);
146
+ return value;
147
+ }
148
+ function timestamp(value, label) {
149
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new CredentialError2("VERIFICATION_FAILED", `${label} is invalid`);
150
+ return new Date(value).toISOString();
151
+ }
152
+ function jwtPayload(credential) {
153
+ const part = credential.split("~", 1)[0]?.split(".")[1];
154
+ if (!part) throw new CredentialError2("VERIFICATION_FAILED", "Issued SD-JWT is invalid");
155
+ try {
156
+ return object(JSON.parse(new TextDecoder().decode(decodeBase64Url(part))), "SD-JWT payload");
157
+ } catch (cause) {
158
+ if (cause instanceof CredentialError2) throw cause;
159
+ throw new CredentialError2("VERIFICATION_FAILED", "Issued SD-JWT is invalid", { cause });
160
+ }
161
+ }
162
+ var SERVER_CODES = /* @__PURE__ */ new Set(["REQUEST_EXPIRED", "ISSUER_UNREADY", "UNSUPPORTED_PROFILE", "UNSUPPORTED_VERSION", "SIGNATURE_REJECTED"]);
163
+ function serverError(value, response, honorServerRecoverable) {
164
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
165
+ const body = value;
166
+ const keys = Object.keys(body).sort();
167
+ if (keys.join(",") !== "code,correlationId,recoverable,state,type" || body.type !== "tinycloud.credentials/error/v1" || typeof body.code !== "string" || typeof body.recoverable !== "boolean" || typeof body.state !== "string" || typeof body.correlationId !== "string") return void 0;
168
+ if (body.code === "PROOF_REJECTED") return body.recoverable ? new CredentialError2("PROOF_REJECTED", "The proof was not accepted", { state: body.state, correlationId: body.correlationId }) : new CredentialError2("VERIFICATION_FAILED", "The proof attempt limit was reached", { state: "proof_attempts_exhausted", correlationId: body.correlationId });
169
+ if (body.code === "RATE_LIMITED") return new CredentialError2("ISSUER_UNREADY", "Too many verification codes were requested", { state: "rate_limited", correlationId: body.correlationId, retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
170
+ if (body.code === "CHALLENGE_EXPIRED") return new CredentialError2("REQUEST_EXPIRED", "The credential challenge expired", { state: body.state, correlationId: body.correlationId, recoverable: honorServerRecoverable ? body.recoverable : void 0 });
171
+ if (!SERVER_CODES.has(body.code)) return void 0;
172
+ const code = body.code;
173
+ return new CredentialError2(code, `OpenCredentials rejected the request: ${code}`, {
174
+ state: body.state,
175
+ correlationId: body.correlationId,
176
+ retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0,
177
+ recoverable: honorServerRecoverable ? body.recoverable : void 0
178
+ });
179
+ }
180
+ var OpenCredentialsHttpTransport = class {
181
+ constructor(descriptor, fetchFn = globalThis.fetch.bind(globalThis), options = {}) {
182
+ this.descriptor = descriptor;
183
+ this.fetchFn = fetchFn;
184
+ this.options = options;
185
+ this.nonces = /* @__PURE__ */ new Map();
186
+ this.bindings = /* @__PURE__ */ new Map();
187
+ }
188
+ url(endpoint, id) {
189
+ return new URL(credentialEndpointPath(endpoint, id), this.descriptor.issuer.origin).href;
190
+ }
191
+ async request(endpoint, input) {
192
+ let response;
193
+ try {
194
+ const init = { method: input.method ?? "GET", credentials: "omit", cache: "no-store", redirect: "error", referrerPolicy: "no-referrer", signal: input.signal, headers: { accept: "application/json", ...input.body === void 0 ? {} : { "content-type": "application/json" }, ...input.verifier === void 0 ? {} : { authorization: `Bearer ${input.verifier}` } }, ...input.body === void 0 ? {} : { body: JSON.stringify(input.body) } };
195
+ response = await this.fetchFn(this.url(endpoint, input.id), init);
196
+ } catch (cause) {
197
+ if (cause instanceof DOMException && cause.name === "AbortError") throw new CredentialError2("CANCELED", "Credential acquisition was canceled", { cause });
198
+ throw new CredentialError2("OFFLINE", "OpenCredentials is unavailable", { cause });
199
+ }
200
+ if (!response.ok) {
201
+ let parsed;
202
+ try {
203
+ parsed = await response.json();
204
+ } catch {
205
+ parsed = void 0;
206
+ }
207
+ const typed = serverError(parsed, response, this.options.honorServerRecoverable === true);
208
+ if (typed) throw typed;
209
+ if (response.status === 410) throw new CredentialError2("REQUEST_EXPIRED", "Credential request expired");
210
+ if (response.status === 409) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential request was replayed or substituted");
211
+ if (response.status === 503) throw new CredentialError2("ISSUER_UNREADY", "Credential issuer is not ready", { retryAfterMs: Number(response.headers.get("retry-after")) * 1e3 || void 0 });
212
+ throw new CredentialError2("VERIFICATION_FAILED", "OpenCredentials rejected the request");
213
+ }
214
+ if (response.status === 204) return void 0;
215
+ try {
216
+ return await response.json();
217
+ } catch (cause) {
218
+ throw new CredentialError2("VERIFICATION_FAILED", "OpenCredentials response is invalid", { cause });
219
+ }
220
+ }
221
+ async create(input) {
222
+ const body = object(await this.request("request", { method: "POST", body: { protocol: input.descriptor.protocol, profile: input.descriptor.profile, profileVersion: input.descriptor.profileVersion, descriptorDigest: input.descriptorDigest, requirementDigest: input.requirementDigest, holderDid: input.holderDid, inputs: input.inputs ?? input.requirement.claims, audience: "tinycloud://credentials", openerOrigin: input.openerOrigin, completionOrigin: input.openerOrigin, completionContext: "sdk-acquisition", completionVerifierChallenge: input.completionVerifierChallenge }, signal: input.signal }), "create response");
223
+ if (body.type !== "tinycloud.credentials/acquisition-request/v1" || body.protocol !== input.descriptor.protocol || body.next !== "challenge" || body.endpoint !== "challenge") throw new CredentialError2("VERIFICATION_FAILED", "create response is invalid");
224
+ const requestId = opaque(body.requestId, "requestId");
225
+ return { requestId, locator: requestId, expiresAt: timestamp(body.expiresAt, "expiresAt"), correlationId: requestId };
226
+ }
227
+ proofStep() {
228
+ return this.descriptor.steps.some((step) => step.type === "mailbox_otp") ? "mailbox_otp" : "collect_input";
229
+ }
230
+ async state(id, verifier, signal) {
231
+ const raw = object(await this.request("state", { id, verifier, signal }), "state response");
232
+ if (raw.type !== "tinycloud.credentials/acquisition-state/v1" || raw.requestId !== id || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential state response is substituted");
233
+ const base = { type: "OpenCredentialsAcquisitionState", version: 1, requestId: id, transitionId: `${String(raw.state)}:${String(raw.resultPreviouslyRead ?? false)}`, correlationId: id };
234
+ if (raw.state === "challenge_required") {
235
+ const type = this.proofStep();
236
+ return { ...base, state: "pending", nextStep: { id: type, type, version: 1, constraints: { challengeRequired: true } } };
237
+ }
238
+ if (raw.state === "proof_required") {
239
+ const type = this.proofStep();
240
+ this.nonces.set(id, opaque(raw.challengeNonce, "challengeNonce"));
241
+ return { ...base, state: "pending", nextStep: { id: type, type, version: 1, constraints: {} } };
242
+ }
243
+ if (raw.state === "holder_binding_required" || raw.state === "holder_signature_required") return { ...base, state: "pending", nextStep: { id: "holder_signature", type: "holder_signature", version: 1, constraints: {} } };
244
+ if (raw.state === "ready_to_issue") return { ...base, state: "ready_to_issue" };
245
+ if (raw.state === "issued") return { ...base, state: "complete" };
246
+ throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential state is unsupported");
247
+ }
248
+ async beginStep(id, verifier, stepId, signal) {
249
+ const step = this.proofStep();
250
+ if (stepId !== step) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential challenge step is substituted");
251
+ const challenge = object(await this.request("challenge", { id, verifier, method: "POST", body: { step, stepVersion: 1 }, signal }), "challenge response");
252
+ if (challenge.type !== "tinycloud.credentials/challenge/v1" || challenge.step !== step || challenge.stepVersion !== 1) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential challenge is substituted");
253
+ this.nonces.set(id, opaque(challenge.challengeNonce, "challengeNonce"));
254
+ }
255
+ async submitStep(id, verifier, stepId, proof, signal) {
256
+ const step = this.proofStep();
257
+ if (stepId !== step) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential proof step is substituted");
258
+ const nonce = this.nonces.get(id);
259
+ if (!nonce) throw new CredentialError2("REQUEST_SUBSTITUTED", "Credential challenge is missing");
260
+ await this.request("proof", { id, verifier, method: "POST", body: { step, stepVersion: 1, challengeNonce: nonce, proof }, signal });
261
+ }
262
+ async holderBinding(id, verifier, signal) {
263
+ const raw = object(await this.request("holder_binding", { id, verifier, signal }), "holder binding response");
264
+ if (raw.type !== "tinycloud.credentials/holder-binding-request/v1" || raw.signingDomain !== "tinycloud.credentials/holder-binding/v1" || raw.alg !== "EdDSA" || typeof raw.canonical !== "string") throw new CredentialError2("REQUEST_SUBSTITUTED", "Holder binding response is invalid");
265
+ const binding = validateCredentialHolderBinding2(raw.binding);
266
+ if (encodeBase64Url2(holderBindingCanonicalBytes(binding)) !== raw.canonical) throw new CredentialError2("REQUEST_SUBSTITUTED", "Holder binding canonical bytes were substituted");
267
+ this.bindings.set(id, binding);
268
+ return binding;
269
+ }
270
+ async submitHolderSignature(id, verifier, signature, signal, key) {
271
+ const binding = this.bindings.get(id);
272
+ if (!binding) throw new CredentialError2("REQUEST_SUBSTITUTED", "Holder binding was not retrieved");
273
+ const kid = key?.kid ?? `${binding.holderDid}#${binding.holderDid.replace("did:key:", "")}`;
274
+ await this.request("holder_signature", { id, verifier, method: "POST", body: { alg: key?.alg ?? "EdDSA", kid, signature }, signal });
275
+ }
276
+ async issue(id, verifier, signal) {
277
+ await this.request("issue", { id, verifier, method: "POST", signal });
278
+ }
279
+ async result(id, verifier, signal) {
280
+ const raw = object(await this.request("result", { id, verifier, signal }), "result response");
281
+ if (raw.type !== "tinycloud.credentials/result/v1" || raw.protocol !== this.descriptor.protocol || raw.profile !== this.descriptor.profile || raw.profileVersion !== 1 || raw.format !== "vc+sd-jwt" || raw.descriptorDigest !== await canonicalDigest(this.descriptor) || raw.issuer !== this.descriptor.issuer.did || raw.issuerKid !== this.descriptor.issuer.kid || typeof raw.credential !== "string" || await sha256Base64Url(raw.credential) !== raw.credentialDigest) throw new CredentialError2("VERIFICATION_FAILED", "Issued result metadata is invalid");
282
+ const holder = object(raw.holderBinding, "result holder binding");
283
+ if (raw.subject !== holder.did || holder.signingDomain !== "tinycloud.credentials/holder-binding/v1") throw new CredentialError2("HOLDER_MISMATCH", "Issued holder binding is invalid");
284
+ const claimsRaw = object(raw.claims, "result claims");
285
+ const claims = {};
286
+ for (const [name, value] of Object.entries(claimsRaw)) {
287
+ if (typeof value !== "string") throw new CredentialError2("VERIFICATION_FAILED", "Issued claims are invalid");
288
+ claims[name] = value;
289
+ }
290
+ const validity = object(raw.validity, "result validity");
291
+ const payload = jwtPayload(raw.credential);
292
+ if (typeof payload.jti !== "string") throw new CredentialError2("VERIFICATION_FAILED", "Credential id is invalid");
293
+ const issuedAt = timestamp(validity.issuedAt, "issuedAt");
294
+ const expiresAt = timestamp(validity.expiresAt, "expiresAt");
295
+ const status = object(raw.status, "result status");
296
+ if (status.type !== "none" || status.freshnessSeconds !== this.descriptor.status.freshnessSeconds) throw new CredentialError2("VERIFICATION_FAILED", "Credential status is invalid");
297
+ return { type: "OpenCredentialsIssuedCredential", version: 1, protocol: this.descriptor.protocol, profile: { id: this.descriptor.profile, version: 1 }, credentialType: { id: this.descriptor.format.vct, version: 1 }, schema: this.descriptor.format.vct, format: "vc+sd-jwt", issuerDid: raw.issuer, issuerKid: raw.issuerKid, subjectDid: raw.subject, holderDid: raw.subject, claims, claimsDigest: await canonicalDigest(claims), descriptorDigest: raw.descriptorDigest, credentialId: payload.jti, issuedAt, notBefore: issuedAt, expiresAt, status: { method: "none", freshnessSeconds: status.freshnessSeconds }, credential: raw.credential };
298
+ }
299
+ async issuerMetadata(signal) {
300
+ return await this.request("issuer_metadata", { signal });
301
+ }
302
+ async checkStatus(status, _signal) {
303
+ return status.method === "none" && status.freshnessSeconds === this.descriptor.status.freshnessSeconds;
304
+ }
305
+ };
306
+
307
+ // src/credentials/email-proof.ts
308
+ var EMAIL_PROOF_PINS = Object.freeze({
309
+ witnessOrigin: "https://witness.credentials.org",
310
+ issuerDid: "did:web:issuer.credentials.org",
311
+ issuerKid: "did:web:issuer.credentials.org#controller",
312
+ issuerX: "eEJI4xEobto4HtQ7Pg9R1vBOwcpfGRTlXDG5QfqLnGQ",
313
+ descriptorDigest: "1tg-qphmKBVtNwzVg9xyz-xxqt_xtMXAsQyXw46m8S0"
314
+ });
315
+ var EMAIL_PROOF_CREDENTIAL_TYPE = { id: "opencredentials.email/v1", version: 1 };
316
+ var EMAIL_PROOF_PENDING_MAX_MS = 15 * 6e4;
317
+ var DISCOVERY_URL = `${EMAIL_PROOF_PINS.witnessOrigin}/.well-known/opencredentials`;
318
+ var EMAIL_PROOF_ISSUER_KEYS = [
319
+ {
320
+ kid: EMAIL_PROOF_PINS.issuerKid,
321
+ alg: "EdDSA",
322
+ jwk: { kty: "OKP", crv: "Ed25519", x: EMAIL_PROOF_PINS.issuerX },
323
+ validFrom: "2020-01-01T00:00:00Z",
324
+ validUntil: "2100-01-01T00:00:00Z"
325
+ }
326
+ ];
327
+ var DEFAULT_EMAIL_PROOF_PROFILE = "tinycloud.email-proof/v1";
328
+ var PROFILE_PINS = {
329
+ "tinycloud.email-proof/v1": { descriptorDigest: EMAIL_PROOF_PINS.descriptorDigest, requiredStep: "mailbox_otp", enabled: true }
330
+ };
331
+ var OPENER_ORIGIN = "https://localhost";
332
+ var STEP_TIMEOUT_MS = 6e4;
333
+ var HOLDER_DID_PREFIX = "did:key:";
334
+ var HOLDER_DID_PATTERN = /^did:key:z6Mk[^#]+$/;
335
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
336
+ var INSPECT = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
337
+ var RECOVERABILITY = {
338
+ ADDRESS_INVALID: false,
339
+ HOLDER_INVALID: false,
340
+ CODE_INVALID: true,
341
+ RATE_LIMITED: true,
342
+ ISSUER_UNAVAILABLE: true,
343
+ NETWORK: true,
344
+ PROFILE_UNAVAILABLE: false,
345
+ CODE_REJECTED: true,
346
+ CODE_ATTEMPTS_EXHAUSTED: false,
347
+ SIGNATURE_REJECTED: true,
348
+ CREDENTIAL_INVALID: false,
349
+ SESSION_EXPIRED: false,
350
+ SESSION_INVALID: false,
351
+ CANCELED: false,
352
+ FAILED: false
353
+ };
354
+ var MESSAGES = {
355
+ ADDRESS_INVALID: "Email address is invalid.",
356
+ HOLDER_INVALID: "Holder must be a did:key Ed25519 identifier.",
357
+ CODE_INVALID: "Verification code must be 8 digits.",
358
+ ISSUER_UNAVAILABLE: "Email verification is unavailable right now.",
359
+ NETWORK: "Could not reach the email verification service.",
360
+ PROFILE_UNAVAILABLE: "The witness does not offer the email verification profile.",
361
+ CODE_REJECTED: "Verification code was not accepted.",
362
+ CODE_ATTEMPTS_EXHAUSTED: "Too many wrong codes. Request a new code.",
363
+ SIGNATURE_REJECTED: "Holder signature was rejected.",
364
+ CREDENTIAL_INVALID: "Issued credential failed verification.",
365
+ SESSION_EXPIRED: "Email verification request expired. Request a new code.",
366
+ SESSION_INVALID: "Email verification request is no longer valid. Request a new code.",
367
+ CANCELED: "Email verification was canceled.",
368
+ FAILED: "Email verification failed."
369
+ };
370
+ function rateLimitMessage(retryAfterMs) {
371
+ const head = "Too many codes were requested for this address.";
372
+ if (retryAfterMs === void 0 || !Number.isFinite(retryAfterMs) || retryAfterMs <= 0) return `${head} Try again in about an hour.`;
373
+ const minutes = Math.max(1, Math.ceil(retryAfterMs / 6e4));
374
+ return `${head} Try again in ${minutes} ${minutes === 1 ? "minute" : "minutes"}.`;
375
+ }
376
+ var EmailProofError = class extends Error {
377
+ constructor(step, code, retryAfterMs, recoverable = RECOVERABILITY[code]) {
378
+ super(code === "RATE_LIMITED" ? rateLimitMessage(retryAfterMs) : MESSAGES[code]);
379
+ this.name = "EmailProofError";
380
+ this.step = step;
381
+ this.code = code;
382
+ this.retryAfterMs = retryAfterMs;
383
+ this.recoverable = recoverable;
384
+ }
385
+ };
386
+ function restoreEmailProofPending(fields) {
387
+ const shown = {
388
+ type: fields.type,
389
+ version: fields.version,
390
+ profile: fields.profile,
391
+ address: fields.address,
392
+ requestId: fields.requestId,
393
+ holderDid: fields.holderDid,
394
+ descriptorDigest: fields.descriptorDigest,
395
+ requirementDigest: fields.requirementDigest,
396
+ openerOrigin: fields.openerOrigin,
397
+ createdAt: fields.createdAt,
398
+ expiresAt: fields.expiresAt
399
+ };
400
+ const pending = { ...shown };
401
+ hide(pending, "verifier", fields.verifier);
402
+ hide(pending, "toJSON", () => ({ ...shown }));
403
+ hide(pending, INSPECT, () => ({ ...shown, verifier: "[redacted]" }));
404
+ return Object.freeze(pending);
405
+ }
406
+ function hide(target, key, value) {
407
+ Object.defineProperty(target, key, { value, enumerable: false, writable: false, configurable: false });
408
+ }
409
+ function pendingDeadlineMs(pending) {
410
+ return Math.min(Date.parse(pending.expiresAt), Date.parse(pending.createdAt) + EMAIL_PROOF_PENDING_MAX_MS);
411
+ }
412
+ var CodeRequested = class extends Error {
413
+ };
414
+ var pauseForCode = async () => {
415
+ throw new CodeRequested("code requested");
416
+ };
417
+ async function requestEmailProofCode(input) {
418
+ const profile = input.profile ?? DEFAULT_EMAIL_PROOF_PROFILE;
419
+ profilePin(profile, "request");
420
+ const address = normalizeAddress(input.address);
421
+ assertHolder(input.holder, "request");
422
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
423
+ const signal = input.signal ?? AbortSignal.timeout(STEP_TIMEOUT_MS);
424
+ const requirement = emailRequirement(address, profile);
425
+ const requirementDigest = await credentialRequirementDigest(requirement);
426
+ const descriptor = await witnessDescriptor(profile, requirement, { fetch: input.fetch, signal }, "request");
427
+ const descriptorDigest = await canonicalDigest2(descriptor);
428
+ const verifier = randomVerifier();
429
+ const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true });
430
+ const createdAtMs = now().getTime();
431
+ let created;
432
+ try {
433
+ created = await transport.create({
434
+ descriptor,
435
+ descriptorDigest,
436
+ requirement,
437
+ requirementDigest,
438
+ holderDid: input.holder.did,
439
+ openerOrigin: OPENER_ORIGIN,
440
+ completionVerifierChallenge: await sha256Base64Url2(verifier),
441
+ signal
442
+ });
443
+ } catch (error) {
444
+ throw mapError(error, "request", signal);
445
+ }
446
+ const serverExpiry = Date.parse(created.expiresAt);
447
+ if (!Number.isFinite(serverExpiry)) throw new EmailProofError("request", "FAILED");
448
+ const pending = restoreEmailProofPending({
449
+ type: "TinyCloudEmailProofPending",
450
+ version: 1,
451
+ profile,
452
+ address,
453
+ requestId: created.requestId,
454
+ verifier,
455
+ holderDid: input.holder.did,
456
+ descriptorDigest,
457
+ requirementDigest,
458
+ openerOrigin: OPENER_ORIGIN,
459
+ createdAt: new Date(createdAtMs).toISOString(),
460
+ expiresAt: new Date(Math.min(serverExpiry, createdAtMs + EMAIL_PROOF_PENDING_MAX_MS)).toISOString()
461
+ });
462
+ try {
463
+ await interpretCredentialFlow({
464
+ descriptor,
465
+ requirement,
466
+ requestId: created.requestId,
467
+ verifier,
468
+ holderDid: input.holder.did,
469
+ descriptorDigest,
470
+ requirementDigest,
471
+ openerOrigin: OPENER_ORIGIN,
472
+ transport,
473
+ signing: holderSigning(input.holder),
474
+ handlers: { mailbox_otp: pauseForCode },
475
+ signal
476
+ });
477
+ } catch (error) {
478
+ if (error instanceof CodeRequested) return pending;
479
+ throw mapError(error, "request", signal);
480
+ }
481
+ throw new EmailProofError("request", "FAILED");
482
+ }
483
+ async function completeEmailProof(input) {
484
+ return completeWithIssuerKeys(input, EMAIL_PROOF_ISSUER_KEYS);
485
+ }
486
+ async function completeWithIssuerKeys(input, issuerKeys) {
487
+ const { pending } = input;
488
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
489
+ if (!isPending(pending)) throw new EmailProofError("session", "SESSION_INVALID");
490
+ if (pendingDeadlineMs(pending) <= now().getTime()) throw new EmailProofError("session", "SESSION_EXPIRED");
491
+ if (!/^\d{8}$/.test(input.code)) throw new EmailProofError("code", "CODE_INVALID");
492
+ assertHolder(input.holder, "code");
493
+ if (input.holder.did !== pending.holderDid) throw new EmailProofError("session", "SESSION_INVALID");
494
+ const signal = input.signal ?? AbortSignal.timeout(STEP_TIMEOUT_MS);
495
+ const requirement = emailRequirement(pending.address, pending.profile);
496
+ if (await credentialRequirementDigest(requirement) !== pending.requirementDigest) throw new EmailProofError("session", "SESSION_INVALID");
497
+ const descriptor = await witnessDescriptor(pending.profile, requirement, { fetch: input.fetch, signal }, "session");
498
+ const descriptorDigest = await canonicalDigest2(descriptor);
499
+ if (descriptorDigest !== pending.descriptorDigest) throw new EmailProofError("session", "SESSION_INVALID");
500
+ const transport = new OpenCredentialsHttpTransport(descriptor, input.fetch, { honorServerRecoverable: true });
501
+ try {
502
+ const state = await transport.state(pending.requestId, pending.verifier, signal);
503
+ if (state.nextStep?.constraints.challengeRequired === true) throw new EmailProofError("session", "SESSION_INVALID");
504
+ await interpretCredentialFlow({
505
+ descriptor,
506
+ requirement,
507
+ requestId: pending.requestId,
508
+ verifier: pending.verifier,
509
+ holderDid: pending.holderDid,
510
+ descriptorDigest,
511
+ requirementDigest: pending.requirementDigest,
512
+ openerOrigin: pending.openerOrigin,
513
+ transport,
514
+ signing: holderSigning(input.holder),
515
+ handlers: { mailbox_otp: async () => ({ otp: input.code }) },
516
+ signal
517
+ });
518
+ } catch (error) {
519
+ throw mapError(error, "code", signal);
520
+ }
521
+ try {
522
+ const envelope = await transport.result(pending.requestId, pending.verifier, signal);
523
+ const verified = await verifyIssuedCredential({
524
+ envelope,
525
+ descriptor,
526
+ descriptorDigest,
527
+ requirement,
528
+ holderDid: pending.holderDid,
529
+ issuerMetadata: pinnedIssuerMetadata(issuerKeys),
530
+ now: now(),
531
+ checkStatus: (status, statusSignal) => transport.checkStatus(status, statusSignal),
532
+ signal
533
+ });
534
+ return { envelope, verified, credentialId: verified.credentialId, issuedAt: verified.issuedAt, expiresAt: verified.expiresAt, descriptor };
535
+ } catch (error) {
536
+ throw mapError(error, "credential", signal);
537
+ }
538
+ }
539
+ function normalizeAddress(address) {
540
+ const value = address.trim();
541
+ if (value.length > 254 || !EMAIL_PATTERN.test(value)) throw new EmailProofError("request", "ADDRESS_INVALID");
542
+ return value;
543
+ }
544
+ function holderKid(did) {
545
+ return `${did}#${did.slice(HOLDER_DID_PREFIX.length)}`;
546
+ }
547
+ function assertHolder(holder, step) {
548
+ if (!HOLDER_DID_PATTERN.test(holder.did) || holder.alg !== "EdDSA" || holder.kid !== holderKid(holder.did)) throw new EmailProofError(step, "HOLDER_INVALID");
549
+ }
550
+ function profilePin(profile, step) {
551
+ const entry = Object.prototype.hasOwnProperty.call(PROFILE_PINS, profile) ? PROFILE_PINS[profile] : void 0;
552
+ if (!entry || !entry.enabled) throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
553
+ return entry;
554
+ }
555
+ function emailRequirement(address, profile) {
556
+ return createEmailCredentialRequirement({ email: address, profile: { id: profile, version: 1 }, credentialType: EMAIL_PROOF_CREDENTIAL_TYPE });
557
+ }
558
+ async function witnessDescriptor(profile, requirement, options, step) {
559
+ const pin = profilePin(profile, step);
560
+ let descriptor;
561
+ try {
562
+ descriptor = await discoverCatalogDescriptor(requirement, DISCOVERY_URL, { fetch: options.fetch, signal: options.signal });
563
+ } catch (error) {
564
+ throw discoveryFailure(error, options.signal, step);
565
+ }
566
+ if (descriptor.issuer.origin !== EMAIL_PROOF_PINS.witnessOrigin || descriptor.issuer.did !== EMAIL_PROOF_PINS.issuerDid || !descriptor.steps.some((candidate) => candidate.type === pin.requiredStep)) {
567
+ throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
568
+ }
569
+ if (await canonicalDigest2(descriptor) !== pin.descriptorDigest) throw new EmailProofError(step, "PROFILE_UNAVAILABLE");
570
+ return descriptor;
571
+ }
572
+ async function discoverCatalogDescriptor(requirement, discoveryUrl, options = {}) {
573
+ let url;
574
+ try {
575
+ url = new URL(discoveryUrl);
576
+ } catch {
577
+ throw new CredentialError3("UNSUPPORTED_PROFILE", "Discovery URL is invalid");
578
+ }
579
+ if (url.protocol !== "https:" || !["/.well-known/opencredentials", "/v1/credential-types"].includes(url.pathname) || url.search || url.hash) throw new CredentialError3("UNSUPPORTED_PROFILE", "Discovery URL is not an allowed catalog endpoint");
580
+ const init = { credentials: "omit", cache: "no-store", redirect: "error", referrerPolicy: "no-referrer", signal: options.signal };
581
+ let response;
582
+ try {
583
+ response = await (options.fetch ?? globalThis.fetch.bind(globalThis))(url, init);
584
+ } catch (cause) {
585
+ throw new CredentialError3("OFFLINE", "Credential discovery is unavailable", { cause });
586
+ }
587
+ if (!response.ok) throw new CredentialError3(response.status === 503 ? "ISSUER_UNREADY" : "OFFLINE", "Credential discovery is unavailable");
588
+ const catalog = await response.json();
589
+ if (catalog.type !== "tinycloud.credentials/catalog/v1" || catalog.protocol !== "tinycloud.credentials/acquisition/v1" || catalog.catalogVersion !== 1 || !Array.isArray(catalog.profiles)) throw new CredentialError3("DESCRIPTOR_INVALID", "Credential catalog is invalid");
590
+ for (const entryValue of catalog.profiles) {
591
+ if (typeof entryValue !== "object" || entryValue === null || Array.isArray(entryValue)) continue;
592
+ const entry = entryValue;
593
+ if (entry.supported !== true || entry.enabled !== true || entry.readiness !== "ready" && entry.readiness !== "degraded") continue;
594
+ try {
595
+ const candidate = validateCredentialFlowDescriptor(entry.descriptor);
596
+ if (descriptorSatisfiesRequirement(candidate, requirement) && entry.descriptorDigest === await canonicalDigest2(candidate)) return candidate;
597
+ } catch {
598
+ }
599
+ }
600
+ throw new CredentialError3("UNSUPPORTED_PROFILE", "No available credential profile satisfies the requirement");
601
+ }
602
+ function discoveryFailure(error, signal, step) {
603
+ if (signal.aborted && signal.reason?.name !== "TimeoutError") return new EmailProofError(step, "CANCELED");
604
+ return mapError(error, step, signal);
605
+ }
606
+ function pinnedIssuerMetadata(keys) {
607
+ return { type: "OpenCredentialsIssuerMetadata", version: 1, origin: EMAIL_PROOF_PINS.witnessOrigin, issuerDid: EMAIL_PROOF_PINS.issuerDid, keys, cache: { maxAgeSeconds: 3600, etag: "pinned" } };
608
+ }
609
+ function holderSigning(holder) {
610
+ return { autoSign: async (_binding, bytes) => holder.sign(bytes), holderKey: { alg: "EdDSA", kid: holder.kid } };
611
+ }
612
+ function randomVerifier() {
613
+ return encodeBase64Url3(crypto.getRandomValues(new Uint8Array(32)));
614
+ }
615
+ function isPending(value) {
616
+ if (typeof value !== "object" || value === null) return false;
617
+ const pending = value;
618
+ return pending.type === "TinyCloudEmailProofPending" && pending.version === 1 && pending.openerOrigin === OPENER_ORIGIN && [pending.profile, pending.address, pending.requestId, pending.verifier, pending.holderDid, pending.descriptorDigest, pending.requirementDigest, pending.openerOrigin, pending.createdAt, pending.expiresAt].every((field) => typeof field === "string") && Number.isFinite(Date.parse(pending.createdAt)) && Number.isFinite(Date.parse(pending.expiresAt));
619
+ }
620
+ function mapError(value, step, signal) {
621
+ if (value instanceof EmailProofError) return value;
622
+ if (value instanceof DOMException && value.name === "TimeoutError") return new EmailProofError(step, "NETWORK");
623
+ const error = credentialError(value);
624
+ const details = error.details;
625
+ switch (error.code) {
626
+ case "ISSUER_UNREADY":
627
+ return details.state === "rate_limited" ? new EmailProofError(step, "RATE_LIMITED", details.retryAfterMs, error.recoverable) : new EmailProofError(step, "ISSUER_UNAVAILABLE", void 0, error.recoverable);
628
+ case "OFFLINE":
629
+ if (signal?.aborted && signal.reason?.name !== "TimeoutError") return new EmailProofError(step, "CANCELED");
630
+ return new EmailProofError(step, "NETWORK");
631
+ case "UNSUPPORTED_PROFILE":
632
+ case "DESCRIPTOR_INVALID":
633
+ return new EmailProofError(step, "PROFILE_UNAVAILABLE");
634
+ case "PROOF_REJECTED":
635
+ return new EmailProofError("code", "CODE_REJECTED", void 0, true);
636
+ case "VERIFICATION_FAILED":
637
+ if (details.state === "proof_attempts_exhausted") return new EmailProofError("code", "CODE_ATTEMPTS_EXHAUSTED");
638
+ if (step === "credential") return new EmailProofError("credential", "CREDENTIAL_INVALID");
639
+ if (step === "code") return new EmailProofError("code", "CODE_REJECTED", void 0, false);
640
+ return new EmailProofError(step, "FAILED");
641
+ case "HOLDER_MISMATCH":
642
+ return new EmailProofError("credential", "CREDENTIAL_INVALID");
643
+ case "REQUEST_EXPIRED":
644
+ return new EmailProofError("session", "SESSION_EXPIRED");
645
+ case "REQUEST_SUBSTITUTED":
646
+ return new EmailProofError("session", "SESSION_INVALID");
647
+ case "SIGNATURE_REJECTED":
648
+ return new EmailProofError("credential", "SIGNATURE_REJECTED", void 0, error.recoverable);
649
+ case "CANCELED":
650
+ return new EmailProofError(step, "CANCELED");
651
+ default:
652
+ return new EmailProofError(step, "FAILED");
653
+ }
654
+ }
655
+
656
+ // src/share-receive-access.ts
657
+ import { admitPolicyCredentialV4, credentialMatchesRequirement, verifyOwnerNodeBinding } from "@tinycloud/sdk-core";
658
+ import { ShareReceiveError, ShareRecipientClient } from "@tinycloud/share-sdk";
659
+ async function openVerifiedAgentShare(pending, result, requirement, options) {
660
+ const { envelope } = pending;
661
+ if (!credentialMatchesRequirement(requirement, result.verified, options.holder.did, new Date(options.now?.() ?? Date.now()))) {
662
+ throw new ShareReceiveError("authorization-denied", "email credential does not match this share");
663
+ }
664
+ if (envelope.target.nodeAudience !== envelope.attestedEnforcerBinding.nodeAudience) {
665
+ throw new ShareReceiveError("envelope-invalid", "share Node binding is invalid");
666
+ }
667
+ try {
668
+ await verifyOwnerNodeBinding({
669
+ registryUrl: options.registryOrigin,
670
+ ownerDid: envelope.policy.ownerDid,
671
+ nodeOrigin: envelope.target.origin,
672
+ nodeDid: envelope.target.nodeAudience,
673
+ fetch: options.fetch,
674
+ signal: options.signal
675
+ });
676
+ } catch {
677
+ throw new ShareReceiveError("authorization-denied", "share owner Node binding could not be verified");
678
+ }
679
+ let admitted;
680
+ try {
681
+ admitted = await admitPolicyCredentialV4({
682
+ policy: envelope.policy,
683
+ policyCid: envelope.policyCid,
684
+ policyRootCid: envelope.policyRoot.cid,
685
+ enforcementRootCid: envelope.enforcementRoot.cid,
686
+ expectedNodeAudience: envelope.target.nodeAudience,
687
+ expectedEnforcerDid: envelope.attestedEnforcerBinding.enforcerDid,
688
+ requirement,
689
+ credential: result.verified,
690
+ requestedCapabilities: envelope.policy.capabilityCeiling,
691
+ requestedExpiresAt: envelope.expiry,
692
+ sign: options.holder.sign,
693
+ nodeOrigin: envelope.target.origin,
694
+ fetch: options.fetch,
695
+ signal: options.signal
696
+ });
697
+ } catch {
698
+ throw new ShareReceiveError("authorization-denied", "share node refused the email credential");
699
+ }
700
+ const client = new ShareRecipientClient({
701
+ nodeOrigin: envelope.target.origin,
702
+ holderDid: options.holder.did,
703
+ envelope,
704
+ policyAuthorization: { authorization: admitted.session.authorization, cid: admitted.session.cid },
705
+ sign: options.holder.sign,
706
+ fetchFn: options.fetch,
707
+ signal: options.signal
708
+ });
709
+ let opened;
710
+ try {
711
+ const response = await client.nativeInvoke({ action: "get", resource: envelope.resource });
712
+ if (!response.ok) throw new ShareReceiveError("authorization-denied", "share node refused the read");
713
+ opened = await client.decryptV3Content(new Uint8Array(await response.arrayBuffer()));
714
+ } catch (error) {
715
+ if (error instanceof ShareReceiveError) throw error;
716
+ throw new ShareReceiveError("decrypt-failed", "share content could not be opened");
717
+ }
718
+ if (Date.parse(envelope.expiry) <= (options.now?.() ?? Date.now())) throw new ShareReceiveError("expired", "share has expired");
719
+ let text;
720
+ if (/^(text\/|application\/(?:json|xml|javascript)(?:;|$))/.test(opened.mediaType)) {
721
+ try {
722
+ text = new TextDecoder("utf-8", { fatal: true }).decode(opened.bytes);
723
+ } catch {
724
+ }
725
+ }
726
+ return { metadata: pending.metadata, bytes: opened.bytes, ...text === void 0 ? {} : { text }, mediaType: opened.mediaType, untrusted: true };
727
+ }
728
+
729
+ // src/share-receive.ts
730
+ function requirementFor(envelope) {
731
+ if (envelope.recipientMatcher.kind !== "exactEmail" || envelope.policy.schema !== "xyz.tinycloud.policy/policy/v2") {
732
+ throw new ShareReceiveError2("unsupported-target", "this receive path accepts exact-email shares only");
733
+ }
734
+ const policy = envelope.policy;
735
+ return createEmailCredentialRequirement2({
736
+ email: envelope.recipientMatcher.value,
737
+ profile: policy.credentialRequirement.profile,
738
+ credentialType: policy.credentialRequirement.credentialType
739
+ });
740
+ }
741
+ function active(envelope, now) {
742
+ if (Date.parse(envelope.expiry) <= now) throw new ShareReceiveError2("expired", "share has expired");
743
+ }
744
+ async function requestAgentShareEmailProof(link, options) {
745
+ let envelope;
746
+ const metadata = (await inspectShare(link, {
747
+ ...options,
748
+ onResolvedAddressedEnvelope: (value) => {
749
+ if (value.version === 3) envelope = value;
750
+ }
751
+ })).metadata;
752
+ if (envelope === void 0) throw new ShareReceiveError2("envelope-invalid", "share envelope is invalid");
753
+ const requirement = requirementFor(envelope);
754
+ if (await credentialRequirementDigest2(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
755
+ throw new ShareReceiveError2("envelope-invalid", "share credential requirement is invalid");
756
+ }
757
+ const proof = await requestEmailProofCode({ address: requirement.claims.email, holder: options.holder, fetch: options.fetch, signal: options.signal });
758
+ return { metadata, envelope, proof };
759
+ }
760
+ async function completeAgentShareReceive(pending, code, options) {
761
+ const { envelope } = pending;
762
+ active(envelope, options.now?.() ?? Date.now());
763
+ const requirement = requirementFor(envelope);
764
+ if (await credentialRequirementDigest2(requirement) !== envelope.policy.credentialRequirement.requirementDigest) {
765
+ throw new ShareReceiveError2("envelope-invalid", "share credential requirement is invalid");
766
+ }
767
+ if (pending.proof.address !== requirement.claims.email || pending.proof.holderDid !== options.holder.did) {
768
+ throw new ShareReceiveError2("authorization-denied", "email proof does not belong to this recipient session");
769
+ }
770
+ const result = await completeEmailProof({ pending: pending.proof, code, holder: options.holder, fetch: options.fetch, signal: options.signal });
771
+ return openVerifiedAgentShare(pending, result, requirement, options);
772
+ }
773
+ export {
774
+ completeAgentShareReceive,
775
+ requestAgentShareEmailProof
776
+ };
777
+ //# sourceMappingURL=share-receive.js.map