selfstore 1.8.26 → 1.8.27

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/advanced.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-GHSHIDF3.js';
2
2
  import { fromSession as fromSession$2, connect as connect$2 } from './chunk-IDW3WQKF.js';
3
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-AZWS7YC3.js';
3
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-DIO6A5IP.js';
4
4
  import './chunk-GSZSZGSD.js';
5
- export { datedName, pickFromDisk, saveToDisk } from './chunk-LQS7AMOD.js';
5
+ export { datedName, pickFromDisk, saveToDisk } from './chunk-ORVB7IJO.js';
6
6
  import './chunk-FJGWTBAL.js';
7
7
  import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-2TIRUEXR.js';
8
8
  import { FILE_ID_KEY, secondary, owner, unshare, share, createCompanion, account, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect } from './chunk-25U6AKIX.js';
9
9
  import './chunk-LF3CC2P3.js';
10
- export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-RVOBONZF.js';
10
+ export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-N6TIEOP5.js';
11
11
  import './chunk-QBCU7OOP.js';
12
12
  import './chunk-F4ZZEJ5H.js';
13
13
  import './chunk-5UYPKCDB.js';
package/dist/backups.js CHANGED
@@ -2,7 +2,7 @@ export { createBackupsManager, driveBackupsHost } from './chunk-5DAPYC2K.js';
2
2
  import './chunk-2BNTCXQN.js';
3
3
  import './chunk-25U6AKIX.js';
4
4
  import './chunk-LF3CC2P3.js';
5
- import './chunk-RVOBONZF.js';
5
+ import './chunk-N6TIEOP5.js';
6
6
  import './chunk-F4ZZEJ5H.js';
7
7
  import './chunk-5UYPKCDB.js';
8
8
  import './chunk-WXI2NAPA.js';
@@ -12,18 +12,18 @@ async function load(what, mod) {
12
12
  }
13
13
  }
14
14
  async function webdavConnect(opts) {
15
- return (await load("WebDAV", () => import('./webdav-5SHNR7CL.js'))).connect(opts);
15
+ return (await load("WebDAV", () => import('./webdav-5TRDSFB5.js'))).connect(opts);
16
16
  }
17
17
  async function webdavFromSession(opts) {
18
- return (await load("WebDAV", () => import('./webdav-5SHNR7CL.js'))).fromSession(opts);
18
+ return (await load("WebDAV", () => import('./webdav-5TRDSFB5.js'))).fromSession(opts);
19
19
  }
20
20
  async function s3Connect(opts) {
21
- return (await load("S3", () => import('./s3-ABKUTTXE.js'))).connect(opts);
21
+ return (await load("S3", () => import('./s3-UTARQYNR.js'))).connect(opts);
22
22
  }
23
23
  async function s3FromSession(opts) {
24
- return (await load("S3", () => import('./s3-ABKUTTXE.js'))).fromSession(opts);
24
+ return (await load("S3", () => import('./s3-UTARQYNR.js'))).fromSession(opts);
25
25
  }
26
26
 
27
27
  export { s3Connect, s3FromSession, webdavConnect, webdavFromSession };
