@shellui/core 0.2.0 → 0.3.0-beta.1

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 (111) hide show
  1. package/package.json +9 -4
  2. package/src/app.tsx +12 -9
  3. package/src/components/ui/badge.tsx +35 -0
  4. package/src/components/ui/dropdown-menu.tsx +94 -0
  5. package/src/components/ui/sidebar.tsx +1 -1
  6. package/src/constants/urls.ts +8 -0
  7. package/src/features/admin/AdminView.tsx +154 -0
  8. package/src/features/admin/components/AdminForbiddenAccess.tsx +10 -0
  9. package/src/features/auth/AuthProvider.tsx +464 -0
  10. package/src/features/auth/backends/index.ts +41 -0
  11. package/src/features/auth/backends/shellui.ts +278 -0
  12. package/src/features/auth/backends/supabase.ts +300 -0
  13. package/src/features/auth/backends/types.ts +30 -0
  14. package/src/features/auth/components/LoginButton.tsx +360 -0
  15. package/src/features/auth/components/LoginButtonIcons.tsx +48 -0
  16. package/src/features/auth/components/LoginView.tsx +721 -0
  17. package/src/features/auth/components/OAuthCallbackView.tsx +119 -0
  18. package/src/features/auth/hooks/useAuth.tsx +37 -0
  19. package/src/features/auth/types.ts +51 -0
  20. package/src/features/auth/utils/buildSessionFromParams.spec.ts +61 -0
  21. package/src/features/auth/utils/buildSessionFromParams.ts +79 -0
  22. package/src/features/auth/utils/clearStoredAuthSession.spec.ts +23 -0
  23. package/src/features/auth/utils/clearStoredAuthSession.ts +10 -0
  24. package/src/features/auth/utils/clientLoginContext.spec.ts +83 -0
  25. package/src/features/auth/utils/clientLoginContext.ts +89 -0
  26. package/src/features/auth/utils/decodeJwtPayload.spec.ts +17 -0
  27. package/src/features/auth/utils/decodeJwtPayload.ts +24 -0
  28. package/src/features/auth/utils/formatProviderLabel.spec.ts +19 -0
  29. package/src/features/auth/utils/formatProviderLabel.ts +11 -0
  30. package/src/features/auth/utils/getOAuthProviderCandidates.spec.ts +16 -0
  31. package/src/features/auth/utils/getOAuthProviderCandidates.ts +15 -0
  32. package/src/features/auth/utils/getPreferredBackendProvider.spec.ts +15 -0
  33. package/src/features/auth/utils/getPreferredBackendProvider.ts +10 -0
  34. package/src/features/auth/utils/getProviderVisual.spec.ts +30 -0
  35. package/src/features/auth/utils/getProviderVisual.ts +83 -0
  36. package/src/features/auth/utils/getUserFromSdkSettings.spec.ts +32 -0
  37. package/src/features/auth/utils/getUserFromSdkSettings.ts +13 -0
  38. package/src/features/auth/utils/index.ts +21 -0
  39. package/src/features/auth/utils/isLoginMethod.spec.ts +18 -0
  40. package/src/features/auth/utils/isLoginMethod.ts +5 -0
  41. package/src/features/auth/utils/isSessionExpired.spec.ts +23 -0
  42. package/src/features/auth/utils/isSessionExpired.ts +5 -0
  43. package/src/features/auth/utils/normalizeAuthSettings.spec.ts +60 -0
  44. package/src/features/auth/utils/normalizeAuthSettings.ts +71 -0
  45. package/src/features/auth/utils/normalizeNextPath.spec.ts +21 -0
  46. package/src/features/auth/utils/normalizeNextPath.ts +12 -0
  47. package/src/features/auth/utils/normalizeRedirectPath.spec.ts +12 -0
  48. package/src/features/auth/utils/normalizeRedirectPath.ts +3 -0
  49. package/src/features/auth/utils/persistAuthSession.spec.ts +35 -0
  50. package/src/features/auth/utils/persistAuthSession.ts +12 -0
  51. package/src/features/auth/utils/readStoredAuthSession.spec.ts +34 -0
  52. package/src/features/auth/utils/readStoredAuthSession.ts +14 -0
  53. package/src/features/auth/utils/toAuthSessionFromSettingsUser.spec.ts +76 -0
  54. package/src/features/auth/utils/toAuthSessionFromSettingsUser.ts +36 -0
  55. package/src/features/config/types.ts +55 -0
  56. package/src/features/layouts/AppLayout.tsx +8 -6
  57. package/src/features/layouts/appbar/AppBarLayout.tsx +42 -23
  58. package/src/features/layouts/fullscreen/FullscreenLayout.tsx +3 -2
  59. package/src/features/layouts/sidebar/SidebarInner.tsx +7 -5
  60. package/src/features/layouts/sidebar/SidebarLayout.tsx +16 -3
  61. package/src/features/layouts/utils.ts +54 -0
  62. package/src/features/layouts/windows/WindowsLayout.tsx +22 -4
  63. package/src/features/legal/LegalDocumentContent.tsx +102 -0
  64. package/src/features/legal/LegalDocumentView.tsx +42 -0
  65. package/src/features/legal/LegalDocumentsIndexView.tsx +51 -0
  66. package/src/features/legal/LegalDocumentsLinks.tsx +29 -0
  67. package/src/features/legal/legalDocuments.ts +62 -0
  68. package/src/features/settings/SettingsIcons.tsx +20 -0
  69. package/src/features/settings/SettingsProvider.tsx +347 -245
  70. package/src/features/settings/SettingsRoutes.tsx +8 -0
  71. package/src/features/settings/SettingsView.tsx +43 -8
  72. package/src/features/settings/components/Develop.tsx +2 -2
  73. package/src/features/settings/components/LegalDocumentsPanel.tsx +46 -0
  74. package/src/features/settings/components/UserIcon.tsx +20 -0
  75. package/src/features/settings/components/UserSettingsPanel.tsx +438 -0
  76. package/src/features/settings/components/createUserSettingsRoute.tsx +43 -0
  77. package/src/features/settings/utils/buildSettingsForPropagation.spec.ts +167 -0
  78. package/src/features/settings/utils/buildSettingsForPropagation.ts +61 -0
  79. package/src/features/settings/utils/flattenNavigationItems.spec.ts +17 -0
  80. package/src/features/settings/utils/flattenNavigationItems.ts +12 -0
  81. package/src/features/settings/utils/getAvailableThemesForSettings.spec.ts +15 -0
  82. package/src/features/settings/utils/getAvailableThemesForSettings.ts +16 -0
  83. package/src/features/settings/utils/getBrowserTimezone.spec.ts +11 -0
  84. package/src/features/settings/utils/getBrowserTimezone.ts +7 -0
  85. package/src/features/settings/utils/getPreferenceSnapshot.spec.ts +35 -0
  86. package/src/features/settings/utils/getPreferenceSnapshot.ts +10 -0
  87. package/src/features/settings/utils/getResolvedAppearanceForSettings.spec.ts +97 -0
  88. package/src/features/settings/utils/getResolvedAppearanceForSettings.ts +48 -0
  89. package/src/features/settings/utils/index.ts +12 -0
  90. package/src/features/settings/utils/isSameUser.spec.ts +35 -0
  91. package/src/features/settings/utils/isSameUser.ts +17 -0
  92. package/src/features/settings/utils/mergePreferencesIntoSettings.spec.ts +108 -0
  93. package/src/features/settings/utils/mergePreferencesIntoSettings.ts +47 -0
  94. package/src/features/settings/utils/resolveColorMode.spec.ts +29 -0
  95. package/src/features/settings/utils/resolveColorMode.ts +6 -0
  96. package/src/features/settings/utils/resolveLabel.spec.ts +17 -0
  97. package/src/features/settings/utils/resolveLabel.ts +7 -0
  98. package/src/features/settings/utils/toAbsoluteFontUrls.spec.ts +26 -0
  99. package/src/features/settings/utils/toAbsoluteFontUrls.ts +15 -0
  100. package/src/features/settings/utils/toSettingsUser.spec.ts +49 -0
  101. package/src/features/settings/utils/toSettingsUser.ts +15 -0
  102. package/src/i18n/translations/en/common.json +14 -0
  103. package/src/i18n/translations/en/settings.json +45 -0
  104. package/src/i18n/translations/fr/common.json +14 -0
  105. package/src/i18n/translations/fr/settings.json +45 -0
  106. package/src/index.css +37 -0
  107. package/src/index.ts +6 -0
  108. package/src/routes/components/NavigationItemRoute.tsx +32 -1
  109. package/src/routes/components/NotFoundView.tsx +13 -3
  110. package/src/routes/hooks/useNavigationItems.ts +19 -4
  111. package/src/routes/routes.tsx +87 -0
