@tinycloud/cli 1.3.0-beta.43 → 1.3.0-beta.44

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.js CHANGED
@@ -26578,6 +26578,25 @@ var ShareHistoryRetryError = class extends Error {
26578
26578
  }
26579
26579
  code = "SHARE_HISTORY_RETRY";
26580
26580
  };
26581
+ var ShareHistoryUnreadableError = class extends Error {
26582
+ constructor() {
26583
+ super("share history is unavailable");
26584
+ this.name = "ShareHistoryUnreadableError";
26585
+ }
26586
+ };
26587
+ var ShareHistoryReadError = class extends Error {
26588
+ constructor() {
26589
+ super("share history could not be read");
26590
+ this.name = "ShareHistoryReadError";
26591
+ }
26592
+ };
26593
+ var HISTORY_WRITE_ERROR_CODES = { EACCES: true, EPERM: true, EROFS: true, ENOSPC: true, EDQUOT: true, EIO: true };
26594
+ function shareHistoryReason(error) {
26595
+ if (error instanceof ShareHistoryUnreadableError) return "HISTORY_UNREADABLE";
26596
+ if (error instanceof ShareHistoryReadError) return "HISTORY_READ_FAILED";
26597
+ const code4 = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
26598
+ return typeof code4 === "string" && HISTORY_WRITE_ERROR_CODES[code4] === true ? "HISTORY_WRITE_FAILED" : "HISTORY_UNAVAILABLE";
26599
+ }
26581
26600
 
26582
26601
  // src/commands/share.ts
26583
26602
  var SHARE_ORIGIN = "https://share.tinycloud.xyz";
@@ -26759,16 +26778,18 @@ function expires(value) {
26759
26778
  }
26760
26779
  async function rememberPublishedShare(result) {
26761
26780
  const record2 = historyRecordForPublishedShare(result);
26762
- if (shareServices.records === void 0) return { record: record2, recorded: false };
26763
26781
  try {
26782
+ if (shareServices.records === void 0) throw new Error("sender history is not configured");
26764
26783
  await shareServices.records.put(record2);
26765
- return { record: record2, recorded: true };
26766
- } catch {
26767
- process.stderr.write("Warning: share was published but not recorded in sender history; future share list, share notify, and share revoke by ID will not find it.\n");
26768
- return { record: record2, recorded: false };
26784
+ return { record: record2, history: { recorded: true } };
26785
+ } catch (error) {
26786
+ const reason = shareHistoryReason(error);
26787
+ process.stderr.write(`Warning: share was published but not recorded in sender history (${reason}); future share list, share notify, and share revoke by ID will not find it. Keep share ID ${result.metadata.shareId} and CID ${result.link.cid}.
26788
+ `);
26789
+ return { record: record2, history: { recorded: false, reason } };
26769
26790
  }
26770
26791
  }
26771
- async function notifyRecordedShare(record2, recipient, adapter, storage) {
26792
+ async function notifyRecordedShare(record2, recipient, adapter, storage, published) {
26772
26793
  const result = await notifyShare({ shareId: record2.shareId, recipient, record: record2, adapter, checkDeliveryWindow: true });
26773
26794
  if (result.state !== "partial-failure" && storage !== void 0) {
26774
26795
  const mailbox = canonicalMailbox2(recipient).email;
@@ -26780,8 +26801,11 @@ async function notifyRecordedShare(record2, recipient, adapter, storage) {
26780
26801
  deliveredRecipients: current.deliveredRecipients?.includes(mailbox) ? current.deliveredRecipients : [...current.deliveredRecipients ?? [], mailbox]
26781
26802
  }));
26782
26803
  if (updated === void 0) throw new Error("sender history record was removed");
26783
- } catch {
26784
- process.stderr.write("Warning: delivery succeeded, but could not record its confirmation in sender history.\n");
26804
+ } catch (error) {
26805
+ const reason = shareHistoryReason(error);
26806
+ const identifiers = published === void 0 ? "" : ` Keep share ID ${published.shareId} and CID ${published.cid}.`;
26807
+ process.stderr.write(`Warning: delivery succeeded, but could not record its confirmation in sender history (${reason}).${identifiers}
26808
+ `);
26785
26809
  }