28
- //# sourceMappingURL=chunk-FQ5O4PHK.js.map
29
- //# sourceMappingURL=chunk-FQ5O4PHK.js.map
28
+ //# sourceMappingURL=chunk-5V3THD54.js.map
29
+ //# sourceMappingURL=chunk-5V3THD54.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-FQ5O4PHK.js","sourcesContent":["// The two destinations that reach a server the user runs - WebDAV and an\n// S3-compatible bucket - loaded on the gesture that uses them instead of with\n// the module that offers them.\n//\n// Why these two and not all four: Drive and the disk file both open a picker or\n// a consent popup, and a browser only allows that inside the transient\n// activation of the user's click. An `await import()` before the call can spend\n// that activation and turn a working button into a silently blocked popup. A\n// WebDAV or S3 connection is plain fetch behind a form the user already\n// submitted - nothing to preserve, so the chunk can arrive late.\n//\n// What it buys: an app that offers Drive and a disk file - the common case -\n// stops shipping the SigV4 request signer and the WebDAV client. Before this,\n// importing the connect journey (so: any of the widgets) pulled both in whether\n// or not the app named them, which made \"destinations are opt-in\" true of the\n// API and false of the bytes.\n//\n// `selfstore/advanced` keeps its STATIC `webdavTarget` / `s3Target`: naming one\n// of those is the opt-in, and an app that wrote the import wants the code.\n\nimport { SelfstoreError } from '../../selfstore/errors';\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport type { WebdavConnectOptions } from './webdav';\nimport type { S3ConnectOptions } from './s3';\n\n/** A chunk that does not arrive is the destination not answering: same code,\n * same retry, rather than a bare Error escaping a public path. */\nasync function load<T>(what: string, mod: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await mod();\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t`The ${what} support could not be loaded (${e instanceof Error ? e.message : String(e)}).`\n\t\t);\n\t}\n}\n\nexport async function webdavConnect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).connect(opts);\n}\n\nexport async function webdavFromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).fromSession(opts);\n}\n\nexport async function s3Connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).connect(opts);\n}\n\nexport async function s3FromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).fromSession(opts);\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-5V3THD54.js","sourcesContent":["// The two destinations that reach a server the user runs - WebDAV and an\n// S3-compatible bucket - loaded on the gesture that uses them instead of with\n// the module that offers them.\n//\n// Why these two and not all four: Drive and the disk file both open a picker or\n// a consent popup, and a browser only allows that inside the transient\n// activation of the user's click. An `await import()` before the call can spend\n// that activation and turn a working button into a silently blocked popup. A\n// WebDAV or S3 connection is plain fetch behind a form the user already\n// submitted - nothing to preserve, so the chunk can arrive late.\n//\n// What it buys: an app that offers Drive and a disk file - the common case -\n// stops shipping the SigV4 request signer and the WebDAV client. Before this,\n// importing the connect journey (so: any of the widgets) pulled both in whether\n// or not the app named them, which made \"destinations are opt-in\" true of the\n// API and false of the bytes.\n//\n// `selfstore/advanced` keeps its STATIC `webdavTarget` / `s3Target`: naming one\n// of those is the opt-in, and an app that wrote the import wants the code.\n\nimport { SelfstoreError } from '../../selfstore/errors';\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport type { WebdavConnectOptions } from './webdav';\nimport type { S3ConnectOptions } from './s3';\n\n/** A chunk that does not arrive is the destination not answering: same code,\n * same retry, rather than a bare Error escaping a public path. */\nasync function load<T>(what: string, mod: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await mod();\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t`The ${what} support could not be loaded (${e instanceof Error ? e.message : String(e)}).`\n\t\t);\n\t}\n}\n\nexport async function webdavConnect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).connect(opts);\n}\n\nexport async function webdavFromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).fromSession(opts);\n}\n\nexport async function s3Connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).connect(opts);\n}\n\nexport async function s3FromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).fromSession(opts);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-GSZSZGSD.js';
2
- import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-RVOBONZF.js';
2
+ import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-N6TIEOP5.js';
3
3
  import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
4
4
  import { openManifest, GROUP_KEYING } from './chunk-F4ZZEJ5H.js';
5
5
  import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-5UYPKCDB.js';
@@ -74,6 +74,14 @@ var storeError = (code, message) => ({
74
74
  message
75
75
  });
76
76
  var RESERVED = "__store";
77
+ function isBookkeeping(v) {
78
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
79
+ const { schemaVersion, meta } = v;
80
+ if (schemaVersion !== void 0 && !(Number.isInteger(schemaVersion) && schemaVersion >= 0)) {
81
+ return false;
82
+ }
83
+ return meta === void 0 || meta === null || typeof meta === "object" && !Array.isArray(meta);
84
+ }
77
85
  var KEY = {
78
86
  mode: "mode",
79
87
  version: "version",
@@ -389,8 +397,11 @@ function createLocalStore(opts) {
389
397
  }
390
398
  function readBookkeeping(snap, sidecar) {
391
399
  if (RESERVED in snap.collections) delete snap.collections[RESERVED];
392
- const s = sidecar;
393
- return { fromVersion: s?.schemaVersion ?? 0, sync: s?.meta ?? null };
400
+ if (sidecar === null) return { fromVersion: 0, sync: null };
401
+ if (!isBookkeeping(sidecar)) {
402
+ throw new SelfstoreError("BAD_FORMAT", "Unreadable sync metadata (sync.json).");
403
+ }
404
+ return { fromVersion: sidecar.schemaVersion ?? 0, sync: sidecar.meta ?? null };
394
405
  }
395
406
  const migrated = (snap, fromV) => opts.migrate && fromV < version ? opts.migrate(fromV, snap) : snap;
396
407
  function mergeFiles(local, remote) {
@@ -559,6 +570,24 @@ function createLocalStore(opts) {
559
570
  );
560
571
  }
561
572
  }
