stellar-drive 1.0.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/README.md +607 -0
- package/dist/actions/remoteChange.d.ts +204 -0
- package/dist/actions/remoteChange.d.ts.map +1 -0
- package/dist/actions/remoteChange.js +424 -0
- package/dist/actions/remoteChange.js.map +1 -0
- package/dist/actions/truncateTooltip.d.ts +56 -0
- package/dist/actions/truncateTooltip.d.ts.map +1 -0
- package/dist/actions/truncateTooltip.js +312 -0
- package/dist/actions/truncateTooltip.js.map +1 -0
- package/dist/auth/crypto.d.ts +41 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +50 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/deviceVerification.d.ts +283 -0
- package/dist/auth/deviceVerification.d.ts.map +1 -0
- package/dist/auth/deviceVerification.js +575 -0
- package/dist/auth/deviceVerification.js.map +1 -0
- package/dist/auth/displayUtils.d.ts +98 -0
- package/dist/auth/displayUtils.d.ts.map +1 -0
- package/dist/auth/displayUtils.js +145 -0
- package/dist/auth/displayUtils.js.map +1 -0
- package/dist/auth/loginGuard.d.ts +134 -0
- package/dist/auth/loginGuard.d.ts.map +1 -0
- package/dist/auth/loginGuard.js +276 -0
- package/dist/auth/loginGuard.js.map +1 -0
- package/dist/auth/offlineCredentials.d.ts +105 -0
- package/dist/auth/offlineCredentials.d.ts.map +1 -0
- package/dist/auth/offlineCredentials.js +176 -0
- package/dist/auth/offlineCredentials.js.map +1 -0
- package/dist/auth/offlineSession.d.ts +96 -0
- package/dist/auth/offlineSession.d.ts.map +1 -0
- package/dist/auth/offlineSession.js +145 -0
- package/dist/auth/offlineSession.js.map +1 -0
- package/dist/auth/resolveAuthState.d.ts +85 -0
- package/dist/auth/resolveAuthState.d.ts.map +1 -0
- package/dist/auth/resolveAuthState.js +249 -0
- package/dist/auth/resolveAuthState.js.map +1 -0
- package/dist/auth/singleUser.d.ts +498 -0
- package/dist/auth/singleUser.d.ts.map +1 -0
- package/dist/auth/singleUser.js +1282 -0
- package/dist/auth/singleUser.js.map +1 -0
- package/dist/bin/commands.d.ts +14 -0
- package/dist/bin/commands.d.ts.map +1 -0
- package/dist/bin/commands.js +68 -0
- package/dist/bin/commands.js.map +1 -0
- package/dist/bin/install-pwa.d.ts +41 -0
- package/dist/bin/install-pwa.d.ts.map +1 -0
- package/dist/bin/install-pwa.js +4594 -0
- package/dist/bin/install-pwa.js.map +1 -0
- package/dist/config.d.ts +249 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +395 -0
- package/dist/config.js.map +1 -0
- package/dist/conflicts.d.ts +306 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +807 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/crdt/awareness.d.ts +128 -0
- package/dist/crdt/awareness.d.ts.map +1 -0
- package/dist/crdt/awareness.js +284 -0
- package/dist/crdt/awareness.js.map +1 -0
- package/dist/crdt/channel.d.ts +165 -0
- package/dist/crdt/channel.d.ts.map +1 -0
- package/dist/crdt/channel.js +522 -0
- package/dist/crdt/channel.js.map +1 -0
- package/dist/crdt/config.d.ts +58 -0
- package/dist/crdt/config.d.ts.map +1 -0
- package/dist/crdt/config.js +123 -0
- package/dist/crdt/config.js.map +1 -0
- package/dist/crdt/helpers.d.ts +104 -0
- package/dist/crdt/helpers.d.ts.map +1 -0
- package/dist/crdt/helpers.js +116 -0
- package/dist/crdt/helpers.js.map +1 -0
- package/dist/crdt/offline.d.ts +58 -0
- package/dist/crdt/offline.d.ts.map +1 -0
- package/dist/crdt/offline.js +130 -0
- package/dist/crdt/offline.js.map +1 -0
- package/dist/crdt/persistence.d.ts +65 -0
- package/dist/crdt/persistence.d.ts.map +1 -0
- package/dist/crdt/persistence.js +171 -0
- package/dist/crdt/persistence.js.map +1 -0
- package/dist/crdt/provider.d.ts +109 -0
- package/dist/crdt/provider.d.ts.map +1 -0
- package/dist/crdt/provider.js +543 -0
- package/dist/crdt/provider.js.map +1 -0
- package/dist/crdt/store.d.ts +111 -0
- package/dist/crdt/store.d.ts.map +1 -0
- package/dist/crdt/store.js +158 -0
- package/dist/crdt/store.js.map +1 -0
- package/dist/crdt/types.d.ts +281 -0
- package/dist/crdt/types.d.ts.map +1 -0
- package/dist/crdt/types.js +26 -0
- package/dist/crdt/types.js.map +1 -0
- package/dist/data.d.ts +502 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +862 -0
- package/dist/data.js.map +1 -0
- package/dist/database.d.ts +153 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +325 -0
- package/dist/database.js.map +1 -0
- package/dist/debug.d.ts +87 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +135 -0
- package/dist/debug.js.map +1 -0
- package/dist/demo.d.ts +131 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +168 -0
- package/dist/demo.js.map +1 -0
- package/dist/deviceId.d.ts +47 -0
- package/dist/deviceId.d.ts.map +1 -0
- package/dist/deviceId.js +106 -0
- package/dist/deviceId.js.map +1 -0
- package/dist/diagnostics.d.ts +292 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +378 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine.d.ts +230 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +2636 -0
- package/dist/engine.js.map +1 -0
- package/dist/entries/actions.d.ts +16 -0
- package/dist/entries/actions.d.ts.map +1 -0
- package/dist/entries/actions.js +29 -0
- package/dist/entries/actions.js.map +1 -0
- package/dist/entries/auth.d.ts +19 -0
- package/dist/entries/auth.d.ts.map +1 -0
- package/dist/entries/auth.js +50 -0
- package/dist/entries/auth.js.map +1 -0
- package/dist/entries/config.d.ts +15 -0
- package/dist/entries/config.d.ts.map +1 -0
- package/dist/entries/config.js +20 -0
- package/dist/entries/config.js.map +1 -0
- package/dist/entries/crdt.d.ts +32 -0
- package/dist/entries/crdt.d.ts.map +1 -0
- package/dist/entries/crdt.js +52 -0
- package/dist/entries/crdt.js.map +1 -0
- package/dist/entries/kit.d.ts +22 -0
- package/dist/entries/kit.d.ts.map +1 -0
- package/dist/entries/kit.js +58 -0
- package/dist/entries/kit.js.map +1 -0
- package/dist/entries/stores.d.ts +22 -0
- package/dist/entries/stores.d.ts.map +1 -0
- package/dist/entries/stores.js +57 -0
- package/dist/entries/stores.js.map +1 -0
- package/dist/entries/types.d.ts +23 -0
- package/dist/entries/types.d.ts.map +1 -0
- package/dist/entries/types.js +12 -0
- package/dist/entries/types.js.map +1 -0
- package/dist/entries/utils.d.ts +12 -0
- package/dist/entries/utils.d.ts.map +1 -0
- package/dist/entries/utils.js +42 -0
- package/dist/entries/utils.js.map +1 -0
- package/dist/entries/vite.d.ts +20 -0
- package/dist/entries/vite.d.ts.map +1 -0
- package/dist/entries/vite.js +26 -0
- package/dist/entries/vite.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +234 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/auth.d.ts +80 -0
- package/dist/kit/auth.d.ts.map +1 -0
- package/dist/kit/auth.js +75 -0
- package/dist/kit/auth.js.map +1 -0
- package/dist/kit/confirm.d.ts +111 -0
- package/dist/kit/confirm.d.ts.map +1 -0
- package/dist/kit/confirm.js +169 -0
- package/dist/kit/confirm.js.map +1 -0
- package/dist/kit/loads.d.ts +187 -0
- package/dist/kit/loads.d.ts.map +1 -0
- package/dist/kit/loads.js +208 -0
- package/dist/kit/loads.js.map +1 -0
- package/dist/kit/server.d.ts +175 -0
- package/dist/kit/server.d.ts.map +1 -0
- package/dist/kit/server.js +297 -0
- package/dist/kit/server.js.map +1 -0
- package/dist/kit/sw.d.ts +176 -0
- package/dist/kit/sw.d.ts.map +1 -0
- package/dist/kit/sw.js +320 -0
- package/dist/kit/sw.js.map +1 -0
- package/dist/queue.d.ts +306 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +925 -0
- package/dist/queue.js.map +1 -0
- package/dist/realtime.d.ts +280 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +1031 -0
- package/dist/realtime.js.map +1 -0
- package/dist/runtime/runtimeConfig.d.ts +110 -0
- package/dist/runtime/runtimeConfig.d.ts.map +1 -0
- package/dist/runtime/runtimeConfig.js +260 -0
- package/dist/runtime/runtimeConfig.js.map +1 -0
- package/dist/schema.d.ts +150 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +891 -0
- package/dist/schema.js.map +1 -0
- package/dist/stores/authState.d.ts +204 -0
- package/dist/stores/authState.d.ts.map +1 -0
- package/dist/stores/authState.js +336 -0
- package/dist/stores/authState.js.map +1 -0
- package/dist/stores/factories.d.ts +140 -0
- package/dist/stores/factories.d.ts.map +1 -0
- package/dist/stores/factories.js +157 -0
- package/dist/stores/factories.js.map +1 -0
- package/dist/stores/network.d.ts +48 -0
- package/dist/stores/network.d.ts.map +1 -0
- package/dist/stores/network.js +261 -0
- package/dist/stores/network.js.map +1 -0
- package/dist/stores/remoteChanges.d.ts +417 -0
- package/dist/stores/remoteChanges.d.ts.map +1 -0
- package/dist/stores/remoteChanges.js +626 -0
- package/dist/stores/remoteChanges.js.map +1 -0
- package/dist/stores/sync.d.ts +165 -0
- package/dist/stores/sync.d.ts.map +1 -0
- package/dist/stores/sync.js +275 -0
- package/dist/stores/sync.js.map +1 -0
- package/dist/supabase/auth.d.ts +219 -0
- package/dist/supabase/auth.d.ts.map +1 -0
- package/dist/supabase/auth.js +459 -0
- package/dist/supabase/auth.js.map +1 -0
- package/dist/supabase/client.d.ts +88 -0
- package/dist/supabase/client.d.ts.map +1 -0
- package/dist/supabase/client.js +313 -0
- package/dist/supabase/client.js.map +1 -0
- package/dist/supabase/validate.d.ts +118 -0
- package/dist/supabase/validate.d.ts.map +1 -0
- package/dist/supabase/validate.js +208 -0
- package/dist/supabase/validate.js.map +1 -0
- package/dist/sw/build/vite-plugin.d.ts +149 -0
- package/dist/sw/build/vite-plugin.d.ts.map +1 -0
- package/dist/sw/build/vite-plugin.js +517 -0
- package/dist/sw/build/vite-plugin.js.map +1 -0
- package/dist/sw/sw.js +664 -0
- package/dist/types.d.ts +363 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +85 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +156 -0
- package/dist/utils.js.map +1 -0
- package/package.json +117 -0
- package/src/components/DeferredChangesBanner.svelte +477 -0
- package/src/components/DemoBanner.svelte +110 -0
- package/src/components/SyncStatus.svelte +1732 -0
package/dist/debug.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Debug Logging Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides opt-in debug logging gated by a localStorage flag. When debug
|
|
5
|
+
* mode is enabled (`localStorage.<prefix>_debug_mode === 'true'`), all
|
|
6
|
+
* debug calls forward to the browser console. When disabled, they are
|
|
7
|
+
* silently dropped — zero runtime cost.
|
|
8
|
+
*
|
|
9
|
+
* The prefix is configurable via {@link _setDebugPrefix} (set by
|
|
10
|
+
* {@link config.ts#initEngine}) so multiple engine instances on the same
|
|
11
|
+
* origin don't collide.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Enable debug mode from the browser console:
|
|
15
|
+
* localStorage.setItem('myapp_debug_mode', 'true');
|
|
16
|
+
*
|
|
17
|
+
* // Or programmatically:
|
|
18
|
+
* import { setDebugMode } from 'stellar-drive';
|
|
19
|
+
* setDebugMode(true);
|
|
20
|
+
*/
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Internal State
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/** Cached result of the localStorage check (avoids repeated reads). */
|
|
25
|
+
let debugEnabled = null;
|
|
26
|
+
/** Configurable prefix for the localStorage key (default: `'stellar'`). */
|
|
27
|
+
let debugPrefix = 'stellar';
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Internal Helpers
|
|
30
|
+
// =============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Set the prefix used for the localStorage debug flag key.
|
|
33
|
+
*
|
|
34
|
+
* Called internally by {@link config.ts#initEngine} — not part of the
|
|
35
|
+
* public API.
|
|
36
|
+
*
|
|
37
|
+
* @param prefix - Application-specific prefix (e.g., `'myapp'`).
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export function _setDebugPrefix(prefix) {
|
|
41
|
+
debugPrefix = prefix;
|
|
42
|
+
}
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Public API
|
|
45
|
+
// =============================================================================
|
|
46
|
+
/**
|
|
47
|
+
* Check whether debug mode is currently enabled.
|
|
48
|
+
*
|
|
49
|
+
* Reads `localStorage.<prefix>_debug_mode` on the first call and caches
|
|
50
|
+
* the result for subsequent calls. Returns `false` in SSR environments
|
|
51
|
+
* where `localStorage` is unavailable.
|
|
52
|
+
*
|
|
53
|
+
* @returns `true` if debug logging is active.
|
|
54
|
+
*/
|
|
55
|
+
export function isDebugMode() {
|
|
56
|
+
if (debugEnabled !== null)
|
|
57
|
+
return debugEnabled;
|
|
58
|
+
debugEnabled =
|
|
59
|
+
typeof localStorage !== 'undefined' &&
|
|
60
|
+
localStorage.getItem(`${debugPrefix}_debug_mode`) === 'true';
|
|
61
|
+
return debugEnabled;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Enable or disable debug mode at runtime.
|
|
65
|
+
*
|
|
66
|
+
* Persists the setting to localStorage so it survives page reloads.
|
|
67
|
+
*
|
|
68
|
+
* @param enabled - `true` to enable debug logging, `false` to disable.
|
|
69
|
+
*/
|
|
70
|
+
export function setDebugMode(enabled) {
|
|
71
|
+
debugEnabled = enabled;
|
|
72
|
+
localStorage.setItem(`${debugPrefix}_debug_mode`, enabled ? 'true' : 'false');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Log a debug message at the `console.log` level.
|
|
76
|
+
*
|
|
77
|
+
* No-op when debug mode is disabled.
|
|
78
|
+
*
|
|
79
|
+
* @param args - Arguments forwarded to `console.log`.
|
|
80
|
+
*/
|
|
81
|
+
export function debugLog(...args) {
|
|
82
|
+
if (isDebugMode())
|
|
83
|
+
console.log(...args);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Log a debug message at the `console.warn` level.
|
|
87
|
+
*
|
|
88
|
+
* No-op when debug mode is disabled.
|
|
89
|
+
*
|
|
90
|
+
* @param args - Arguments forwarded to `console.warn`.
|
|
91
|
+
*/
|
|
92
|
+
export function debugWarn(...args) {
|
|
93
|
+
if (isDebugMode())
|
|
94
|
+
console.warn(...args);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Log a debug message at the `console.error` level.
|
|
98
|
+
*
|
|
99
|
+
* No-op when debug mode is disabled.
|
|
100
|
+
*
|
|
101
|
+
* @param args - Arguments forwarded to `console.error`.
|
|
102
|
+
*/
|
|
103
|
+
export function debugError(...args) {
|
|
104
|
+
if (isDebugMode())
|
|
105
|
+
console.error(...args);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Unified debug logging function with configurable severity level.
|
|
109
|
+
*
|
|
110
|
+
* Replaces the individual `debugLog` / `debugWarn` / `debugError` calls
|
|
111
|
+
* when a single import is preferred.
|
|
112
|
+
*
|
|
113
|
+
* @param level - Console severity: `'log'`, `'warn'`, or `'error'`.
|
|
114
|
+
* @param args - Arguments forwarded to the corresponding `console` method.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* debug('log', '[SYNC] Starting push...');
|
|
118
|
+
* debug('error', '[SYNC] Push failed:', error);
|
|
119
|
+
*/
|
|
120
|
+
export function debug(level, ...args) {
|
|
121
|
+
if (!isDebugMode())
|
|
122
|
+
return;
|
|
123
|
+
switch (level) {
|
|
124
|
+
case 'log':
|
|
125
|
+
console.log(...args);
|
|
126
|
+
break;
|
|
127
|
+
case 'warn':
|
|
128
|
+
console.warn(...args);
|
|
129
|
+
break;
|
|
130
|
+
case 'error':
|
|
131
|
+
console.error(...args);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,gFAAgF;AAChF,iBAAiB;AACjB,gFAAgF;AAEhF,uEAAuE;AACvE,IAAI,YAAY,GAAmB,IAAI,CAAC;AAExC,2EAA2E;AAC3E,IAAI,WAAW,GAAG,SAAS,CAAC;AAE5B,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,WAAW,GAAG,MAAM,CAAC;AACvB,CAAC;AAED,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW;IACzB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,YAAY,CAAC;IAC/C,YAAY;QACV,OAAO,YAAY,KAAK,WAAW;YACnC,YAAY,CAAC,OAAO,CAAC,GAAG,WAAW,aAAa,CAAC,KAAK,MAAM,CAAC;IAC/D,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,YAAY,GAAG,OAAO,CAAC;IACvB,YAAY,CAAC,OAAO,CAAC,GAAG,WAAW,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAG,IAAe;IACzC,IAAI,WAAW,EAAE;QAAE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAG,IAAe;IAC1C,IAAI,WAAW,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,GAAG,IAAe;IAC3C,IAAI,WAAW,EAAE;QAAE,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,KAAK,CAAC,KAA+B,EAAE,GAAG,IAAe;IACvE,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO;IAC3B,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,KAAK;YACR,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;YACrB,MAAM;QACR,KAAK,MAAM;YACT,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;YACtB,MAAM;QACR,KAAK,OAAO;YACV,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;YACvB,MAAM;IACV,CAAC;AACH,CAAC"}
|
package/dist/demo.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Demo Mode State Module
|
|
3
|
+
*
|
|
4
|
+
* Provides a completely isolated sandbox for consumer PWA apps. When demo mode
|
|
5
|
+
* is active, the app uses a separate Dexie database (`${name}_demo`), makes
|
|
6
|
+
* zero Supabase connections, and skips all sync/auth/email/device-verification
|
|
7
|
+
* flows. Writes go to the sandboxed DB and are dropped on page refresh (mock
|
|
8
|
+
* data is re-seeded).
|
|
9
|
+
*
|
|
10
|
+
* **Security model:**
|
|
11
|
+
* Demo mode does NOT "bypass" auth — it replaces the entire data layer.
|
|
12
|
+
* The real database is never opened. No Supabase client is created. If someone
|
|
13
|
+
* manually sets the localStorage flag on a real instance, they see an empty or
|
|
14
|
+
* seeded demo DB — there is no path to real user data.
|
|
15
|
+
*
|
|
16
|
+
* Callers of `setDemoMode()` must trigger a **full page reload**
|
|
17
|
+
* (`window.location.href`) to ensure complete engine teardown and
|
|
18
|
+
* reinitialization with the correct database.
|
|
19
|
+
*
|
|
20
|
+
* @module demo
|
|
21
|
+
* @see {@link config.ts} for demo DB name switching in `initEngine()`
|
|
22
|
+
* @see {@link engine.ts} for sync guards that check `isDemoMode()`
|
|
23
|
+
*/
|
|
24
|
+
import Dexie from 'dexie';
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for demo mode, provided by the consumer app.
|
|
27
|
+
*
|
|
28
|
+
* Contains a callback to seed mock data and a mock user profile for the
|
|
29
|
+
* demo session. Registered via `initEngine({ demo: demoConfig })`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const demoConfig: DemoConfig = {
|
|
34
|
+
* seedData: async (db) => {
|
|
35
|
+
* await db.table('items').bulkPut([
|
|
36
|
+
* { id: '1', name: 'Sample Item', ... },
|
|
37
|
+
* ]);
|
|
38
|
+
* },
|
|
39
|
+
* mockProfile: {
|
|
40
|
+
* email: 'demo@example.com',
|
|
41
|
+
* firstName: 'Demo',
|
|
42
|
+
* lastName: 'User',
|
|
43
|
+
* },
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface DemoConfig {
|
|
48
|
+
/** Consumer callback that populates the demo Dexie DB with mock data. */
|
|
49
|
+
seedData: (db: Dexie) => Promise<void>;
|
|
50
|
+
/** Mock user profile for the demo session. */
|
|
51
|
+
mockProfile: {
|
|
52
|
+
email: string;
|
|
53
|
+
firstName: string;
|
|
54
|
+
lastName: string;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check whether demo mode is currently active.
|
|
60
|
+
*
|
|
61
|
+
* SSR-safe: returns `false` on the server (no `localStorage` access).
|
|
62
|
+
*
|
|
63
|
+
* @returns `true` if the demo mode localStorage flag is set.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isDemoMode(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Activate or deactivate demo mode.
|
|
68
|
+
*
|
|
69
|
+
* Sets a localStorage flag that is read during engine initialization.
|
|
70
|
+
* **The caller must trigger a full page reload** after calling this
|
|
71
|
+
* to ensure the engine reinitializes with the correct (demo or real) database.
|
|
72
|
+
*
|
|
73
|
+
* @param enabled - `true` to enter demo mode, `false` to exit.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* setDemoMode(true);
|
|
78
|
+
* window.location.href = '/';
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function setDemoMode(enabled: boolean): void;
|
|
82
|
+
/**
|
|
83
|
+
* Register the demo configuration.
|
|
84
|
+
*
|
|
85
|
+
* Called by `initEngine()` when the consumer provides a `demo` config.
|
|
86
|
+
*
|
|
87
|
+
* @param config - The demo configuration from the consumer app.
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export declare function registerDemoConfig(config: DemoConfig): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get the currently registered demo configuration.
|
|
93
|
+
*
|
|
94
|
+
* @returns The demo config, or `null` if none is registered.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getDemoConfig(): DemoConfig | null;
|
|
97
|
+
/**
|
|
98
|
+
* Set the prefix used for the demo mode localStorage key.
|
|
99
|
+
*
|
|
100
|
+
* Called by `initEngine()` to propagate the app prefix.
|
|
101
|
+
*
|
|
102
|
+
* @param prefix - The application prefix (e.g. `'myapp'`).
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export declare function _setDemoPrefix(prefix: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Seed the demo database with mock data.
|
|
108
|
+
*
|
|
109
|
+
* Idempotent per page load: no-ops if data has already been seeded
|
|
110
|
+
* (prevents re-seeding on SvelteKit client-side navigations).
|
|
111
|
+
*
|
|
112
|
+
* Steps:
|
|
113
|
+
* 1. Check `_demoSeeded` flag — return if already seeded.
|
|
114
|
+
* 2. Clear all app tables (using engine config's table definitions).
|
|
115
|
+
* 3. Clear system tables (`syncQueue`, `conflictHistory`).
|
|
116
|
+
* 4. Call the consumer's `seedData(db)` callback.
|
|
117
|
+
* 5. Set `_demoSeeded = true`.
|
|
118
|
+
*
|
|
119
|
+
* @throws {Error} If no demo config is registered.
|
|
120
|
+
*/
|
|
121
|
+
export declare function seedDemoData(): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Delete the demo Dexie database entirely.
|
|
124
|
+
*
|
|
125
|
+
* Called when deactivating demo mode to clean up the sandboxed database.
|
|
126
|
+
* The caller should trigger a full page reload after this.
|
|
127
|
+
*
|
|
128
|
+
* @param dbName - The name of the demo database to delete (e.g. `'myapp-db_demo'`).
|
|
129
|
+
*/
|
|
130
|
+
export declare function cleanupDemoDatabase(dbName: string): Promise<void>;
|
|
131
|
+
//# sourceMappingURL=demo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,QAAQ,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,8CAA8C;IAC9C,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAmBD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAGpC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAOlD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAgClD;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvE"}
|
package/dist/demo.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Demo Mode State Module
|
|
3
|
+
*
|
|
4
|
+
* Provides a completely isolated sandbox for consumer PWA apps. When demo mode
|
|
5
|
+
* is active, the app uses a separate Dexie database (`${name}_demo`), makes
|
|
6
|
+
* zero Supabase connections, and skips all sync/auth/email/device-verification
|
|
7
|
+
* flows. Writes go to the sandboxed DB and are dropped on page refresh (mock
|
|
8
|
+
* data is re-seeded).
|
|
9
|
+
*
|
|
10
|
+
* **Security model:**
|
|
11
|
+
* Demo mode does NOT "bypass" auth — it replaces the entire data layer.
|
|
12
|
+
* The real database is never opened. No Supabase client is created. If someone
|
|
13
|
+
* manually sets the localStorage flag on a real instance, they see an empty or
|
|
14
|
+
* seeded demo DB — there is no path to real user data.
|
|
15
|
+
*
|
|
16
|
+
* Callers of `setDemoMode()` must trigger a **full page reload**
|
|
17
|
+
* (`window.location.href`) to ensure complete engine teardown and
|
|
18
|
+
* reinitialization with the correct database.
|
|
19
|
+
*
|
|
20
|
+
* @module demo
|
|
21
|
+
* @see {@link config.ts} for demo DB name switching in `initEngine()`
|
|
22
|
+
* @see {@link engine.ts} for sync guards that check `isDemoMode()`
|
|
23
|
+
*/
|
|
24
|
+
import Dexie from 'dexie';
|
|
25
|
+
import { getDb } from './database';
|
|
26
|
+
import { getEngineConfig, getDexieTableFor } from './config';
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Module State
|
|
29
|
+
// =============================================================================
|
|
30
|
+
/** The registered demo configuration (set via `registerDemoConfig`). */
|
|
31
|
+
let _demoConfig = null;
|
|
32
|
+
/** Whether demo data has been seeded in this page load (prevents re-seeding). */
|
|
33
|
+
let _demoSeeded = false;
|
|
34
|
+
/** The app prefix, used to namespace the localStorage demo flag. */
|
|
35
|
+
let _demoPrefix = '';
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Public API
|
|
38
|
+
// =============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Check whether demo mode is currently active.
|
|
41
|
+
*
|
|
42
|
+
* SSR-safe: returns `false` on the server (no `localStorage` access).
|
|
43
|
+
*
|
|
44
|
+
* @returns `true` if the demo mode localStorage flag is set.
|
|
45
|
+
*/
|
|
46
|
+
export function isDemoMode() {
|
|
47
|
+
if (typeof localStorage === 'undefined')
|
|
48
|
+
return false;
|
|
49
|
+
return localStorage.getItem(`${_demoPrefix}_demo_mode`) === 'true';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Activate or deactivate demo mode.
|
|
53
|
+
*
|
|
54
|
+
* Sets a localStorage flag that is read during engine initialization.
|
|
55
|
+
* **The caller must trigger a full page reload** after calling this
|
|
56
|
+
* to ensure the engine reinitializes with the correct (demo or real) database.
|
|
57
|
+
*
|
|
58
|
+
* @param enabled - `true` to enter demo mode, `false` to exit.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* setDemoMode(true);
|
|
63
|
+
* window.location.href = '/';
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function setDemoMode(enabled) {
|
|
67
|
+
if (typeof localStorage === 'undefined')
|
|
68
|
+
return;
|
|
69
|
+
if (enabled) {
|
|
70
|
+
localStorage.setItem(`${_demoPrefix}_demo_mode`, 'true');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
localStorage.removeItem(`${_demoPrefix}_demo_mode`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Register the demo configuration.
|
|
78
|
+
*
|
|
79
|
+
* Called by `initEngine()` when the consumer provides a `demo` config.
|
|
80
|
+
*
|
|
81
|
+
* @param config - The demo configuration from the consumer app.
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export function registerDemoConfig(config) {
|
|
85
|
+
_demoConfig = config;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the currently registered demo configuration.
|
|
89
|
+
*
|
|
90
|
+
* @returns The demo config, or `null` if none is registered.
|
|
91
|
+
*/
|
|
92
|
+
export function getDemoConfig() {
|
|
93
|
+
return _demoConfig;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Set the prefix used for the demo mode localStorage key.
|
|
97
|
+
*
|
|
98
|
+
* Called by `initEngine()` to propagate the app prefix.
|
|
99
|
+
*
|
|
100
|
+
* @param prefix - The application prefix (e.g. `'myapp'`).
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
export function _setDemoPrefix(prefix) {
|
|
104
|
+
_demoPrefix = prefix;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Seed the demo database with mock data.
|
|
108
|
+
*
|
|
109
|
+
* Idempotent per page load: no-ops if data has already been seeded
|
|
110
|
+
* (prevents re-seeding on SvelteKit client-side navigations).
|
|
111
|
+
*
|
|
112
|
+
* Steps:
|
|
113
|
+
* 1. Check `_demoSeeded` flag — return if already seeded.
|
|
114
|
+
* 2. Clear all app tables (using engine config's table definitions).
|
|
115
|
+
* 3. Clear system tables (`syncQueue`, `conflictHistory`).
|
|
116
|
+
* 4. Call the consumer's `seedData(db)` callback.
|
|
117
|
+
* 5. Set `_demoSeeded = true`.
|
|
118
|
+
*
|
|
119
|
+
* @throws {Error} If no demo config is registered.
|
|
120
|
+
*/
|
|
121
|
+
export async function seedDemoData() {
|
|
122
|
+
if (_demoSeeded)
|
|
123
|
+
return;
|
|
124
|
+
if (!_demoConfig) {
|
|
125
|
+
throw new Error('No demo config registered. Pass `demo` to initEngine().');
|
|
126
|
+
}
|
|
127
|
+
const db = getDb();
|
|
128
|
+
const config = getEngineConfig();
|
|
129
|
+
/* Clear all app tables */
|
|
130
|
+
for (const table of config.tables) {
|
|
131
|
+
const dexieName = getDexieTableFor(table);
|
|
132
|
+
try {
|
|
133
|
+
await db.table(dexieName).clear();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* Table may not exist in the demo DB — safe to ignore. */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/* Clear system tables */
|
|
140
|
+
for (const systemTable of ['syncQueue', 'conflictHistory']) {
|
|
141
|
+
try {
|
|
142
|
+
await db.table(systemTable).clear();
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* Safe to ignore if table doesn't exist. */
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/* Call consumer's seed function */
|
|
149
|
+
await _demoConfig.seedData(db);
|
|
150
|
+
_demoSeeded = true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Delete the demo Dexie database entirely.
|
|
154
|
+
*
|
|
155
|
+
* Called when deactivating demo mode to clean up the sandboxed database.
|
|
156
|
+
* The caller should trigger a full page reload after this.
|
|
157
|
+
*
|
|
158
|
+
* @param dbName - The name of the demo database to delete (e.g. `'myapp-db_demo'`).
|
|
159
|
+
*/
|
|
160
|
+
export async function cleanupDemoDatabase(dbName) {
|
|
161
|
+
try {
|
|
162
|
+
await Dexie.delete(dbName);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* Ignore deletion errors — the DB may not exist. */
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=demo.js.map
|
package/dist/demo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAyC7D,gFAAgF;AAChF,eAAe;AACf,gFAAgF;AAEhF,wEAAwE;AACxE,IAAI,WAAW,GAAsB,IAAI,CAAC;AAE1C,iFAAiF;AACjF,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB,oEAAoE;AACpE,IAAI,WAAW,GAAG,EAAE,CAAC;AAErB,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,WAAW,YAAY,CAAC,KAAK,MAAM,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAO;IAChD,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,CAAC,OAAO,CAAC,GAAG,WAAW,YAAY,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,YAAY,CAAC,UAAU,CAAC,GAAG,WAAW,YAAY,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,WAAW,GAAG,MAAM,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,WAAW,GAAG,MAAM,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,WAAW;QAAE,OAAO;IACxB,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IAEjC,0BAA0B;IAC1B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,WAAW,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,EAAE,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE/B,WAAW,GAAG,IAAI,CAAC;AACrB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc;IACtD,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stable Device Identifier Management
|
|
3
|
+
*
|
|
4
|
+
* Generates and persists a unique device identifier in localStorage. The ID
|
|
5
|
+
* survives page reloads and browser restarts, providing a stable "fingerprint"
|
|
6
|
+
* for the current browser profile.
|
|
7
|
+
*
|
|
8
|
+
* The device ID serves two critical roles in the sync engine:
|
|
9
|
+
* 1. **Echo suppression** — Realtime subscription payloads include `device_id`,
|
|
10
|
+
* allowing the engine to skip changes that originated from this device.
|
|
11
|
+
* 2. **Deterministic conflict tiebreaker** — When two operations have identical
|
|
12
|
+
* timestamps, the lexicographically lower `device_id` wins. This ensures
|
|
13
|
+
* all devices resolve the same conflict the same way.
|
|
14
|
+
*
|
|
15
|
+
* The localStorage key is prefixed (e.g., `myapp_device_id`) so multiple engine
|
|
16
|
+
* instances on the same origin don't collide.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link conflicts.ts#resolveByTimestamp} for the tiebreaker logic
|
|
19
|
+
* @see {@link realtime.ts#isOwnDeviceChange} for echo suppression
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Set the prefix used for the localStorage device ID key.
|
|
23
|
+
*
|
|
24
|
+
* Called internally by {@link config.ts#initEngine} — not part of the
|
|
25
|
+
* public API.
|
|
26
|
+
*
|
|
27
|
+
* @param prefix - Application-specific prefix (e.g., `'myapp'`).
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function _setDeviceIdPrefix(prefix: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get or create a stable device identifier for this browser/device.
|
|
33
|
+
*
|
|
34
|
+
* On the first call, generates a random UUID v4 and persists it to
|
|
35
|
+
* localStorage. Subsequent calls return the cached value.
|
|
36
|
+
*
|
|
37
|
+
* Returns `'ssr-placeholder'` in SSR contexts (no localStorage) — the
|
|
38
|
+
* placeholder is never used for real sync operations since the engine
|
|
39
|
+
* only runs client-side.
|
|
40
|
+
*
|
|
41
|
+
* @returns A UUID v4 string identifying this device.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const id = getDeviceId(); // e.g., "a3f2b1c4-..."
|
|
45
|
+
*/
|
|
46
|
+
export declare function getDeviceId(): string;
|
|
47
|
+
//# sourceMappingURL=deviceId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceId.d.ts","sourceRoot":"","sources":["../src/deviceId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAaH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,QAEhD;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAcpC"}
|
package/dist/deviceId.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stable Device Identifier Management
|
|
3
|
+
*
|
|
4
|
+
* Generates and persists a unique device identifier in localStorage. The ID
|
|
5
|
+
* survives page reloads and browser restarts, providing a stable "fingerprint"
|
|
6
|
+
* for the current browser profile.
|
|
7
|
+
*
|
|
8
|
+
* The device ID serves two critical roles in the sync engine:
|
|
9
|
+
* 1. **Echo suppression** — Realtime subscription payloads include `device_id`,
|
|
10
|
+
* allowing the engine to skip changes that originated from this device.
|
|
11
|
+
* 2. **Deterministic conflict tiebreaker** — When two operations have identical
|
|
12
|
+
* timestamps, the lexicographically lower `device_id` wins. This ensures
|
|
13
|
+
* all devices resolve the same conflict the same way.
|
|
14
|
+
*
|
|
15
|
+
* The localStorage key is prefixed (e.g., `myapp_device_id`) so multiple engine
|
|
16
|
+
* instances on the same origin don't collide.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link conflicts.ts#resolveByTimestamp} for the tiebreaker logic
|
|
19
|
+
* @see {@link realtime.ts#isOwnDeviceChange} for echo suppression
|
|
20
|
+
*/
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Internal State
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/** Configurable prefix for the localStorage key (default: `'stellar'`). */
|
|
25
|
+
let _deviceIdPrefix = 'stellar';
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Internal Helpers
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Set the prefix used for the localStorage device ID key.
|
|
31
|
+
*
|
|
32
|
+
* Called internally by {@link config.ts#initEngine} — not part of the
|
|
33
|
+
* public API.
|
|
34
|
+
*
|
|
35
|
+
* @param prefix - Application-specific prefix (e.g., `'myapp'`).
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function _setDeviceIdPrefix(prefix) {
|
|
39
|
+
_deviceIdPrefix = prefix;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the full localStorage key for the device ID.
|
|
43
|
+
*
|
|
44
|
+
* @returns The prefixed key string (e.g., `'myapp_device_id'`).
|
|
45
|
+
*/
|
|
46
|
+
function getDeviceIdKey() {
|
|
47
|
+
return `${_deviceIdPrefix}_device_id`;
|
|
48
|
+
}
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Public API
|
|
51
|
+
// =============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Get or create a stable device identifier for this browser/device.
|
|
54
|
+
*
|
|
55
|
+
* On the first call, generates a random UUID v4 and persists it to
|
|
56
|
+
* localStorage. Subsequent calls return the cached value.
|
|
57
|
+
*
|
|
58
|
+
* Returns `'ssr-placeholder'` in SSR contexts (no localStorage) — the
|
|
59
|
+
* placeholder is never used for real sync operations since the engine
|
|
60
|
+
* only runs client-side.
|
|
61
|
+
*
|
|
62
|
+
* @returns A UUID v4 string identifying this device.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const id = getDeviceId(); // e.g., "a3f2b1c4-..."
|
|
66
|
+
*/
|
|
67
|
+
export function getDeviceId() {
|
|
68
|
+
if (typeof localStorage === 'undefined') {
|
|
69
|
+
/* SSR context — return a placeholder that won't be used for sync. */
|
|
70
|
+
return 'ssr-placeholder';
|
|
71
|
+
}
|
|
72
|
+
let deviceId = localStorage.getItem(getDeviceIdKey());
|
|
73
|
+
if (!deviceId) {
|
|
74
|
+
deviceId = generateUUID();
|
|
75
|
+
localStorage.setItem(getDeviceIdKey(), deviceId);
|
|
76
|
+
}
|
|
77
|
+
return deviceId;
|
|
78
|
+
}
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// UUID Generation
|
|
81
|
+
// =============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Generate a UUID v4 (random UUID).
|
|
84
|
+
*
|
|
85
|
+
* Prefers the native `crypto.randomUUID()` API (available in modern browsers
|
|
86
|
+
* and Node 19+). Falls back to a manual implementation using `Math.random()`
|
|
87
|
+
* for older environments.
|
|
88
|
+
*
|
|
89
|
+
* @returns A lowercase UUID v4 string (e.g., `"550e8400-e29b-41d4-a716-446655440000"`).
|
|
90
|
+
*/
|
|
91
|
+
function generateUUID() {
|
|
92
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
93
|
+
return crypto.randomUUID();
|
|
94
|
+
}
|
|
95
|
+
/*
|
|
96
|
+
* Fallback for older browsers that lack crypto.randomUUID().
|
|
97
|
+
* Replaces 'x' with a random hex digit and 'y' with a digit
|
|
98
|
+
* constrained to the UUID v4 variant bits (8, 9, a, or b).
|
|
99
|
+
*/
|
|
100
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
101
|
+
const r = (Math.random() * 16) | 0;
|
|
102
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
103
|
+
return v.toString(16);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=deviceId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceId.js","sourceRoot":"","sources":["../src/deviceId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,gFAAgF;AAChF,iBAAiB;AACjB,gFAAgF;AAEhF,2EAA2E;AAC3E,IAAI,eAAe,GAAG,SAAS,CAAC;AAEhC,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,eAAe,GAAG,MAAM,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,OAAO,GAAG,eAAe,YAAY,CAAC;AACxC,CAAC;AAED,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW;IACzB,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,qEAAqE;QACrE,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,IAAI,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAEtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,YAAY,EAAE,CAAC;QAC1B,YAAY,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gFAAgF;AAChF,kBAAkB;AAClB,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,SAAS,YAAY;IACnB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QACnE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1C,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC"}
|