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.
Files changed (246) hide show
  1. package/README.md +607 -0
  2. package/dist/actions/remoteChange.d.ts +204 -0
  3. package/dist/actions/remoteChange.d.ts.map +1 -0
  4. package/dist/actions/remoteChange.js +424 -0
  5. package/dist/actions/remoteChange.js.map +1 -0
  6. package/dist/actions/truncateTooltip.d.ts +56 -0
  7. package/dist/actions/truncateTooltip.d.ts.map +1 -0
  8. package/dist/actions/truncateTooltip.js +312 -0
  9. package/dist/actions/truncateTooltip.js.map +1 -0
  10. package/dist/auth/crypto.d.ts +41 -0
  11. package/dist/auth/crypto.d.ts.map +1 -0
  12. package/dist/auth/crypto.js +50 -0
  13. package/dist/auth/crypto.js.map +1 -0
  14. package/dist/auth/deviceVerification.d.ts +283 -0
  15. package/dist/auth/deviceVerification.d.ts.map +1 -0
  16. package/dist/auth/deviceVerification.js +575 -0
  17. package/dist/auth/deviceVerification.js.map +1 -0
  18. package/dist/auth/displayUtils.d.ts +98 -0
  19. package/dist/auth/displayUtils.d.ts.map +1 -0
  20. package/dist/auth/displayUtils.js +145 -0
  21. package/dist/auth/displayUtils.js.map +1 -0
  22. package/dist/auth/loginGuard.d.ts +134 -0
  23. package/dist/auth/loginGuard.d.ts.map +1 -0
  24. package/dist/auth/loginGuard.js +276 -0
  25. package/dist/auth/loginGuard.js.map +1 -0
  26. package/dist/auth/offlineCredentials.d.ts +105 -0
  27. package/dist/auth/offlineCredentials.d.ts.map +1 -0
  28. package/dist/auth/offlineCredentials.js +176 -0
  29. package/dist/auth/offlineCredentials.js.map +1 -0
  30. package/dist/auth/offlineSession.d.ts +96 -0
  31. package/dist/auth/offlineSession.d.ts.map +1 -0
  32. package/dist/auth/offlineSession.js +145 -0
  33. package/dist/auth/offlineSession.js.map +1 -0
  34. package/dist/auth/resolveAuthState.d.ts +85 -0
  35. package/dist/auth/resolveAuthState.d.ts.map +1 -0
  36. package/dist/auth/resolveAuthState.js +249 -0
  37. package/dist/auth/resolveAuthState.js.map +1 -0
  38. package/dist/auth/singleUser.d.ts +498 -0
  39. package/dist/auth/singleUser.d.ts.map +1 -0
  40. package/dist/auth/singleUser.js +1282 -0
  41. package/dist/auth/singleUser.js.map +1 -0
  42. package/dist/bin/commands.d.ts +14 -0
  43. package/dist/bin/commands.d.ts.map +1 -0
  44. package/dist/bin/commands.js +68 -0
  45. package/dist/bin/commands.js.map +1 -0
  46. package/dist/bin/install-pwa.d.ts +41 -0
  47. package/dist/bin/install-pwa.d.ts.map +1 -0
  48. package/dist/bin/install-pwa.js +4594 -0
  49. package/dist/bin/install-pwa.js.map +1 -0
  50. package/dist/config.d.ts +249 -0
  51. package/dist/config.d.ts.map +1 -0
  52. package/dist/config.js +395 -0
  53. package/dist/config.js.map +1 -0
  54. package/dist/conflicts.d.ts +306 -0
  55. package/dist/conflicts.d.ts.map +1 -0
  56. package/dist/conflicts.js +807 -0
  57. package/dist/conflicts.js.map +1 -0
  58. package/dist/crdt/awareness.d.ts +128 -0
  59. package/dist/crdt/awareness.d.ts.map +1 -0
  60. package/dist/crdt/awareness.js +284 -0
  61. package/dist/crdt/awareness.js.map +1 -0
  62. package/dist/crdt/channel.d.ts +165 -0
  63. package/dist/crdt/channel.d.ts.map +1 -0
  64. package/dist/crdt/channel.js +522 -0
  65. package/dist/crdt/channel.js.map +1 -0
  66. package/dist/crdt/config.d.ts +58 -0
  67. package/dist/crdt/config.d.ts.map +1 -0
  68. package/dist/crdt/config.js +123 -0
  69. package/dist/crdt/config.js.map +1 -0
  70. package/dist/crdt/helpers.d.ts +104 -0
  71. package/dist/crdt/helpers.d.ts.map +1 -0
  72. package/dist/crdt/helpers.js +116 -0
  73. package/dist/crdt/helpers.js.map +1 -0
  74. package/dist/crdt/offline.d.ts +58 -0
  75. package/dist/crdt/offline.d.ts.map +1 -0
  76. package/dist/crdt/offline.js +130 -0
  77. package/dist/crdt/offline.js.map +1 -0
  78. package/dist/crdt/persistence.d.ts +65 -0
  79. package/dist/crdt/persistence.d.ts.map +1 -0
  80. package/dist/crdt/persistence.js +171 -0
  81. package/dist/crdt/persistence.js.map +1 -0
  82. package/dist/crdt/provider.d.ts +109 -0
  83. package/dist/crdt/provider.d.ts.map +1 -0
  84. package/dist/crdt/provider.js +543 -0
  85. package/dist/crdt/provider.js.map +1 -0
  86. package/dist/crdt/store.d.ts +111 -0
  87. package/dist/crdt/store.d.ts.map +1 -0
  88. package/dist/crdt/store.js +158 -0
  89. package/dist/crdt/store.js.map +1 -0
  90. package/dist/crdt/types.d.ts +281 -0
  91. package/dist/crdt/types.d.ts.map +1 -0
  92. package/dist/crdt/types.js +26 -0
  93. package/dist/crdt/types.js.map +1 -0
  94. package/dist/data.d.ts +502 -0
  95. package/dist/data.d.ts.map +1 -0
  96. package/dist/data.js +862 -0
  97. package/dist/data.js.map +1 -0
  98. package/dist/database.d.ts +153 -0
  99. package/dist/database.d.ts.map +1 -0
  100. package/dist/database.js +325 -0
  101. package/dist/database.js.map +1 -0
  102. package/dist/debug.d.ts +87 -0
  103. package/dist/debug.d.ts.map +1 -0
  104. package/dist/debug.js +135 -0
  105. package/dist/debug.js.map +1 -0
  106. package/dist/demo.d.ts +131 -0
  107. package/dist/demo.d.ts.map +1 -0
  108. package/dist/demo.js +168 -0
  109. package/dist/demo.js.map +1 -0
  110. package/dist/deviceId.d.ts +47 -0
  111. package/dist/deviceId.d.ts.map +1 -0
  112. package/dist/deviceId.js +106 -0
  113. package/dist/deviceId.js.map +1 -0
  114. package/dist/diagnostics.d.ts +292 -0
  115. package/dist/diagnostics.d.ts.map +1 -0
  116. package/dist/diagnostics.js +378 -0
  117. package/dist/diagnostics.js.map +1 -0
  118. package/dist/engine.d.ts +230 -0
  119. package/dist/engine.d.ts.map +1 -0
  120. package/dist/engine.js +2636 -0
  121. package/dist/engine.js.map +1 -0
  122. package/dist/entries/actions.d.ts +16 -0
  123. package/dist/entries/actions.d.ts.map +1 -0
  124. package/dist/entries/actions.js +29 -0
  125. package/dist/entries/actions.js.map +1 -0
  126. package/dist/entries/auth.d.ts +19 -0
  127. package/dist/entries/auth.d.ts.map +1 -0
  128. package/dist/entries/auth.js +50 -0
  129. package/dist/entries/auth.js.map +1 -0
  130. package/dist/entries/config.d.ts +15 -0
  131. package/dist/entries/config.d.ts.map +1 -0
  132. package/dist/entries/config.js +20 -0
  133. package/dist/entries/config.js.map +1 -0
  134. package/dist/entries/crdt.d.ts +32 -0
  135. package/dist/entries/crdt.d.ts.map +1 -0
  136. package/dist/entries/crdt.js +52 -0
  137. package/dist/entries/crdt.js.map +1 -0
  138. package/dist/entries/kit.d.ts +22 -0
  139. package/dist/entries/kit.d.ts.map +1 -0
  140. package/dist/entries/kit.js +58 -0
  141. package/dist/entries/kit.js.map +1 -0
  142. package/dist/entries/stores.d.ts +22 -0
  143. package/dist/entries/stores.d.ts.map +1 -0
  144. package/dist/entries/stores.js +57 -0
  145. package/dist/entries/stores.js.map +1 -0
  146. package/dist/entries/types.d.ts +23 -0
  147. package/dist/entries/types.d.ts.map +1 -0
  148. package/dist/entries/types.js +12 -0
  149. package/dist/entries/types.js.map +1 -0
  150. package/dist/entries/utils.d.ts +12 -0
  151. package/dist/entries/utils.d.ts.map +1 -0
  152. package/dist/entries/utils.js +42 -0
  153. package/dist/entries/utils.js.map +1 -0
  154. package/dist/entries/vite.d.ts +20 -0
  155. package/dist/entries/vite.d.ts.map +1 -0
  156. package/dist/entries/vite.js +26 -0
  157. package/dist/entries/vite.js.map +1 -0
  158. package/dist/index.d.ts +77 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +234 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/kit/auth.d.ts +80 -0
  163. package/dist/kit/auth.d.ts.map +1 -0
  164. package/dist/kit/auth.js +75 -0
  165. package/dist/kit/auth.js.map +1 -0
  166. package/dist/kit/confirm.d.ts +111 -0
  167. package/dist/kit/confirm.d.ts.map +1 -0
  168. package/dist/kit/confirm.js +169 -0
  169. package/dist/kit/confirm.js.map +1 -0
  170. package/dist/kit/loads.d.ts +187 -0
  171. package/dist/kit/loads.d.ts.map +1 -0
  172. package/dist/kit/loads.js +208 -0
  173. package/dist/kit/loads.js.map +1 -0
  174. package/dist/kit/server.d.ts +175 -0
  175. package/dist/kit/server.d.ts.map +1 -0
  176. package/dist/kit/server.js +297 -0
  177. package/dist/kit/server.js.map +1 -0
  178. package/dist/kit/sw.d.ts +176 -0
  179. package/dist/kit/sw.d.ts.map +1 -0
  180. package/dist/kit/sw.js +320 -0
  181. package/dist/kit/sw.js.map +1 -0
  182. package/dist/queue.d.ts +306 -0
  183. package/dist/queue.d.ts.map +1 -0
  184. package/dist/queue.js +925 -0
  185. package/dist/queue.js.map +1 -0
  186. package/dist/realtime.d.ts +280 -0
  187. package/dist/realtime.d.ts.map +1 -0
  188. package/dist/realtime.js +1031 -0
  189. package/dist/realtime.js.map +1 -0
  190. package/dist/runtime/runtimeConfig.d.ts +110 -0
  191. package/dist/runtime/runtimeConfig.d.ts.map +1 -0
  192. package/dist/runtime/runtimeConfig.js +260 -0
  193. package/dist/runtime/runtimeConfig.js.map +1 -0
  194. package/dist/schema.d.ts +150 -0
  195. package/dist/schema.d.ts.map +1 -0
  196. package/dist/schema.js +891 -0
  197. package/dist/schema.js.map +1 -0
  198. package/dist/stores/authState.d.ts +204 -0
  199. package/dist/stores/authState.d.ts.map +1 -0
  200. package/dist/stores/authState.js +336 -0
  201. package/dist/stores/authState.js.map +1 -0
  202. package/dist/stores/factories.d.ts +140 -0
  203. package/dist/stores/factories.d.ts.map +1 -0
  204. package/dist/stores/factories.js +157 -0
  205. package/dist/stores/factories.js.map +1 -0
  206. package/dist/stores/network.d.ts +48 -0
  207. package/dist/stores/network.d.ts.map +1 -0
  208. package/dist/stores/network.js +261 -0
  209. package/dist/stores/network.js.map +1 -0
  210. package/dist/stores/remoteChanges.d.ts +417 -0
  211. package/dist/stores/remoteChanges.d.ts.map +1 -0
  212. package/dist/stores/remoteChanges.js +626 -0
  213. package/dist/stores/remoteChanges.js.map +1 -0
  214. package/dist/stores/sync.d.ts +165 -0
  215. package/dist/stores/sync.d.ts.map +1 -0
  216. package/dist/stores/sync.js +275 -0
  217. package/dist/stores/sync.js.map +1 -0
  218. package/dist/supabase/auth.d.ts +219 -0
  219. package/dist/supabase/auth.d.ts.map +1 -0
  220. package/dist/supabase/auth.js +459 -0
  221. package/dist/supabase/auth.js.map +1 -0
  222. package/dist/supabase/client.d.ts +88 -0
  223. package/dist/supabase/client.d.ts.map +1 -0
  224. package/dist/supabase/client.js +313 -0
  225. package/dist/supabase/client.js.map +1 -0
  226. package/dist/supabase/validate.d.ts +118 -0
  227. package/dist/supabase/validate.d.ts.map +1 -0
  228. package/dist/supabase/validate.js +208 -0
  229. package/dist/supabase/validate.js.map +1 -0
  230. package/dist/sw/build/vite-plugin.d.ts +149 -0
  231. package/dist/sw/build/vite-plugin.d.ts.map +1 -0
  232. package/dist/sw/build/vite-plugin.js +517 -0
  233. package/dist/sw/build/vite-plugin.js.map +1 -0
  234. package/dist/sw/sw.js +664 -0
  235. package/dist/types.d.ts +363 -0
  236. package/dist/types.d.ts.map +1 -0
  237. package/dist/types.js +18 -0
  238. package/dist/types.js.map +1 -0
  239. package/dist/utils.d.ts +85 -0
  240. package/dist/utils.d.ts.map +1 -0
  241. package/dist/utils.js +156 -0
  242. package/dist/utils.js.map +1 -0
  243. package/package.json +117 -0
  244. package/src/components/DeferredChangesBanner.svelte +477 -0
  245. package/src/components/DemoBanner.svelte +110 -0
  246. package/src/components/SyncStatus.svelte +1732 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayUtils.d.ts","sourceRoot":"","sources":["../../src/auth/displayUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAQnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,GAAG,IAAI,EACvB,cAAc,EAAE,kBAAkB,GAAG,IAAI,EACzC,QAAQ,GAAE,MAAmB,GAC5B,MAAM,CA6BR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,GAAG,IAAI,EACvB,cAAc,EAAE,kBAAkB,GAAG,IAAI,GACxC,MAAM,CAaR;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,GAAG,IAAI,EACvB,cAAc,EAAE,kBAAkB,GAAG,IAAI,EACzC,QAAQ,GAAE,MAAY,GACrB,MAAM,CAMR"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * @fileoverview Auth Display Utilities