573
+ async function convergeBeforeRewrite(where) {
574
+ await requireReady(where);
575
+ const seen = await statRemote();
576
+ if (seen === null || remoteVersion === null || seen === remoteVersion) return;
577
+ const pulled = await pullRemoteIntoLocal("push", seen);
578
+ if (locked() || needsAttention) {
579
+ throw new SelfstoreError(
580
+ "TARGET_WRITE_FAILED",
581
+ `${where}(): the pre-rewrite converge needs attention; resolve it first.`
582
+ );
583
+ }
584
+ if (pulled === "unreachable") {
585
+ throw new SelfstoreError(
586
+ "TARGET_WRITE_FAILED",
587
+ `${where}(): the destination is unreachable; try again online.`
588
+ );
589
+ }
590
+ }
562
591
  async function setRemoteVersion(v) {
563
592
  remoteVersion = v;
564
593
  if (v === null) await kv.del(KEY.remoteVersion);
@@ -593,8 +622,12 @@ function createLocalStore(opts) {
593
622
  }
594
623
  const seen = await statRemote();
595
624
  if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
596
- await pullRemoteIntoLocal(source, seen);
625
+ const pulled = await pullRemoteIntoLocal(source, seen);
597
626
  if (locked() || needsAttention) return false;
627
+ if (pulled === "unreachable") {
628
+ lastError = transient("Save deferred: destination momentarily unreachable.");
629
+ return false;
630
+ }
598
631
  }
599
632
  const snap = await gather();
600
633
  const digest = await contentDigest(snap);
