@tinycloud/share-sdk 1.0.0 → 1.1.0-beta.1

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
@@ -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,
@@ -5280,11 +5281,15 @@ var contentMetadataSchema = external_exports.object({
5280
5281
  /** Encrypted presentation discriminator. The fixed entry point is index.html. */
5281
5282
  artifact: external_exports.literal("html").optional()
5282
5283
  }).strict();
5284
+ var deliveryEmailSchema = external_exports.string().email();
5285
+ function isEnvelopeDeliveryEmail(value) {
5286
+ return deliveryEmailSchema.safeParse(value).success;
5287
+ }
5283
5288
  var unsignedShareEnvelopeV2BaseSchema = external_exports.object({
5284
5289
  version: external_exports.literal(2),
5285
5290
  shareId: external_exports.string().min(1),
5286
5291
  recipientMatcher: recipientMatcherSchema,
5287
- deliveryEmail: external_exports.string().email().optional(),
5292
+ deliveryEmail: deliveryEmailSchema.optional(),
5288
5293
  actions: external_exports.array(shareActionSchema).min(1).max(3),
5289
5294
  resource: resourceSelectorSchema,
5290
5295
  target: v2TargetSchema,
@@ -5424,7 +5429,7 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5424
5429
  version: external_exports.literal(3),
5425
5430
  shareId: external_exports.string().min(1),
5426
5431
  recipientMatcher: recipientMatcherSchema,
5427
- deliveryEmail: external_exports.string().email().optional(),
5432
+ deliveryEmail: deliveryEmailSchema.optional(),
5428
5433
  actions: external_exports.array(shareActionSchema).min(1).max(3),
5429
5434
  resource: resourceSelectorSchema,
5430
5435
  target: v3TargetSchema,
@@ -6797,6 +6802,7 @@ function prepareAddressedShare(request) {
6797
6802
  }
6798
6803
  if (request.actions.length === 0 || request.policyActions.length === 0) throw new TypeError("addressed share actions are empty");
6799
6804
  if (request.policyActions.some((action) => !OWNER_SHARE_ACTIONS.has(action))) throw new TypeError("addressed share action is not supported");
6805
+ if (request.deliveryEmail !== void 0 && !isEnvelopeDeliveryEmail(request.deliveryEmail)) throw new TypeError("delivery email is not a valid envelope address");
6800
6806
  const target = normalizeShareTarget(request.target);
6801
6807
  if (target.kind === "bearer") throw new TypeError("addressed target is required");
6802
6808
  if (target.kind === "recipientDid") return { target };
@@ -7778,8 +7784,25 @@ function historyRecordForPublishedShare(result, now = /* @__PURE__ */ new Date()
7778
7784
  }
7779
7785
  var MemorySenderShareRecordStorage = class {
7780
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
+ }
7781
7793
  async put(record) {
7782
- this.records.set(record.shareId, record);
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
+ });
7783
7806
  }
7784
7807
  async list() {
7785
7808
  return [...this.records.values()];
@@ -7788,7 +7811,9 @@ var MemorySenderShareRecordStorage = class {
7788
7811
  return this.records.get(shareId);
7789
7812
  }
7790
7813
  async delete(shareId) {
7791
- this.records.delete(shareId);
7814
+ return this.serialize(async () => {
7815
+ this.records.delete(shareId);
7816
+ });
7792
7817
  }
7793
7818
  };
7794
7819
  var MemorySenderShareKeyStorage = class {
@@ -7872,6 +7897,11 @@ var SenderShareStore = class {
7872
7897
  }
7873
7898
  /** Mark a share revoked locally after the caller's node-side revoke call succeeds. */
7874
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
+ }
7875
7905
  const record = await this.records.get(shareId);
7876
7906
  if (record === void 0) throw new Error(`Unknown share: ${shareId}`);
7877
7907
  const revoked = { ...record, revokedAt };
@@ -7919,11 +7949,12 @@ var EncryptedSenderShareHistory = class {
7919
7949
 
7920
7950
  // src/notify.ts
7921
7951
  var ShareNotifyError = class extends Error {
7922
- code = "delivery-failed";
7923
- constructor(message = "share delivery did not complete") {
7952
+ constructor(message = "share delivery did not complete", reason) {
7924
7953
  super(message);
7954
+ this.reason = reason;
7925
7955
  this.name = "ShareNotifyError";
7926
7956
  }
7957
+ code = "delivery-failed";
7927
7958
  };
7928
7959
  function recipientMatchesShareRecord(record, recipient) {
7929
7960
  const matcher = record.recipientMatcher;
@@ -7933,6 +7964,11 @@ function recipientMatchesShareRecord(record, recipient) {
7933
7964
  if (matcher.kind === "emailDomain") return mailbox.domain === matcher.value;
7934
7965
  return false;
7935
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
+ }
7936
7972
  async function notifyShare(input) {
7937
7973
  const mailbox = canonicalMailbox(input.recipient);
7938
7974
  if (!input.shareId || mailbox === void 0) throw new ShareNotifyError("recipient is invalid");
@@ -7943,6 +7979,19 @@ async function notifyShare(input) {
7943
7979
  const idempotencyKey = input.idempotencyKey ?? await defaultIdempotencyKey(input.shareId, recipient);
7944
7980
  const attemptsLimit = input.maxAttempts ?? 3;
7945
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
+ }
7946
7995
  let attempts = 0;
7947
7996
  let lastError;
7948
7997
  while (attempts < attemptsLimit) {
@@ -7950,8 +7999,27 @@ async function notifyShare(input) {
7950
7999
  attempts += 1;
7951
8000
  try {
7952
8001
  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 } });
7953
- return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state, idempotencyKey, attempts };
8002
+ return {
8003
+ protocol: "tinycloud-share",
8004
+ version: 1,
8005
+ shareId: input.shareId,
8006
+ state: state === "delivered" && input.record?.deliveredRecipients?.includes(recipient) ? "already-delivered" : state,
8007
+ idempotencyKey,
8008
+ attempts
8009
+ };
7954
8010
  } catch (error) {
8011
+ if (error instanceof ShareNotifyError && error.reason === "delivery-window-expired") {
8012
+ return {
8013
+ protocol: "tinycloud-share",
8014
+ version: 1,
8015
+ shareId: input.shareId,
8016
+ state: "partial-failure",
8017
+ idempotencyKey,
8018
+ attempts,
8019
+ retryable: false,
8020
+ reason: "delivery-window-expired"
8021
+ };
8022
+ }
7955
8023
  lastError = error;
7956
8024
  }
7957
8025
  }
@@ -8114,7 +8182,11 @@ async function revokeShare(input) {
8114
8182
  });
8115
8183
  }
8116
8184
  const revokedAt = (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
8117
- if (input.records !== void 0) await input.records.put({ ...input.record, revokedAt });
8185
+ if (input.records?.update !== void 0) {
8186
+ await input.records.update(input.record.shareId, (record) => ({ ...record, revokedAt }));
8187
+ } else if (input.records !== void 0) {
8188
+ await input.records.put({ ...input.record, revokedAt });
8189
+ }
8118
8190
  return { state: "revoked", target, delegationCid, revokedAt };
8119
8191
  }
8120
8192
  function redactRecord(record, revealLink, link) {
@@ -8741,6 +8813,7 @@ async function openNativeShare(sharing, link) {
8741
8813
  restoreDelegatedShareKey,
8742
8814
  resumeShareAuthorization,
8743
8815
  revokeShare,
8816
+ shareDeliveryWindowExpiresAt,
8744
8817
  showShare,
8745
8818
  targetAuthorizationMethod,
8746
8819
  toBase64Url,