@datalayer/core 1.2.10 → 1.2.13

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 (47) hide show
  1. package/README.md +5 -5
  2. package/lib/api/iam/authentication.js +6 -4
  3. package/lib/api/iam/healthz.js +2 -2
  4. package/lib/api/iam/oauth2.js +18 -12
  5. package/lib/api/iam/secrets.js +1 -2
  6. package/lib/api/mcp/observability.d.ts +9 -2
  7. package/lib/api/mcp/observability.js +40 -15
  8. package/lib/client/auth/strategies.js +1 -1
  9. package/lib/client/mixins/IAMMixin.js +2 -2
  10. package/lib/components/checkout/StripeCheckout.d.ts +1 -1
  11. package/lib/components/checkout/StripeCheckout.js +1 -1
  12. package/lib/components/display/VisuallyHidden.d.ts +1 -2
  13. package/lib/components/sharing/sandboxSharing.js +7 -1
  14. package/lib/config/Configuration.d.ts +4 -4
  15. package/lib/config/Configuration.js +7 -7
  16. package/lib/config/index.d.ts +1 -0
  17. package/lib/config/index.js +1 -0
  18. package/lib/config/planes.d.ts +45 -0
  19. package/lib/config/planes.js +53 -0
  20. package/lib/hooks/useBackdrop.d.ts +4 -5
  21. package/lib/hooks/useCache.d.ts +52 -19
  22. package/lib/hooks/useCache.js +310 -198
  23. package/lib/hooks/useContents.js +3 -0
  24. package/lib/hooks/useMcp.js +1 -1
  25. package/lib/hooks/useNavigate.js +10 -0
  26. package/lib/hooks/useWindowSize.js +1 -1
  27. package/lib/models/IAMProvidersSpecs.d.ts +10 -5
  28. package/lib/models/IAMProvidersSpecs.js +0 -23
  29. package/lib/models/URN.d.ts +37 -0
  30. package/lib/models/URN.js +59 -1
  31. package/lib/navigation/adapters/react-router.d.ts +1 -1
  32. package/lib/navigation/adapters/react-router.js +3 -0
  33. package/lib/state/sessionEnd.js +1 -1
  34. package/lib/state/substates/CoreState.js +3 -16
  35. package/lib/state/substates/IAMState.d.ts +3 -3
  36. package/lib/state/substates/NavigationState.d.ts +92 -0
  37. package/lib/state/substates/NavigationState.js +304 -0
  38. package/lib/state/substates/index.d.ts +1 -0
  39. package/lib/state/substates/index.js +1 -0
  40. package/lib/utils/Handles.js +1 -1
  41. package/lib/utils/Screencapture.d.ts +15 -0
  42. package/lib/utils/Screencapture.js +26 -23
  43. package/lib/views/mcp/McpDashboard.js +15 -10
  44. package/lib/views/mcp/McpHome.js +13 -6
  45. package/lib/views/mcp/types.d.ts +4 -0
  46. package/package.json +9 -7
  47. package/scripts/generate-mcp-types.py +15 -9
@@ -14,10 +14,17 @@
14
14
  * `window.open` otherwise, so a button gets it too. `newTab` in the options
15
15
  * opens a new tab outright.
16
16
  *
17
+ * Every navigation that stays in this tab is also **recorded** against the
18
+ * shell the reader is in ({@link state/substates/NavigationState}). That is
19
+ * what lets a reader switch from Agentify back to Home and find the page they
20
+ * left rather than a landing page, and it is done here — the one place every
21
+ * navigation goes through — rather than in an effect per layout.
22
+ *
17
23
  * @module hooks/useNavigate
18
24
  */
19
25
  import { useCallback, useMemo } from 'react';
20
26
  import { useLayoutStore } from '../state/index.js';
27
+ import { navigationStore } from '../state/substates/NavigationState.js';
21
28
  import { createNativeNavigate } from '../navigation/adapters/native.js';