26786
26810
  }
26787
26811
  }
@@ -26870,17 +26894,17 @@ function registerShareCommand(program2) {
26870
26894
  }
26871
26895
  throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
26872
26896
  }
26873
- const { record: record2, recorded } = await rememberPublishedShare(result);
26897
+ const { record: record2, history } = await rememberPublishedShare(result);
26874
26898
  let notification;
26875
26899
  if (notifyRecipient !== void 0) {
26876
26900
  if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
26877
- notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, recorded ? shareServices.records : void 0);
26901
+ notification = await notifyRecordedShare(record2, notifyRecipient, shareServices.delivery, history.recorded ? shareServices.records : void 0, { shareId: result.metadata.shareId, cid: result.link.cid });
26878
26902
  if (notification.state === "partial-failure") process.exitCode = 9;
26879
26903
  if (notification.reason === "delivery-window-expired") process.stderr.write(NOTIFY_WINDOW_MESSAGE);
26880
26904
  }
26881
26905
  if (result.metadata.expiryClamped === true) process.stderr.write(`Share expiry clamped to session expiry (${result.metadata.expiresAt}).
26882
26906
  `);
26883
- if (json) writeJson2({ ...redactPublishedShare(result), expiryClamped: result.metadata.expiryClamped === true, ...artifact === void 0 || result.metadata.artifact === void 0 ? {} : { artifactId: result.metadata.artifact.id, version: result.metadata.artifact.version, mode: result.metadata.artifact.mode }, ...notification === void 0 ? {} : { notification } });
26907
+ if (json) writeJson2({ ...redactPublishedShare(result), history, expiryClamped: result.metadata.expiryClamped === true, ...artifact === void 0 || result.metadata.artifact === void 0 ? {} : { artifactId: result.metadata.artifact.id, version: result.metadata.artifact.version, mode: result.metadata.artifact.mode }, ...notification === void 0 ? {} : { notification } });
26884
26908
  else {
26885
26909
  if (result.metadata.artifact !== void 0) process.stderr.write(`Artifact ${result.metadata.artifact.id} v${result.metadata.artifact.version} (${result.metadata.artifact.mode})
26886
26910
  `);
@@ -27254,9 +27278,9 @@ function createEncryptedProfileHistory(profileName, sessionSigner) {
27254
27278
  const legacyPath = async (profile) => join12(await ProfileManager.getCacheDir(profile), "share-history-v1.bin");
27255
27279
  const b64 = (value) => Buffer.from(value).toString("base64url");
27256
27280
  const unb64 = (value) => {
27257
- if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share history is unavailable");
27281
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new ShareHistoryUnreadableError();
27258
27282
  const bytes = new Uint8Array(Buffer.from(value, "base64url"));
27259
- if (b64(bytes) !== value) throw new Error("share history is unavailable");
27283
+ if (b64(bytes) !== value) throw new ShareHistoryUnreadableError();
27260
27284
  return bytes;
27261
27285
  };
27262
27286
  const isStoredRecord = (value) => typeof value === "object" && value !== null && "shareId" in value && typeof value.shareId === "string";
@@ -27285,44 +27309,54 @@ function createEncryptedProfileHistory(profileName, sessionSigner) {
27285
27309
  const legacyKey = await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["decrypt"]);
27286
27310
  return preparedKeys = { profile, identity: snapshot.fingerprint, material, legacyKey };
27287
27311
  };
27288
- const preparedSalt = async (profile) => {
27312
+ const readIfPresent = async (file) => {
27289
27313
  try {
27290
- const envelope = JSON.parse(await readFile7(await path(profile), "utf8"));
27291
- if (envelope.version !== HISTORY_VERSION) throw new Error("share history is unavailable");
27292
- const salt = unb64(envelope.kdfSalt);
27293
- if (salt.length < 16) throw new Error("share history is unavailable");
27294
- return salt;
27314
+ return await readFile7(file);
27295
27315
  } catch (error) {
27296
- if (error.code === "ENOENT") return crypto.getRandomValues(new Uint8Array(16));
27297
- throw new Error("share history is unavailable");
27316
+ if (error.code === "ENOENT") return void 0;
27317
+ throw new ShareHistoryReadError();
27298
27318
  }
27299
27319
  };
27300
- const read4 = async (profile, ready) => {
27320
+ const parseEnvelope = (text) => {
27321
+ let envelope;
27301
27322
  try {
27302
- const envelope = JSON.parse(await readFile7(await path(profile), "utf8"));
27303
- if (envelope.version !== HISTORY_VERSION) throw new Error("share history is unavailable");
27304
- if (envelope.kdfSalt !== b64(ready.salt)) throw saltChanged;
27305
- const iv = unb64(envelope.iv);
27306
- const ciphertext = unb64(envelope.ciphertext);
27307
- if (iv.length !== 12 || ciphertext.length <= 16) throw new Error("share history is unavailable");
27308
- const bytes = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, ready.key, ciphertext);
27323
+ envelope = JSON.parse(text);
27324
+ } catch {
27325
+ throw new ShareHistoryUnreadableError();
27326
+ }
27327
+ if (typeof envelope !== "object" || envelope === null || envelope.version !== HISTORY_VERSION) throw new ShareHistoryUnreadableError();
27328
+ return envelope;
27329
+ };
27330
+ const decryptRecords = async (key, iv, ciphertext) => {
27331
+ try {
27332
+ const bytes = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
27309
27333
  const values = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
27310
27334
  return Array.isArray(values) ? values.filter(isStoredRecord) : [];
27311
- } catch (error) {
27312
- if (error === saltChanged) throw error;
27313
- if (error.code !== "ENOENT") throw new Error("share history is unavailable");
27314
- try {
27315
- const legacy = new Uint8Array(await readFile7(await legacyPath(profile)));
27316
- if (legacy.length <= 12) return [];
27317
- const bytes = await crypto.subtle.decrypt({ name: "AES-GCM", iv: legacy.slice(0, 12) }, ready.legacyKey, legacy.slice(12));
27318
- const values = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
27319
- return Array.isArray(values) ? values.filter(isStoredRecord) : [];
27320
- } catch (legacyError) {
27321
- if (legacyError.code === "ENOENT") return [];
27322
- throw new Error("share history is unavailable");
27323
- }
27335
+ } catch {
27336
+ throw new ShareHistoryUnreadableError();
27324
27337
  }
27325
27338
  };
27339
+ const preparedSalt = async (profile) => {
27340
+ const file = await readIfPresent(await path(profile));
27341
+ if (file === void 0) return crypto.getRandomValues(new Uint8Array(16));
27342
+ const salt = unb64(parseEnvelope(file.toString("utf8")).kdfSalt);
27343
+ if (salt.length < 16) throw new ShareHistoryUnreadableError();
27344
+ return salt;
27345
+ };
27346
+ const read4 = async (profile, ready) => {
27347
+ const file = await readIfPresent(await path(profile));
27348
+ if (file === void 0) {
27349
+ const legacy = await readIfPresent(await legacyPath(profile));
27350
+ if (legacy === void 0 || legacy.length <= 12) return [];
27351
+ return decryptRecords(ready.legacyKey, new Uint8Array(legacy.subarray(0, 12)), new Uint8Array(legacy.subarray(12)));
27352
+ }
27353
+ const envelope = parseEnvelope(file.toString("utf8"));
27354
+ if (envelope.kdfSalt !== b64(ready.salt)) throw saltChanged;
27355
+ const iv = unb64(envelope.iv);
27356
+ const ciphertext = unb64(envelope.ciphertext);
27357
+ if (iv.length !== 12 || ciphertext.length <= 16) throw new ShareHistoryUnreadableError();
27358
+ return decryptRecords(ready.key, iv, ciphertext);
27359
+ };
27326
27360
  const write = async (profile, values, ready) => {
27327
27361
  const iv = crypto.getRandomValues(new Uint8Array(12));
27328
27362
  const bytes = new TextEncoder().encode(JSON.stringify(values));