@tinycloud/node-sdk 3.6.2-beta.0 → 3.7.0-beta.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/index.cjs CHANGED
@@ -21713,17 +21713,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21713
21713
  throw new Error("WASM binding does not support invokeAny");
21714
21714
  }
21715
21715
  const routedEntries = entries;
21716
- const operations = routedEntries.flatMap((entry) => {
21717
- const operation = this.operationFromInvokeAnyEntry(entry);
21718
- return operation ? [operation] : [];
21719
- });
21720
- const authorityCid = routedEntries.length === 1 ? routedEntries[0]?.revokeAuthorityCid : void 0;
21721
- const explicitGrant = authorityCid === void 0 ? void 0 : this.runtimePermissionGrants.find((grant2) => grant2.delegation.cid === authorityCid);
21722
- if (authorityCid !== void 0 && !explicitGrant) {
21723
- throw new Error(`Explicit revocation authority ${authorityCid} is not installed.`);
21724
- }
21725
- const grant = explicitGrant ?? this.findGrantForOperations(operations);
21726
- const invocationSession = explicitGrant ? explicitGrant.session : !grant || grant.provenance === "primary" ? session : grant.session;
21716
+ const { session: invocationSession, grant } = this.selectInvokeAnySession(session, entries);
21727
21717
  const caveatPreservingEntries = routedEntries.map(({ revokeAuthorityCid: _cid, ...entry }) => {
21728
21718
  const requested = this.operationFromInvokeAnyEntry(entry);
21729
21719
  const granted = grant && requested && grant.operations.find(
@@ -21961,6 +21951,27 @@ var _TinyCloudNode = class _TinyCloudNode {
21961
21951
  get siweDomain() {
21962
21952
  return this.config.domain ?? "app.tinycloud.xyz";
21963
21953
  }
21954
+ /**
21955
+ * The session (key, issuer, proof) that signs an invokeAny call for `entries`:
21956
+ * the explicit revocation authority, else the covering non-primary runtime
21957
+ * grant, else the caller's session. Everything that re-signs the resulting
21958
+ * invocation must use this session's key.
21959
+ */
21960
+ selectInvokeAnySession(session, entries) {
21961
+ const routedEntries = entries;
21962
+ const operations = routedEntries.flatMap((entry) => {
21963
+ const operation = this.operationFromInvokeAnyEntry(entry);
21964
+ return operation ? [operation] : [];
21965
+ });
21966
+ const authorityCid = routedEntries.length === 1 ? routedEntries[0]?.revokeAuthorityCid : void 0;
21967
+ const explicitGrant = authorityCid === void 0 ? void 0 : this.runtimePermissionGrants.find((grant2) => grant2.delegation.cid === authorityCid);
21968
+ if (authorityCid !== void 0 && !explicitGrant) {
21969
+ throw new Error(`Explicit revocation authority ${authorityCid} is not installed.`);
21970
+ }
21971
+ const grant = explicitGrant ?? this.findGrantForOperations(operations);
21972
+ const invocationSession = explicitGrant ? explicitGrant.session : !grant || grant.provenance === "primary" ? session : grant.session;
21973
+ return { session: invocationSession, grant };
21974
+ }
21964
21975
  /**
21965
21976
  * SharingService for receive-only access (no session required), bound to the
21966
21977
  * current service graph. This lets session-only users receive sharing links
@@ -24380,7 +24391,13 @@ var _TinyCloudNode = class _TinyCloudNode {
24380
24391
  async bindInvocationAudience(authorization, audience) {
24381
24392
  const session = this.currentTinyCloudSession() ?? this._activeServiceSession;
24382
24393
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
24383
- return rewriteInvocationAudience(authorization, audience, session.jwk);
24394
+ const payload = JSON.parse(
24395
+ new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1] ?? ""))
24396
+ );
24397
+ const did = (value) => String(value).split("#", 1)[0];
24398
+ const signer = [session, ...this.runtimePermissionGrants.map((grant) => grant.session)].find((candidate) => did(candidate.verificationMethod) === did(payload.iss));
24399
+ if (!signer) throw new Error("invocation issuer does not match a held session");
24400
+ return rewriteInvocationAudience(authorization, audience, signer.jwk);
24384
24401
  }
24385
24402
  /**
24386
24403
  * Connect a wallet to upgrade from session-only mode to wallet mode.
@@ -24715,23 +24732,25 @@ var _TinyCloudNode = class _TinyCloudNode {
24715
24732
  throw new Error("WASM binding does not support invocation CID computation");
24716
24733
  }
24717
24734
  const session = binding?.session ?? this.requireEncryptionSession();
24735
+ const entries = [
24736
+ {
24737
+ resource: input.networkId,
24738
+ service: "encryption",
24739
+ path: input.networkId,
24740
+ action: input.action
24741
+ }
24742
+ ];
24718
24743
  const headers = this.invokeAnyWithRuntimePermissions(
24719
24744
  session,
24720
- [
24721
- {
24722
- resource: input.networkId,
24723
- service: "encryption",
24724
- path: input.networkId,
24725
- action: input.action
24726
- }
24727
- ],
24745
+ entries,
24728
24746
  [input.facts]
24729
24747
  );
24748
+ const signingSession = this.selectInvokeAnySession(session, entries).session;
24730
24749
  const authorization = authorizationHeader(headers);
24731
24750
  const audienceBound = await rewriteInvocationAudience(
24732
24751
  authorization,
24733
24752
  input.targetNode,
24734
- session.jwk
24753
+ signingSession.jwk
24735
24754
  );
24736
24755
  binding?.graph.assertActive();
24737
24756
  return {
@@ -24784,7 +24803,11 @@ var _TinyCloudNode = class _TinyCloudNode {
24784
24803
  path: networkId,
24785
24804
  actions: ["tinycloud.encryption/decrypt"]
24786
24805
  }) : void 0;
24787
- throw new import_sdk_core10.DecryptTransportResponseError(response.status, permissionHint);
24806
+ throw new import_sdk_core10.DecryptTransportResponseError(
24807
+ response.status,
24808
+ permissionHint,
24809
+ response.status === 404 && networkId === this.getDefaultEncryptionNetworkId() && await response.text().catch(() => "") === "network not found" ? `Network not found: ${networkId}. Sign in again with a current SDK to create the account's encryption network.` : void 0
24810
+ );
24788
24811
  }
24789
24812
  return await response.json();
24790
24813
  }
@@ -25249,8 +25272,14 @@ var _TinyCloudNode = class _TinyCloudNode {
25249
25272
  fetch: globalThis.fetch.bind(globalThis)
25250
25273
  });
25251
25274
  }
25252
- /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
25275
+ /**
25276
+ * Authorize one short-lived v3 delivery. `shareUrl` is the short `/s/<id>#v=4&k=…&c=…` link: the node fetches the
25277
+ * sealed body by id and opens it with the link's key, so the body of the request carries the link and nothing else
25278
+ * of the share. `envelope` is the signed v3 envelope the sender holds; it checks the node's admission here and is
25279
+ * not sent.
25280
+ */
25253
25281
  async authorizeShareDeliveryV3(input) {
25282
+ const envelopeRef = await (0, import_sdk_core10.shortDeliveryLinkBodyCid)(input.shareUrl, input.trustedOrigins);
25254
25283
  const session = this.currentTinyCloudSession();
25255
25284
  const serviceSession = this._serviceContext?.session;
25256
25285
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
@@ -25260,15 +25289,12 @@ var _TinyCloudNode = class _TinyCloudNode {
25260
25289
  }
25261
25290
  const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
25262
25291
  const body = {
25263
- envelope: input.envelope,
25264
- sealedEnvelope: input.sealedEnvelope,
25265
- envelopeKey: input.envelopeKey,
25266
- shareCid: input.shareCid,
25267
25292
  recipientEmail: input.recipientEmail,
25268
25293
  shareUrl: input.shareUrl,
25269
25294
  documentName: input.documentName,
25270
25295
  jti: base64UrlEncode(jtiDigest.slice(0, 16)),
25271
- expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
25296
+ // Re-normalised through Date, so the sent value is always `YYYY-MM-DDTHH:mm:ss.SSSZ` (UTC, three digits).
25297
+ expiresAt: new Date(input.expiresAt).toISOString()
25272
25298
  };
25273
25299
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core10.canonicalizeEncryptionJson)(body)))));
25274
25300
  const request = { ...body, requestBodyDigest };