22
29
  // Import React Router hooks from our wrapper
23
30
  import { useNavigateRR } from '../navigation/adapters/react-router.js';
@@ -125,6 +132,9 @@ export const useNavigate = () => {
125
132
  window.scrollTo(0, 0);
126
133
  document.body.scrollTop = 0;
127
134
  }
135
+ // Where the reader is now, for the shell they are in. A new tab and a
136
+ // step back through history are both already past by here.
137
+ navigationStore.getState().rememberRoute(to);
128
138
  if (rrNavigate) {
129
139
  rrNavigate(to, { replace: options.replace, state: options.state });
130
140
  return;
@@ -50,7 +50,7 @@ export function useWindowSize() {
50
50
  return windowSize;
51
51
  }
52
52
  const breakpointSwitch = (value) => {
53
- let current = BreakpointSize.XXLARGE;
53
+ let current;
54
54
  switch (true) {
55
55
  case value >= 320 && value < 544:
56
56
  current = BreakpointSize.XSMALL;
@@ -1,9 +1,15 @@
1
1
  import { IUser } from './User';
2
2
  export type IIAMProviderName = 'bluesky' | 'discord' | 'github' | 'google' | 'linkedin' | 'okta' | 'x';
3
+ /** Providers whose access token is intentionally available to browser code. */
4
+ export type IBrowserTokenProviderName = Exclude<IIAMProviderName, 'bluesky' | 'linkedin'>;
3
5
  export type IIAMProviderSpec = {
4
6
  name: IIAMProviderName;
5
7
  oauth2CallbackServerRoute: string;
6
8
  oauth2CallbackUIRoute: string;
9
+ };
10
+ /** Provider metadata used only when its access token may be read by the UI. */
11
+ export type IBrowserTokenProviderSpec = IIAMProviderSpec & {
12
+ name: IBrowserTokenProviderName;
7
13
  accessTokenCookieName: (user: IUser) => string;
8
14
  refreshTokenCookieName: string;
9
15
  userInfoURL: string;
@@ -13,11 +19,10 @@ export type IIAMProviderSpec = {
13
19
  };
14
20
  export declare class IAMProvidersSpecs {
15
21
  private constructor();
16
- static getProvider(providerIAMProvidersType: any): IIAMProviderSpec;
17
- static readonly Bluesky: IIAMProviderSpec;
18
- static readonly GitHub: IIAMProviderSpec;
19
- static readonly Google: IIAMProviderSpec;
22
+ static getProvider(providerIAMProvidersType: IBrowserTokenProviderName): IBrowserTokenProviderSpec;
23
+ static readonly GitHub: IBrowserTokenProviderSpec;
24
+ static readonly Google: IBrowserTokenProviderSpec;
20
25
  static readonly LinkedIn: IIAMProviderSpec;
21
- static readonly Okta: IIAMProviderSpec;
26
+ static readonly Okta: IBrowserTokenProviderSpec;
22
27
  }
23
28
  export default IAMProvidersSpecs;
@@ -6,18 +6,12 @@ export class IAMProvidersSpecs {
6
6
  constructor() { }
7
7
  static getProvider(providerIAMProvidersType) {
8
8
  switch (providerIAMProvidersType) {
9
- case 'bluesky': {
10
- return this.Bluesky;
11
- }
12
9
  case 'github': {
13
10
  return this.GitHub;
14
11
  }
15
12
  case 'google': {
16
13
  return this.Google;
17
14
  }
18
- case 'linkedin': {
19
- return this.LinkedIn;
20
- }
21
15
  case 'okta': {
22
16
  return this.Okta;
23
17
  }
@@ -27,17 +21,6 @@ export class IAMProvidersSpecs {
27
21
  }
28
22
  }
29
23
  }
30
- static Bluesky = {
31
- name: 'bluesky',
32
- oauth2CallbackServerRoute: 'jupyter_iam/oauth2/callback',
33
- oauth2CallbackUIRoute: '/iam/oauth2/bluesky/callback',
34
- accessTokenCookieName: (user) => `__datalayer__bluesky_access_token_${user.id}`,
35
- refreshTokenCookieName: '__datalayer__bluesky_refresh_token',
36
- userInfoURL: '',
37
- tokenRefreshURL: '',
38
- postShareURL: '',
39
- registerUploadURL: '',
40
- };
41
24
  static GitHub = {
42
25
  name: 'github',
43
26
  oauth2CallbackServerRoute: 'jupyter_iam/oauth2/callback',
@@ -64,12 +47,6 @@ export class IAMProvidersSpecs {
64
47
  name: 'linkedin',
65
48
  oauth2CallbackServerRoute: 'jupyter_iam/oauth2/callback',
66
49
  oauth2CallbackUIRoute: '/iam/oauth2/linkedin/callback',
67
- accessTokenCookieName: (user) => `__datalayer__linkedin_access_token_${user.id}`,
68
- refreshTokenCookieName: '__datalayer__linkedin_refresh_token',
69
- userInfoURL: 'https://api.linkedin.com/v2/userinfo',
70
- tokenRefreshURL: '',
71
- postShareURL: 'https://api.linkedin.com/v2/ugcPosts',
72
- registerUploadURL: 'https://api.linkedin.com/v2/assets?action=registerUpload',
73
50
  };
74
51
  static Okta = {
75
52
  name: 'okta',
@@ -1,3 +1,6 @@
1
+ import type { IIAMProviderName } from './IAMProvidersSpecs';
2
+ /** What an identity provider's account URN starts with, before its name. */
3
+ export declare const IAM_PROVIDER_URN_PREFIX = "urn:dla:iam:ext::";
1
4
  export declare class URN implements IURN {
2
5
  private _partition;
3
6
  private _service;
@@ -12,7 +15,41 @@ export declare class URN implements IURN {
12
15
  get account(): string;
13
16
  get type(): string;
14
17
  get uid(): string;
18
+ /** An identity provider's account (`urn:dla:iam:ext::github:…`). */
19
+ get isIAMProviderAccount(): boolean;
20
+ toString(): string;
15
21
  }
22
+ /** A URN, or `undefined` when the value is not one — never a throw. */
23
+ export declare function parseURN(value?: string | null): URN | undefined;
24
+ /**
25
+ * Where an account came from, read from its `origin`.
26
+ *
27
+ * - `datalayer`: made on Datalayer, with a password.
28
+ * - `iam-provider`: made by signing in with an identity provider, which it
29
+ * still signs in through (`urn:dla:iam:ext::<provider>:<account id>`).
30
+ * - `urn`: any other URN — an AWS Marketplace or enterprise origin.
31
+ * - `unknown`: nothing recorded, or a value that is none of the above.
32
+ */
33
+ export type IAccountOrigin = {
34
+ kind: 'datalayer';
35
+ } | {
36
+ kind: 'iam-provider';
37
+ provider: IIAMProviderName;
38
+ accountId: string;
39
+ urn: URN;
40
+ } | {
41
+ kind: 'urn';
42
+ urn: URN;
43
+ } | {
44
+ kind: 'unknown';
45
+ value: string;
46
+ };
47
+ export declare function accountOriginOf(origin?: string | null): IAccountOrigin;
48
+ /**
49
+ * Whether an account signs in through a provider: disconnecting that
50
+ * provider would leave it nothing to sign in with.
51
+ */
52
+ export declare function signsInWith(origin: string | null | undefined, provider: IIAMProviderName): boolean;
16
53
  /**
17
54
  Datalayer Uniform Resource Name (URN)
18
55
 
package/lib/models/URN.js CHANGED
@@ -2,6 +2,8 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ /** What an identity provider's account URN starts with, before its name. */
6
+ export const IAM_PROVIDER_URN_PREFIX = 'urn:dla:iam:ext::';
5
7
  export class URN {
6
8
  _partition;
7
9
  _service;
@@ -17,7 +19,8 @@ export class URN {
17
19
  this._region = parts[3];
18
20
  this._account = parts[4];
19
21
  this._type = parts[5];
20
- this._uid = parts[6];
22
+ // The rest, colons and all: a provider's account id may carry them.
23
+ this._uid = parts.slice(6).join(':');
21
24
  }
22
25
  get partition() {
23
26
  return this._partition;
@@ -37,4 +40,59 @@ export class URN {
37
40
  get uid() {
38
41
  return this._uid;
39
42
  }
43
+ /** An identity provider's account (`urn:dla:iam:ext::github:…`). */
44
+ get isIAMProviderAccount() {
45
+ return this._service === 'iam' && this._region === 'ext' && !!this._type;
46
+ }
47
+ toString() {
48
+ return [
49
+ 'urn',
50
+ this._partition,
51
+ this._service,
52
+ this._region,
53
+ this._account,
54
+ this._type,
55
+ this._uid,
56
+ ].join(':');
57
+ }
58
+ }
59
+ /** A URN, or `undefined` when the value is not one — never a throw. */
60
+ export function parseURN(value) {
61
+ const text = (value ?? '').trim();
62
+ if (!text.toLowerCase().startsWith('urn:') || text.split(':').length < 7) {
63
+ return undefined;
64
+ }
65
+ return new URN(text);
66
+ }
67
+ const IAM_PROVIDER_NAMES = [
68
+ 'bluesky',
69
+ 'discord',
70
+ 'github',
71
+ 'google',
72
+ 'linkedin',
73
+ 'okta',
74
+ 'x',
75
+ ];
76
+ export function accountOriginOf(origin) {
77
+ const value = (origin ?? '').trim();
78
+ if (value === 'datalayer') {
79
+ return { kind: 'datalayer' };
80
+ }
81
+ const urn = parseURN(value);
82
+ if (!urn) {
83
+ return { kind: 'unknown', value };
84
+ }
85
+ const provider = urn.type.toLowerCase();
86
+ if (urn.isIAMProviderAccount && IAM_PROVIDER_NAMES.includes(provider)) {
87
+ return { kind: 'iam-provider', provider, accountId: urn.uid, urn };
88
+ }
89
+ return { kind: 'urn', urn };
90
+ }
91
+ /**
92
+ * Whether an account signs in through a provider: disconnecting that
93
+ * provider would leave it nothing to sign in with.
94
+ */
95
+ export function signsInWith(origin, provider) {
96
+ const parsed = accountOriginOf(origin);
97
+ return parsed.kind === 'iam-provider' && parsed.provider === provider;
40
98
  }
@@ -1,4 +1,4 @@
1
1
  import * as ReactRouterDom from 'react-router-dom';
2
2
  export declare const useParamsRR: typeof ReactRouterDom.useParams;
3
3
  export declare const useLocationRR: typeof ReactRouterDom.useLocation;
4
- export declare const useNavigateRR: typeof ReactRouterDom.useNavigate;
4
+ export declare const useNavigateRR: () => ReactRouterDom.NavigateFunction;
@@ -9,4 +9,7 @@ import * as ReactRouterDom from 'react-router-dom';
9
9
  // Export hooks for use in our navigation hooks
10
10
  export const useParamsRR = ReactRouterDom.useParams;
11
11
  export const useLocationRR = ReactRouterDom.useLocation;
12
+ // Typed here rather than inferred: the inferred type carries React Router's
13
+ // own doc comment, whose links (`redirect`, `To`, `<ScrollRestoration>`) point
14
+ // at symbols these docs do not include, and typedoc fails the build on them.
12
15
  export const useNavigateRR = ReactRouterDom.useNavigate;
@@ -142,7 +142,7 @@ export const claimSessionState = (userUid) => {
142
142
  return;
143
143
  }
144
144
  const localStorage = storageOf('localStorage');
145
- let owner = null;
145
+ let owner;
146
146
  try {
147
147
  owner = localStorage?.getItem(SESSION_OWNER_KEY) ?? null;
148
148
  }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { createStore } from 'zustand/vanilla';
6
6
  import { useStore } from 'zustand';
7
+ import { DEFAULT_PLANE_URLS } from '../../config/planes.js';
7
8
  import { configLogger } from '../../utils/Logger.js';
8
9
  let loadConfigurationFromServer = true;
9
10
  let initialConfiguration = {
@@ -15,22 +16,8 @@ let initialConfiguration = {
15
16
  whiteLabel: true,
16
17
  loadConfigurationFromServer: true,
17
18
  jupyterServerless: false,
18
- iamUrl: 'https://prod1.datalayer.run',
19
- contentsUrl: 'https://r1.datalayer.run',
20
- managerUrl: 'https://prod1.datalayer.run',
21
- runtimesUrl: 'https://r1.datalayer.run',
22
- schedulerUrl: 'https://r1.datalayer.run',
23
- libraryUrl: 'https://prod1.datalayer.run',
24
- spacerUrl: 'https://prod1.datalayer.run',
25
- aiAgentsUrl: 'https://r1.datalayer.run',
26
- aiInferenceUrl: 'https://r1.datalayer.run',
27
- jupyterMcpServerUrl: 'https://mcp.datalayer.run/mcp',
28
- otelUrl: 'https://prod1.datalayer.run',
29
- // Defaults to prod so telemetry is consumed from the production OTEL service
30
- growthUrl: 'https://prod1.datalayer.run',
31
- inboundsUrl: 'https://prod1.datalayer.run',
32
- successUrl: 'https://prod1.datalayer.run',
33
- supportUrl: 'https://prod1.datalayer.run',
19
+ // Which plane each service is on is said once, in `config/planes`.
20
+ ...DEFAULT_PLANE_URLS,
34
21
  launcher: {
35
22
  category: 'Datalayer',
36
23
  name: 'Datalayer',
@@ -1,4 +1,4 @@
1
- import { IUser, IIAMProviderName } from '../../models';
1
+ import { IUser, IIAMProviderName, IBrowserTokenProviderName } from '../../models';
2
2
  import type { ICredits, ICreditsReservation } from '../../models';
3
3
  /**
4
4
  * Limit to warn about low credits in milliseconds.
@@ -55,8 +55,8 @@ export type IAMState = IIAMState & {
55
55
  */
56
56
  refreshCredits: () => Promise<void>;
57
57
  checkIAMToken: (token: string) => Promise<void>;
58
- setIAMProviderAccessToken: (provider: IIAMProviderName, accessToken?: string | null) => void;
59
- getIAMProviderAccessToken: (user: IUser, provider: IIAMProviderName) => string | undefined;
58
+ setIAMProviderAccessToken: (provider: IBrowserTokenProviderName, accessToken?: string | null) => void;
59
+ getIAMProviderAccessToken: (user: IUser, provider: IBrowserTokenProviderName) => string | undefined;
60
60
  refreshUser: () => Promise<void>;
61
61
  refreshUserByTokenStored: () => Promise<void>;
62
62
  refreshUserByToken: (token: string) => Promise<void>;
@@ -0,0 +1,92 @@
1
+ /** The signed-in shell: the platform, the business cases, or the admin. */
2
+ /**
3
+ * The four shells of the signed-in application.
4
+ *
5
+ * `studio` is where an agentic UI application is composed, validated and
6
+ * shipped — the default for somebody who has not chosen, and what the root
7
+ * shows. `home` is the whole platform, shown as *Power*, at `/power`: the key
8
+ * is kept so a cookie written before the Studio existed still names the
9
+ * shell it meant. `agentify` is the business cases; `admin` is the
10
+ * platform's administration.
11
+ */
12
+ export type NavigationView = 'studio' | 'home' | 'agentify' | 'admin';
13
+ /** The shells, and where each one opens when it has not been visited. */
14
+ export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
15
+ /** Well-known view identifiers for {@link NavigationState.tabsByView}. */
16
+ export declare const LIBRARY_VIEW = "library";
17
+ export declare const AGENTS_VIEW = "agents";
18
+ export declare const SETTINGS_VIEW = "settings";
19
+ export declare const DATALAYER_VIEW = "datalayer";
20
+ export declare const CONTENTS_VIEW = "contents";
21
+ export declare const TUTOR_VIEW = "tutor";
22
+ export type NavigationState = {
23
+ /** The shell the reader last chose. */
24
+ view: NavigationView;
25
+ /** State which shell is being shown. */
26
+ setView: (view: NavigationView) => void;
27
+ /** Swap the platform and the business cases. */
28
+ toggleView: () => void;
29
+ /** The page each shell was last on. */
30
+ lastRouteByView: Partial<Record<NavigationView, string>>;
31
+ /**
32
+ * Remember a page as the current shell's, or as another's when told.
33
+ *
34
+ * Called by {@link useNavigate} for every navigation, which is what makes
35
+ * this the one place the answer comes from.
36
+ */
37
+ rememberRoute: (route: string, view?: NavigationView) => void;
38
+ /** Where a shell was left, or where it opens when it never was. */
39
+ routeForView: (view: NavigationView) => string;
40
+ /** Last selected tab, by view identifier. */
41
+ tabsByView: Record<string, string>;
42
+ /** Keep the tab a view was left on. */
43
+ setViewTab: (view: string, tab: string) => void;
44
+ /** The tab a view was left on, if any. */
45
+ getViewTab: (view: string) => string | undefined;
46
+ /**
47
+ * The route a signed-out reader asked for, to resume after signing in.
48
+ *
49
+ * Never persisted: a reload must land on the default route, and the OAuth
50
+ * round trip carries its target in `post_auth_redirect` instead.
51
+ */
52
+ requestedRoute?: string;
53
+ setRequestedRoute: (route: string) => void;
54
+ clearRequestedRoute: () => void;
55
+ /** The address the reader was on before the documentation. */
56
+ docsOrigin?: string;
57
+ setDocsOrigin: (origin: string) => void;
58
+ };
59
+ /**
60
+ * Whether a page is worth remembering as a shell's.
61
+ *
62
+ * Not the sign-in pages, the OAuth callbacks or the documentation: coming
63
+ * back to a shell should land on the work, not on the door the reader came
64
+ * through or on a page that belongs to no shell.
65
+ */
66
+ export declare function isRememberableRoute(route: string): boolean;
67
+ /**
68
+ * The shell a route belongs to, read off its first segment.
69
+ *
70
+ * The store used to trust whatever `view` said when a route was recorded,
71
+ * and `view` was only ever written by the header toggle. A reader who
72
+ * reached Home through a sidebar link while the toggle still said Agentify
73
+ * had their Home pages filed under Agentify — and the toggle, seeing
74
+ * "Agentify" already chosen, did nothing when they clicked it.
75
+ */
76
+ export declare function viewForRoute(route: string): NavigationView;
77
+ export declare const navigationStore: import("zustand").StoreApi<NavigationState>;
78
+ /**
79
+ * The hook, shaped like a `zustand` store created with `create`: callable
80
+ * with or without a selector, and carrying `getState`, `setState` and
81
+ * `subscribe` for the callers — a router guard, a test — that read it outside
82
+ * a render.
83
+ */
84
+ export interface UseNavigationStore {
85
+ (): NavigationState;
86
+ <T>(selector: (state: NavigationState) => T): T;
87
+ getState: () => NavigationState;
88
+ setState: typeof navigationStore.setState;
89
+ subscribe: typeof navigationStore.subscribe;
90
+ }
91
+ export declare const useNavigationStore: UseNavigationStore;
92
+ export default useNavigationStore;