@tinycloud/share-sdk 1.0.1-beta.0 → 1.1.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 +93 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +92 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -78,6 +78,7 @@ __export(index_exports, {
|
|
|
78
78
|
restoreDelegatedShareKey: () => restoreDelegatedShareKey,
|
|
79
79
|
resumeShareAuthorization: () => resumeShareAuthorization,
|
|
80
80
|
revokeShare: () => revokeShare,
|
|
81
|
+
shareDeliveryWindowExpiresAt: () => shareDeliveryWindowExpiresAt,
|
|
81
82
|
showShare: () => showShare,
|
|
82
83
|
targetAuthorizationMethod: () => targetAuthorizationMethod,
|
|
83
84
|
toBase64Url: () => toBase64Url,
|
|
@@ -7783,8 +7784,25 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
|
|
|
7783
7784
|
}
|
|
7784
7785
|
var MemorySenderShareRecordStorage = class {
|
|
7785
7786
|
records = /* @__PURE__ */ new Map();
|
|
7787
|
+
operation = Promise.resolve();
|
|
7788
|
+
serialize(action) {
|
|
7789
|
+
const next = this.operation.then(action, action);
|
|
7790
|
+
this.operation = next.then(() => void 0, () => void 0);
|
|
7791
|
+
return next;
|
|
7792
|
+
}
|
|
7786
7793
|
async put(record) {
|
|
7787
|
-
this.
|
|
7794
|
+
return this.serialize(async () => {
|
|
7795
|
+
this.records.set(record.shareId, record);
|
|
7796
|
+
});
|
|
7797
|
+
}
|
|
7798
|
+
async update(shareId, change) {
|
|
7799
|
+
return this.serialize(async () => {
|
|
7800
|
+
const current = this.records.get(shareId);
|
|
7801
|
+
if (current === void 0) return void 0;
|
|
7802
|
+
const updated = await change(current);
|
|
7803
|
+
this.records.set(shareId, updated);
|
|
7804
|
+
return updated;
|
|
7805
|
+
});
|
|
7788
7806
|
}
|
|
7789
7807
|
async list() {
|
|
7790
7808
|
return [...this.records.values()];
|
|
@@ -7793,7 +7811,9 @@ var MemorySenderShareRecordStorage = class {
|
|
|
7793
7811
|
return this.records.get(shareId);
|
|
7794
7812
|
}
|
|
7795
7813
|
async delete(shareId) {
|
|
7796
|
-
this.
|
|
7814
|
+
return this.serialize(async () => {
|
|
7815
|
+
this.records.delete(shareId);
|
|
7816
|
+
});
|
|
7797
7817
|
}
|
|
7798
7818
|
};
|
|
7799
7819
|
var MemorySenderShareKeyStorage = class {
|
|
@@ -7877,6 +7897,11 @@ var SenderShareStore = class {
|
|
|
7877
7897
|
}
|
|
7878
7898
|
/** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
|
|
7879
7899
|
async markRevoked(shareId, revokedAt = this.now().toISOString()) {
|
|
7900
|
+
if (this.records.update !== void 0) {
|
|
7901
|
+
const updated = await this.records.update(shareId, (record2) => ({ ...record2, revokedAt }));
|
|
7902
|
+
if (updated === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7903
|
+
return updated;
|
|
7904
|
+
}
|
|
7880
7905
|
const record = await this.records.get(shareId);
|
|
7881
7906
|
if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
|
|
7882
7907
|
const revoked = { ...record, revokedAt };
|
|
@@ -7924,11 +7949,12 @@ var EncryptedSenderShareHistory = class {
|
|
|
7924
7949
|
|
|
7925
7950
|
// src/notify.ts
|
|
7926
7951
|
var ShareNotifyError = class extends Error {
|
|
7927
|
-
|
|
7928
|
-
constructor(message = "share delivery did not complete") {
|
|
7952
|
+
constructor(message = "share delivery did not complete", reason) {
|
|
7929
7953
|
super(message);
|
|
7954
|
+
this.reason = reason;
|
|
7930
7955
|
this.name = "ShareNotifyError";
|
|
7931
7956
|
}
|
|
7957
|
+
code = "delivery-failed";
|
|
7932
7958
|
};
|
|
7933
7959
|
function recipientMatchesShareRecord(record, recipient) {
|
|
7934
7960
|
const matcher = record.recipientMatcher;
|
|
@@ -7938,6 +7964,11 @@ function recipientMatchesShareRecord(record, recipient) {
|
|
|
7938
7964
|
if (matcher.kind === "emailDomain") return mailbox.domain === matcher.value;
|
|
7939
7965
|
return false;
|
|
7940
7966
|
}
|
|
7967
|
+
function shareDeliveryWindowExpiresAt(record) {
|
|
7968
|
+
const expiresAt = Math.min(Date.parse(record.expiresAt), Date.parse(record.registeredAt) + 5 * 60 * 1e3);
|
|
7969
|
+
if (!Number.isFinite(expiresAt)) throw new ShareNotifyError("share delivery history has invalid timestamps");
|
|
7970
|
+
return expiresAt;
|
|
7971
|
+
}
|
|
7941
7972
|
async function notifyShare(input) {
|
|
7942
7973
|
const mailbox = canonicalMailbox(input.recipient);
|
|
7943
7974
|
if (!input.shareId || mailbox === void 0) throw new ShareNotifyError("recipient is invalid");
|
|
@@ -7948,19 +7979,59 @@ async function notifyShare(input) {
|
|
|
7948
7979
|
const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId, recipient);
|
|
7949
7980
|
const attemptsLimit = input.maxAttempts ?? 3;
|
|
7950
7981
|
if (!Number.isSafeInteger(attemptsLimit) || attemptsLimit < 1 || attemptsLimit > 8) throw new ShareNotifyError("maxAttempts is invalid");
|
|
7982
|
+
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
7983
|
+
if (input.checkDeliveryWindow && input.record !== void 0 && shareDeliveryWindowExpiresAt(input.record) <= Date.now()) {
|
|
7984
|
+
return {
|
|
7985
|
+
protocol: "tinycloud-share",
|
|
7986
|
+
version: 1,
|
|
7987
|
+
shareId: input.shareId,
|
|
7988
|
+
state: "partial-failure",
|
|
7989
|
+
idempotencyKey,
|
|
7990
|
+
attempts: 1,
|
|
7991
|
+
retryable: false,
|
|
7992
|
+
reason: "delivery-window-expired"
|
|
7993
|
+
};
|
|
7994
|
+
}
|
|
7951
7995
|
let attempts = 0;
|
|
7952
|
-
let lastError;
|
|
7953
7996
|
while (attempts < attemptsLimit) {
|
|
7954
7997
|
if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
|
|
7955
7998
|
attempts += 1;
|
|
7956
7999
|
try {
|
|
7957
8000
|
const state = await input.adapter.deliver({ shareId: input.shareId, recipient, idempotencyKey, ...input.record === void 0 ? {} : { record: input.record }, ...input.signal === void 0 ? {} : { signal: input.signal } });
|
|
7958
|
-
return {
|
|
8001
|
+
return {
|
|
8002
|
+
protocol: "tinycloud-share",
|
|
8003
|
+
version: 1,
|
|
8004
|
+
shareId: input.shareId,
|
|
8005
|
+
state: state === "delivered" && input.record?.deliveredRecipients?.includes(recipient) ? "already-delivered" : state,
|
|
8006
|
+
idempotencyKey,
|
|
8007
|
+
attempts
|
|
8008
|
+
};
|
|
7959
8009
|
} catch (error) {
|
|
7960
|
-
|
|
8010
|
+
if (error instanceof ShareNotifyError && error.reason === "delivery-window-expired") {
|
|
8011
|
+
return {
|
|
8012
|
+
protocol: "tinycloud-share",
|
|
8013
|
+
version: 1,
|
|
8014
|
+
shareId: input.shareId,
|
|
8015
|
+
state: "partial-failure",
|
|
8016
|
+
idempotencyKey,
|
|
8017
|
+
attempts,
|
|
8018
|
+
retryable: false,
|
|
8019
|
+
reason: "delivery-window-expired"
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
if (error !== null && typeof error === "object" && "status" in error && (error.status === 401 || error.status === 403)) {
|
|
8023
|
+
return {
|
|
8024
|
+
protocol: "tinycloud-share",
|
|
8025
|
+
version: 1,
|
|
8026
|
+
shareId: input.shareId,
|
|
8027
|
+
state: "partial-failure",
|
|
8028
|
+
idempotencyKey,
|
|
8029
|
+
attempts,
|
|
8030
|
+
retryable: false
|
|
8031
|
+
};
|
|
8032
|
+
}
|
|
7961
8033
|
}
|
|
7962
8034
|
}
|
|
7963
|
-
void lastError;
|
|
7964
8035
|
return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state: "partial-failure", idempotencyKey, attempts, retryable: true };
|
|
7965
8036
|
}
|
|
7966
8037
|
async function defaultIdempotencyKey(shareId, recipient) {
|
|
@@ -8119,7 +8190,11 @@ async function revokeShare(input) {
|
|
|
8119
8190
|
});
|
|
8120
8191
|
}
|
|
8121
8192
|
const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
8122
|
-
if (input.records !== void 0)
|
|
8193
|
+
if (input.records?.update !== void 0) {
|
|
8194
|
+
await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt }));
|
|
8195
|
+
} else if (input.records !== void 0) {
|
|
8196
|
+
await input.records.put({ ...input.record, revokedAt });
|
|
8197
|
+
}
|
|
8123
8198
|
return { state: "revoked", target, delegationCid, revokedAt };
|
|
8124
8199
|
}
|
|
8125
8200
|
function redactRecord(record, revealLink, link) {
|
|
@@ -8382,6 +8457,10 @@ function parseV3InlineEncryptedEnvelope(bytes3, expected) {
|
|
|
8382
8457
|
}
|
|
8383
8458
|
return record;
|
|
8384
8459
|
}
|
|
8460
|
+
async function recipientHttpError(response, context) {
|
|
8461
|
+
const body = (await response.text().catch(() => "")).trim().slice(0, 512);
|
|
8462
|
+
return Object.assign(new Error(`${context} (${response.status})${body ? `: ${body}` : ""}`), { status: response.status });
|
|
8463
|
+
}
|
|
8385
8464
|
var ShareRecipientClient = class {
|
|
8386
8465
|
constructor(options) {
|
|
8387
8466
|
this.options = options;
|
|
@@ -8455,7 +8534,7 @@ var ShareRecipientClient = class {
|
|
|
8455
8534
|
body: JSON.stringify({ policyCid: envelope.policyCid, recipientDid: this.options.holderDid, requestedCapabilities: envelope.policy.capabilityCeiling }),
|
|
8456
8535
|
...this.options.signal === void 0 ? {} : { signal: this.options.signal }
|
|
8457
8536
|
});
|
|
8458
|
-
if (!challengeResponse.ok) throw
|
|
8537
|
+
if (!challengeResponse.ok) throw await recipientHttpError(challengeResponse, "v3 policy challenge rejected");
|
|
8459
8538
|
const challenge = object3(await challengeResponse.json(), "v3 policy challenge");
|
|
8460
8539
|
if (typeof challenge.challengeId !== "string" || typeof challenge.nonce !== "string" || challenge.policyCid !== envelope.policyCid || challenge.recipientDid !== this.options.holderDid || challenge.nodeAudience !== attestedEnforcer.nodeAudience || challenge.enforcerDid !== void 0 && challenge.enforcerDid !== attestedEnforcer.enforcerDid || typeof challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(challenge.expiresAt)) || Date.parse(challenge.expiresAt) <= Date.now()) throw new Error("v3 policy challenge binding mismatch");
|
|
8461
8540
|
const material = await this.options.buildPresentation({
|
|
@@ -8480,14 +8559,14 @@ var ShareRecipientClient = class {
|
|
|
8480
8559
|
const requestedExpiresAt = requestedSessionExpiry(envelope.expiry);
|
|
8481
8560
|
let delegationResponse = await mint(requestedExpiresAt === void 0 ? mintBody : { ...mintBody, requestedExpiresAt });
|
|
8482
8561
|
if (requestedExpiresAt !== void 0 && delegationResponse.status === 422) delegationResponse = await mint(mintBody);
|
|
8483
|
-
if (!delegationResponse.ok) throw
|
|
8562
|
+
if (!delegationResponse.ok) throw await recipientHttpError(delegationResponse, "v3 policy delegation rejected");
|
|
8484
8563
|
const delegation = object3(await delegationResponse.json(), "v3 policy delegation");
|
|
8485
8564
|
if (delegation.admitted !== true || typeof delegation.sessionCid !== "string" || typeof delegation.authorization !== "string") throw new Error("v3 policy delegation response is not admitted");
|
|
8486
8565
|
const compact = verifyV3PolicyAuthorization({ authorization: delegation.authorization, cid: delegation.sessionCid, envelope, holderDid: this.options.holderDid });
|
|
8487
8566
|
const fact = compact.payload.fct[0];
|
|
8488
8567
|
this.options.onStage?.("delegation-import");
|
|
8489
8568
|
const imported = await this.fetchFn(new URL("/delegate", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { Authorization: delegation.authorization }, ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
8490
|
-
if (!imported.ok) throw
|
|
8569
|
+
if (!imported.ok) throw await recipientHttpError(imported, "ordinary delegation import rejected");
|
|
8491
8570
|
this.signer = material.sign;
|
|
8492
8571
|
this.v3Authorization = delegation.authorization;
|
|
8493
8572
|
this.v3NodeAudience = fact.nodeAudience;
|
|
@@ -8522,7 +8601,7 @@ var ShareRecipientClient = class {
|
|
|
8522
8601
|
try {
|
|
8523
8602
|
this.options.onStage?.("decryption");
|
|
8524
8603
|
const response = await this.fetchFn(new URL("/invoke", this.options.nodeOrigin), { method: "POST", redirect: "error", headers: { accept: "application/json", "content-type": "application/json", Authorization: invocation.authorization }, body: JSON.stringify(body), ...this.options.signal === void 0 ? {} : { signal: this.options.signal } });
|
|
8525
|
-
if (!response.ok) throw
|
|
8604
|
+
if (!response.ok) throw await recipientHttpError(response, "v3 decrypt invocation rejected");
|
|
8526
8605
|
const value = object3(await response.json(), "v3 decrypt response");
|
|
8527
8606
|
const allowed = ["type", "targetNode", "networkId", "invocationCid", "encryptedSymmetricKeyHash", "receiverPublicKeyHash", "wrappedKey", "alg", "keyVersion", "requestHash", "nodeId", "nodeSignature"];
|
|
8528
8607
|
if (Object.keys(value).length !== allowed.length || Object.keys(value).some((key) => !allowed.includes(key)) || value.type !== "tinycloud.encryption.decrypt-result/v1" || value.targetNode !== body.targetNode || value.nodeId !== body.targetNode || value.networkId !== body.networkId || value.invocationCid !== invocation.cid || value.encryptedSymmetricKeyHash !== body.encryptedSymmetricKeyHash || value.receiverPublicKeyHash !== receiverPublicKeyHash || value.alg !== body.alg || value.keyVersion !== body.keyVersion || value.requestHash !== hex3((0, import_sha2565.sha256)(new TextEncoder().encode(`${invocation.cid}${bodyHash}`))) || typeof value.wrappedKey !== "string" || typeof value.nodeSignature !== "string") throw new Error("v3 decrypt response binding is invalid");
|
|
@@ -8746,6 +8825,7 @@ async function openNativeShare(sharing, link) {
|
|
|
8746
8825
|
restoreDelegatedShareKey,
|
|
8747
8826
|
resumeShareAuthorization,
|
|
8748
8827
|
revokeShare,
|
|
8828
|
+
shareDeliveryWindowExpiresAt,
|
|
8749
8829
|
showShare,
|
|
8750
8830
|
targetAuthorizationMethod,
|
|
8751
8831
|
toBase64Url,
|