3
+ *
4
+ * Pure helper functions that resolve user-facing display values (first name,
5
+ * user ID, avatar initial) from the auth state. Each function handles the
6
+ * full fallback chain across online (Supabase session) and offline (cached
7
+ * credentials) modes, so consuming components don't need to duplicate the
8
+ * resolution logic.
9
+ *
10
+ * Resolution strategy (consistent across all helpers):
11
+ * 1. Check the Supabase session (`Session.user`) first.
12
+ * 2. Fall back to the offline credential cache (`OfflineCredentials`).
13
+ * 3. Return a caller-provided fallback or a sensible default.
14
+ *
15
+ * These functions are stateless and framework-agnostic — they accept plain
16
+ * data and return plain values. Wrap them in `$derived` / `$derived.by` in
17
+ * Svelte 5 components to make them reactive.
18
+ *
19
+ * @module auth/displayUtils
20
+ */
21
+ import { getUserProfile } from '../supabase/auth';
22
+ import { isDemoMode, getDemoConfig } from '../demo';
23
+ // =============================================================================
24
+ // PUBLIC API
25
+ // =============================================================================
26
+ /**
27
+ * Resolve the user's first name for greeting / display purposes.
28
+ *
29
+ * Fallback chain:
30
+ * 1. `firstName` / `first_name` from the Supabase session profile
31
+ * (extracted via `getUserProfile()`, which respects the app's
32
+ * `profileExtractor` config)
33
+ * 2. Email username (everything before `@`) from the Supabase session
34
+ * 3. `firstName` from the offline cached profile
35
+ * 4. Email username from the offline cached profile
36
+ * 5. The provided `fallback` string (default: `'Explorer'`)
37
+ *
38
+ * @param session - The current Supabase session, or `null`.
39
+ * @param offlineProfile - The cached offline credentials, or `null`.
40
+ * @param fallback - Value returned when no name can be resolved.
41
+ * Defaults to `'Explorer'`.
42
+ * @returns The resolved first name string.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // In a Svelte 5 component:
47
+ * const firstName = $derived(
48
+ * resolveFirstName($authState.session, $authState.offlineProfile)
49
+ * );
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // With a custom fallback:
55
+ * const greeting = resolveFirstName(session, offline, 'there');
56
+ * // → "Hey, there!" when no name is available
57
+ * ```
58
+ */
59
+ export function resolveFirstName(session, offlineProfile, fallback = 'Explorer') {
60
+ /* ── Demo: use mock profile from config ── */
61
+ if (isDemoMode()) {
62
+ const config = getDemoConfig();
63
+ if (config?.mockProfile.firstName) {
64
+ return config.mockProfile.firstName;
65
+ }
66
+ }
67
+ /* ── Online: check session profile fields ── */
68
+ if (session?.user) {
69
+ const profile = getUserProfile(session.user);
70
+ if (profile.firstName || profile.first_name) {
71
+ return (profile.firstName || profile.first_name);
72
+ }
73
+ if (session.user.email) {
74
+ return session.user.email.split('@')[0];
75
+ }
76
+ }
77
+ /* ── Offline: check cached credential profile ── */
78
+ if (offlineProfile?.profile?.firstName) {
79
+ return offlineProfile.profile.firstName;
80
+ }
81
+ if (offlineProfile?.email) {
82
+ return offlineProfile.email.split('@')[0];
83
+ }
84
+ return fallback;
85
+ }
86
+ /**
87
+ * Resolve the current user's UUID from auth state.
88
+ *
89
+ * Checks the Supabase session first, then falls back to the offline
90
+ * credential cache. Returns an empty string when no user is authenticated.
91
+ *
92
+ * @param session - The current Supabase session, or `null`.
93
+ * @param offlineProfile - The cached offline credentials, or `null`.
94
+ * @returns The user's UUID, or `''` if unauthenticated.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const userId = resolveUserId(data.session, data.offlineProfile);
99
+ * if (!userId) {
100
+ * error = 'Not authenticated';
101
+ * return;
102
+ * }
103
+ * ```
104
+ */
105
+ export function resolveUserId(session, offlineProfile) {
106
+ /* ── Demo: return synthetic user ID ── */
107
+ if (isDemoMode()) {
108
+ return 'demo-user';
109
+ }
110
+ if (session?.user?.id) {
111
+ return session.user.id;
112
+ }
113
+ if (offlineProfile?.userId) {
114
+ return offlineProfile.userId;
115
+ }
116
+ return '';
117
+ }
118
+ /**
119
+ * Resolve a single uppercase initial letter for avatar display.
120
+ *
121
+ * Uses {@link resolveFirstName} to derive the name, then returns the
122
+ * first character uppercased. If the resolved name is empty, returns
123
+ * the `fallback` character.
124
+ *
125
+ * @param session - The current Supabase session, or `null`.
126
+ * @param offlineProfile - The cached offline credentials, or `null`.
127
+ * @param fallback - Character to use when no initial can be derived.
128
+ * Defaults to `'?'`.
129
+ * @returns A single uppercase character.
130
+ *
131
+ * @example
132
+ * ```svelte
133
+ * <span class="avatar">
134
+ * {resolveAvatarInitial($authState.session, $authState.offlineProfile)}
135
+ * </span>
136
+ * ```
137
+ */
138
+ export function resolveAvatarInitial(session, offlineProfile, fallback = '?') {
139
+ const name = resolveFirstName(session, offlineProfile, '');
140
+ if (name) {
141
+ return name.charAt(0).toUpperCase();
142
+ }
143
+ return fallback;
144
+ }
145
+ //# sourceMappingURL=displayUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayUtils.js","sourceRoot":"","sources":["../../src/auth/displayUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEpD,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAuB,EACvB,cAAyC,EACzC,WAAmB,UAAU;IAE7B,8CAA8C;IAC9C,IAAI,UAAU,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;QAC/B,IAAI,MAAM,EAAE,WAAW,CAAC,SAAS,EAAE,CAAC;YAClC,OAAO,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC;QACtC,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAC5C,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,UAAU,CAAW,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,oDAAoD;IACpD,IAAI,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;QACvC,OAAO,cAAc,CAAC,OAAO,CAAC,SAAmB,CAAC;IACpD,CAAC;IACD,IAAI,cAAc,EAAE,KAAK,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAuB,EACvB,cAAyC;IAEzC,0CAA0C;IAC1C,IAAI,UAAU,EAAE,EAAE,CAAC;QACjB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,cAAc,EAAE,MAAM,EAAE,CAAC;QAC3B,OAAO,cAAc,CAAC,MAAM,CAAC;IAC/B,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAuB,EACvB,cAAyC,EACzC,WAAmB,GAAG;IAEtB,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;IAC3D,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @fileoverview Login Guard -- Local Credential Pre-Check & Rate Limiting
3
+ *
4
+ * Minimizes Supabase auth API requests by verifying credentials locally first.
5
+ * Only calls Supabase when the local hash matches (correct password) or when no
6
+ * local hash exists (with rate limiting).
7
+ *
8
+ * Architecture:
9
+ * - Maintains **in-memory-only** state (resets on page refresh) for failure
10
+ * counters and rate-limit timers.
11
+ * - Two operational strategies:
12
+ * 1. `local-match`: A cached hash exists and the user's input matches it.
13
+ * Proceed to Supabase for authoritative verification.
14
+ * 2. `no-cache`: No cached hash is available. Proceed to Supabase but apply
15
+ * exponential backoff on repeated failures.
16
+ * - After a configurable number of consecutive local mismatches, the cached hash
17
+ * is invalidated (it may be stale from a server-side password change) and the
18
+ * guard falls back to rate-limited Supabase mode.
19
+ *
20
+ * Security considerations:
21
+ * - The guard is a **client-side optimization**, not a security boundary. It
22
+ * reduces unnecessary network calls and provides a better UX (instant
23
+ * rejection for wrong passwords) but Supabase remains the authoritative
24
+ * verifier.
25
+ * - Rate limiting is in-memory only and resets on page refresh; server-side rate
26
+ * limits in Supabase are still the primary defense against brute-force.
27
+ * - Cached hashes are SHA-256 digests stored in IndexedDB. They are invalidated
28
+ * when stale-hash scenarios are detected (local match but Supabase rejects).
29
+ *
30
+ * @module auth/loginGuard
31
+ */
32
+ /**
33
+ * Strategy used when proceeding to the Supabase auth call.
34
+ *
35
+ * - `'local-match'` -- The user's input matched a locally cached hash.
36
+ * Supabase is called for authoritative confirmation.
37
+ * - `'no-cache'` -- No local hash was available (or it was invalidated).
38
+ * Supabase is called directly, subject to rate limiting.
39
+ */
40
+ export type PreCheckStrategy = 'local-match' | 'no-cache';
41
+ /**
42
+ * Result of the local pre-check.
43
+ *
44
+ * - `proceed: true` -- The caller should continue with the Supabase auth call.
45
+ * - `proceed: false` -- The attempt was rejected locally; `error` contains a
46
+ * user-facing message and `retryAfterMs` (if present) indicates how long
47
+ * the user should wait.
48
+ */
49
+ export type PreCheckResult = {
50
+ proceed: true;
51
+ strategy: PreCheckStrategy;
52
+ } | {
53
+ proceed: false;
54
+ error: string;
55
+ retryAfterMs?: number;
56
+ };
57
+ /**
58
+ * Pre-check login credentials locally before calling Supabase.
59
+ *
60
+ * Reads `singleUserConfig.gateHash`, hashes input, and compares.
61
+ *
62
+ * Returns `{ proceed: true, strategy }` to allow Supabase call,
63
+ * or `{ proceed: false, error, retryAfterMs? }` to reject locally.
64
+ *
65
+ * @param input - The plaintext password or gate code entered by the user.
66
+ * @returns A promise resolving to a {@link PreCheckResult}.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const result = await preCheckLogin(password);
71
+ * if (result.proceed) {
72
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
73
+ * if (error) await onLoginFailure(result.strategy);
74
+ * else onLoginSuccess();
75
+ * } else {
76
+ * showError(result.error);
77
+ * }
78
+ * ```
79
+ *
80
+ * @see {@link onLoginSuccess} -- must be called after a successful Supabase login.
81
+ * @see {@link onLoginFailure} -- must be called after a failed Supabase login.
82
+ */
83
+ export declare function preCheckLogin(input: string): Promise<PreCheckResult>;
84
+ /**
85
+ * Called after a successful Supabase login.
86
+ *
87
+ * Resets all login guard counters (local failure count, rate-limit attempts,
88
+ * and the next-allowed-attempt timestamp) so the user starts fresh.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
93
+ * if (!error) onLoginSuccess();
94
+ * ```
95
+ */
96
+ export declare function onLoginSuccess(): void;
97
+ /**
98
+ * Called after a failed Supabase login.
99
+ *
100
+ * Behavior depends on the strategy that was used:
101
+ *
102
+ * - `'local-match'`: Supabase rejected a locally-matched password, meaning the
103
+ * cached hash is **stale** (password changed server-side). The cached hash is
104
+ * invalidated so future attempts go through rate-limited Supabase mode.
105
+ * - `'no-cache'`: Increment the rate-limit counter and apply exponential
106
+ * backoff (base * 2^(n-1), capped at MAX_DELAY_MS).
107
+ *
108
+ * @param strategy - The {@link PreCheckStrategy} that was returned by
109
+ * {@link preCheckLogin} for this attempt.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const result = await preCheckLogin(password);
114
+ * if (result.proceed) {
115
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
116
+ * if (error) await onLoginFailure(result.strategy);
117
+ * }
118
+ * ```
119
+ */
120
+ export declare function onLoginFailure(strategy: PreCheckStrategy): Promise<void>;
121
+ /**
122
+ * Full reset of all login guard state.
123
+ *
124
+ * Call on sign-out or app reset to clear failure counters and rate-limit
125
+ * timers so the next login attempt starts with a clean slate.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * await supabase.auth.signOut();
130
+ * resetLoginGuard();
131
+ * ```
132
+ */
133
+ export declare function resetLoginGuard(): void;
134
+ //# sourceMappingURL=loginGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginGuard.d.ts","sourceRoot":"","sources":["../../src/auth/loginGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAmDH;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,UAAU,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GAC7C;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAoD7D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAuE1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAKrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC"}
@@ -0,0 +1,276 @@
1
+ /**
2
+ * @fileoverview Login Guard -- Local Credential Pre-Check & Rate Limiting
3
+ *
4
+ * Minimizes Supabase auth API requests by verifying credentials locally first.
5
+ * Only calls Supabase when the local hash matches (correct password) or when no
6
+ * local hash exists (with rate limiting).
7
+ *
8
+ * Architecture:
9
+ * - Maintains **in-memory-only** state (resets on page refresh) for failure
10
+ * counters and rate-limit timers.
11
+ * - Two operational strategies:
12
+ * 1. `local-match`: A cached hash exists and the user's input matches it.
13
+ * Proceed to Supabase for authoritative verification.
14
+ * 2. `no-cache`: No cached hash is available. Proceed to Supabase but apply
15
+ * exponential backoff on repeated failures.
16
+ * - After a configurable number of consecutive local mismatches, the cached hash
17
+ * is invalidated (it may be stale from a server-side password change) and the
18
+ * guard falls back to rate-limited Supabase mode.
19
+ *
20
+ * Security considerations:
21
+ * - The guard is a **client-side optimization**, not a security boundary. It
22
+ * reduces unnecessary network calls and provides a better UX (instant
23
+ * rejection for wrong passwords) but Supabase remains the authoritative
24
+ * verifier.
25
+ * - Rate limiting is in-memory only and resets on page refresh; server-side rate
26
+ * limits in Supabase are still the primary defense against brute-force.
27
+ * - Cached hashes are SHA-256 digests stored in IndexedDB. They are invalidated
28
+ * when stale-hash scenarios are detected (local match but Supabase rejects).
29
+ *
30
+ * @module auth/loginGuard
31
+ */
32
+ import { hashValue } from './crypto';
33
+ import { getEngineConfig } from '../config';
34
+ import { debugLog, debugWarn } from '../debug';
35
+ // =============================================================================
36
+ // CONSTANTS
37
+ // =============================================================================
38
+ /**
39
+ * Number of consecutive local hash mismatches before the cached hash is
40
+ * invalidated. Prevents a permanently stale hash from locking out the user.
41
+ */
42
+ const LOCAL_FAILURE_THRESHOLD = 5;
43
+ /** Base delay (in milliseconds) for the first rate-limited retry. */
44
+ const BASE_DELAY_MS = 1000;
45
+ /** Maximum delay cap (in milliseconds) to prevent absurdly long waits. */
46
+ const MAX_DELAY_MS = 30000;
47
+ /** Multiplier for exponential backoff between rate-limited attempts. */
48
+ const BACKOFF_MULTIPLIER = 2;
49
+ // =============================================================================
50
+ // IN-MEMORY STATE
51
+ // =============================================================================
52
+ /**
53
+ * Tracks how many times the local hash comparison has failed consecutively.
54
+ * Once this reaches `LOCAL_FAILURE_THRESHOLD`, the cached hash is invalidated.
55
+ */
56
+ let consecutiveLocalFailures = 0;
57
+ /**
58
+ * Number of failed Supabase login attempts in no-cache mode. Used to compute
59
+ * the exponential backoff delay.
60
+ */
61
+ let rateLimitAttempts = 0;
62
+ /**
63
+ * Timestamp (ms since epoch) before which the next login attempt is blocked.
64
+ * Zero means no rate limit is active.
65
+ */
66
+ let nextAllowedAttempt = 0;
67
+ // =============================================================================
68
+ // INTERNAL HELPERS
69
+ // =============================================================================
70
+ /**
71
+ * Check whether the current rate-limit window allows a new attempt.
72
+ *
73
+ * @returns An object indicating whether the attempt is allowed, and if not,
74
+ * how many milliseconds remain until the next allowed attempt.
75
+ */
76
+ function checkRateLimit() {
77
+ const now = Date.now();
78
+ if (nextAllowedAttempt > now) {
79
+ return { allowed: false, retryAfterMs: nextAllowedAttempt - now };
80
+ }
81
+ return { allowed: true };
82
+ }
83
+ /**
84
+ * Invalidate the locally cached gate hash in IndexedDB.
85
+ *
86
+ * Called when the guard determines the cached hash is stale (e.g., the user
87
+ * changed their PIN on another device, or too many consecutive local
88
+ * mismatches have occurred).
89
+ *
90
+ * @throws Never -- errors are caught and logged via `debugWarn`.
91
+ */
92
+ async function invalidateCachedHash() {
93
+ try {
94
+ const config = getEngineConfig();
95
+ const db = config.db;
96
+ if (db) {
97
+ const record = await db.table('singleUserConfig').get('config');
98
+ if (record && record.gateHash) {
99
+ await db.table('singleUserConfig').update('config', {
100
+ gateHash: undefined,
101
+ updatedAt: new Date().toISOString()
102
+ });
103
+ debugLog('[LoginGuard] Invalidated single-user gateHash');
104
+ }
105
+ }
106
+ }
107
+ catch (e) {
108
+ debugWarn('[LoginGuard] Failed to invalidate cached hash:', e);
109
+ }
110
+ }
111
+ // =============================================================================
112
+ // PUBLIC API
113
+ // =============================================================================
114
+ /**
115
+ * Pre-check login credentials locally before calling Supabase.
116
+ *
117
+ * Reads `singleUserConfig.gateHash`, hashes input, and compares.
118
+ *
119
+ * Returns `{ proceed: true, strategy }` to allow Supabase call,
120
+ * or `{ proceed: false, error, retryAfterMs? }` to reject locally.
121
+ *
122
+ * @param input - The plaintext password or gate code entered by the user.
123
+ * @returns A promise resolving to a {@link PreCheckResult}.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const result = await preCheckLogin(password);
128
+ * if (result.proceed) {
129
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
130
+ * if (error) await onLoginFailure(result.strategy);
131
+ * else onLoginSuccess();
132
+ * } else {
133
+ * showError(result.error);
134
+ * }
135
+ * ```
136
+ *
137
+ * @see {@link onLoginSuccess} -- must be called after a successful Supabase login.
138
+ * @see {@link onLoginFailure} -- must be called after a failed Supabase login.
139
+ */
140
+ export async function preCheckLogin(input) {
141
+ try {
142
+ let cachedHash;
143
+ const config = getEngineConfig();
144
+ const db = config.db;
145
+ if (db) {
146
+ const record = await db.table('singleUserConfig').get('config');
147
+ cachedHash = record?.gateHash;
148
+ }
149
+ if (cachedHash) {
150
+ /* We have a cached hash -- compare locally before touching the network. */
151
+ const inputHash = await hashValue(input);
152
+ if (inputHash === cachedHash) {
153
+ /* Local match -- proceed to Supabase for authoritative verification.
154
+ We never trust the local hash alone because it could be stale. */
155
+ debugLog('[LoginGuard] Local hash match, proceeding to Supabase');
156
+ return { proceed: true, strategy: 'local-match' };
157
+ }
158
+ /* Mismatch -- reject locally to avoid a needless Supabase round-trip. */
159
+ consecutiveLocalFailures++;
160
+ debugWarn(`[LoginGuard] Local hash mismatch (${consecutiveLocalFailures}/${LOCAL_FAILURE_THRESHOLD})`);
161
+ if (consecutiveLocalFailures >= LOCAL_FAILURE_THRESHOLD) {
162
+ /* Threshold exceeded -- the cached hash may be stale (password changed
163
+ on another device). Invalidate it so subsequent attempts go directly
164
+ to Supabase in rate-limited mode. */
165
+ debugWarn('[LoginGuard] Threshold exceeded, invalidating cached hash');
166
+ await invalidateCachedHash();
167
+ consecutiveLocalFailures = 0;
168
+ /* Fall through to rate-limited Supabase mode */
169
+ const rateCheck = checkRateLimit();
170
+ if (!rateCheck.allowed) {
171
+ return {
172
+ proceed: false,
173
+ error: 'Too many attempts. Please wait before trying again.',
174
+ retryAfterMs: rateCheck.retryAfterMs
175
+ };
176
+ }
177
+ return { proceed: true, strategy: 'no-cache' };
178
+ }
179
+ return { proceed: false, error: 'Incorrect password or code' };
180
+ }
181
+ /* No cached hash -- rate-limited Supabase mode */
182
+ const rateCheck = checkRateLimit();
183
+ if (!rateCheck.allowed) {
184
+ return {
185
+ proceed: false,
186
+ error: 'Too many attempts. Please wait before trying again.',
187
+ retryAfterMs: rateCheck.retryAfterMs
188
+ };
189
+ }
190
+ debugLog('[LoginGuard] No cached hash, proceeding to Supabase (rate-limited mode)');
191
+ return { proceed: true, strategy: 'no-cache' };
192
+ }
193
+ catch (e) {
194
+ /* On any error, allow Supabase call (fail open for auth). A strict
195
+ "fail closed" policy here would lock users out of their own app
196
+ due to an IndexedDB read error. */
197
+ debugWarn('[LoginGuard] Pre-check error, falling through to Supabase:', e);
198
+ return { proceed: true, strategy: 'no-cache' };
199
+ }
200
+ }
201
+ /**
202
+ * Called after a successful Supabase login.
203
+ *
204
+ * Resets all login guard counters (local failure count, rate-limit attempts,
205
+ * and the next-allowed-attempt timestamp) so the user starts fresh.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
210
+ * if (!error) onLoginSuccess();
211
+ * ```
212
+ */
213
+ export function onLoginSuccess() {
214
+ consecutiveLocalFailures = 0;
215
+ rateLimitAttempts = 0;
216
+ nextAllowedAttempt = 0;
217
+ debugLog('[LoginGuard] Login success, counters reset');
218
+ }
219
+ /**
220
+ * Called after a failed Supabase login.
221
+ *
222
+ * Behavior depends on the strategy that was used:
223
+ *
224
+ * - `'local-match'`: Supabase rejected a locally-matched password, meaning the
225
+ * cached hash is **stale** (password changed server-side). The cached hash is
226
+ * invalidated so future attempts go through rate-limited Supabase mode.
227
+ * - `'no-cache'`: Increment the rate-limit counter and apply exponential
228
+ * backoff (base * 2^(n-1), capped at MAX_DELAY_MS).
229
+ *
230
+ * @param strategy - The {@link PreCheckStrategy} that was returned by
231
+ * {@link preCheckLogin} for this attempt.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * const result = await preCheckLogin(password);
236
+ * if (result.proceed) {
237
+ * const { error } = await supabase.auth.signInWithPassword({ email, password });
238
+ * if (error) await onLoginFailure(result.strategy);
239
+ * }
240
+ * ```
241
+ */
242
+ export async function onLoginFailure(strategy) {
243
+ if (strategy === 'local-match') {
244
+ /* Stale hash: local match but Supabase rejected -- invalidate the cache
245
+ so the user is not stuck in a loop of false local matches. */
246
+ debugWarn('[LoginGuard] Stale hash detected, invalidating cached hash');
247
+ await invalidateCachedHash();
248
+ }
249
+ else {
250
+ /* No-cache mode: apply exponential backoff to throttle brute-force
251
+ attempts that bypass the local pre-check. */
252
+ rateLimitAttempts++;
253
+ const delay = Math.min(BASE_DELAY_MS * Math.pow(BACKOFF_MULTIPLIER, rateLimitAttempts - 1), MAX_DELAY_MS);
254
+ nextAllowedAttempt = Date.now() + delay;
255
+ debugWarn(`[LoginGuard] Rate limit applied: ${delay}ms delay (attempt ${rateLimitAttempts})`);
256
+ }
257
+ }
258
+ /**
259
+ * Full reset of all login guard state.
260
+ *
261
+ * Call on sign-out or app reset to clear failure counters and rate-limit
262
+ * timers so the next login attempt starts with a clean slate.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * await supabase.auth.signOut();
267
+ * resetLoginGuard();
268
+ * ```
269
+ */
270
+ export function resetLoginGuard() {
271
+ consecutiveLocalFailures = 0;
272
+ rateLimitAttempts = 0;
273
+ nextAllowedAttempt = 0;
274
+ debugLog('[LoginGuard] Guard reset');
275
+ }
276
+ //# sourceMappingURL=loginGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginGuard.js","sourceRoot":"","sources":["../../src/auth/loginGuard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @fileoverview Offline Credentials Management
3
+ *
4
+ * Handles caching, retrieval, and update of user credentials in IndexedDB
5
+ * for offline fallback support. When the user successfully authenticates
6
+ * online via Supabase, their credentials are cached locally (with the
7
+ * password SHA-256-hashed) so profile data is available while offline.
8
+ *
9
+ * Architecture:
10
+ * - Credentials are stored as a singleton record (key: `'current_user'`) in the
11
+ * `offlineCredentials` IndexedDB table.
12
+ * - Only one set of credentials is cached at a time.
13
+ * - The profile blob is extracted via the host app's `profileExtractor` config
14
+ * callback, or falls back to raw Supabase `user_metadata`.
15
+ *
16
+ * Security considerations:
17
+ * - Passwords are **always** hashed with SHA-256 before storage. The plaintext
18
+ * password is never persisted.
19
+ * - New writes always hash the password before storage.
20
+ * - A paranoid read-back verification is performed after `cacheOfflineCredentials`
21
+ * to ensure the password was actually persisted (guards against silent
22
+ * IndexedDB write failures).
23
+ * - Credentials are cleared on logout via `clearOfflineCredentials`.
24
+ *
25
+ * @module auth/offlineCredentials
26
+ */
27
+ import type { OfflineCredentials } from '../types';
28
+ import type { User, Session } from '@supabase/supabase-js';
29
+ /**
30
+ * Cache user credentials for offline login.
31
+ *
32
+ * Called after a successful Supabase login to persist a hashed copy of the
33
+ * user's credentials in IndexedDB. Subsequent offline logins will verify
34
+ * against these cached credentials.
35
+ *
36
+ * @param email - The user's email address (used for offline identity matching).
37
+ * @param password - The user's plaintext password. Will be SHA-256-hashed before storage.
38
+ * @param user - The Supabase `User` object, used to extract `userId` and profile data.
39
+ * @param _session - The Supabase `Session` object. Currently unused but accepted for
40
+ * API symmetry with the online auth flow (reserved for future use).
41
+ *
42
+ * @throws {Error} If `email` or `password` is empty (prevents storing incomplete credentials).
43
+ * @throws {Error} If the write-back verification fails (password not persisted in IndexedDB).
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const { data } = await supabase.auth.signInWithPassword({ email, password });
48
+ * if (data.user && data.session) {
49
+ * await cacheOfflineCredentials(email, password, data.user, data.session);
50
+ * }
51
+ * ```
52
+ *
53
+ * @see {@link getOfflineCredentials} to retrieve the cached credentials.
54
+ * @see {@link clearOfflineCredentials} to remove them on logout.
55
+ */
56
+ export declare function cacheOfflineCredentials(email: string, password: string, user: User, _session: Session): Promise<void>;
57
+ /**
58
+ * Get cached offline credentials from IndexedDB.
59
+ *
60
+ * Returns the singleton `OfflineCredentials` record, or `null` if no
61
+ * credentials have been cached (e.g., user has never logged in online
62
+ * on this device).
63
+ *
64
+ * @returns The cached credentials, or `null` if none exist.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const creds = await getOfflineCredentials();
69
+ * if (creds) {
70
+ * console.log('Cached user:', creds.email);
71
+ * }
72
+ * ```
73
+ */
74
+ export declare function getOfflineCredentials(): Promise<OfflineCredentials | null>;
75
+ /**
76
+ * Update the user profile in cached credentials after an online profile update.
77
+ *
78
+ * Replaces the entire `profile` blob with the provided object and updates
79
+ * the `cachedAt` timestamp.
80
+ *
81
+ * @param profile - The new profile data to cache (e.g., `{ firstName, lastName, avatar }`).
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * await supabase.auth.updateUser({ data: { firstName: 'Jane' } });
86
+ * await updateOfflineCredentialsProfile({ firstName: 'Jane', lastName: 'Doe' });
87
+ * ```
88
+ */
89
+ export declare function updateOfflineCredentialsProfile(profile: Record<string, unknown>): Promise<void>;
90
+ /**
91
+ * Clear all cached offline credentials from IndexedDB.
92
+ *
93
+ * Must be called on logout to ensure no stale credentials remain on the
94
+ * device that could be used for unauthorized offline access.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * await supabase.auth.signOut();
99
+ * await clearOfflineCredentials();
100
+ * ```
101
+ *
102
+ * @see {@link cacheOfflineCredentials} for storing credentials on login.
103
+ */
104
+ export declare function clearOfflineCredentials(): Promise<void>;
105
+ //# sourceMappingURL=offlineCredentials.d.ts.map