@@ -763,10 +796,12 @@ function createLocalStore(opts) {
763
796
  async function pullRemoteIntoLocal(source, seen) {
764
797
  if (!durable) return null;
765
798
  const marker = seen === void 0 ? await statRemote() : seen;
766
- let remote = null;
799
+ let remote;
767
800
  try {
768
801
  remote = await takeFreshPrefetch(durable) ?? await durable.load();
769
- } catch {
802
+ } catch (e) {
803
+ logger.warn("[selfstore] download of the durable copy failed", e);
804
+ return "unreachable";
770
805
  }
771
806
  if (!remote) return null;
772
807
  const decoded = await importLocked(remote);
@@ -891,7 +926,7 @@ function createLocalStore(opts) {
891
926
  if (durable) {
892
927
  const r = await pullRemoteIntoLocal(source);
893
928
  if (r === "locked") return null;
894
- entry = r;
929
+ entry = r === "unreachable" ? null : r;
895
930
  }
896
931
  entry = await foldPeers(source) ?? entry;
897
932
  if (durable) await pushDurable(source);
@@ -1458,17 +1493,7 @@ function createLocalStore(opts) {
1458
1493
  );
1459
1494
  }
1460
1495
  return serialize(async () => {
1461
- await requireReady("setEncryption");
1462
- const seen = await statRemote();
1463
- if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
1464
- await pullRemoteIntoLocal("push", seen);
1465
- if (locked() || needsAttention) {
1466
- throw new SelfstoreError(
1467
- "TARGET_WRITE_FAILED",
1468
- "setEncryption(): the pre-rewrite converge needs attention; resolve it first."
1469
- );
1470
- }
1471
- }
1496
+ await convergeBeforeRewrite("setEncryption");
1472
1497
  const prevEncrypted = encrypted;
1473
1498
  const prevPass = pass;
1474
1499
  const prevEnvelope = envelope;
@@ -1524,17 +1549,7 @@ function createLocalStore(opts) {
1524
1549
  );
1525
1550
  }
1526
1551
  return serialize(async () => {
1527
- await requireReady("addEncryptionKey");
1528
- const seen = await statRemote();
1529
- if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
1530
- await pullRemoteIntoLocal("push", seen);
1531
- if (locked() || needsAttention) {
1532
- throw new SelfstoreError(
1533
- "TARGET_WRITE_FAILED",
1534
- "addEncryptionKey(): the pre-rewrite converge needs attention; resolve it first."
1535
- );
1536
- }
1537
- }
1552
+ await convergeBeforeRewrite("addEncryptionKey");
1538
1553
  if (!envelope) {
1539
1554
  const dataKey = crypto.getRandomValues(new Uint8Array(32));
1540
1555
  envelope = { dataKey, slots: [await mintSlot(pass, dataKey)] };
@@ -1590,17 +1605,7 @@ function createLocalStore(opts) {
1590
1605
  throw new TypeError("removeEncryptionKey(): the backup has no password.");
1591
1606
  }
1592
1607
  return serialize(async () => {
1593
- await requireReady("removeEncryptionKey");
1594
- const seen = await statRemote();
1595
- if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
1596
- await pullRemoteIntoLocal("push", seen);
1597
- if (locked() || needsAttention) {
1598
- throw new SelfstoreError(
1599
- "TARGET_WRITE_FAILED",
1600
- "removeEncryptionKey(): the pre-rewrite converge needs attention; resolve it first."
1601
- );
1602
- }
1603
- }
1608
+ await convergeBeforeRewrite("removeEncryptionKey");
1604
1609
  if (!envelope) {
1605
1610
  throw new TypeError(
1606
1611
  "removeEncryptionKey(): no key slots known - the file predates the envelope or has not been read yet."
@@ -1667,17 +1672,7 @@ function createLocalStore(opts) {
1667
1672
  );
1668
1673
  }
1669
1674
  return serialize(async () => {
1670
- await requireReady("setExternalEncryption");
1671
- const seen = await statRemote();
1672
- if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
1673
- await pullRemoteIntoLocal("push", seen);
1674
- if (locked() || needsAttention) {
1675
- throw new SelfstoreError(
1676
- "TARGET_WRITE_FAILED",
1677
- "setExternalEncryption(): the pre-rewrite converge needs attention; resolve it first."
1678
- );
1679
- }
1680
- }
1675
+ await convergeBeforeRewrite("setExternalEncryption");
1681
1676
  const prevEncrypted = encrypted;
1682
1677
  const prevPass = pass;
1683
1678
  const prevEnvelope = envelope;
@@ -1738,17 +1733,7 @@ function createLocalStore(opts) {
1738
1733
  );
1739
1734
  }
1740
1735
  return serialize(async () => {
1741
- await requireReady("addExternalKey");
1742
- const seen = await statRemote();
1743
- if (seen !== null && remoteVersion !== null && seen !== remoteVersion) {
1744
- await pullRemoteIntoLocal("push", seen);
1745
- if (locked() || needsAttention) {
1746
- throw new SelfstoreError(
1747
- "TARGET_WRITE_FAILED",
1748
- "addExternalKey(): the pre-rewrite converge needs attention; resolve it first."
1749
- );
1750
- }
1751
- }
1736
+ await convergeBeforeRewrite("addExternalKey");
1752
1737
  if (!envelope && pass) {
1753
1738
  const dataKey = crypto.getRandomValues(new Uint8Array(32));
1754
1739
  envelope = { dataKey, slots: [await mintSlot(pass, dataKey)] };
@@ -2165,5 +2150,5 @@ function gisDriveAuth(opts) {
2165
2150
  }
2166
2151
 
2167
2152
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity };
2168
- //# sourceMappingURL=chunk-AZWS7YC3.js.map
2169
- //# sourceMappingURL=chunk-AZWS7YC3.js.map
2153
+ //# sourceMappingURL=chunk-DIO6A5IP.js.map
2154
+ //# sourceMappingURL=chunk-DIO6A5IP.js.map