selfstore 1.8.23 → 1.8.24

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.
@@ -2,7 +2,7 @@ import { B as BackupTarget } from './store-CIbiNoiE.js';
2
2
  export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, e as PasswordCheck, f as PasswordPolicy, g as PasswordRequirement, P as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, o as Snapshot, p as SnapshotFile, q as StatusAction, r as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-CIbiNoiE.js';
3
3
  import { K as KV } from './cache-ilsK3Tki.js';
4
4
  export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-ilsK3Tki.js';
5
- export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-8H61XxbL.js';
5
+ export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-l61rREx1.js';
6
6
  import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-BsbVn3fw.js';
7
7
  export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-BsbVn3fw.js';
8
8
  import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-86CHuTij.js';
package/dist/advanced.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-JZHWAX34.js';
2
2
  import { fromSession as fromSession$2, connect as connect$2 } from './chunk-LUNYYH6D.js';
3
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-DJZ5S3FI.js';
3
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-VRHSBY3T.js';
4
4
  export { datedName, pickFromDisk, saveToDisk } from './chunk-AUH35AAQ.js';
5
5
  import './chunk-QSLHEE2L.js';
6
6
  import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-GW5CYP4C.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/installation.ts","../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAS,SAAS,EAAA,EAAqB;AACtC,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,uBAAA,CAAwB,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAA;AAC5F;AAEA,SAAS,YAAA,GAAwB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,UAAA,GAAa,4BAA4B,GAAG,OAAA,KAAY,IAAA;AAEnF,EAAA,MAAM,GAAA,GAAO,UAAmD,UAAA,KAAe,IAAA;AAC/E,EAAA,OAAO,WAAA,IAAe,GAAA;AACvB;AAOO,SAAS,aAAA,GAA+B;AAC9C,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAM;AAAA,EACzC;AACA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,IAAI,CAAC,QAAA,CAAS,SAAA,CAAU,SAAS,KAAK,SAAA,EAAW;AAChD,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAU;AAAA,EAClC;AACA,EAAA,MAAM,QAAQ,kBAAA,CAAmB,IAAA,CAAK,UAAU,SAAS,CAAA,IAAK,UAAU,cAAA,GAAiB,CAAA;AACzF,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,mBAAA;AAAA,IACN,MAAA,EAAQ,QAAQ,wBAAA,GAA2B,iBAAA;AAAA,IAC3C,SAAA,EAAW;AAAA,GACZ;AACD;;;ACmEO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb,WAAA;AAAA,EACA,eAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAkBO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * Whether this browser will keep the local store, and what the user can do\n * about it.\n *\n * One browser does not merely inconvenience a local-first app, it DELETES its\n * data: Safari erases script-writable storage after seven days of browser use\n * without interaction with the site. Someone who closes for three weeks comes\n * back to an empty app, and nothing warned them - which is also the moment they\n * notice it last.\n *\n * Adding the app to the Dock or the Home Screen switches its storage to\n * persistent mode, which WebKit exempts from its eviction triggers. WebKit does\n * not write anywhere that this neutralises the inactivity rule by name, so this\n * module reports an advisory, never a guarantee.\n *\n * The library ships no user-facing copy: it names the situation, the host writes\n * the sentence.\n */\n\n/** What stands between this browser and keeping the data. */\nexport type StorageRisk =\n\t/** Nothing known against it: the store survives until the user clears it. */\n\t| 'none'\n\t/** Data is erased when the tab closes (private browsing). Refuse to set up here. */\n\t| 'ephemeral'\n\t/** Erased after a stretch of not opening the app, unless it is installed. */\n\t| 'evicted-when-idle';\n\nexport interface StorageAdvice {\n\trisk: StorageRisk;\n\t/** The gesture that removes the risk, when there is one. Stable key; the\n\t * host owns the wording and the platform-specific instructions. */\n\tremedy?: 'install-to-dock' | 'install-to-home-screen';\n\t/** Already running as an installed app, so the remedy is done. */\n\tinstalled: boolean;\n}\n\n/** Real Safari, not Chrome or Edge on macOS - all three carry \"Safari\" in the\n * user agent, and only Safari carries none of the others' tokens. */\nfunction isSafari(ua: string): boolean {\n\treturn /Safari\\//.test(ua) && !/Chrome|Chromium|Edg\\//.test(ua) && !/OPR\\/|Android/.test(ua);\n}\n\nfunction isStandalone(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst displayMode = window.matchMedia?.('(display-mode: standalone)')?.matches === true;\n\t// iOS and iPadOS kept their own flag, older than display-mode.\n\tconst ios = (navigator as Navigator & { standalone?: boolean }).standalone === true;\n\treturn displayMode || ios;\n}\n\n/**\n * What this browser will do with the local store, and the one gesture that\n * changes it. Call it before setting up, and again in whatever screen owns the\n * data: the advice disappears on its own once the remedy is applied.\n */\nexport function storageAdvice(): StorageAdvice {\n\tif (typeof navigator === 'undefined' || typeof window === 'undefined') {\n\t\treturn { risk: 'none', installed: false };\n\t}\n\tconst installed = isStandalone();\n\tif (!isSafari(navigator.userAgent) || installed) {\n\t\treturn { risk: 'none', installed };\n\t}\n\tconst touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;\n\treturn {\n\t\trisk: 'evicted-when-idle',\n\t\tremedy: touch ? 'install-to-home-screen' : 'install-to-dock',\n\t\tinstalled: false\n\t};\n}\n","/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- Will this browser keep the data, and what can the user do about it ---\nexport { storageAdvice, type StorageAdvice, type StorageRisk } from '../persistence/installation';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\taccount as driveAccount,\n\tcreateCompanion as driveCreateCompanion,\n\tshare as driveShare,\n\tunshare as driveUnshare,\n\towner as driveOwner,\n\tsecondary as driveSecondary,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported,\n\t/** Let the user point at an EXISTING backup file and adopt it as the\n\t * destination. Gate on isOpenSupported(): unlike creating, this has no\n\t * download fallback. */\n\topenExisting: fileOpenExisting\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. `account()` names WHICH Google account holds them,\n * because a brand name is not an address.\n *\n * The last five are the Drive side of SHARING, on companion files the app\n * creates next to the backup: `createCompanion` mints one, `share` publishes it\n * on a link, `unshare` makes it private again, `owner` says whose Drive it\n * lives on, and `secondary` is a read-write target over it whose lifecycle is\n * its own - detaching a shared copy leaves the user's real backup and their\n * Drive session untouched. That is what a ShareBackend needs from Drive and\n * what every app was rewriting; what stays yours is reading ANOTHER account's\n * file, which the drive.file scope cannot do without a Picker gesture or a\n * relay you host. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\taccount: driveAccount,\n\tcreateCompanion: driveCreateCompanion,\n\tshare: driveShare,\n\tunshare: driveUnshare,\n\towner: driveOwner,\n\tsecondary: driveSecondary,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type {\n\tDriveAuth,\n\tDriveOptions,\n\tDriveBackupInfo,\n\tDriveAccount\n} from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport {\n\tgisDriveAuth,\n\ttype GisDriveAuthOptions,\n\ttype GisTokenPersistence\n} from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk, datedName } from '../selfstore/targets/local';\n"]}
1
+ {"version":3,"sources":["../src/persistence/installation.ts","../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAS,SAAS,EAAA,EAAqB;AACtC,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,uBAAA,CAAwB,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAA;AAC5F;AAEA,SAAS,YAAA,GAAwB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,UAAA,GAAa,4BAA4B,GAAG,OAAA,KAAY,IAAA;AAEnF,EAAA,MAAM,GAAA,GAAO,UAAmD,UAAA,KAAe,IAAA;AAC/E,EAAA,OAAO,WAAA,IAAe,GAAA;AACvB;AAOO,SAAS,aAAA,GAA+B;AAC9C,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAM;AAAA,EACzC;AACA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,IAAI,CAAC,QAAA,CAAS,SAAA,CAAU,SAAS,KAAK,SAAA,EAAW;AAChD,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAU;AAAA,EAClC;AACA,EAAA,MAAM,QAAQ,kBAAA,CAAmB,IAAA,CAAK,UAAU,SAAS,CAAA,IAAK,UAAU,cAAA,GAAiB,CAAA;AACzF,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,mBAAA;AAAA,IACN,MAAA,EAAQ,QAAQ,wBAAA,GAA2B,iBAAA;AAAA,IAC3C,SAAA,EAAW;AAAA,GACZ;AACD;;;ACmEO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb,WAAA;AAAA,EACA,eAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAkBO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * Whether this browser will keep the local store, and what the user can do\n * about it.\n *\n * One browser does not merely inconvenience a local-first app, it DELETES its\n * data: Safari erases script-writable storage after seven days of browser use\n * without interaction with the site. Someone who closes for three weeks comes\n * back to an empty app, and nothing warned them - which is also the moment they\n * notice it last.\n *\n * Adding the app to the Dock or the Home Screen switches its storage to\n * persistent mode, which WebKit exempts from its eviction triggers. WebKit does\n * not write anywhere that this neutralises the inactivity rule by name, so this\n * module reports an advisory, never a guarantee.\n *\n * The library ships no user-facing copy: it names the situation, the host writes\n * the sentence.\n */\n\n/** What stands between this browser and keeping the data. */\nexport type StorageRisk =\n\t/** Nothing known against it: the store survives until the user clears it. */\n\t| 'none'\n\t/** Data is erased when the tab closes (private browsing). Refuse to set up here. */\n\t| 'ephemeral'\n\t/** Erased after a stretch of not opening the app, unless it is installed. */\n\t| 'evicted-when-idle';\n\nexport interface StorageAdvice {\n\trisk: StorageRisk;\n\t/** The gesture that removes the risk, when there is one. Stable key; the\n\t * host owns the wording and the platform-specific instructions. */\n\tremedy?: 'install-to-dock' | 'install-to-home-screen';\n\t/** Already running as an installed app, so the remedy is done. */\n\tinstalled: boolean;\n}\n\n/** Real Safari, not Chrome or Edge on macOS - all three carry \"Safari\" in the\n * user agent, and only Safari carries none of the others' tokens. */\nfunction isSafari(ua: string): boolean {\n\treturn /Safari\\//.test(ua) && !/Chrome|Chromium|Edg\\//.test(ua) && !/OPR\\/|Android/.test(ua);\n}\n\nfunction isStandalone(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst displayMode = window.matchMedia?.('(display-mode: standalone)')?.matches === true;\n\t// iOS and iPadOS kept their own flag, older than display-mode.\n\tconst ios = (navigator as Navigator & { standalone?: boolean }).standalone === true;\n\treturn displayMode || ios;\n}\n\n/**\n * What this browser will do with the local store, and the one gesture that\n * changes it. Call it before setting up, and again in whatever screen owns the\n * data: the advice disappears on its own once the remedy is applied.\n */\nexport function storageAdvice(): StorageAdvice {\n\tif (typeof navigator === 'undefined' || typeof window === 'undefined') {\n\t\treturn { risk: 'none', installed: false };\n\t}\n\tconst installed = isStandalone();\n\tif (!isSafari(navigator.userAgent) || installed) {\n\t\treturn { risk: 'none', installed };\n\t}\n\tconst touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;\n\treturn {\n\t\trisk: 'evicted-when-idle',\n\t\tremedy: touch ? 'install-to-home-screen' : 'install-to-dock',\n\t\tinstalled: false\n\t};\n}\n","/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- Will this browser keep the data, and what can the user do about it ---\nexport { storageAdvice, type StorageAdvice, type StorageRisk } from '../persistence/installation';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\taccount as driveAccount,\n\tcreateCompanion as driveCreateCompanion,\n\tshare as driveShare,\n\tunshare as driveUnshare,\n\towner as driveOwner,\n\tsecondary as driveSecondary,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported,\n\t/** Let the user point at an EXISTING backup file and adopt it as the\n\t * destination. Gate on isOpenSupported(): unlike creating, this has no\n\t * download fallback. */\n\topenExisting: fileOpenExisting\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. `account()` names WHICH Google account holds them,\n * because a brand name is not an address.\n *\n * The last five are the Drive side of SHARING, on companion files the app\n * creates next to the backup: `createCompanion` mints one, `share` publishes it\n * on a link, `unshare` makes it private again, `owner` says whose Drive it\n * lives on, and `secondary` is a read-write target over it whose lifecycle is\n * its own - detaching a shared copy leaves the user's real backup and their\n * Drive session untouched. That is what a ShareBackend needs from Drive and\n * what every app was rewriting; what stays yours is reading ANOTHER account's\n * file, which the drive.file scope cannot do without a Picker gesture or a\n * relay you host. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\taccount: driveAccount,\n\tcreateCompanion: driveCreateCompanion,\n\tshare: driveShare,\n\tunshare: driveUnshare,\n\towner: driveOwner,\n\tsecondary: driveSecondary,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type {\n\tDriveAuth,\n\tDriveOptions,\n\tDriveBackupInfo,\n\tDriveAccount\n} from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport {\n\tgisDriveAuth,\n\tpreloadGoogleIdentity,\n\ttype GisDriveAuthOptions,\n\ttype GisTokenPersistence\n} from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk, datedName } from '../selfstore/targets/local';\n"]}
@@ -2040,20 +2040,28 @@ var DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file";
2040
2040
  var TOKEN_TIMEOUT_MS = 12e4;
2041
2041
  var EXPIRY_MARGIN_MS = 6e4;
2042
2042
  var DEFAULT_TOKEN_TTL_S = 3600;
2043
+ var GIS_SRC = "https://accounts.google.com/gsi/client";
2043
2044
  var gisLoading = null;
2044
2045
  function loadGIS() {
2045
2046
  if (typeof globalThis.window === "undefined") return Promise.resolve();
2046
2047
  if (globalThis.window.google?.accounts) return Promise.resolve();
2047
2048
  gisLoading ??= new Promise((resolve, reject) => {
2048
2049
  const s = document.createElement("script");
2049
- s.src = "https://accounts.google.com/gsi/client";
2050
+ s.src = GIS_SRC;
2050
2051
  s.async = true;
2051
2052
  s.onload = () => resolve();
2052
- s.onerror = () => reject(new Error("GIS failed to load"));
2053
+ s.onerror = () => {
2054
+ s.remove();
2055
+ gisLoading = null;
2056
+ reject(new Error("GIS failed to load"));
2057
+ };
2053
2058
  document.head.appendChild(s);
2054
2059
  });
2055
2060
  return gisLoading;
2056
2061
  }
2062
+ function preloadGoogleIdentity() {
2063
+ loadGIS().catch(() => void 0);
2064
+ }
2057
2065
  function requestToken(clientId, scope, prompt, hint, onToken) {
2058
2066
  return new Promise((resolve, reject) => {
2059
2067
  let settled = false;
@@ -2156,6 +2164,6 @@ function gisDriveAuth(opts) {
2156
2164
  };
2157
2165
  }
2158
2166
 
2159
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
2160
- //# sourceMappingURL=chunk-DJZ5S3FI.js.map
2161
- //# sourceMappingURL=chunk-DJZ5S3FI.js.map
2167
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity };
2168
+ //# sourceMappingURL=chunk-VRHSBY3T.js.map
2169
+ //# sourceMappingURL=chunk-VRHSBY3T.js.map