@datalayer/core 0.0.17 → 0.0.19

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 (107) hide show
  1. package/README.md +9 -13
  2. package/lib/client/auth/AuthenticationManager.d.ts +95 -0
  3. package/lib/client/auth/AuthenticationManager.js +214 -0
  4. package/lib/client/auth/index.d.ts +8 -0
  5. package/lib/client/auth/index.js +17 -0
  6. package/lib/client/auth/storage.d.ts +154 -0
  7. package/lib/client/auth/storage.js +447 -0
  8. package/lib/client/auth/strategies.d.ts +54 -0
  9. package/lib/client/auth/strategies.js +238 -0
  10. package/lib/client/auth/types.d.ts +151 -0
  11. package/lib/{examples/index.js → client/auth/types.js} +4 -2
  12. package/lib/client/base.d.ts +3 -0
  13. package/lib/client/base.js +9 -0
  14. package/lib/client/index.d.ts +1 -0
  15. package/lib/client/index.js +2 -0
  16. package/lib/components/auth/Login.d.ts +40 -0
  17. package/lib/components/auth/Login.js +173 -0
  18. package/lib/components/auth/Login.stories.d.ts +54 -0
  19. package/lib/components/auth/Login.stories.js +104 -0
  20. package/lib/components/auth/LoginToken.d.ts +16 -0
  21. package/lib/components/auth/LoginToken.js +63 -0
  22. package/lib/components/auth/index.d.ts +5 -0
  23. package/lib/components/auth/index.js +16 -0
  24. package/lib/components/avatars/BoringAvatar.d.ts +6 -15
  25. package/lib/components/avatars/BoringAvatar.js +30 -34
  26. package/lib/components/avatars/BoringAvatar.stories.d.ts +7 -16
  27. package/lib/components/avatars/UserProfileAvatar.d.ts +1 -6
  28. package/lib/components/avatars/UserProfileAvatar.js +3 -8
  29. package/lib/components/buttons/DownloadCSVButton.d.ts +2 -7
  30. package/lib/components/buttons/DownloadCSVButton.js +1 -5
  31. package/lib/components/buttons/DownloadJsonButton.d.ts +3 -10
  32. package/lib/components/buttons/DownloadJsonButton.js +1 -7
  33. package/lib/components/buttons/UploadButton.d.ts +1 -4
  34. package/lib/components/buttons/UploadButton.js +3 -7
  35. package/lib/components/chat/ChatComponent.js +4 -0
  36. package/lib/components/chat/display/ReasoningPart.js +4 -0
  37. package/lib/components/chat/display/ToolPart.js +4 -0
  38. package/lib/components/chat/display/index.js +4 -0
  39. package/lib/components/chat/handler.js +4 -0
  40. package/lib/components/chat/index.js +4 -0
  41. package/lib/components/display/CenteredSpinner.d.ts +1 -4
  42. package/lib/components/display/CenteredSpinner.js +1 -5
  43. package/lib/components/display/HorizontalCenter.d.ts +1 -4
  44. package/lib/components/display/HorizontalCenter.js +1 -5
  45. package/lib/components/flashes/FlashClosable.d.ts +1 -4
  46. package/lib/components/flashes/FlashClosable.js +1 -5
  47. package/lib/components/flashes/FlashDisclaimer.js +1 -1
  48. package/lib/components/index.d.ts +1 -0
  49. package/lib/components/index.js +1 -0
  50. package/lib/components/notebooks/JupyterNotebook.d.ts +1 -6
  51. package/lib/components/notebooks/JupyterNotebook.js +1 -5
  52. package/lib/components/snapshots/RuntimeSnapshotMenu.d.ts +1 -4
  53. package/lib/components/snapshots/RuntimeSnapshotMenu.js +1 -5
  54. package/lib/config/Configuration.js +1 -1
  55. package/lib/examples/CellExample.js +11 -47
  56. package/lib/examples/lexical-theme.css +436 -0
  57. package/lib/examples/notebooks/Matplotlib.ipynb.json +1 -1
  58. package/lib/examples/notebooks/NotebookExample1.ipynb.json +1 -1
  59. package/lib/hooks/useAIJupyterChat.js +4 -0
  60. package/lib/hooks/useBackdrop.d.ts +4 -4
  61. package/lib/hooks/useBackdrop.js +5 -9
  62. package/lib/hooks/useCache.d.ts +5 -1
  63. package/lib/hooks/useCache.js +27 -14
  64. package/lib/hooks/useMobile.js +4 -0
  65. package/lib/hooks/useScreenshot.d.ts +3 -5
  66. package/lib/hooks/useScreenshot.js +1 -8
  67. package/lib/models/Outbound.d.ts +2 -0
  68. package/lib/models/Outbound.js +3 -1
  69. package/lib/state/substates/CoreState.js +1 -1
  70. package/lib/state/substates/IAMState.js +15 -6
  71. package/lib/tools/adapters/agui/AgUIToolAdapter.d.ts +75 -0
  72. package/lib/tools/adapters/agui/AgUIToolAdapter.js +244 -0
  73. package/lib/tools/adapters/agui/index.d.ts +10 -0
  74. package/lib/tools/adapters/agui/index.js +19 -0
  75. package/lib/tools/adapters/agui/lexicalHooks.d.ts +27 -0
  76. package/lib/tools/adapters/agui/lexicalHooks.js +64 -0
  77. package/lib/tools/adapters/agui/notebookHooks.d.ts +27 -0
  78. package/lib/tools/adapters/agui/notebookHooks.js +61 -0
  79. package/lib/tools/index.d.ts +6 -0
  80. package/lib/tools/index.js +18 -0
  81. package/lib/types.js +2 -3
  82. package/lib/utils/cli/index.d.ts +4 -0
  83. package/lib/utils/cli/index.js +13 -0
  84. package/lib/utils/cli/query.d.ts +6 -0
  85. package/lib/utils/cli/query.js +26 -0
  86. package/lib/utils/index.d.ts +1 -0
  87. package/lib/utils/index.js +1 -0
  88. package/package.json +49 -7
  89. package/lib/examples/ChatExample.d.ts +0 -8
  90. package/lib/examples/ChatExample.js +0 -51
  91. package/lib/examples/DatalayerNotebookExample.d.ts +0 -16
  92. package/lib/examples/DatalayerNotebookExample.js +0 -75
  93. package/lib/examples/NativeNavigationExample.d.ts +0 -8
  94. package/lib/examples/NativeNavigationExample.js +0 -97
  95. package/lib/examples/NotebookMutationsKernel.d.ts +0 -2
  96. package/lib/examples/NotebookMutationsKernel.js +0 -115
  97. package/lib/examples/NotebookMutationsServiceManager.d.ts +0 -2
  98. package/lib/examples/NotebookMutationsServiceManager.js +0 -107
  99. package/lib/examples/ReactRouterExample.d.ts +0 -6
  100. package/lib/examples/ReactRouterExample.js +0 -175
  101. package/lib/examples/example-selector.d.ts +0 -22
  102. package/lib/examples/example-selector.js +0 -46
  103. package/lib/examples/index.d.ts +0 -2
  104. package/lib/examples/main.d.ts +0 -1
  105. package/lib/examples/main.js +0 -153
  106. package/lib/examples/notebooks/OutputIPyWidgetsExample.d.ts +0 -145
  107. package/lib/examples/notebooks/OutputIPyWidgetsExample.js +0 -153
