selfstore 1.8.6 → 1.8.7

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-BiQ-BuVl.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, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, n as Snapshot, o as SnapshotFile, p as StatusAction, q as StatusDescriptor, r as StatusInput, s as StorageState, t 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-BiQ-BuVl.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 { d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-C52j3DeH.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-VLHtF3vT.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-DvsnA-FK.js';
7
7
  export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-DvsnA-FK.js';
8
8
  import { c as connect$1, f as fromSession$1, p as preview, a as adopt, b as findOrCreateOwnFile, l as listBackups, d as createBackup, e as deleteBackup, r as renameBackup, g as account } from './drive-CTYvGg_7.js';
package/dist/advanced.js CHANGED
@@ -1,4 +1,4 @@
1
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-ELDSPUTS.js';
1
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-TXAMWBG5.js';
2
2
  import './chunk-SKWHKM25.js';
3
3
  export { datedName, pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
4
4
  import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1, fromSession3, connect3, peer, fromSession2, connect2 } from './chunk-TKWU52LK.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;;;AC8DO,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,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAQO,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;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\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. */\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\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 { gisDriveAuth } 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;;;AC8DO,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,QAAAA;AAAA,EACT,WAAA,EAAaC,YAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,QAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAQO,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;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\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. */\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\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"]}
@@ -2007,7 +2007,7 @@ function loadGIS() {
2007
2007
  });
2008
2008
  return gisLoading;
2009
2009
  }
2010
- function requestToken(clientId, scope, prompt, onToken) {
2010
+ function requestToken(clientId, scope, prompt, hint, onToken) {
2011
2011
  return new Promise((resolve, reject) => {
2012
2012
  let settled = false;
2013
2013
  const finish = (fn) => {
@@ -2020,6 +2020,9 @@ function requestToken(clientId, scope, prompt, onToken) {
2020
2020
  const client = globalThis.window.google.accounts.oauth2.initTokenClient({
2021
2021
  client_id: clientId,
2022
2022
  scope,
2023
+ // Naming the account skips the chooser. A wrong or stale hint costs
2024
+ // nothing: Google falls back to asking, which is what it did anyway.
2025
+ ...hint ? { hint } : {},
2023
2026
  callback(resp) {
2024
2027
  if (resp.error || !resp.access_token) {
2025
2028
  finish(() => reject(new Error(resp.error ?? "token_failed")));
@@ -2037,27 +2040,64 @@ function requestToken(clientId, scope, prompt, onToken) {
2037
2040
  }
2038
2041
  function gisDriveAuth(opts) {
2039
2042
  const scope = opts.scope ?? DRIVE_SCOPE;
2043
+ const key = `selfstore.drive.token.${opts.clientId}`;
2044
+ const kept = (job) => {
2045
+ if (opts.persist !== "session") return void 0;
2046
+ try {
2047
+ const s = globalThis.sessionStorage;
2048
+ return s ? job(s) : void 0;
2049
+ } catch {
2050
+ return void 0;
2051
+ }
2052
+ };
2040
2053
  let token = null;
2041
2054
  let expiry = 0;
2055
+ function restore() {
2056
+ if (token) return;
2057
+ const raw = kept((s) => s.getItem(key));
2058
+ if (!raw) return;
2059
+ try {
2060
+ const held = JSON.parse(raw);
2061
+ if (typeof held.token === "string" && typeof held.expiry === "number") {
2062
+ token = held.token;
2063
+ expiry = held.expiry;
2064
+ }
2065
+ } catch {
2066
+ kept((s) => s.removeItem(key));
2067
+ }
2068
+ }
2042
2069
  const fresh = () => !!token && Date.now() < expiry - EXPIRY_MARGIN_MS;
2043
2070
  function cache(t, exp) {
2044
2071
  token = t;
2045
2072
  expiry = exp;
2073
+ kept((s) => s.setItem(key, JSON.stringify({ token: t, expiry: exp })));
2046
2074
  }
2047
2075
  function clear() {
2048
2076
  token = null;
2049
2077
  expiry = 0;
2078
+ kept((s) => s.removeItem(key));
2050
2079
  }
2080
+ const hintNow = () => {
2081
+ const h = typeof opts.hint === "function" ? opts.hint() : opts.hint;
2082
+ return h || void 0;
2083
+ };
2051
2084
  return {
2052
2085
  async token(tokenOpts) {
2086
+ if (tokenOpts?.forceRefresh) clear();
2087
+ else restore();
2053
2088
  if (!tokenOpts?.forceRefresh && fresh()) return token;
2054
2089
  await loadGIS();
2055
- return requestToken(opts.clientId, scope, "", cache);
2090
+ return requestToken(opts.clientId, scope, "", hintNow(), cache);
2056
2091
  },
2057
2092
  async reconnect() {
2058
2093
  await loadGIS();
2059
2094
  try {
2060
- await requestToken(opts.clientId, scope, "consent", cache);
2095
+ await requestToken(opts.clientId, scope, "", hintNow(), cache);
2096
+ return true;
2097
+ } catch {
2098
+ }
2099
+ try {
2100
+ await requestToken(opts.clientId, scope, "consent", hintNow(), cache);
2061
2101
  return true;
2062
2102
  } catch {
2063
2103
  return false;
@@ -2070,5 +2110,5 @@ function gisDriveAuth(opts) {
2070
2110
  }
2071
2111
 
2072
2112
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
2073
- //# sourceMappingURL=chunk-ELDSPUTS.js.map
2074
- //# sourceMappingURL=chunk-ELDSPUTS.js.map
2113
+ //# sourceMappingURL=chunk-TXAMWBG5.js.map
2114
+ //# sourceMappingURL=chunk-TXAMWBG5.js.map