@@ -0,0 +1,464 @@
1
+ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { getLogger, shellui, type Settings } from '@shellui/sdk';
3
+ import urls from '../../constants/urls';
4
+ import { useConfig } from '../config/useConfig';
5
+ import { createAuthBackend } from './backends';
6
+ import { AuthContext, type AuthContextValue } from './hooks/useAuth';
7
+ import type { AuthEvent, AuthSession, AuthUser, UserPreferences } from './types';
8
+ import {
9
+ clearStoredAuthSession,
10
+ getAccessTokenFromSdkSettings,
11
+ getUserFromSdkSettings,
12
+ isSessionExpired,
13
+ persistAuthSession,
14
+ readStoredAuthSession,
15
+ toAuthSessionFromSettingsUser,
16
+ } from './utils';
17
+
18
+ const logger = getLogger('shellcore');
19
+
20
+ /** Refresh the access token this many seconds before `expiresAt` so API calls stay valid (JWT exp is absolute). */
21
+ const PROACTIVE_REFRESH_LEEWAY_SECONDS = 90;
22
+ /** Periodic check while the shell tab is open (also mitigates background-tab timer throttling). */
23
+ const TOKEN_REFRESH_TICK_MS = 45_000;
24
+
25
+ type LoginMessagePayload = {
26
+ method?: 'oauth' | 'web3';
27
+ provider?: string;
28
+ oauthClientId?: number;
29
+ chain?: 'ethereum';
30
+ redirectPath?: string;
31
+ };
32
+
33
+ export const AuthProvider = ({ children }: { children: ReactNode }) => {
34
+ const { config } = useConfig();
35
+ const backend = useMemo(
36
+ () => createAuthBackend(config.backend),
37
+ [config.backend?.publishableKey, config.backend?.type, config.backend?.url],
38
+ );
39
+
40
+ const [session, setSession] = useState<AuthSession | null>(null);
41
+ const [isLoading, setIsLoading] = useState(true);
42
+ const [error, setError] = useState<string | null>(null);
43
+ const [authEvent, setAuthEvent] = useState<AuthEvent>(null);
44
+ const sessionRef = useRef<AuthSession | null>(null);
45
+ sessionRef.current = session;
46
+ const refreshInFlightRef = useRef(false);
47
+
48
+ useEffect(() => {
49
+ let cancelled = false;
50
+
51
+ const initializeSession = async () => {
52
+ setIsLoading(true);
53
+ setError(null);
54
+
55
+ if (typeof window === 'undefined') {
56
+ setIsLoading(false);
57
+ return;
58
+ }
59
+
60
+ const isIframe = window.parent !== window;
61
+ const now = Math.floor(Date.now() / 1000);
62
+ const sessionFromHash = backend.readSessionFromCallback(window.location.hash, now);
63
+ if (sessionFromHash) {
64
+ if (!cancelled) {
65
+ persistAuthSession(sessionFromHash);
66
+ setSession(sessionFromHash);
67
+ setAuthEvent('oauth_callback');
68
+ if (window.location.hash) {
69
+ window.history.replaceState(
70
+ {},
71
+ document.title,
72
+ window.location.pathname + window.location.search,
73
+ );
74
+ }
75
+ setIsLoading(false);
76
+ }
77
+ return;
78
+ }
79
+
80
+ if (isIframe) {
81
+ const sdkUser = getUserFromSdkSettings();
82
+ const sdkAccessToken = getAccessTokenFromSdkSettings();
83
+ if (!cancelled) {
84
+ setSession(sdkUser ? toAuthSessionFromSettingsUser(sdkUser, sdkAccessToken) : null);
85
+ setIsLoading(false);
86
+ }
87
+ return;
88
+ }
89
+
90
+ const stored = readStoredAuthSession();
91
+ if (!stored) {
92
+ if (!cancelled) {
93
+ setSession(null);
94
+ setIsLoading(false);
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (!isSessionExpired(stored)) {
100
+ if (!cancelled) {
101
+ setSession(stored);
102
+ setIsLoading(false);
103
+ }
104
+ return;
105
+ }
106
+
107
+ try {
108
+ const restored = await backend.restoreSession(stored, now);
109
+ if (!restored) {
110
+ clearStoredAuthSession();
111
+ if (!cancelled) {
112
+ setSession(null);
113
+ setIsLoading(false);
114
+ }
115
+ return;
116
+ }
117
+
118
+ if (!cancelled) {
119
+ persistAuthSession(restored);
120
+ setSession(restored);
121
+ setIsLoading(false);
122
+ }
123
+ } catch {
124
+ clearStoredAuthSession();
125
+ if (!cancelled) {
126
+ setSession(null);
127
+ setIsLoading(false);
128
+ }
129
+ }
130
+ };
131
+
132
+ void initializeSession();
133
+ return () => {
134
+ cancelled = true;
135
+ };
136
+ }, [backend]);
137
+
138
+ useEffect(() => {
139
+ if (typeof window === 'undefined' || window.parent !== window || isLoading) {
140
+ return;
141
+ }
142
+ if (!session?.refreshToken) {
143
+ return;
144
+ }
145
+
146
+ let cancelled = false;
147
+
148
+ const runRefresh = async () => {
149
+ if (cancelled) return;
150
+ const current = sessionRef.current;
151
+ if (!current?.refreshToken || refreshInFlightRef.current) return;
152
+
153
+ const now = Math.floor(Date.now() / 1000);
154
+ const secondsLeft = current.expiresAt - now;
155
+ if (secondsLeft > PROACTIVE_REFRESH_LEEWAY_SECONDS && !isSessionExpired(current)) {
156
+ return;
157
+ }
158
+
159
+ refreshInFlightRef.current = true;
160
+ try {
161
+ const next = await backend.refreshAuthSession(current, now);
162
+ if (cancelled || !next) return;
163
+ persistAuthSession(next);
164
+ sessionRef.current = next;
165
+ setSession(next);
166
+ } catch (err) {
167
+ logger.error('Token refresh failed', { err });
168
+ const message = err instanceof Error ? err.message : '';
169
+ if (/\b401\b/.test(message)) {
170
+ clearStoredAuthSession();
171
+ sessionRef.current = null;
172
+ setSession(null);
173
+ }
174
+ } finally {
175
+ refreshInFlightRef.current = false;
176
+ }
177
+ };
178
+
179
+ const maybeRefreshFromTimer = () => {
180
+ void runRefresh();
181
+ };
182
+
183
+ const maybeRefreshOnResume = () => {
184
+ if (cancelled || document.visibilityState !== 'visible') return;
185
+ const current = sessionRef.current;
186
+ if (!current?.refreshToken || !isSessionExpired(current)) return;
187
+ void runRefresh();
188
+ };
189
+
190
+ maybeRefreshFromTimer();
191
+ const intervalId = window.setInterval(maybeRefreshFromTimer, TOKEN_REFRESH_TICK_MS);
192
+ document.addEventListener('visibilitychange', maybeRefreshOnResume);
193
+ window.addEventListener('focus', maybeRefreshOnResume);
194
+
195
+ return () => {
196
+ cancelled = true;
197
+ window.clearInterval(intervalId);
198
+ document.removeEventListener('visibilitychange', maybeRefreshOnResume);
199
+ window.removeEventListener('focus', maybeRefreshOnResume);
200
+ };
201
+ }, [backend, isLoading, session?.refreshToken, session?.expiresAt]);
202
+
203
+ useEffect(() => {
204
+ if (typeof window === 'undefined' || window.parent === window) {
205
+ return;
206
+ }
207
+
208
+ const syncSessionFromSettings = (message: { payload: unknown }) => {
209
+ const payload = message.payload as { settings?: Settings };
210
+ const settingsUser = payload.settings?.user ?? null;
211
+ const settingsAccessToken = payload.settings?.accessToken ?? null;
212
+ setSession(
213
+ settingsUser ? toAuthSessionFromSettingsUser(settingsUser, settingsAccessToken) : null,
214
+ );
215
+ };
216
+
217
+ const cleanupSettingsUpdated = shellui.addMessageListener(
218
+ 'SHELLUI_SETTINGS_UPDATED',
219
+ (message) => {
220
+ syncSessionFromSettings(message);
221
+ },
222
+ );
223
+
224
+ return () => {
225
+ cleanupSettingsUpdated();
226
+ };
227
+ }, []);
228
+
229
+ const startOAuth = useCallback(
230
+ (provider: string, redirectPath = urls.login, oauthClientId?: number) => {
231
+ try {
232
+ setError(null);
233
+ backend.startOAuth(provider, redirectPath, oauthClientId);
234
+ return true;
235
+ } catch (err) {
236
+ setError(err instanceof Error ? err.message : 'Unable to start OAuth login.');
237
+ return false;
238
+ }
239
+ },
240
+ [backend],
241
+ );
242
+
243
+ const completeOAuthCallback = useCallback(
244
+ async ({
245
+ provider,
246
+ code,
247
+ redirectUri,
248
+ oauthClientId,
249
+ }: {
250
+ provider: string;
251
+ code: string;
252
+ redirectUri: string;
253
+ oauthClientId?: number;
254
+ }) => {
255
+ try {
256
+ setError(null);
257
+ const now = Math.floor(Date.now() / 1000);
258
+ const nextSession = await backend.exchangeOAuthCode({
259
+ provider,
260
+ code,
261
+ redirectUri,
262
+ oauthClientId,
263
+ nowSeconds: now,
264
+ });
265
+ if (!nextSession) {
266
+ setError('Unable to complete OAuth login.');
267
+ return false;
268
+ }
269
+ persistAuthSession(nextSession);
270
+ setSession(nextSession);
271
+ setAuthEvent('oauth_callback');
272
+ return true;
273
+ } catch (err) {
274
+ setError(err instanceof Error ? err.message : 'Unable to complete OAuth login.');
275
+ return false;
276
+ }
277
+ },
278
+ [backend],
279
+ );
280
+
281
+ const startWeb3Ethereum = useCallback(async () => {
282
+ try {
283
+ setError(null);
284
+ const nextSession = await backend.startWeb3Ethereum();
285
+ if (!nextSession) {
286
+ setError('Unable to complete Ethereum wallet login.');
287
+ return false;
288
+ }
289
+ persistAuthSession(nextSession);
290
+ setSession(nextSession);
291
+ return true;
292
+ } catch (err) {
293
+ setError(err instanceof Error ? err.message : 'Unable to start Ethereum wallet login.');
294
+ return false;
295
+ }
296
+ }, [backend]);
297
+
298
+ const getAuthSettings = useCallback(() => backend.getAuthSettings(), [backend]);
299
+
300
+ const sendMagicLink = useCallback(
301
+ async (email: string, redirectPath = urls.login) => {
302
+ try {
303
+ await backend.sendMagicLink(email, redirectPath);
304
+ } catch (err) {
305
+ const message = err instanceof Error ? err.message : 'Could not send magic link.';
306
+ throw new Error(message);
307
+ }
308
+ },
309
+ [backend],
310
+ );
311
+
312
+ const syncUserPreferences = useCallback(
313
+ async (preferences: UserPreferences) => {
314
+ try {
315
+ await backend.syncUserPreferences(session, preferences);
316
+ const now = Math.floor(Date.now() / 1000);
317
+ let nextSession: AuthSession | null = null;
318
+ try {
319
+ nextSession = session ? await backend.refreshAuthSession(session, now) : null;
320
+ } catch (refreshErr) {
321
+ logger.error('Failed to refresh auth session after syncing preferences', { refreshErr });
322
+ }
323
+ if (!nextSession && session) {
324
+ nextSession = { ...session, userPreferences: preferences };
325
+ }
326
+ if (nextSession) {
327
+ // Keep stored snapshot aligned with what we synced (JWT claims can lag the PUT).
328
+ nextSession = { ...nextSession, userPreferences: preferences };
329
+ if (typeof window !== 'undefined' && window.parent === window) {
330
+ persistAuthSession(nextSession);
331
+ }
332
+ setSession(nextSession);
333
+ }
334
+ } catch (err) {
335
+ logger.error('Failed to sync user preferences to auth provider metadata', { err });
336
+ }
337
+ },
338
+ [backend, session],
339
+ );
340
+
341
+ const loadUserPreferences = useCallback(async () => {
342
+ try {
343
+ return await backend.loadUserPreferences(session);
344
+ } catch (err) {
345
+ logger.error('Failed to load user preferences from auth provider metadata', { err });
346
+ return null;
347
+ }
348
+ }, [backend, session]);
349
+
350
+ const logout = useCallback(async () => {
351
+ try {
352
+ await backend.logout(session);
353
+ } catch {
354
+ // Even if API sign-out fails, still clear local session.
355
+ }
356
+
357
+ clearStoredAuthSession();
358
+ setSession(null);
359
+ setAuthEvent(null);
360
+ setError(null);
361
+ }, [backend, session]);
362
+
363
+ useEffect(() => {
364
+ if (typeof window === 'undefined' || window.parent !== window) {
365
+ return;
366
+ }
367
+
368
+ const cleanup = shellui.addMessageListener('SHELLUI_LOGOUT', () => {
369
+ void logout();
370
+ });
371
+
372
+ return () => cleanup();
373
+ }, [logout]);
374
+
375
+ useEffect(() => {
376
+ if (typeof window === 'undefined' || window.parent !== window) {
377
+ return;
378
+ }
379
+
380
+ const cleanup = shellui.addMessageListener('SHELLUI_LOGIN', (message) => {
381
+ const payload = (message.payload ?? {}) as LoginMessagePayload;
382
+ if (payload.method === 'web3') {
383
+ if (payload.chain && payload.chain !== 'ethereum') {
384
+ return;
385
+ }
386
+ void (async () => {
387
+ const started = await startWeb3Ethereum();
388
+ if (started && typeof window !== 'undefined') {
389
+ window.postMessage({ type: 'SHELLUI_CLOSE_MODAL', payload: {} }, '*');
390
+ }
391
+ })();
392
+ return;
393
+ }
394
+ if (payload.method !== 'oauth' || typeof payload.provider !== 'string') {
395
+ return;
396
+ }
397
+
398
+ const provider = payload.provider.trim();
399
+ if (!provider) {
400
+ return;
401
+ }
402
+
403
+ startOAuth(provider, payload.redirectPath || urls.login, payload.oauthClientId);
404
+ });
405
+
406
+ return () => cleanup();
407
+ }, [startOAuth, startWeb3Ethereum]);
408
+
409
+ const clearAuthEvent = useCallback(() => setAuthEvent(null), []);
410
+ const user = useMemo<AuthUser | null>(
411
+ () =>
412
+ session
413
+ ? {
414
+ id: session.userId,
415
+ email: session.userEmail,
416
+ name: session.userName,
417
+ profilePicture: session.userAvatarUrl,
418
+ isStaff: session.userIsStaff,
419
+ isCompanyOwner: session.userIsCompanyOwner === true,
420
+ authProvider: session.provider,
421
+ groups: session.userGroups ?? [],
422
+ }
423
+ : null,
424
+ [session],
425
+ );
426
+
427
+ const value = useMemo<AuthContextValue>(
428
+ () => ({
429
+ session,
430
+ user,
431
+ isAuthenticated: session !== null && !isSessionExpired(session),
432
+ isLoading,
433
+ error,
434
+ authEvent,
435
+ clearAuthEvent,
436
+ completeOAuthCallback,
437
+ startOAuth,
438
+ startWeb3Ethereum,
439
+ getAuthSettings,
440
+ sendMagicLink,
441
+ syncUserPreferences,
442
+ loadUserPreferences,
443
+ logout,
444
+ }),
445
+ [
446
+ session,
447
+ user,
448
+ isLoading,
449
+ error,
450
+ authEvent,
451
+ clearAuthEvent,
452
+ completeOAuthCallback,
453
+ startOAuth,
454
+ startWeb3Ethereum,
455
+ getAuthSettings,
456
+ sendMagicLink,
457
+ syncUserPreferences,
458
+ loadUserPreferences,
459
+ logout,
460
+ ],
461
+ );
462
+
463
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
464
+ };
@@ -0,0 +1,41 @@
1
+ import type { BackendConfig } from '../../config/types';
2
+ import { createShellUIAuthBackend } from './shellui';
3
+ import { createSupabaseAuthBackend } from './supabase';
4
+ import type { AuthBackend } from './types';
5
+
6
+ const createNoopBackend = (): AuthBackend => ({
7
+ type: 'none',
8
+ readSessionFromCallback: () => null,
9
+ restoreSession: async () => null,
10
+ refreshAuthSession: async () => null,
11
+ startOAuth: () => {
12
+ throw new Error('No auth backend configured.');
13
+ },
14
+ startWeb3Ethereum: async () => {
15
+ throw new Error('No auth backend configured.');
16
+ },
17
+ logout: async () => {},
18
+ getAuthSettings: async () => ({ methods: [], oauthProviders: [], oauthClients: [] }),
19
+ sendMagicLink: async () => {
20
+ throw new Error('No auth backend configured.');
21
+ },
22
+ syncUserPreferences: async () => {},
23
+ loadUserPreferences: async () => null,
24
+ });
25
+
26
+ export const createAuthBackend = (backendConfig: BackendConfig | undefined): AuthBackend => {
27
+ if (!backendConfig) return createNoopBackend();
28
+ if (backendConfig.type === 'shellui') {
29
+ return createShellUIAuthBackend({
30
+ backendUrl: backendConfig.url?.replace(/\/+$/, '') ?? null,
31
+ companyId: backendConfig.companyId,
32
+ });
33
+ }
34
+ if (backendConfig.type === 'supabase') {
35
+ return createSupabaseAuthBackend({
36
+ backendUrl: backendConfig.url?.replace(/\/+$/, '') ?? null,
37
+ publishableKey: backendConfig.publishableKey,
38
+ });
39
+ }
40
+ return createNoopBackend();
41
+ };