@@ -0,0 +1,238 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /*
6
+ * Copyright (c) 2023-2025 Datalayer, Inc.
7
+ *
8
+ * Datalayer License
9
+ */
10
+ /**
11
+ * Authentication strategy implementations
12
+ */
13
+ import * as authentication from '../../api/iam/authentication';
14
+ import * as profile from '../../api/iam/profile';
15
+ import { UserDTO } from '../../models/UserDTO';
16
+ /**
17
+ * Base authentication strategy with common functionality
18
+ */
19
+ class BaseAuthStrategy {
20
+ iamRunUrl;
21
+ storage;
22
+ constructor(iamRunUrl, storage) {
23
+ this.iamRunUrl = iamRunUrl;
24
+ this.storage = storage;
25
+ }
26
+ /**
27
+ * Validate a token by calling whoami
28
+ */
29
+ async validateToken(token) {
30
+ const response = await profile.whoami(token, this.iamRunUrl);
31
+ if (!response || !response.profile) {
32
+ throw new Error('Invalid response from profile API');
33
+ }
34
+ const userData = {
35
+ id: response.profile.id,
36
+ uid: response.profile.uid,
37
+ handle_s: response.profile.handle_s || response.profile.handle,
38
+ email_s: response.profile.email_s || response.profile.email,
39
+ first_name_t: response.profile.first_name_t || response.profile.first_name || '',
40
+ last_name_t: response.profile.last_name_t || response.profile.last_name || '',
41
+ avatar_url_s: response.profile.avatar_url_s || response.profile.avatar_url,
42
+ };
43
+ return new UserDTO(userData, undefined);
44
+ }
45
+ }
46
+ /**
47
+ * Token-based authentication strategy
48
+ * Authenticates using an existing token
49
+ */
50
+ export class TokenAuthStrategy extends BaseAuthStrategy {
51
+ canHandle(options) {
52
+ return !!options.token;
53
+ }
54
+ async authenticate(options) {
55
+ if (!options.token) {
56
+ throw new Error('Token is required for token authentication');
57
+ }
58
+ // Validate the token
59
+ const user = await this.validateToken(options.token);
60
+ // Store the token if requested
61
+ if (!options.noStore && this.storage) {
62
+ if (this.storage.setToken) {
63
+ await this.storage.setToken(options.token);
64
+ }
65
+ if (this.storage.setUser) {
66
+ await this.storage.setUser(user);
67
+ }
68
+ }
69
+ return { user, token: options.token };
70
+ }
71
+ }
72
+ /**
73
+ * Credentials-based authentication strategy
74
+ * Authenticates using handle and password
75
+ */
76
+ export class CredentialsAuthStrategy extends BaseAuthStrategy {
77
+ canHandle(options) {
78
+ return !!options.handle && !!options.password;
79
+ }
80
+ async authenticate(options) {
81
+ if (!options.handle || !options.password) {
82
+ throw new Error('Handle and password are required for credentials authentication');
83
+ }
84
+ // Call the login API
85
+ const response = await authentication.login({ handle: options.handle, password: options.password }, this.iamRunUrl);
86
+ if (!response || !response.success || !response.token) {
87
+ throw new Error(response?.message || 'Login failed');
88
+ }
89
+ const token = response.token;
90
+ // Get user profile
91
+ const user = await this.validateToken(token);
92
+ // Store the token if requested
93
+ if (!options.noStore && this.storage) {
94
+ this.storage.setToken?.(token);
95
+ this.storage.setUser?.(user);
96
+ }
97
+ return { user, token };
98
+ }
99
+ }
100
+ /**
101
+ * Storage-based authentication strategy
102
+ * Authenticates using a token from storage
103
+ */
104
+ export class StorageAuthStrategy extends BaseAuthStrategy {
105
+ canHandle(options) {
106
+ // Can handle if:
107
+ // 1. Storage is available
108
+ // 2. No other auth method is provided (this is the fallback strategy)
109
+ if (!this.storage || !this.storage.isAvailable()) {
110
+ return false;
111
+ }
112
+ // Return true if no explicit auth method provided (empty options or noStore only)
113
+ // This allows the authenticate() method to check storage async
114
+ const hasExplicitAuthMethod = !!options.token ||
115
+ !!options.handle ||
116
+ !!options.password ||
117
+ !!options.useBrowser;
118
+ return !hasExplicitAuthMethod;
119
+ }
120
+ async authenticate(options) {
121
+ if (!this.storage) {
122
+ throw new Error('Storage is required for storage-based authentication');
123
+ }
124
+ // Try async getTokenAsync first (for VS Code async keytar support)
125
+ let token = null;
126
+ if ('getTokenAsync' in this.storage &&
127
+ typeof this.storage.getTokenAsync === 'function') {
128
+ token = await this.storage.getTokenAsync();
129
+ }
130
+ else if (this.storage.getToken) {
131
+ token = this.storage.getToken();
132
+ }
133
+ if (!token) {
134
+ throw new Error('No token found in storage');
135
+ }
136
+ // Validate the token
137
+ const user = await this.validateToken(token);
138
+ return { user, token };
139
+ }
140
+ }
141
+ /**
142
+ * Browser OAuth strategy
143
+ * Authenticates using browser-based OAuth flow with GitHub or LinkedIn
144
+ */
145
+ export class BrowserOAuthStrategy extends BaseAuthStrategy {
146
+ canHandle(options) {
147
+ return !!options.useBrowser;
148
+ }
149
+ async authenticate(options) {
150
+ // Import OAuth2 API
151
+ const { getOAuth2AuthzUrl } = await import('../../api/iam/oauth2');
152
+ // Default to GitHub provider
153
+ const provider = options.oauthProvider || 'github';
154
+ if (provider !== 'github' && provider !== 'linkedin') {
155
+ throw new Error(`Unsupported OAuth provider: ${provider}. Use 'github' or 'linkedin'.`);
156
+ }
157
+ // Generate callback URI
158
+ const callbackUri = options.callbackUri || `${window.location.origin}/auth/callback`;
159
+ try {
160
+ // Step 1: Get OAuth authorization URL
161
+ const authzResponse = await getOAuth2AuthzUrl(provider, callbackUri, this.iamRunUrl, options.nonce);
162
+ // Step 2: Open OAuth URL in popup or redirect
163
+ if (options.usePopup !== false) {
164
+ // Use popup window
165
+ const popup = window.open(authzResponse.loginURL, 'oauth-login', 'width=600,height=700,left=100,top=100');
166
+ if (!popup) {
167
+ throw new Error('Failed to open OAuth popup. Please allow popups for this site.');
168
+ }
169
+ // Wait for callback - Datalayer redirects to our callback with ?user=<json>&token=<token>
170
+ const result = await this.waitForOAuthCallback(popup, callbackUri);
171
+ // Extract token and user from callback result
172
+ const token = result.token;
173
+ const user = result.user;
174
+ if (!token) {
175
+ throw new Error('No token received from OAuth callback');
176
+ }
177
+ if (!user) {
178
+ throw new Error('No user data received from OAuth callback');
179
+ }
180
+ // Store token if requested
181
+ if (!options.noStore && this.storage) {
182
+ this.storage.setToken?.(token);
183
+ this.storage.setUser?.(user);
184
+ }
185
+ return { user, token };
186
+ }
187
+ else {
188
+ // Redirect to OAuth URL
189
+ window.location.href = authzResponse.loginURL;
190
+ // Return a pending promise (page will redirect)
191
+ return new Promise(() => {
192
+ // This promise never resolves because we redirect
193
+ });
194
+ }
195
+ }
196
+ catch (error) {
197
+ throw new Error(`OAuth authentication failed: ${error instanceof Error ? error.message : String(error)}`);
198
+ }
199
+ }
200
+ /**
201
+ * Wait for OAuth callback in popup window
202
+ * Expects: { user, token, error } from the Datalayer OAuth callback
203
+ */
204
+ async waitForOAuthCallback(popup, callbackUri) {
205
+ return new Promise((resolve, reject) => {
206
+ const timeout = setTimeout(() => {
207
+ popup.close();
208
+ reject(new Error('OAuth authentication timed out'));
209
+ }, 5 * 60 * 1000); // 5 minute timeout
210
+ // Listen for message from popup
211
+ const handleMessage = (event) => {
212
+ // Verify origin
213
+ if (event.origin !== new URL(callbackUri).origin) {
214
+ return;
215
+ }
216
+ clearTimeout(timeout);
217
+ window.removeEventListener('message', handleMessage);
218
+ popup.close();
219
+ if (event.data.error) {
220
+ reject(new Error(`OAuth error: ${event.data.error}`));
221
+ }
222
+ else {
223
+ resolve(event.data);
224
+ }
225
+ };
226
+ window.addEventListener('message', handleMessage);
227
+ // Check if popup was closed manually
228
+ const checkClosed = setInterval(() => {
229
+ if (popup.closed) {
230
+ clearInterval(checkClosed);
231
+ clearTimeout(timeout);
232
+ window.removeEventListener('message', handleMessage);
233
+ reject(new Error('OAuth popup was closed'));
234
+ }
235
+ }, 1000);
236
+ });
237
+ }
238
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Authentication types and interfaces for the Datalayer SDK
3
+ */
4
+ import { UserDTO } from '../../models/UserDTO';
5
+ /**
6
+ * Authentication credentials for login
7
+ */
8
+ export interface AuthCredentials {
9
+ handle: string;
10
+ password: string;
11
+ }
12
+ /**
13
+ * Authentication options for login flow
14
+ */
15
+ export interface AuthOptions {
16
+ /**
17
+ * Existing authentication token
18
+ */
19
+ token?: string;
20
+ /**
21
+ * User handle for credentials-based auth
22
+ */
23
+ handle?: string;
24
+ /**
25
+ * Password for credentials-based auth
26
+ */
27
+ password?: string;
28
+ /**
29
+ * Use browser-based OAuth flow
30
+ */
31
+ useBrowser?: boolean;
32
+ /**
33
+ * OAuth provider (github or linkedin)
34
+ */
35
+ oauthProvider?: 'github' | 'linkedin';
36
+ /**
37
+ * Callback URI for OAuth redirect
38
+ */
39
+ callbackUri?: string;
40
+ /**
41
+ * Nonce for OAuth state parameter
42
+ */
43
+ nonce?: string;
44
+ /**
45
+ * Use popup window for OAuth (default: true). If false, redirects the current page.
46
+ */
47
+ usePopup?: boolean;
48
+ /**
49
+ * Don't store the token after authentication
50
+ */
51
+ noStore?: boolean;
52
+ }
53
+ /**
54
+ * Authentication result containing user and token
55
+ */
56
+ export interface AuthResult {
57
+ user: UserDTO;
58
+ token: string;
59
+ }
60
+ /**
61
+ * Token storage backend interface
62
+ */
63
+ export interface TokenStorage {
64
+ /**
65
+ * Get token from storage
66
+ */
67
+ get(key: string): string | null;
68
+ /**
69
+ * Set token in storage
70
+ */
71
+ set(key: string, value: string): void;
72
+ /**
73
+ * Delete token from storage
74
+ */
75
+ delete(key: string): void;
76
+ /**
77
+ * Check if storage is available
78
+ */
79
+ isAvailable(): boolean;
80
+ /**
81
+ * Get stored authentication token (convenience method)
82
+ */
83
+ getToken?(): string | null;
84
+ /**
85
+ * Store authentication token (convenience method)
86
+ * May be async to support keyring storage
87
+ */
88
+ setToken?(token: string): void | Promise<void>;
89
+ /**
90
+ * Delete authentication token (convenience method)
91
+ * May be async to support keyring storage
92
+ */
93
+ deleteToken?(): void | Promise<void>;
94
+ /**
95
+ * Store user data (optional)
96
+ * May be async to support keyring storage
97
+ */
98
+ setUser?(user: any): void | Promise<void>;
99
+ /**
100
+ * Clear all authentication data (optional)
101
+ * May be async to support keyring storage
102
+ */
103
+ clear?(): void | Promise<void>;
104
+ }
105
+ /**
106
+ * Authentication strategy interface
107
+ */
108
+ export interface AuthStrategy {
109
+ /**
110
+ * Authenticate using this strategy
111
+ */
112
+ authenticate(options: AuthOptions): Promise<AuthResult>;
113
+ /**
114
+ * Check if this strategy can handle the given options
115
+ */
116
+ canHandle(options: AuthOptions): boolean;
117
+ }
118
+ /**
119
+ * Browser OAuth configuration
120
+ */
121
+ export interface BrowserOAuthConfig {
122
+ /**
123
+ * OAuth provider name (e.g., 'github', 'linkedin')
124
+ */
125
+ provider: string;
126
+ /**
127
+ * Callback URL for OAuth redirect
128
+ */
129
+ callbackUrl: string;
130
+ /**
131
+ * IAM server URL
132
+ */
133
+ iamUrl: string;
134
+ }
135
+ /**
136
+ * Token validation result
137
+ */
138
+ export interface TokenValidationResult {
139
+ /**
140
+ * Whether the token is valid
141
+ */
142
+ valid: boolean;
143
+ /**
144
+ * User associated with the token (if valid)
145
+ */
146
+ user?: UserDTO;
147
+ /**
148
+ * Error message (if invalid)
149
+ */
150
+ error?: string;
151
+ }
@@ -2,5 +2,7 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- export * from './NotebookMutationsKernel';
6
- export * from './NotebookMutationsServiceManager';
5
+ /**
6
+ * Authentication types and interfaces for the Datalayer SDK
7
+ */
8
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { EnvironmentDTO } from '../models/EnvironmentDTO';
2
+ import { AuthenticationManager } from './auth';
2
3
  /** Handlers for SDK method lifecycle events. */
3
4
  export interface SDKHandlers {
4
5
  /** Called before any SDK method execution */
@@ -35,6 +36,8 @@ export declare class DatalayerClientBase {
35
36
  readonly environments: EnvironmentDTO[];
36
37
  /** Method lifecycle handlers */
37
38
  readonly handlers?: SDKHandlers;
39
+ /** Authentication manager */
40
+ readonly auth: AuthenticationManager;
38
41
  /**
39
42
  * Create a DatalayerClient base instance.
40
43
  * @param config - Client configuration options
@@ -7,6 +7,7 @@
7
7
  * @module client/base
8
8
  */
9
9
  import { DEFAULT_SERVICE_URLS } from '../api/constants';
10
+ import { AuthenticationManager } from './auth';
10
11
  /** Base Client class providing core configuration and token management. */
11
12
  export class DatalayerClientBase {
12
13
  /** URL for IAM service */
@@ -21,6 +22,8 @@ export class DatalayerClientBase {
21
22
  environments = [];
22
23
  /** Method lifecycle handlers */
23
24
  handlers;
25
+ /** Authentication manager */
26
+ auth;
24
27
  /**
25
28
  * Create a DatalayerClient base instance.
26
29
  * @param config - Client configuration options
@@ -32,6 +35,12 @@ export class DatalayerClientBase {
32
35
  this.spacerRunUrl = config.spacerRunUrl || DEFAULT_SERVICE_URLS.SPACER;
33
36
  this.token = config.token;
34
37
  this.handlers = config.handlers;
38
+ // Initialize authentication manager
39
+ this.auth = new AuthenticationManager(this.iamRunUrl);
40
+ // If a token was provided, store it in the auth manager
41
+ if (this.token) {
42
+ this.auth.storeToken(this.token);
43
+ }
35
44
  }
36
45
  /**
37
46
  * Get the current configuration including service URLs and token.
@@ -68,6 +68,7 @@ export { HealthCheck } from '../models/HealthCheck';
68
68
  export type { HealthCheckJSON } from '../models/HealthCheck';
69
69
  export { ItemTypes } from './constants';
70
70
  export type { ItemType } from './constants';
71
+ export * from './auth';
71
72
  export interface DatalayerClient {
72
73
  getToken(): string | undefined;
73
74
  setToken(token: string): Promise<void>;
@@ -77,3 +77,5 @@ export { ItemDTO as Item } from '../models/ItemDTO';
77
77
  export { HealthCheck } from '../models/HealthCheck';
78
78
  // Export constants
79
79
  export { ItemTypes } from './constants';
80
+ // Export authentication module
81
+ export * from './auth';
@@ -0,0 +1,40 @@
1
+ export interface ILoginProps {
2
+ /**
3
+ * Page heading
4
+ */
5
+ heading?: string;
6
+ /**
7
+ * Home page route
8
+ */
9
+ homeRoute: string;
10
+ /**
11
+ * Login page route
12
+ */
13
+ loginRoute?: string;
14
+ /**
15
+ * Join page route
16
+ */
17
+ joinRoute?: string;
18
+ /**
19
+ * Join confirmation route
20
+ */
21
+ joinConfirmRoute?: string;
22
+ /**
23
+ * Password route
24
+ */
25
+ passwordRoute?: string;
26
+ /**
27
+ * Show Email Login form
28
+ */
29
+ showEmailLogin?: boolean;
30
+ /**
31
+ * Show GitHub Login button
32
+ */
33
+ showGitHubLogin?: boolean;
34
+ /**
35
+ * Show token Login buttons
36
+ */
37
+ showTokenLogin?: boolean;
38
+ }
39
+ export declare const Login: (props: ILoginProps) => JSX.Element;
40
+ export default Login;
@@ -0,0 +1,173 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /*
7
+ * Copyright (c) 2021-2024 Datalayer, Inc.
8
+ *
9
+ * Datalayer License
10
+ */
11
+ import { useEffect, useState } from 'react';
12
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
13
+ import { EyeIcon, EyeClosedIcon, MarkGithubIcon } from '@primer/octicons-react';
14
+ import { Button, FormControl, Heading, Link, PageLayout, TextInput, } from '@primer/react';
15
+ import { Box } from '@datalayer/primer-addons';
16
+ import { asUser, IAMProvidersSpecs } from '../../models';
17
+ import { useIAMStore } from '../../state';
18
+ import { CenteredSpinner } from '../display';
19
+ import { isInsideJupyterLab, validateLength } from '../../utils';
20
+ import { useNavigate, useCache, useToast, useIAM } from '../../hooks';
21
+ import { LoginToken } from './LoginToken';
22
+ export const Login = (props) => {
23
+ const { heading, homeRoute, loginRoute, showEmailLogin = true, showGitHubLogin = true, showTokenLogin = true, } = props;
24
+ const { useLogin, useOAuth2AuthorizationURL } = useCache({ loginRoute });
25
+ const loginMutation = useLogin();
26
+ const getOAuth2URLMutation = useOAuth2AuthorizationURL();
27
+ const { loginAndNavigate, setLogin } = useIAM();
28
+ const { externalToken, iamProvidersAuthorizationURL, addIAMProviderAuthorizationURL, iamRunUrl, logout, checkIAMToken, } = useIAMStore();
29
+ const { enqueueToast } = useToast();
30
+ const navigate = useNavigate();
31
+ const [loading, setLoading] = useState(false);
32
+ const [loadingWithToken, setLoadingWithToken] = useState(-1);
33
+ const [formValues, setFormValues] = useState({
34
+ handle: undefined,
35
+ password: undefined,
36
+ });
37
+ const [validationResult, setValidationResult] = useState({
38
+ handle: undefined,
39
+ password: undefined,
40
+ });
41
+ const [passwordVisibility, setPasswordVisibility] = useState(false);
42
+ useEffect(() => {
43
+ const initIAMProvider = (iamProvider) => {
44
+ const callbackURI = isInsideJupyterLab()
45
+ ? URLExt.join(PageConfig.getBaseUrl(), iamProvider.oauth2CallbackServerRoute)
46
+ : location.protocol +
47
+ '//' +
48
+ location.hostname +
49
+ ':' +
50
+ location.port +
51
+ iamProvider.oauth2CallbackUIRoute;
52
+ const queryArgs = {
53
+ provider: iamProvider.name,
54
+ callback_uri: callbackURI,
55
+ };
56
+ /*
57
+ const xsrfTokenMatch = document.cookie.match('\\b_xsrf=([^;]*)\\b');
58
+ if (xsrfTokenMatch) {
59
+ queryArgs['xsrf'] = xsrfTokenMatch[1];
60
+ }
61
+ */
62
+ getOAuth2URLMutation.mutate(queryArgs, {
63
+ onSuccess: authUrl => {
64
+ if (authUrl) {
65
+ addIAMProviderAuthorizationURL(iamProvider.name, authUrl);
66
+ }
67
+ else {
68
+ console.error(`Failed to get the Login URL from Datalayer IAM for provider ${iamProvider.name}.`);
69
+ }
70
+ },
71
+ });
72
+ };
73
+ if (!iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name]) {
74
+ initIAMProvider(IAMProvidersSpecs.GitHub);
75
+ }
76
+ if (!iamProvidersAuthorizationURL[IAMProvidersSpecs.LinkedIn.name]) {
77
+ initIAMProvider(IAMProvidersSpecs.LinkedIn);
78
+ }
79
+ }, [iamRunUrl, iamProvidersAuthorizationURL, addIAMProviderAuthorizationURL]);
80
+ useEffect(() => {
81
+ if (externalToken) {
82
+ setLoadingWithToken(1);
83
+ loginAndNavigate(externalToken, logout, checkIAMToken, navigate, homeRoute)
84
+ .catch(error => {
85
+ console.debug('Failed to login with token from cookie..', error);
86
+ enqueueToast('Failed to check authentication.', { variant: 'error' });
87
+ })
88
+ .finally(() => {
89
+ setLoadingWithToken(-1);
90
+ });
91
+ }
92
+ }, [externalToken]);
93
+ const handleKeyDown = (event) => {
94
+ if (event.key === 'Enter') {
95
+ submit();
96
+ }
97
+ };
98
+ const handleHandleChange = (event) => {
99
+ setFormValues(prevFormValues => ({
100
+ ...prevFormValues,
101
+ handle: event.target.value,
102
+ }));
103
+ };
104
+ const handlePasswordChange = (event) => {
105
+ setFormValues(prevFormValues => ({
106
+ ...prevFormValues,
107
+ password: event.target.value,
108
+ }));
109
+ };
110
+ const submit = async () => {
111
+ if (loading ||
112
+ validationResult.handle !== '' ||
113
+ validationResult.password !== '') {
114
+ return;
115
+ }
116
+ setLoading(true);
117
+ loginMutation.mutate({
118
+ handle: formValues.handle,
119
+ password: formValues.password,
120
+ }, {
121
+ onSuccess: (resp) => {
122
+ if (resp.success) {
123
+ setValidationResult({ handle: '', password: '' });
124
+ const user = asUser(resp.user);
125
+ const token = resp.token;
126
+ setLogin(user, token);
127
+ navigate(homeRoute);
128
+ }
129
+ else {
130
+ enqueueToast('Failed to login. Check your username and password.', {
131
+ variant: 'warning',
132
+ });
133
+ setValidationResult({
134
+ handle: '',
135
+ password: 'Invalid credentials',
136
+ });
137
+ console.debug(`Failed to login: ${resp.message}`, resp.errors ?? '');
138
+ }
139
+ },
140
+ onSettled: () => {
141
+ setLoading(false);
142
+ },
143
+ });
144
+ };
145
+ useEffect(() => {
146
+ setValidationResult({
147
+ ...validationResult,
148
+ handle: formValues.handle === undefined
149
+ ? undefined
150
+ : validateLength(formValues.handle, 1)
151
+ ? ''
152
+ : 'Your username may not be empty.',
153
+ password: formValues.password === undefined
154
+ ? undefined
155
+ : validateLength(formValues.password, 1)
156
+ ? ''
157
+ : 'Your password may not be empty.',
158
+ });
159
+ }, [formValues]);
160
+ return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Login to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => {
161
+ setPasswordVisibility(!passwordVisibility);
162
+ }, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility
163
+ ? 'Hide password'
164
+ : 'Show password', sx: { color: 'var(--fgColor-muted)' } }), sx: { overflow: 'visible' } }), validationResult.password && (_jsx(FormControl.Validation, { variant: validationResult.password ? 'error' : 'success', children: validationResult.password }))] }) }), _jsxs(Box, { mt: 5, children: [_jsx(Button, { variant: "primary", disabled: loading ||
165
+ validationResult.handle !== '' ||
166
+ validationResult.password !== '', onClick: submit, children: loading
167
+ ? 'Login…'
168
+ : heading
169
+ ? 'Login with Datalayer'
170
+ : 'Login' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.app/password", target: "_blank", children: "Forgot password?" })] })] })), _jsx(Box, { children: _jsxs(Box, { display: "flex", flexDirection: "column", sx: { margin: 'auto' }, children: [showGitHubLogin &&
171
+ iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: MarkGithubIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name], as: "a", style: { margin: '10px 0' }, children: "Login with GitHub" })), showTokenLogin && (_jsx(LoginToken, { homeRoute: homeRoute, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] }));
172
+ };
173
+ export default Login;