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
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Supabase Authentication Module
|
|
3
|
+
*
|
|
4
|
+
* Provides core authentication utilities on top of Supabase Auth for the
|
|
5
|
+
* single-user PIN/password gate system:
|
|
6
|
+
*
|
|
7
|
+
* - **Sign-out & teardown**: Full 10-step teardown sequence to ensure no stale
|
|
8
|
+
* data leaks across sessions.
|
|
9
|
+
*
|
|
10
|
+
* - **Session management**: `getSession()` falls back to localStorage when the
|
|
11
|
+
* device is offline, ensuring the app can still render authenticated views
|
|
12
|
+
* with stale-but-usable session data.
|
|
13
|
+
*
|
|
14
|
+
* - **Profile CRUD**: Read and update user profile metadata on Supabase and
|
|
15
|
+
* in the offline credential cache.
|
|
16
|
+
*
|
|
17
|
+
* - **Email confirmation & OTP**: Resend confirmation emails and verify OTP
|
|
18
|
+
* token hashes from confirmation links.
|
|
19
|
+
*
|
|
20
|
+
* Security considerations:
|
|
21
|
+
* - Corrupted sessions are detected and automatically cleared to prevent
|
|
22
|
+
* infinite error loops.
|
|
23
|
+
* - Sign-out follows a strict 10-step teardown sequence to ensure no stale
|
|
24
|
+
* data leaks across user boundaries.
|
|
25
|
+
*
|
|
26
|
+
* Integration patterns:
|
|
27
|
+
* - Used internally by single-user auth (`../auth/singleUser.ts`) and the
|
|
28
|
+
* scaffolded confirm page (`../kit/confirm.ts`).
|
|
29
|
+
* - Works in tandem with `./client.ts` (lazy Supabase singleton) and
|
|
30
|
+
* `../engine.ts` (sync engine lifecycle).
|
|
31
|
+
* - Offline credential helpers live in `../auth/offlineCredentials.ts`.
|
|
32
|
+
*
|
|
33
|
+
* @module supabase/auth
|
|
34
|
+
*/
|
|
35
|
+
import { supabase } from './client';
|
|
36
|
+
import { clearOfflineCredentials, updateOfflineCredentialsProfile } from '../auth/offlineCredentials';
|
|
37
|
+
import { clearOfflineSession } from '../auth/offlineSession';
|
|
38
|
+
import { resetLoginGuard } from '../auth/loginGuard';
|
|
39
|
+
import { debugWarn, debugError } from '../debug';
|
|
40
|
+
import { getEngineConfig } from '../config';
|
|
41
|
+
import { syncStatusStore } from '../stores/sync';
|
|
42
|
+
import { authState } from '../stores/authState';
|
|
43
|
+
import { isDemoMode } from '../demo';
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// SECTION: Helpers
|
|
46
|
+
// =============================================================================
|
|
47
|
+
/**
|
|
48
|
+
* Get the email confirmation redirect URL.
|
|
49
|
+
*
|
|
50
|
+
* Points to the `/confirm` page (or the path configured via
|
|
51
|
+
* `auth.confirmRedirectPath`) which handles the token verification flow
|
|
52
|
+
* after a user clicks the link in their confirmation email.
|
|
53
|
+
*
|
|
54
|
+
* @returns The fully-qualified redirect URL, e.g. `https://app.example.com/confirm`.
|
|
55
|
+
* Falls back to a relative `/confirm` path in SSR environments where
|
|
56
|
+
* `window` is unavailable.
|
|
57
|
+
*
|
|
58
|
+
* @see {@link resendConfirmationEmail} — uses this URL
|
|
59
|
+
*/
|
|
60
|
+
function getConfirmRedirectUrl() {
|
|
61
|
+
if (typeof window !== 'undefined') {
|
|
62
|
+
const path = getEngineConfig().auth?.confirmRedirectPath || '/confirm';
|
|
63
|
+
return `${window.location.origin}${path}`;
|
|
64
|
+
}
|
|
65
|
+
// Fallback for SSR (shouldn't be called, but just in case)
|
|
66
|
+
return '/confirm';
|
|
67
|
+
}
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// SECTION: Sign Out
|
|
70
|
+
// =============================================================================
|
|
71
|
+
/**
|
|
72
|
+
* Sign the current user out and perform a full teardown of local state.
|
|
73
|
+
*
|
|
74
|
+
* The teardown follows a strict **10-step sequence** to ensure no stale data
|
|
75
|
+
* leaks between user sessions:
|
|
76
|
+
*
|
|
77
|
+
* 1. Stop the sync engine (dynamic import avoids circular deps).
|
|
78
|
+
* 2. Clear the pending sync queue (unless `preserveLocalData` is set).
|
|
79
|
+
* 3. Clear the local cache (unless `preserveLocalData` is set).
|
|
80
|
+
* 4. Clear the offline session token.
|
|
81
|
+
* 5. Clear offline credentials (only when online, to preserve offline re-login).
|
|
82
|
+
* 6. Call `supabase.auth.signOut()`.
|
|
83
|
+
* 7. Remove all `sb-*` keys from localStorage (Supabase internal storage).
|
|
84
|
+
* 8. Reset the login guard (brute-force counters).
|
|
85
|
+
* 9. Reset the sync status store.
|
|
86
|
+
* 10. Reset the auth state store.
|
|
87
|
+
*
|
|
88
|
+
* Each step is wrapped in its own try/catch so that a failure in one step
|
|
89
|
+
* does not prevent subsequent cleanup from running.
|
|
90
|
+
*
|
|
91
|
+
* @param options - Optional flags to control teardown behavior.
|
|
92
|
+
* @param options.preserveOfflineCredentials - When `true`, offline credentials
|
|
93
|
+
* are kept so the user can re-authenticate without network access.
|
|
94
|
+
* @param options.preserveLocalData - When `true`, pending sync queue and local
|
|
95
|
+
* cache are retained.
|
|
96
|
+
* @returns An object with an `error` field (`null` on success).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* await signOut(); // full teardown
|
|
101
|
+
* await signOut({ preserveLocalData: true }); // keep cached data
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export async function signOut(options) {
|
|
105
|
+
if (isDemoMode()) {
|
|
106
|
+
authState.reset();
|
|
107
|
+
syncStatusStore.reset();
|
|
108
|
+
return { error: null };
|
|
109
|
+
}
|
|
110
|
+
// 1. Stop sync engine (import dynamically to avoid circular deps)
|
|
111
|
+
try {
|
|
112
|
+
const { stopSyncEngine, clearLocalCache, clearPendingSyncQueue } = await import('../engine');
|
|
113
|
+
await stopSyncEngine();
|
|
114
|
+
if (!options?.preserveLocalData) {
|
|
115
|
+
// 2. Clear pending sync queue
|
|
116
|
+
await clearPendingSyncQueue();
|
|
117
|
+
// 3. Clear local cache
|
|
118
|
+
await clearLocalCache();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
debugError('[Auth] Failed to stop engine/clear data:', e);
|
|
123
|
+
}
|
|
124
|
+
// 4. Clear offline session
|
|
125
|
+
try {
|
|
126
|
+
await clearOfflineSession();
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
debugError('[Auth] Failed to clear offline session:', e);
|
|
130
|
+
}
|
|
131
|
+
// 5. Clear offline credentials (only if online, for offline re-login preservation)
|
|
132
|
+
try {
|
|
133
|
+
if (!options?.preserveOfflineCredentials) {
|
|
134
|
+
/* Only clear when online — if the user is offline, we want to keep
|
|
135
|
+
the cached credentials so they can still sign back in without
|
|
136
|
+
network access. This is a deliberate security trade-off favouring
|
|
137
|
+
availability over strict credential erasure. */
|
|
138
|
+
const isOnline = typeof navigator !== 'undefined' && navigator.onLine;
|
|
139
|
+
if (isOnline) {
|
|
140
|
+
await clearOfflineCredentials();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
debugError('[Auth] Failed to clear offline credentials:', e);
|
|
146
|
+
}
|
|
147
|
+
// 6. Supabase auth signOut
|
|
148
|
+
const { error } = await supabase.auth.signOut();
|
|
149
|
+
// 7. Clear sb-* localStorage keys
|
|
150
|
+
try {
|
|
151
|
+
if (typeof localStorage !== 'undefined') {
|
|
152
|
+
const keys = Object.keys(localStorage).filter((k) => k.startsWith('sb-'));
|
|
153
|
+
keys.forEach((k) => localStorage.removeItem(k));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Ignore storage errors
|
|
158
|
+
}
|
|
159
|
+
// 8. Reset login guard state
|
|
160
|
+
resetLoginGuard();
|
|
161
|
+
// 9. Reset sync status store
|
|
162
|
+
syncStatusStore.reset();
|
|
163
|
+
// 10. Reset auth state store
|
|
164
|
+
authState.reset();
|
|
165
|
+
return { error: error?.message || null };
|
|
166
|
+
}
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// SECTION: Session Management
|
|
169
|
+
// =============================================================================
|
|
170
|
+
/**
|
|
171
|
+
* Get the current Supabase session.
|
|
172
|
+
*
|
|
173
|
+
* When the device is **online**, this delegates to `supabase.auth.getSession()`
|
|
174
|
+
* which may trigger a token refresh if the access token is close to expiry.
|
|
175
|
+
*
|
|
176
|
+
* When the device is **offline**, or if the Supabase call fails with a
|
|
177
|
+
* corrupted-session error, this falls back to reading the session directly
|
|
178
|
+
* from localStorage via {@link getSessionFromStorage}. The returned session
|
|
179
|
+
* may be expired, but callers can use {@link isSessionExpired} to check and
|
|
180
|
+
* should handle offline mode appropriately (e.g. show cached data, queue
|
|
181
|
+
* mutations for later sync).
|
|
182
|
+
*
|
|
183
|
+
* @returns The current `Session` object, or `null` if no valid session exists.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const session = await getSession();
|
|
188
|
+
* if (session && !isSessionExpired(session)) {
|
|
189
|
+
* // Fully authenticated
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @see {@link getSessionFromStorage} — direct localStorage fallback
|
|
194
|
+
* @see {@link isSessionExpired} — expiry check helper
|
|
195
|
+
* @see {@link getValidSession} — combined convenience wrapper
|
|
196
|
+
*/
|
|
197
|
+
export async function getSession() {
|
|
198
|
+
if (isDemoMode())
|
|
199
|
+
return null;
|
|
200
|
+
const isOffline = typeof navigator !== 'undefined' && !navigator.onLine;
|
|
201
|
+
try {
|
|
202
|
+
const { data, error } = await supabase.auth.getSession();
|
|
203
|
+
if (error) {
|
|
204
|
+
debugError('[Auth] getSession error:', error.message);
|
|
205
|
+
// If offline and we got an error, don't clear session - it might just be a network issue
|
|
206
|
+
if (isOffline) {
|
|
207
|
+
debugWarn('[Auth] Offline - keeping session despite error');
|
|
208
|
+
// Try to get session from localStorage directly
|
|
209
|
+
return getSessionFromStorage();
|
|
210
|
+
}
|
|
211
|
+
/* Detect corrupted session data (e.g. "can't access property 'hash'
|
|
212
|
+
of undefined") which can occur when localStorage is partially written.
|
|
213
|
+
Signing out clears the corrupt state so subsequent calls succeed. */
|
|
214
|
+
if (error.message?.includes('hash') || error.message?.includes('undefined')) {
|
|
215
|
+
debugWarn('[Auth] Detected corrupted session, attempting to clear');
|
|
216
|
+
await supabase.auth.signOut();
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return data.session;
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
debugError('[Auth] Unexpected error getting session:', e);
|
|
224
|
+
// If offline, don't clear anything - try to get from storage
|
|
225
|
+
if (isOffline) {
|
|
226
|
+
debugWarn('[Auth] Offline - attempting to get session from storage');
|
|
227
|
+
return getSessionFromStorage();
|
|
228
|
+
}
|
|
229
|
+
// Attempt to clear any corrupted state when online
|
|
230
|
+
try {
|
|
231
|
+
await supabase.auth.signOut();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Ignore signOut errors
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Read the session directly from localStorage, bypassing Supabase's
|
|
241
|
+
* built-in token refresh logic.
|
|
242
|
+
*
|
|
243
|
+
* This is used as a **fallback** when the device is offline and the normal
|
|
244
|
+
* `supabase.auth.getSession()` call fails. The returned session may be
|
|
245
|
+
* expired, but it still contains the user identity, which is sufficient for
|
|
246
|
+
* rendering cached offline views.
|
|
247
|
+
*
|
|
248
|
+
* Supabase stores its auth token in localStorage under a key matching the
|
|
249
|
+
* pattern `sb-{project-ref}-auth-token`. The internal structure has changed
|
|
250
|
+
* between Supabase versions, so we check for both `currentSession` (older)
|
|
251
|
+
* and `session` (newer) shapes.
|
|
252
|
+
*
|
|
253
|
+
* @returns The cached `Session`, or `null` if nothing usable is found.
|
|
254
|
+
*/
|
|
255
|
+
function getSessionFromStorage() {
|
|
256
|
+
try {
|
|
257
|
+
// Supabase stores session in localStorage with key pattern: sb-{project-ref}-auth-token
|
|
258
|
+
const keys = Object.keys(localStorage);
|
|
259
|
+
const sessionKey = keys.find((k) => k.includes('-auth-token'));
|
|
260
|
+
if (!sessionKey)
|
|
261
|
+
return null;
|
|
262
|
+
const stored = localStorage.getItem(sessionKey);
|
|
263
|
+
if (!stored)
|
|
264
|
+
return null;
|
|
265
|
+
const parsed = JSON.parse(stored);
|
|
266
|
+
if (parsed?.currentSession) {
|
|
267
|
+
return parsed.currentSession;
|
|
268
|
+
}
|
|
269
|
+
// Newer Supabase versions use different structure
|
|
270
|
+
if (parsed?.session) {
|
|
271
|
+
return parsed.session;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
debugError('[Auth] Failed to get session from storage:', e);
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Check whether a session's access token has expired.
|
|
282
|
+
*
|
|
283
|
+
* The `expires_at` field on a Supabase session is a **Unix timestamp in
|
|
284
|
+
* seconds**. We compare it against `Date.now() / 1000` (which is in
|
|
285
|
+
* milliseconds, hence the division).
|
|
286
|
+
*
|
|
287
|
+
* @param session - The session to check, or `null`.
|
|
288
|
+
* @returns `true` if the session is `null`, missing `expires_at`, or past
|
|
289
|
+
* its expiry time; `false` otherwise.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* if (isSessionExpired(session)) {
|
|
294
|
+
* // Prompt re-authentication or attempt token refresh
|
|
295
|
+
* }
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
export function isSessionExpired(session) {
|
|
299
|
+
if (!session)
|
|
300
|
+
return true;
|
|
301
|
+
// expires_at is in seconds
|
|
302
|
+
const expiresAt = session.expires_at;
|
|
303
|
+
if (!expiresAt)
|
|
304
|
+
return true;
|
|
305
|
+
return Date.now() / 1000 > expiresAt;
|
|
306
|
+
}
|
|
307
|
+
// =============================================================================
|
|
308
|
+
// SECTION: User Profile
|
|
309
|
+
// =============================================================================
|
|
310
|
+
/**
|
|
311
|
+
* Extract the user's profile from their Supabase `user_metadata`.
|
|
312
|
+
*
|
|
313
|
+
* If the engine config provides a custom `auth.profileExtractor`, it is
|
|
314
|
+
* invoked to transform the raw metadata into the app's profile shape.
|
|
315
|
+
* Otherwise the raw `user_metadata` object is returned as-is.
|
|
316
|
+
*
|
|
317
|
+
* @param user - The Supabase `User` object (may be `null`).
|
|
318
|
+
* @returns A key-value record representing the user's profile fields.
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* ```ts
|
|
322
|
+
* const profile = getUserProfile(session.user);
|
|
323
|
+
* console.log(profile.display_name);
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
export function getUserProfile(user) {
|
|
327
|
+
const config = getEngineConfig();
|
|
328
|
+
if (config.auth?.profileExtractor && user) {
|
|
329
|
+
return config.auth.profileExtractor(user.user_metadata || {});
|
|
330
|
+
}
|
|
331
|
+
return user?.user_metadata || {};
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Update the current user's profile metadata on Supabase.
|
|
335
|
+
*
|
|
336
|
+
* The profile data is transformed through `auth.profileToMetadata` (if
|
|
337
|
+
* configured) before being sent. On success the offline credential cache
|
|
338
|
+
* is also updated so that the profile stays consistent across online and
|
|
339
|
+
* offline modes.
|
|
340
|
+
*
|
|
341
|
+
* @param profile - The updated profile fields to persist.
|
|
342
|
+
* @returns An object with an `error` field (`null` on success).
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* const { error } = await updateProfile({ display_name: 'New Name' });
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* @see {@link updateOfflineCredentialsProfile} — keeps the offline cache in sync
|
|
350
|
+
*/
|
|
351
|
+
export async function updateProfile(profile) {
|
|
352
|
+
if (isDemoMode())
|
|
353
|
+
return { error: null };
|
|
354
|
+
const config = getEngineConfig();
|
|
355
|
+
const metadata = config.auth?.profileToMetadata
|
|
356
|
+
? config.auth.profileToMetadata(profile)
|
|
357
|
+
: profile;
|
|
358
|
+
const { error } = await supabase.auth.updateUser({
|
|
359
|
+
data: metadata
|
|
360
|
+
});
|
|
361
|
+
if (!error) {
|
|
362
|
+
// Update offline cache
|
|
363
|
+
try {
|
|
364
|
+
await updateOfflineCredentialsProfile(profile);
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
debugError('[Auth] Failed to update offline profile:', e);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return { error: error?.message || null };
|
|
371
|
+
}
|
|
372
|
+
// =============================================================================
|
|
373
|
+
// SECTION: Email Confirmation & OTP
|
|
374
|
+
// =============================================================================
|
|
375
|
+
/**
|
|
376
|
+
* Resend the signup confirmation email for a given address.
|
|
377
|
+
*
|
|
378
|
+
* The caller should enforce a client-side cooldown (recommended: 30 seconds)
|
|
379
|
+
* to prevent abuse, since Supabase may not always rate-limit resends on its
|
|
380
|
+
* own.
|
|
381
|
+
*
|
|
382
|
+
* @param email - The email address that needs a new confirmation link.
|
|
383
|
+
* @returns An object with an `error` field (`null` on success).
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const { error } = await resendConfirmationEmail('user@example.com');
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
export async function resendConfirmationEmail(email) {
|
|
391
|
+
if (isDemoMode())
|
|
392
|
+
return { error: null };
|
|
393
|
+
const { error } = await supabase.auth.resend({
|
|
394
|
+
type: 'signup',
|
|
395
|
+
email,
|
|
396
|
+
options: {
|
|
397
|
+
emailRedirectTo: getConfirmRedirectUrl()
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
return { error: error?.message || null };
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Verify an OTP token hash received from a confirmation email link.
|
|
404
|
+
*
|
|
405
|
+
* This absorbs the direct Supabase call that would otherwise live in the
|
|
406
|
+
* confirm page component, keeping all auth logic centralised in this module.
|
|
407
|
+
*
|
|
408
|
+
* @param tokenHash - The `token_hash` query parameter from the confirmation URL.
|
|
409
|
+
* @param type - The type of OTP: `'signup'`, `'email'`, or `'email_change'`.
|
|
410
|
+
* @returns An object with an `error` field (`null` on success).
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* // On the /confirm page:
|
|
415
|
+
* const hash = new URL(location.href).searchParams.get('token_hash');
|
|
416
|
+
* const { error } = await verifyOtp(hash, 'signup');
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
export async function verifyOtp(tokenHash, type) {
|
|
420
|
+
if (isDemoMode())
|
|
421
|
+
return { error: null };
|
|
422
|
+
const { error } = await supabase.auth.verifyOtp({
|
|
423
|
+
token_hash: tokenHash,
|
|
424
|
+
type
|
|
425
|
+
});
|
|
426
|
+
return { error: error?.message || null };
|
|
427
|
+
}
|
|
428
|
+
// =============================================================================
|
|
429
|
+
// SECTION: Convenience Wrappers
|
|
430
|
+
// =============================================================================
|
|
431
|
+
/**
|
|
432
|
+
* Get a valid (non-expired) session, or `null`.
|
|
433
|
+
*
|
|
434
|
+
* This is a convenience wrapper that combines {@link getSession} and
|
|
435
|
+
* {@link isSessionExpired} into a single call, useful when the caller only
|
|
436
|
+
* cares about sessions that can still be used for API requests.
|
|
437
|
+
*
|
|
438
|
+
* @returns A non-expired `Session`, or `null`.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```ts
|
|
442
|
+
* const session = await getValidSession();
|
|
443
|
+
* if (!session) {
|
|
444
|
+
* redirectToLogin();
|
|
445
|
+
* }
|
|
446
|
+
* ```
|
|
447
|
+
*
|
|
448
|
+
* @see {@link getSession}
|
|
449
|
+
* @see {@link isSessionExpired}
|
|
450
|
+
*/
|
|
451
|
+
export async function getValidSession() {
|
|
452
|
+
const session = await getSession();
|
|
453
|
+
if (!session)
|
|
454
|
+
return null;
|
|
455
|
+
if (isSessionExpired(session))
|
|
456
|
+
return null;
|
|
457
|
+
return session;
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/supabase/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Supabase Client — Lazy Initialization via ES Proxy
|
|
3
|
+
*
|
|
4
|
+
* This module exports a single `supabase` constant that looks and behaves
|
|
5
|
+
* exactly like a `SupabaseClient` instance, but is actually an ES `Proxy`
|
|
6
|
+
* that defers client creation until the **first property access**. This
|
|
7
|
+
* "lazy singleton" pattern solves a critical bootstrapping problem:
|
|
8
|
+
*
|
|
9
|
+
* The Supabase URL and anon key are loaded at **runtime** (via
|
|
10
|
+
* `getConfig()` from `../runtime/runtimeConfig`), not at build time.
|
|
11
|
+
* Modules that `import { supabase }` at the top level would otherwise
|
|
12
|
+
* crash because the config has not been initialized yet when the import
|
|
13
|
+
* executes.
|
|
14
|
+
*
|
|
15
|
+
* How the Proxy pattern works:
|
|
16
|
+
* 1. `supabase` is exported as `new Proxy({} as SupabaseClient, handler)`.
|
|
17
|
+
* 2. The handler's `get` trap intercepts every property access (e.g.
|
|
18
|
+
* `supabase.auth`, `supabase.from(...)`).
|
|
19
|
+
* 3. On first access, `getOrCreateClient()` reads the runtime config and
|
|
20
|
+
* calls `createClient(url, key, options)` to build the real client.
|
|
21
|
+
* 4. The real client is cached in a module-level `realClient` variable;
|
|
22
|
+
* subsequent accesses reuse it (standard singleton).
|
|
23
|
+
* 5. Function values are `.bind(client)` to preserve `this` context.
|
|
24
|
+
*
|
|
25
|
+
* Additional responsibilities:
|
|
26
|
+
* - **Corrupted session cleanup**: Before the client is created, any
|
|
27
|
+
* malformed `sb-*` entries in localStorage are detected and removed to
|
|
28
|
+
* prevent "can't access property 'hash'" runtime errors.
|
|
29
|
+
* - **Unhandled rejection handler**: A global listener catches Supabase
|
|
30
|
+
* auth errors that escape normal error handling, clears storage, and
|
|
31
|
+
* performs a single guarded page reload to recover.
|
|
32
|
+
* - **iOS PWA detection**: The client sends a custom `x-client-info`
|
|
33
|
+
* header indicating whether it is running as a standalone PWA on iOS,
|
|
34
|
+
* which helps with server-side debugging of session eviction issues.
|
|
35
|
+
*
|
|
36
|
+
* Security considerations:
|
|
37
|
+
* - The anon key is a **public** key (safe to include in client bundles).
|
|
38
|
+
* - PKCE flow is used instead of the implicit flow for stronger OAuth
|
|
39
|
+
* security and better compatibility with PWA environments.
|
|
40
|
+
* - Session persistence uses localStorage; the module proactively scrubs
|
|
41
|
+
* corrupted entries to prevent denial-of-service via bad local state.
|
|
42
|
+
*
|
|
43
|
+
* @module supabase/client
|
|
44
|
+
*/
|
|
45
|
+
import { type SupabaseClient } from '@supabase/supabase-js';
|
|
46
|
+
/**
|
|
47
|
+
* Override the storage key prefix used by the Supabase client.
|
|
48
|
+
*
|
|
49
|
+
* Must be called **before** the first access to the `supabase` export,
|
|
50
|
+
* since the prefix is baked into the client options at creation time.
|
|
51
|
+
*
|
|
52
|
+
* @param prefix - The new prefix string (e.g. the app's name).
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* _setClientPrefix('myapp');
|
|
57
|
+
* // Later accesses will use storageKey 'myapp-auth'
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function _setClientPrefix(prefix: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* The public Supabase client — a Proxy-based lazy singleton.
|
|
63
|
+
*
|
|
64
|
+
* **Why a Proxy?**
|
|
65
|
+
* The Supabase URL and anon key are not available at import time (they come
|
|
66
|
+
* from a runtime config that is loaded asynchronously). A Proxy lets every
|
|
67
|
+
* module `import { supabase }` at the top level without worrying about
|
|
68
|
+
* initialization order. The real client is created transparently on first
|
|
69
|
+
* property access.
|
|
70
|
+
*
|
|
71
|
+
* **How it works:**
|
|
72
|
+
* - The `get` trap intercepts every property read (e.g. `supabase.auth`,
|
|
73
|
+
* `supabase.from`).
|
|
74
|
+
* - It calls `getOrCreateClient()` to ensure the real client exists.
|
|
75
|
+
* - It forwards the property access via `Reflect.get`.
|
|
76
|
+
* - Function values are `.bind(client)` to keep `this` correct when the
|
|
77
|
+
* caller destructures methods (e.g. `const { from } = supabase`).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { supabase } from './client';
|
|
82
|
+
*
|
|
83
|
+
* // Works immediately — the Proxy defers creation until this line runs:
|
|
84
|
+
* const { data } = await supabase.from('users').select('*');
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare const supabase: SupabaseClient;
|
|
88
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/supabase/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAgB1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,QAE9C;AAqOD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,QAAQ,EAAE,cASrB,CAAC"}
|