@sybilion/uilib 1.1.0 → 1.2.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.
- package/README.md +8 -8
- package/assets/{mini-app-global.css → standalone-global.css} +1 -1
- package/dist/esm/components/ui/Image/Image.styl.js +1 -1
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.js +28 -0
- package/dist/esm/components/ui/NavUserHeader/NavUserHeader.styl.js +7 -0
- package/dist/esm/components/ui/Sidebar/Sidebar.styl.js +2 -2
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js +48 -0
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.js +7 -0
- package/dist/esm/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.js +38 -0
- package/dist/esm/index.js +6 -5
- package/dist/esm/sybilion-auth/SybilionAuthProvider.js +208 -0
- package/dist/esm/sybilion-auth/authPaths.js +7 -0
- package/dist/esm/sybilion-auth/exchangeSybilionToken.js +44 -0
- package/dist/esm/types/src/components/ui/Input/Input.d.ts +1 -1
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.d.ts +2 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/NavUserHeader.types.d.ts +25 -0
- package/dist/esm/types/src/components/ui/NavUserHeader/index.d.ts +2 -0
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.d.ts +11 -0
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.d.ts +8 -0
- package/dist/esm/types/src/components/widgets/SidebarDatasetsItemsGrouped/index.d.ts +3 -0
- package/dist/esm/types/src/docs/pages/NavUserHeaderPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/SidebarDatasetsItemsGroupedPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/SybilionAuthProviderPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +4 -1
- package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +42 -0
- package/dist/esm/types/src/sybilion-auth/authPaths.d.ts +3 -0
- package/dist/esm/types/src/sybilion-auth/exchangeSybilionToken.d.ts +4 -0
- package/dist/esm/types/src/sybilion-auth/index.d.ts +4 -0
- package/dist/esm/types/src/{mini-app/miniAppDataTypes.d.ts → types/sybilionDatasetSnapshots.d.ts} +5 -8
- package/docs/standalone-apps.md +181 -0
- package/package.json +15 -3
- package/src/components/ui/Image/Image.styl +1 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl +125 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.styl.d.ts +28 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.tsx +148 -0
- package/src/components/ui/NavUserHeader/NavUserHeader.types.ts +27 -0
- package/src/components/ui/NavUserHeader/avatar.svg +4 -0
- package/src/components/ui/NavUserHeader/index.ts +5 -0
- package/src/components/ui/Sidebar/Sidebar.styl +0 -25
- package/src/components/ui/Sidebar/Sidebar.styl.d.ts +0 -1
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl +29 -0
- package/src/{mini-app/MiniAppRoot.styl.d.ts → components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.styl.d.ts} +4 -2
- package/src/components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.tsx +128 -0
- package/src/components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.ts +51 -0
- package/src/components/widgets/SidebarDatasetsItemsGrouped/index.ts +10 -0
- package/src/docs/pages/NavUserHeaderPage.tsx +89 -0
- package/src/docs/pages/SidebarDatasetsItemsGroupedPage.tsx +136 -0
- package/src/docs/pages/SybilionAuthProviderPage.tsx +40 -0
- package/src/docs/registry.ts +15 -3
- package/src/index.ts +4 -1
- package/src/sybilion-auth/SybilionAuthProvider.tsx +344 -0
- package/src/sybilion-auth/authPaths.ts +6 -0
- package/src/sybilion-auth/exchangeSybilionToken.ts +51 -0
- package/src/sybilion-auth/index.ts +17 -0
- package/src/{mini-app/miniAppDataTypes.ts → types/sybilionDatasetSnapshots.ts} +5 -8
- package/dist/esm/mini-app/MiniAppRoot.js +0 -82
- package/dist/esm/mini-app/MiniAppRoot.styl.js +0 -7
- package/dist/esm/mini-app/miniAppChatBridge.js +0 -45
- package/dist/esm/mini-app/miniAppDataClient.js +0 -98
- package/dist/esm/mini-app/miniAppProtocol.js +0 -153
- package/dist/esm/mini-app/miniAppThemeConfig.js +0 -40
- package/dist/esm/types/src/docs/pages/MiniAppRootPage.d.ts +0 -1
- package/dist/esm/types/src/mini-app/MiniAppRoot.d.ts +0 -18
- package/dist/esm/types/src/mini-app/index.d.ts +0 -10
- package/dist/esm/types/src/mini-app/miniAppChatBridge.d.ts +0 -6
- package/dist/esm/types/src/mini-app/miniAppDataClient.d.ts +0 -16
- package/dist/esm/types/src/mini-app/miniAppProtocol.d.ts +0 -89
- package/dist/esm/types/src/mini-app/miniAppThemeConfig.d.ts +0 -3
- package/docs/workspace-mini-apps.md +0 -51
- package/src/docs/pages/MiniAppRootPage.tsx +0 -58
- package/src/mini-app/MiniAppRoot.styl +0 -24
- package/src/mini-app/MiniAppRoot.tsx +0 -150
- package/src/mini-app/index.ts +0 -43
- package/src/mini-app/miniAppChatBridge.ts +0 -55
- package/src/mini-app/miniAppDataClient.ts +0 -165
- package/src/mini-app/miniAppProtocol.ts +0 -247
- package/src/mini-app/miniAppThemeConfig.ts +0 -45
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
2
|
+
|
|
3
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
4
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
5
|
+
|
|
6
|
+
export default function SybilionAuthProviderPage() {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<AppPageHeader
|
|
10
|
+
breadcrumbs={[{ label: 'SybilionAuthProvider' }]}
|
|
11
|
+
title="SybilionAuthProvider"
|
|
12
|
+
subheader="Auth0 SPA → sdk.auth.loginWithAuth0Identity → Sybilion JWT. Pass createSybilionSDK instance via sdk prop; greenfield: docs/standalone-apps.md (yarn add includes @sybilion/sdk, @auth0/auth0-react)."
|
|
13
|
+
actions={<DocsHeaderActions />}
|
|
14
|
+
/>
|
|
15
|
+
<PageContentSection title="Exports">
|
|
16
|
+
<p style={{ marginTop: 0 }}>
|
|
17
|
+
From <code>@sybilion/uilib</code>:
|
|
18
|
+
</p>
|
|
19
|
+
<ul>
|
|
20
|
+
<li>
|
|
21
|
+
<code>SybilionAuthProvider</code> (<code>sdk</code> prop)
|
|
22
|
+
</li>
|
|
23
|
+
<li>
|
|
24
|
+
<code>getSybilionApiOriginFromSdk</code>
|
|
25
|
+
</li>
|
|
26
|
+
<li>
|
|
27
|
+
<code>useSybilionAuth</code>
|
|
28
|
+
</li>
|
|
29
|
+
<li>
|
|
30
|
+
<code>useSybilionApiFetch</code>,{' '}
|
|
31
|
+
<code>createSybilionApiFetch</code>, <code>sybilionApiFetch</code>
|
|
32
|
+
</li>
|
|
33
|
+
<li>
|
|
34
|
+
<code>exchangeAuth0AccessTokenForSybilionJwt</code> (advanced)
|
|
35
|
+
</li>
|
|
36
|
+
</ul>
|
|
37
|
+
</PageContentSection>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -66,6 +66,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
66
66
|
section: 'Navigation',
|
|
67
67
|
load: () => import('./pages/BreadcrumbPage'),
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
slug: 'nav-user-header',
|
|
71
|
+
title: 'NavUserHeader',
|
|
72
|
+
section: 'Navigation',
|
|
73
|
+
load: () => import('./pages/NavUserHeaderPage'),
|
|
74
|
+
},
|
|
69
75
|
{
|
|
70
76
|
slug: 'card',
|
|
71
77
|
title: 'Card',
|
|
@@ -157,10 +163,10 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
157
163
|
load: () => import('./pages/InteractiveContentPage'),
|
|
158
164
|
},
|
|
159
165
|
{
|
|
160
|
-
slug: '
|
|
161
|
-
title: '
|
|
166
|
+
slug: 'sybilion-auth-provider',
|
|
167
|
+
title: 'SybilionAuthProvider',
|
|
162
168
|
section: 'Layout',
|
|
163
|
-
load: () => import('./pages/
|
|
169
|
+
load: () => import('./pages/SybilionAuthProviderPage'),
|
|
164
170
|
},
|
|
165
171
|
{
|
|
166
172
|
slug: 'label',
|
|
@@ -252,6 +258,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
252
258
|
section: 'Layout',
|
|
253
259
|
load: () => import('./pages/SidebarPage'),
|
|
254
260
|
},
|
|
261
|
+
{
|
|
262
|
+
slug: 'sidebar-datasets-items-grouped',
|
|
263
|
+
title: 'SidebarDatasetsItemsGrouped',
|
|
264
|
+
section: 'Navigation',
|
|
265
|
+
load: () => import('./pages/SidebarDatasetsItemsGroupedPage'),
|
|
266
|
+
},
|
|
255
267
|
{
|
|
256
268
|
slug: 'skeleton',
|
|
257
269
|
title: 'Skeleton',
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './sybilion-auth';
|
|
2
|
+
export * from './types/sybilionDatasetSnapshots';
|
|
2
3
|
export * from './contexts/chat-context';
|
|
3
4
|
export * from './types/chat-api.types';
|
|
4
5
|
export * from './components/ui/AnalysesSelector';
|
|
@@ -30,6 +31,7 @@ export * from './components/ui/LabelWithId';
|
|
|
30
31
|
export * from './components/ui/LegacyPlatformLink';
|
|
31
32
|
export * from './components/ui/Logo';
|
|
32
33
|
export * from './components/ui/MobileAdaptiveSelector';
|
|
34
|
+
export * from './components/ui/NavUserHeader';
|
|
33
35
|
export * from './components/ui/NumberControl';
|
|
34
36
|
export * from './components/ui/Page';
|
|
35
37
|
export * from './components/ui/Progress';
|
|
@@ -53,3 +55,4 @@ export * from './components/ui/Toggle';
|
|
|
53
55
|
export * from './components/ui/ToggleGroup';
|
|
54
56
|
export * from './components/ui/Tooltip';
|
|
55
57
|
export * from './components/ui/VimeoEmbed';
|
|
58
|
+
export * from './components/widgets/SidebarDatasetsItemsGrouped';
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Auth0Provider,
|
|
3
|
+
type RedirectLoginOptions,
|
|
4
|
+
useAuth0,
|
|
5
|
+
} from '@auth0/auth0-react';
|
|
6
|
+
import type { LoginTokenResponse, SybilionSDK } from '@sybilion/sdk';
|
|
7
|
+
import type { JSX, ReactNode } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
useCallback,
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
} from 'react';
|
|
17
|
+
|
|
18
|
+
import { normalizeApiBaseUrl } from '#uilib/sybilion-auth/authPaths';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TOKEN_KEY = 'sybilion.standalone.jwt';
|
|
21
|
+
|
|
22
|
+
function sybilionJwtFromLoginResponse(body: LoginTokenResponse): string {
|
|
23
|
+
const t = body.data?.token ?? body.token;
|
|
24
|
+
if (!t) throw new Error('Sybilion auth: missing token in login response');
|
|
25
|
+
return t;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Origin (`scheme://host:port`) for paths like `/api/v1/...`; derived from SDK URL layout. */
|
|
29
|
+
export function getSybilionApiOriginFromSdk(sdk: SybilionSDK): string {
|
|
30
|
+
const loginUrl = sdk.http.buildUrl('/v1/auth/login');
|
|
31
|
+
try {
|
|
32
|
+
const baseHref =
|
|
33
|
+
typeof window !== 'undefined' && window.location?.href
|
|
34
|
+
? window.location.href
|
|
35
|
+
: 'http://localhost/';
|
|
36
|
+
const u = new URL(loginUrl, baseHref);
|
|
37
|
+
if (!u.pathname.endsWith('/v1/auth/login')) return '';
|
|
38
|
+
return u.origin;
|
|
39
|
+
} catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SybilionAuthProviderProps = {
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
sdk: SybilionSDK;
|
|
47
|
+
auth0Domain: string;
|
|
48
|
+
auth0ClientId: string;
|
|
49
|
+
redirectUri: string;
|
|
50
|
+
/**
|
|
51
|
+
* Defaults match sybilion-client AuthProvider (Management API audience + metadata scope).
|
|
52
|
+
* Override if your Auth0 SPA app uses a Resource Server audience for `/v1/auth/login`.
|
|
53
|
+
*/
|
|
54
|
+
authorizationParams?: {
|
|
55
|
+
audience?: string;
|
|
56
|
+
scope?: string;
|
|
57
|
+
redirect_uri?: string;
|
|
58
|
+
};
|
|
59
|
+
sybilionTokenStorageKey?: string;
|
|
60
|
+
logoutReturnTo?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type SybilionAuthContextValue = {
|
|
64
|
+
apiBaseUrl: string;
|
|
65
|
+
isLoading: boolean;
|
|
66
|
+
/** Auth0 authenticated and Sybilion JWT present. */
|
|
67
|
+
isAuthenticated: boolean;
|
|
68
|
+
sybilionAccessToken: string | null;
|
|
69
|
+
error: string | null;
|
|
70
|
+
loginWithRedirect: (options?: RedirectLoginOptions) => Promise<void>;
|
|
71
|
+
logout: () => void;
|
|
72
|
+
getAuth0AccessToken: () => Promise<string | undefined>;
|
|
73
|
+
getSybilionAccessToken: () => Promise<string | null>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const SybilionAuthContext = createContext<SybilionAuthContextValue | null>(
|
|
77
|
+
null,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
export function useSybilionAuth(): SybilionAuthContextValue {
|
|
81
|
+
const v = useContext(SybilionAuthContext);
|
|
82
|
+
if (!v) {
|
|
83
|
+
throw new Error('useSybilionAuth must be used within SybilionAuthProvider');
|
|
84
|
+
}
|
|
85
|
+
return v;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** fetch() relative to Sybilion API base with Bearer Sybilion JWT. */
|
|
89
|
+
export async function sybilionApiFetch(
|
|
90
|
+
apiBaseUrl: string,
|
|
91
|
+
bearerToken: string,
|
|
92
|
+
path: string,
|
|
93
|
+
init: RequestInit = {},
|
|
94
|
+
): Promise<Response> {
|
|
95
|
+
const base = normalizeApiBaseUrl(apiBaseUrl);
|
|
96
|
+
const url = path.startsWith('http')
|
|
97
|
+
? path
|
|
98
|
+
: `${base}${path.startsWith('/') ? path : `/${path}`}`;
|
|
99
|
+
const headers = new Headers(init.headers);
|
|
100
|
+
headers.set('Authorization', `Bearer ${bearerToken}`);
|
|
101
|
+
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
|
|
102
|
+
return fetch(url, { ...init, headers });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createSybilionApiFetch(
|
|
106
|
+
apiBaseUrl: string,
|
|
107
|
+
getSybilionAccessToken: () => Promise<string | null>,
|
|
108
|
+
) {
|
|
109
|
+
return async function sybilionFetch(
|
|
110
|
+
path: string,
|
|
111
|
+
init?: RequestInit,
|
|
112
|
+
): Promise<Response> {
|
|
113
|
+
const token = await getSybilionAccessToken();
|
|
114
|
+
if (!token)
|
|
115
|
+
throw new Error('Sybilion API: missing token — user not signed in.');
|
|
116
|
+
return sybilionApiFetch(apiBaseUrl, token, path, init);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Authenticated fetch using {@link useSybilionAuth} context. */
|
|
121
|
+
export function useSybilionApiFetch(): (
|
|
122
|
+
path: string,
|
|
123
|
+
init?: RequestInit,
|
|
124
|
+
) => Promise<Response> {
|
|
125
|
+
const { apiBaseUrl, getSybilionAccessToken } = useSybilionAuth();
|
|
126
|
+
return useMemo(
|
|
127
|
+
() => createSybilionApiFetch(apiBaseUrl, getSybilionAccessToken),
|
|
128
|
+
[apiBaseUrl, getSybilionAccessToken],
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeLs(key: string, value: string | null): void {
|
|
133
|
+
if (typeof localStorage === 'undefined') return;
|
|
134
|
+
try {
|
|
135
|
+
if (value === null) localStorage.removeItem(key);
|
|
136
|
+
else localStorage.setItem(key, value);
|
|
137
|
+
} catch {
|
|
138
|
+
/* quota / blocked */
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function InnerSybilionSession({
|
|
143
|
+
children,
|
|
144
|
+
sdk,
|
|
145
|
+
storageKey,
|
|
146
|
+
logoutReturnTo,
|
|
147
|
+
}: {
|
|
148
|
+
children: ReactNode;
|
|
149
|
+
sdk: SybilionSDK;
|
|
150
|
+
storageKey: string;
|
|
151
|
+
logoutReturnTo?: string;
|
|
152
|
+
}): JSX.Element {
|
|
153
|
+
const auth0 = useAuth0();
|
|
154
|
+
const auth0Ref = useRef(auth0);
|
|
155
|
+
auth0Ref.current = auth0;
|
|
156
|
+
|
|
157
|
+
const [sybilionToken, setSybilionToken] = useState<string | null>(null);
|
|
158
|
+
const [exchangeLoading, setExchangeLoading] = useState(false);
|
|
159
|
+
const [exchangeError, setExchangeError] = useState<string | null>(null);
|
|
160
|
+
|
|
161
|
+
const persistToken = useCallback(
|
|
162
|
+
(t: string | null) => {
|
|
163
|
+
setSybilionToken(t);
|
|
164
|
+
writeLs(storageKey, t);
|
|
165
|
+
},
|
|
166
|
+
[storageKey],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const doLogout = useCallback(() => {
|
|
170
|
+
persistToken(null);
|
|
171
|
+
setExchangeError(null);
|
|
172
|
+
const returnTo =
|
|
173
|
+
logoutReturnTo ??
|
|
174
|
+
(typeof window !== 'undefined' ? window.location.origin : undefined);
|
|
175
|
+
auth0Ref.current.logout({
|
|
176
|
+
logoutParams: returnTo ? { returnTo } : undefined,
|
|
177
|
+
});
|
|
178
|
+
}, [persistToken, logoutReturnTo]);
|
|
179
|
+
|
|
180
|
+
const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
|
|
181
|
+
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
184
|
+
persistToken(null);
|
|
185
|
+
}
|
|
186
|
+
}, [auth0.isAuthenticated, auth0.user?.sub, persistToken]);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
190
|
+
setExchangeLoading(false);
|
|
191
|
+
setExchangeError(null);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let cancelled = false;
|
|
196
|
+
const runExchange = async () => {
|
|
197
|
+
setExchangeLoading(true);
|
|
198
|
+
setExchangeError(null);
|
|
199
|
+
try {
|
|
200
|
+
const access = await auth0Ref.current.getAccessTokenSilently();
|
|
201
|
+
const loginBody = await sdk.auth.loginWithAuth0Identity(access);
|
|
202
|
+
const jwt = sybilionJwtFromLoginResponse(loginBody);
|
|
203
|
+
if (cancelled) return;
|
|
204
|
+
persistToken(jwt);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
if (cancelled) return;
|
|
207
|
+
persistToken(null);
|
|
208
|
+
const msg =
|
|
209
|
+
e instanceof Error ? e.message : 'Sybilion auth exchange failed';
|
|
210
|
+
setExchangeError(msg);
|
|
211
|
+
doLogout();
|
|
212
|
+
} finally {
|
|
213
|
+
if (!cancelled) setExchangeLoading(false);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
runExchange();
|
|
218
|
+
|
|
219
|
+
return () => {
|
|
220
|
+
cancelled = true;
|
|
221
|
+
};
|
|
222
|
+
}, [
|
|
223
|
+
sdk,
|
|
224
|
+
auth0.isAuthenticated,
|
|
225
|
+
auth0.user?.sub,
|
|
226
|
+
persistToken,
|
|
227
|
+
doLogout,
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
const getAuth0AccessToken = useCallback(async () => {
|
|
231
|
+
if (!auth0.isAuthenticated) return undefined;
|
|
232
|
+
try {
|
|
233
|
+
return await auth0.getAccessTokenSilently();
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}, [auth0]);
|
|
238
|
+
|
|
239
|
+
const getSybilionAccessToken = useCallback(async (): Promise<
|
|
240
|
+
string | null
|
|
241
|
+
> => {
|
|
242
|
+
return sybilionToken;
|
|
243
|
+
}, [sybilionToken]);
|
|
244
|
+
|
|
245
|
+
const logout = useCallback(() => {
|
|
246
|
+
doLogout();
|
|
247
|
+
}, [doLogout]);
|
|
248
|
+
|
|
249
|
+
const isAuthenticated = Boolean(
|
|
250
|
+
auth0.isAuthenticated && sybilionToken && !exchangeError,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const isLoading =
|
|
254
|
+
auth0.isLoading ||
|
|
255
|
+
exchangeLoading ||
|
|
256
|
+
(Boolean(auth0.isAuthenticated && auth0.user) &&
|
|
257
|
+
sybilionToken === null &&
|
|
258
|
+
exchangeError === null);
|
|
259
|
+
|
|
260
|
+
const value = useMemo(
|
|
261
|
+
(): SybilionAuthContextValue => ({
|
|
262
|
+
apiBaseUrl,
|
|
263
|
+
isLoading,
|
|
264
|
+
isAuthenticated,
|
|
265
|
+
sybilionAccessToken: sybilionToken,
|
|
266
|
+
error: exchangeError ?? auth0.error?.message ?? null,
|
|
267
|
+
loginWithRedirect: auth0.loginWithRedirect,
|
|
268
|
+
logout,
|
|
269
|
+
getAuth0AccessToken,
|
|
270
|
+
getSybilionAccessToken,
|
|
271
|
+
}),
|
|
272
|
+
[
|
|
273
|
+
apiBaseUrl,
|
|
274
|
+
isLoading,
|
|
275
|
+
isAuthenticated,
|
|
276
|
+
sybilionToken,
|
|
277
|
+
exchangeError,
|
|
278
|
+
auth0.error?.message,
|
|
279
|
+
auth0.loginWithRedirect,
|
|
280
|
+
logout,
|
|
281
|
+
getAuth0AccessToken,
|
|
282
|
+
getSybilionAccessToken,
|
|
283
|
+
],
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<SybilionAuthContext.Provider value={value}>
|
|
288
|
+
{children}
|
|
289
|
+
</SybilionAuthContext.Provider>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function SybilionAuthProvider({
|
|
294
|
+
children,
|
|
295
|
+
sdk,
|
|
296
|
+
auth0Domain,
|
|
297
|
+
auth0ClientId,
|
|
298
|
+
redirectUri,
|
|
299
|
+
authorizationParams,
|
|
300
|
+
sybilionTokenStorageKey = DEFAULT_TOKEN_KEY,
|
|
301
|
+
logoutReturnTo,
|
|
302
|
+
}: SybilionAuthProviderProps): JSX.Element {
|
|
303
|
+
const mergedAuthParams = useMemo(
|
|
304
|
+
() => ({
|
|
305
|
+
redirect_uri: authorizationParams?.redirect_uri ?? redirectUri,
|
|
306
|
+
audience:
|
|
307
|
+
authorizationParams?.audience ?? `https://${auth0Domain}/api/v2/`,
|
|
308
|
+
scope:
|
|
309
|
+
authorizationParams?.scope ??
|
|
310
|
+
'openid profile email offline_access update:current_user_metadata',
|
|
311
|
+
}),
|
|
312
|
+
[
|
|
313
|
+
authorizationParams?.audience,
|
|
314
|
+
authorizationParams?.scope,
|
|
315
|
+
authorizationParams?.redirect_uri,
|
|
316
|
+
redirectUri,
|
|
317
|
+
auth0Domain,
|
|
318
|
+
],
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const cookieOpts =
|
|
322
|
+
typeof window !== 'undefined'
|
|
323
|
+
? { cookieDomain: window.location.hostname }
|
|
324
|
+
: {};
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<Auth0Provider
|
|
328
|
+
domain={auth0Domain}
|
|
329
|
+
clientId={auth0ClientId}
|
|
330
|
+
authorizationParams={mergedAuthParams}
|
|
331
|
+
cacheLocation="localstorage"
|
|
332
|
+
useRefreshTokens
|
|
333
|
+
{...cookieOpts}
|
|
334
|
+
>
|
|
335
|
+
<InnerSybilionSession
|
|
336
|
+
sdk={sdk}
|
|
337
|
+
storageKey={sybilionTokenStorageKey}
|
|
338
|
+
logoutReturnTo={logoutReturnTo}
|
|
339
|
+
>
|
|
340
|
+
{children}
|
|
341
|
+
</InnerSybilionSession>
|
|
342
|
+
</Auth0Provider>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SYBILION_AUTH_LOGIN_PATH,
|
|
3
|
+
normalizeApiBaseUrl,
|
|
4
|
+
} from '#uilib/sybilion-auth/authPaths';
|
|
5
|
+
|
|
6
|
+
/** Default API segment before `/v1/...`; matches {@link createSybilionSDK} default `apiPrefix`. */
|
|
7
|
+
const SYBILION_AUTH_API_PREFIX = '/api';
|
|
8
|
+
|
|
9
|
+
/** POST `{ identity: auth0AccessToken, type: 'auth0' }` → Sybilion API JWT string. */
|
|
10
|
+
export async function exchangeAuth0AccessTokenForSybilionJwt(
|
|
11
|
+
/** API origin only (no trailing slash), same as SDK `baseUrl`. */
|
|
12
|
+
apiBaseUrl: string,
|
|
13
|
+
auth0AccessToken: string,
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
const base = normalizeApiBaseUrl(apiBaseUrl);
|
|
16
|
+
const res = await fetch(`${base}${SYBILION_AUTH_API_PREFIX}${SYBILION_AUTH_LOGIN_PATH}`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
Accept: 'application/json',
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify({ identity: auth0AccessToken, type: 'auth0' }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const json: unknown = await res.json().catch(() => ({}));
|
|
26
|
+
const pickToken = (): string | undefined => {
|
|
27
|
+
if (!json || typeof json !== 'object') return undefined;
|
|
28
|
+
const o = json as Record<string, unknown>;
|
|
29
|
+
const nested = o.data;
|
|
30
|
+
if (nested && typeof nested === 'object' && 'token' in nested) {
|
|
31
|
+
const t = (nested as { token?: unknown }).token;
|
|
32
|
+
return typeof t === 'string' ? t : undefined;
|
|
33
|
+
}
|
|
34
|
+
const t = o.token;
|
|
35
|
+
return typeof t === 'string' ? t : undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const token = pickToken();
|
|
39
|
+
if (!res.ok || token === undefined) {
|
|
40
|
+
const msg =
|
|
41
|
+
json &&
|
|
42
|
+
typeof json === 'object' &&
|
|
43
|
+
'message' in json &&
|
|
44
|
+
typeof (json as { message?: unknown }).message === 'string'
|
|
45
|
+
? (json as { message: string }).message
|
|
46
|
+
: `Sybilion auth failed (${res.status})`;
|
|
47
|
+
throw new Error(msg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
SybilionAuthProviderProps,
|
|
3
|
+
SybilionAuthContextValue,
|
|
4
|
+
} from '#uilib/sybilion-auth/SybilionAuthProvider';
|
|
5
|
+
export {
|
|
6
|
+
SybilionAuthProvider,
|
|
7
|
+
getSybilionApiOriginFromSdk,
|
|
8
|
+
useSybilionAuth,
|
|
9
|
+
sybilionApiFetch,
|
|
10
|
+
createSybilionApiFetch,
|
|
11
|
+
useSybilionApiFetch,
|
|
12
|
+
} from '#uilib/sybilion-auth/SybilionAuthProvider';
|
|
13
|
+
export {
|
|
14
|
+
SYBILION_AUTH_LOGIN_PATH,
|
|
15
|
+
normalizeApiBaseUrl,
|
|
16
|
+
} from '#uilib/sybilion-auth/authPaths';
|
|
17
|
+
export { exchangeAuth0AccessTokenForSybilionJwt } from '#uilib/sybilion-auth/exchangeSybilionToken';
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Subset of Sybilion `Dataset` / API types — use `unknown` where payloads are large or evolving.
|
|
2
|
+
* Loose JSON shapes aligned with Sybilion dataset payloads (standalone apps / widgets).
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
export type
|
|
5
|
+
export type SybilionDatasetSnapshot = {
|
|
7
6
|
id: number;
|
|
8
7
|
user_id?: number;
|
|
9
8
|
name: string;
|
|
@@ -24,16 +23,14 @@ export type MiniAppDataset = {
|
|
|
24
23
|
};
|
|
25
24
|
|
|
26
25
|
/** `forecastData` slice: analysis id (string) → forecast series blob. */
|
|
27
|
-
export type
|
|
26
|
+
export type SybilionForecastMap = Record<string, unknown>;
|
|
28
27
|
|
|
29
|
-
export type
|
|
30
|
-
/** Cached Performance-tab API payload, or null if user never opened Performance / cache empty. */
|
|
28
|
+
export type SybilionPerformanceSnapshot = {
|
|
31
29
|
table: unknown;
|
|
32
|
-
/** In-memory spaghetti lines from shell context, or null. */
|
|
33
30
|
spaghetti: unknown;
|
|
34
31
|
};
|
|
35
32
|
|
|
36
|
-
export type
|
|
33
|
+
export type SybilionDriversComparisonSnapshot = {
|
|
37
34
|
byRegion: unknown;
|
|
38
35
|
byCountry: unknown;
|
|
39
36
|
byCategory: unknown;
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import cn from 'classnames';
|
|
3
|
-
import { createContext, useContext, useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
4
|
-
import { Theme, Scroll } from '@homecode/ui';
|
|
5
|
-
import S from './MiniAppRoot.styl.js';
|
|
6
|
-
import { resolveParentOriginFromReferrer, applyThemeToDocument, buildReadyMessage, isTrustedMiniAppParentMessage, parseThemeSyncMessage } from './miniAppProtocol.js';
|
|
7
|
-
import { getDefaultMiniAppThemeConfig } from './miniAppThemeConfig.js';
|
|
8
|
-
|
|
9
|
-
const defaultTheme = {
|
|
10
|
-
mode: 'light',
|
|
11
|
-
isDarkMode: false,
|
|
12
|
-
};
|
|
13
|
-
function isEmbeddedMiniApp() {
|
|
14
|
-
return typeof window !== 'undefined' && window.parent !== window;
|
|
15
|
-
}
|
|
16
|
-
function themeFromDocument() {
|
|
17
|
-
if (typeof document === 'undefined')
|
|
18
|
-
return defaultTheme;
|
|
19
|
-
const isDark = document.documentElement.classList.contains('dark');
|
|
20
|
-
return {
|
|
21
|
-
mode: isDark ? 'dark' : 'light',
|
|
22
|
-
isDarkMode: isDark,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
const MiniAppShellContext = createContext(null);
|
|
26
|
-
function useMiniAppShellTheme() {
|
|
27
|
-
const v = useContext(MiniAppShellContext);
|
|
28
|
-
if (!v) {
|
|
29
|
-
throw new Error('useMiniAppShellTheme must be used within MiniAppRoot');
|
|
30
|
-
}
|
|
31
|
-
return v;
|
|
32
|
-
}
|
|
33
|
-
function MiniAppRoot({ children, className, appId, onThemeChange, getThemeConfig, }) {
|
|
34
|
-
const [theme, setTheme] = useState(() => isEmbeddedMiniApp() ? defaultTheme : themeFromDocument());
|
|
35
|
-
const onThemeChangeRef = useRef(onThemeChange);
|
|
36
|
-
onThemeChangeRef.current = onThemeChange;
|
|
37
|
-
const getThemeConfigRef = useRef(getThemeConfig ?? getDefaultMiniAppThemeConfig);
|
|
38
|
-
getThemeConfigRef.current = getThemeConfig ?? getDefaultMiniAppThemeConfig;
|
|
39
|
-
const currThemeConfig = useMemo(() => getThemeConfigRef.current(theme.isDarkMode), [theme.isDarkMode]);
|
|
40
|
-
const sendReady = useCallback(() => {
|
|
41
|
-
if (!window.parent || window.parent === window)
|
|
42
|
-
return;
|
|
43
|
-
const payload = appId ? { appId } : {};
|
|
44
|
-
const msg = buildReadyMessage(payload);
|
|
45
|
-
const target = resolveParentOriginFromReferrer();
|
|
46
|
-
if (target) {
|
|
47
|
-
window.parent.postMessage(msg, target);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
window.parent.postMessage(msg, '*');
|
|
51
|
-
}
|
|
52
|
-
}, [appId]);
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (!isEmbeddedMiniApp())
|
|
55
|
-
return;
|
|
56
|
-
applyThemeToDocument(theme.mode);
|
|
57
|
-
}, [theme.mode]);
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const onMessage = (event) => {
|
|
60
|
-
if (!isTrustedMiniAppParentMessage(event))
|
|
61
|
-
return;
|
|
62
|
-
const parsed = parseThemeSyncMessage(event.data);
|
|
63
|
-
if (!parsed)
|
|
64
|
-
return;
|
|
65
|
-
setTheme(parsed);
|
|
66
|
-
onThemeChangeRef.current?.(parsed);
|
|
67
|
-
};
|
|
68
|
-
window.addEventListener('message', onMessage);
|
|
69
|
-
return () => window.removeEventListener('message', onMessage);
|
|
70
|
-
}, []);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
sendReady();
|
|
73
|
-
if (document.readyState === 'complete')
|
|
74
|
-
return;
|
|
75
|
-
window.addEventListener('load', sendReady);
|
|
76
|
-
return () => window.removeEventListener('load', sendReady);
|
|
77
|
-
}, [sendReady]);
|
|
78
|
-
const ctx = useMemo(() => ({ theme }), [theme]);
|
|
79
|
-
return (jsxs(MiniAppShellContext.Provider, { value: ctx, children: [jsx(Theme, { config: currThemeConfig }), jsx(Scroll, { y: true, fadeSize: "l", className: cn(S.root, className), innerClassName: S.inner, offset: { y: { before: 50, after: 50 } }, autoHide: true, children: children })] }));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export { MiniAppRoot, useMiniAppShellTheme };
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import styleInject from 'style-inject';
|
|
2
|
-
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.MiniAppRoot_root__UVklz{height:100vh;min-height:0;overflow:hidden;position:relative;width:100%}@media (max-width:768px){.MiniAppRoot_root__UVklz{flex:1;height:auto;min-height:0}}.MiniAppRoot_inner__1ZFfl{box-sizing:border-box;max-height:100%;padding:var(--page-y-padding) var(--page-x-padding);width:100%}";
|
|
4
|
-
var S = {"root":"MiniAppRoot_root__UVklz","inner":"MiniAppRoot_inner__1ZFfl"};
|
|
5
|
-
styleInject(css_248z);
|
|
6
|
-
|
|
7
|
-
export { S as default };
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { resolveParentOriginFromReferrer, buildChatSendMessage, isTrustedMiniAppParentMessage, parseChatSendResultMessage } from './miniAppProtocol.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
4
|
-
/**
|
|
5
|
-
* Ask the Sybilion host to send a chat message with its auth token.
|
|
6
|
-
* Does not update host ChatSheet state — same session as `chatId` on the agent only.
|
|
7
|
-
*/
|
|
8
|
-
async function sendChatMessage(chatId, message) {
|
|
9
|
-
if (typeof window === 'undefined' || window.parent === window) {
|
|
10
|
-
throw new Error('sendChatMessage requires an embedded mini-app (iframe)');
|
|
11
|
-
}
|
|
12
|
-
const requestId = crypto.randomUUID();
|
|
13
|
-
const target = resolveParentOriginFromReferrer();
|
|
14
|
-
const payload = { requestId, chatId, message };
|
|
15
|
-
const msg = buildChatSendMessage(payload);
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const onMessage = (event) => {
|
|
18
|
-
if (!isTrustedMiniAppParentMessage(event))
|
|
19
|
-
return;
|
|
20
|
-
const parsed = parseChatSendResultMessage(event.data);
|
|
21
|
-
if (!parsed || parsed.requestId !== requestId)
|
|
22
|
-
return;
|
|
23
|
-
window.removeEventListener('message', onMessage);
|
|
24
|
-
clearTimeout(timer);
|
|
25
|
-
if (parsed.ok === true) {
|
|
26
|
-
resolve(parsed.result);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
reject(new Error(parsed.error));
|
|
30
|
-
};
|
|
31
|
-
window.addEventListener('message', onMessage);
|
|
32
|
-
const timer = setTimeout(() => {
|
|
33
|
-
window.removeEventListener('message', onMessage);
|
|
34
|
-
reject(new Error('Mini-app chat request timed out'));
|
|
35
|
-
}, DEFAULT_TIMEOUT_MS);
|
|
36
|
-
if (target) {
|
|
37
|
-
window.parent.postMessage(msg, target);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
window.parent.postMessage(msg, '*');
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export { sendChatMessage };
|