@glance-apps/sync 1.6.0 → 1.8.0
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/CHANGELOG.md +175 -1
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/dbEngine.js +154 -6
- package/src/engine.js +29 -3
- package/src/index.js +16 -3
- package/src/providers.js +34 -1
- package/src/vaultClient.js +158 -9
- package/src/vaultConnect.js +391 -0
- package/types/index.d.ts +124 -1
package/types/index.d.ts
CHANGED
|
@@ -27,7 +27,12 @@ export type SyncErrorCode =
|
|
|
27
27
|
| 'VERIFIER_UNSUPPORTED'
|
|
28
28
|
// DB transport: a row-scoped call (incl. the key verifier) was made before the
|
|
29
29
|
// accountId was populated — retryable once the account id is available.
|
|
30
|
-
| 'ACCOUNT_ID_REQUIRED'
|
|
30
|
+
| 'ACCOUNT_ID_REQUIRED'
|
|
31
|
+
// DB transport, per-account auth: the server rejected this device's
|
|
32
|
+
// credential (401 "invalid credential"). Surfaced with isHardStop === true;
|
|
33
|
+
// the engine persists a halt and stops retrying. Recovery (re-enrollment)
|
|
34
|
+
// is Phase 2.2 — nothing in this version clears the halt.
|
|
35
|
+
| 'CREDENTIAL_INVALID';
|
|
31
36
|
|
|
32
37
|
export type SyncStatus = 'idle' | 'uploading' | 'downloading' | 'success' | 'error';
|
|
33
38
|
|
|
@@ -266,6 +271,14 @@ export interface CloudSyncProvider {
|
|
|
266
271
|
export function webdavFetch(config: SyncEngineConfig): WebdavFetch;
|
|
267
272
|
export function createProviders(config: SyncEngineConfig): Record<string, CloudSyncProvider>;
|
|
268
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Normalizes a raw ETag header value for use in If-Match: strips a weak-
|
|
276
|
+
* validator prefix (`W/"abc"` -> `"abc"`) and the content-coding suffixes
|
|
277
|
+
* Apache mod_deflate/mod_brotli append inside the quotes (`"abc-gzip"` /
|
|
278
|
+
* `"abc-br"` -> `"abc"`). Quotes are preserved; null/undefined pass through.
|
|
279
|
+
*/
|
|
280
|
+
export function normalizeEtag(raw: string | null | undefined): string | null | undefined;
|
|
281
|
+
|
|
269
282
|
// ---------------------------------------------------------------------------
|
|
270
283
|
// Sync engine
|
|
271
284
|
// ---------------------------------------------------------------------------
|
|
@@ -413,10 +426,53 @@ export interface VaultClient {
|
|
|
413
426
|
|
|
414
427
|
export function createVaultClient(config: {
|
|
415
428
|
vaultUrl: string;
|
|
429
|
+
/** Shared device token, or the device's enrolled credential in per-account mode. */
|
|
416
430
|
vaultToken: string;
|
|
417
431
|
fetchImpl?: typeof fetch;
|
|
418
432
|
}): VaultClient;
|
|
419
433
|
|
|
434
|
+
// Per-account auth (vault Phase 1.4b).
|
|
435
|
+
|
|
436
|
+
/** Which trust model a GLANCEvault server runs. Discoverable via fetchVaultHealth. */
|
|
437
|
+
export type VaultAuthMode = 'shared' | 'per-account';
|
|
438
|
+
|
|
439
|
+
export interface VaultHealth {
|
|
440
|
+
status: string;
|
|
441
|
+
version: string;
|
|
442
|
+
schemaVersion: number;
|
|
443
|
+
/** Normalized to 'shared' when the server predates the field. */
|
|
444
|
+
authMode: VaultAuthMode;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Unauthenticated GET /healthz. Safe to call before any token exists. */
|
|
448
|
+
export function fetchVaultHealth(config: {
|
|
449
|
+
vaultUrl: string;
|
|
450
|
+
fetchImpl?: typeof fetch;
|
|
451
|
+
}): Promise<VaultHealth>;
|
|
452
|
+
|
|
453
|
+
export interface VaultEnrollment {
|
|
454
|
+
credentialId: string;
|
|
455
|
+
/** Returned once, never retrievable again. Persist it, then discard the bootstrap secret. */
|
|
456
|
+
credential: string;
|
|
457
|
+
accountId: string;
|
|
458
|
+
deviceId: string;
|
|
459
|
+
createdAt: string;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* POST /enroll — exchanges the bootstrap secret for this device's own credential.
|
|
464
|
+
* Rejects with err.code 'ENROLLMENT_REJECTED' (401, secret not accepted),
|
|
465
|
+
* 'ENROLLMENT_UNSUPPORTED' (404, server runs shared mode or predates enrollment),
|
|
466
|
+
* or 'VAULT_ERROR' with err.status for other failures.
|
|
467
|
+
*/
|
|
468
|
+
export function enrollVaultDevice(config: {
|
|
469
|
+
vaultUrl: string;
|
|
470
|
+
enrollmentSecret: string;
|
|
471
|
+
accountId: string;
|
|
472
|
+
deviceId: string;
|
|
473
|
+
fetchImpl?: typeof fetch;
|
|
474
|
+
}): Promise<VaultEnrollment>;
|
|
475
|
+
|
|
420
476
|
export interface DbSyncEngineConfig {
|
|
421
477
|
// Identity / local state
|
|
422
478
|
storageKeyPrefix: string;
|
|
@@ -502,7 +558,74 @@ export interface DbSyncEngine {
|
|
|
502
558
|
isKeyVerified(): boolean;
|
|
503
559
|
hasEncryptionReady(): boolean;
|
|
504
560
|
|
|
561
|
+
/** True while the persisted credential-rejected stop is set (CREDENTIAL_INVALID). */
|
|
562
|
+
isCredentialHalted(): boolean;
|
|
563
|
+
/** The persisted stop record, or null. Cleared only by recoverVaultSyncEngine after a verified successful re-enrollment. */
|
|
564
|
+
getCredentialHalt(): { message: string; at: string } | null;
|
|
565
|
+
/** True once this instance proved its bearer was superseded by recovery and went inert (in-memory only). */
|
|
566
|
+
isSuperseded(): boolean;
|
|
567
|
+
/** The resolved stable device identity this engine runs as. */
|
|
568
|
+
deviceId: string;
|
|
569
|
+
|
|
505
570
|
vault: VaultClient;
|
|
506
571
|
}
|
|
507
572
|
|
|
508
573
|
export function createDbSyncEngine(config: DbSyncEngineConfig): DbSyncEngine;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Package-owned stable device identifier: generated once (crypto.randomUUID)
|
|
577
|
+
* and persisted under `{prefix}-device-id`. Used for both the device cursor
|
|
578
|
+
* and per-account enrollment. An explicit config.deviceId overrides it.
|
|
579
|
+
*/
|
|
580
|
+
export function getOrCreateDeviceId(storageKeyPrefix: string): string;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The packaged vault connect flow: discovers the server's auth mode, resolves
|
|
584
|
+
* the Bearer value (enrolling with the bootstrap secret when the server is
|
|
585
|
+
* per-account and no credential is stored yet), persists the credential under
|
|
586
|
+
* `{prefix}-vault-credential`, and returns a ready engine. The bootstrap
|
|
587
|
+
* secret is never written to storage, never logged, and never retained past
|
|
588
|
+
* the call. No path re-enrolls automatically once a credential is stored.
|
|
589
|
+
*
|
|
590
|
+
* Typed failures: VAULT_TOKEN_REQUIRED, ENROLLMENT_SECRET_REQUIRED,
|
|
591
|
+
* CREDENTIAL_PERSIST_FAILED, VAULT_UNREACHABLE, plus enrollVaultDevice's
|
|
592
|
+
* ENROLLMENT_REJECTED / ENROLLMENT_UNSUPPORTED.
|
|
593
|
+
*/
|
|
594
|
+
export function connectVaultSyncEngine(
|
|
595
|
+
config: Omit<DbSyncEngineConfig, 'vaultToken'> & {
|
|
596
|
+
vaultToken?: string;
|
|
597
|
+
enrollmentSecret?: string;
|
|
598
|
+
}
|
|
599
|
+
): Promise<{
|
|
600
|
+
engine: DbSyncEngine;
|
|
601
|
+
/** null when discovery failed and the flow fell back to the last known auth state. */
|
|
602
|
+
authMode: VaultAuthMode | null;
|
|
603
|
+
/** True only when this call minted a credential. */
|
|
604
|
+
enrolled: boolean;
|
|
605
|
+
deviceId: string;
|
|
606
|
+
}>;
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* The exit from the credential halt (Phase 2.2): USER-INITIATED re-enrollment
|
|
610
|
+
* with the bootstrap secret. Refuses unless the device is halted (NOT_HALTED)
|
|
611
|
+
* and unless the server runs per-account auth (RECOVERY_UNSUPPORTED /
|
|
612
|
+
* VAULT_UNREACHABLE — recovery never falls back on discovery failure). The
|
|
613
|
+
* stale record's deviceId is used for enrollment so the server (2.1) revokes
|
|
614
|
+
* every still-active predecessor; an explicit config.deviceId differing from
|
|
615
|
+
* it is DEVICE_ID_CONFLICT. Order: canary -> enroll -> persist + verify
|
|
616
|
+
* (overwriting the stale record) -> clear the halt LAST -> fresh engine.
|
|
617
|
+
* Every failure leaves the device halted with its state intact. The secret is
|
|
618
|
+
* confined to the call, exactly as in connectVaultSyncEngine. Call only from
|
|
619
|
+
* a deliberate user action; no code path in this package invokes it.
|
|
620
|
+
*/
|
|
621
|
+
export function recoverVaultSyncEngine(
|
|
622
|
+
config: Omit<DbSyncEngineConfig, 'vaultToken'> & {
|
|
623
|
+
vaultToken?: string;
|
|
624
|
+
enrollmentSecret: string;
|
|
625
|
+
}
|
|
626
|
+
): Promise<{
|
|
627
|
+
engine: DbSyncEngine;
|
|
628
|
+
authMode: 'per-account';
|
|
629
|
+
enrolled: true;
|
|
630
|
+
deviceId: string;
|
|
631
|
+
}>;
|