@@ -25278,9 +25304,16 @@ var _TinyCloudNode = class _TinyCloudNode {
25278
25304
  headers: { accept: "application/json", "content-type": "application/json", authorization },
25279
25305
  body: (0, import_sdk_core10.canonicalizeEncryptionJson)(request)
25280
25306
  });
25281
- if (!response.ok) throw await (0, import_sdk_core10.httpResponseError)(response, "V3 share delivery authorization failed");
25282
- const verified = (0, import_sdk_core10.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
25307
+ if (response.status === 400 || response.status === 422) throw new import_sdk_core10.ShareDeliveryRefusedError();
25308
+ if (!response.ok) {
25309
+ const reply = await response.text().catch(() => response.statusText);
25310
+ const body2 = (await (0, import_sdk_core10.withoutShareLink)(reply, input.shareUrl)).trim().slice(0, 512);
25311
+ throw Object.assign(new Error(`V3 share delivery authorization failed: HTTP ${response.status}${body2 ? ` - ${body2}` : ""}`), { status: response.status });
25312
+ }
25313
+ const verified = await (0, import_sdk_core10.verifyShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
25283
25314
  request,
25315
+ envelope: input.envelope,
25316
+ envelopeRef,
25284
25317
  senderKeyDid: this.credentialHolderDid,
25285
25318
  deliveryAudience: input.deliveryAudience
25286
25319
  });