@lumerahq/cli 0.30.13 → 0.30.15
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/package.json
CHANGED
|
@@ -12,8 +12,8 @@ importers:
|
|
|
12
12
|
.:
|
|
13
13
|
dependencies:
|
|
14
14
|
'@lumerahq/ui':
|
|
15
|
-
specifier: ^0.19.
|
|
16
|
-
version: 0.19.
|
|
15
|
+
specifier: ^0.19.5
|
|
16
|
+
version: 0.19.5(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
|
17
17
|
'@tanstack/react-query':
|
|
18
18
|
specifier: ^5.90.11
|
|
19
19
|
version: 5.101.0(react@19.2.7)
|
|
@@ -457,8 +457,8 @@ packages:
|
|
|
457
457
|
'@jridgewell/trace-mapping@0.3.31':
|
|
458
458
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
|
459
459
|
|
|
460
|
-
'@lumerahq/ui@0.19.
|
|
461
|
-
resolution: {integrity: sha512-
|
|
460
|
+
'@lumerahq/ui@0.19.5':
|
|
461
|
+
resolution: {integrity: sha512-AkX13UMMEkkHLRktms6Gh6vrygcQbMWxq1Du07qNbiVcVCcAPMWr68chkLdUOM0/00Bgb7JfLu1jrodhtFlrGA==}
|
|
462
462
|
peerDependencies:
|
|
463
463
|
react: ^18.0.0 || ^19.0.0
|
|
464
464
|
react-dom: ^18.0.0 || ^19.0.0
|
|
@@ -2311,7 +2311,7 @@ snapshots:
|
|
|
2311
2311
|
'@jridgewell/resolve-uri': 3.1.2
|
|
2312
2312
|
'@jridgewell/sourcemap-codec': 1.5.5
|
|
2313
2313
|
|
|
2314
|
-
'@lumerahq/ui@0.19.
|
|
2314
|
+
'@lumerahq/ui@0.19.5(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
|
2315
2315
|
dependencies:
|
|
2316
2316
|
'@base-ui/react': 1.5.0(@types/react@19.2.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
|
2317
2317
|
'@tanstack/react-query': 5.101.0(react@19.2.7)
|
|
@@ -1,9 +1,47 @@
|
|
|
1
|
-
import { createContext } from 'react';
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ViewerMemberTag = {
|
|
4
|
+
id: string;
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
color: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ViewerContext = {
|
|
11
|
+
version: 1;
|
|
12
|
+
isSignedIn: boolean;
|
|
13
|
+
subjectType: 'anonymous' | 'org_member' | 'public_app_user';
|
|
14
|
+
belongsToOrganization: boolean;
|
|
15
|
+
identity: { id: string; name: string; email: string } | null;
|
|
16
|
+
organizationMembership: {
|
|
17
|
+
id: string;
|
|
18
|
+
role: string;
|
|
19
|
+
isCompanyAdmin: boolean;
|
|
20
|
+
department: ViewerMemberTag | null;
|
|
21
|
+
tags: ViewerMemberTag[];
|
|
22
|
+
} | null;
|
|
23
|
+
appUser: { id: string; role: string } | null;
|
|
24
|
+
};
|
|
2
25
|
|
|
3
26
|
export type AuthContextValue = {
|
|
4
27
|
company?: { id: string; name?: string; apiName?: string };
|
|
5
28
|
user?: { id: string; name: string; email: string; role?: string };
|
|
29
|
+
viewer: ViewerContext;
|
|
6
30
|
sessionToken?: string;
|
|
7
31
|
};
|
|
8
32
|
|
|
9
33
|
export const AuthContext = createContext<AuthContextValue | null>(null);
|
|
34
|
+
|
|
35
|
+
export const useAuth = (): AuthContextValue => {
|
|
36
|
+
const value = useContext(AuthContext);
|
|
37
|
+
if (!value) throw new Error('useAuth must be used inside AuthContext.Provider');
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const hasMemberTag = (viewer: ViewerContext, key: string): boolean =>
|
|
42
|
+
viewer.organizationMembership?.tags.some(
|
|
43
|
+
(tag) => tag.key.trim().toLowerCase() === key.trim().toLowerCase()
|
|
44
|
+
) === true;
|
|
45
|
+
|
|
46
|
+
export const isDepartment = (viewer: ViewerContext, key: string): boolean =>
|
|
47
|
+
viewer.organizationMembership?.department?.key.trim().toLowerCase() === key.trim().toLowerCase();
|
|
@@ -11,7 +11,12 @@ import { StrictMode, useEffect, useRef, useState } from 'react';
|
|
|
11
11
|
import ReactDOM from 'react-dom/client';
|
|
12
12
|
import { Toaster } from 'sonner';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AuthContext,
|
|
16
|
+
type AuthContextValue,
|
|
17
|
+
type ViewerContext,
|
|
18
|
+
type ViewerMemberTag,
|
|
19
|
+
} from './lib/auth';
|
|
15
20
|
import { routeTree } from './routeTree.gen';
|
|
16
21
|
import './styles.css';
|
|
17
22
|
|
|
@@ -39,6 +44,117 @@ declare module '@tanstack/react-router' {
|
|
|
39
44
|
|
|
40
45
|
const ROUTE_STORAGE_KEY = '{{projectName}}-route';
|
|
41
46
|
|
|
47
|
+
type ViewerHostPayload = HostPayload & { viewer?: ViewerContext };
|
|
48
|
+
|
|
49
|
+
type ViewerResponse = {
|
|
50
|
+
version?: number;
|
|
51
|
+
is_signed_in?: boolean;
|
|
52
|
+
subject_type?: 'anonymous' | 'org_member' | 'public_app_user';
|
|
53
|
+
belongs_to_organization?: boolean;
|
|
54
|
+
identity?: { id?: string; name?: string; email?: string } | null;
|
|
55
|
+
organization_membership?: {
|
|
56
|
+
id?: string;
|
|
57
|
+
role?: string;
|
|
58
|
+
is_company_admin?: boolean;
|
|
59
|
+
department?: ViewerMemberTag | null;
|
|
60
|
+
tags?: ViewerMemberTag[];
|
|
61
|
+
} | null;
|
|
62
|
+
app_user?: { id?: string; role?: string } | null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const viewerFromPayload = (payload: ViewerHostPayload): ViewerContext => {
|
|
66
|
+
if (payload.viewer) return payload.viewer;
|
|
67
|
+
const user = payload.user;
|
|
68
|
+
if (user && (payload.subjectType === 'app_user' || payload.subjectType === 'public_app_user')) {
|
|
69
|
+
return {
|
|
70
|
+
version: 1,
|
|
71
|
+
isSignedIn: true,
|
|
72
|
+
subjectType: 'public_app_user',
|
|
73
|
+
belongsToOrganization: false,
|
|
74
|
+
identity: { id: user.id, name: user.name, email: user.email },
|
|
75
|
+
organizationMembership: null,
|
|
76
|
+
appUser: { id: user.id, role: user.role ?? '' },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (user) {
|
|
80
|
+
return {
|
|
81
|
+
version: 1,
|
|
82
|
+
isSignedIn: true,
|
|
83
|
+
subjectType: 'org_member',
|
|
84
|
+
belongsToOrganization: true,
|
|
85
|
+
identity: { id: user.id, name: user.name, email: user.email },
|
|
86
|
+
organizationMembership: {
|
|
87
|
+
id: user.id,
|
|
88
|
+
role: user.role ?? '',
|
|
89
|
+
isCompanyAdmin: user.role?.toLowerCase() === 'admin',
|
|
90
|
+
department: null,
|
|
91
|
+
tags: [],
|
|
92
|
+
},
|
|
93
|
+
appUser: null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
version: 1,
|
|
98
|
+
isSignedIn: false,
|
|
99
|
+
subjectType: 'anonymous',
|
|
100
|
+
belongsToOrganization: false,
|
|
101
|
+
identity: null,
|
|
102
|
+
organizationMembership: null,
|
|
103
|
+
appUser: null,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const viewerFromResponse = (response: ViewerResponse): ViewerContext => ({
|
|
108
|
+
version: 1,
|
|
109
|
+
isSignedIn: response.is_signed_in === true,
|
|
110
|
+
subjectType: response.subject_type ?? 'anonymous',
|
|
111
|
+
belongsToOrganization: response.belongs_to_organization === true,
|
|
112
|
+
identity: response.identity
|
|
113
|
+
? {
|
|
114
|
+
id: response.identity.id ?? '',
|
|
115
|
+
name: response.identity.name ?? '',
|
|
116
|
+
email: response.identity.email ?? '',
|
|
117
|
+
}
|
|
118
|
+
: null,
|
|
119
|
+
organizationMembership: response.organization_membership
|
|
120
|
+
? {
|
|
121
|
+
id: response.organization_membership.id ?? '',
|
|
122
|
+
role: response.organization_membership.role ?? '',
|
|
123
|
+
isCompanyAdmin: response.organization_membership.is_company_admin === true,
|
|
124
|
+
department: response.organization_membership.department ?? null,
|
|
125
|
+
tags: response.organization_membership.tags ?? [],
|
|
126
|
+
}
|
|
127
|
+
: null,
|
|
128
|
+
appUser: response.app_user
|
|
129
|
+
? {
|
|
130
|
+
id: response.app_user.id ?? '',
|
|
131
|
+
role: response.app_user.role ?? '',
|
|
132
|
+
}
|
|
133
|
+
: null,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const resolveViewer = async (payload: ViewerHostPayload): Promise<ViewerContext> => {
|
|
137
|
+
if (payload.viewer) return payload.viewer;
|
|
138
|
+
|
|
139
|
+
// The checked-in lockfile can briefly trail a newly published UI bridge.
|
|
140
|
+
// Keep standalone apps complete during that release window by reading the
|
|
141
|
+
// canonical block from the same authenticated /api/me endpoint.
|
|
142
|
+
if (!isEmbedded()) {
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch('/api/me', { credentials: 'same-origin' });
|
|
145
|
+
if (response.ok) {
|
|
146
|
+
const body = (await response.json()) as { viewer?: ViewerResponse };
|
|
147
|
+
if (body.viewer) return viewerFromResponse(body.viewer);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
// Compatibility fallback below preserves the pre-viewer behavior when
|
|
151
|
+
// an older app host does not yet expose the canonical response.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return viewerFromPayload(payload);
|
|
156
|
+
};
|
|
157
|
+
|
|
42
158
|
function RouteRestorer() {
|
|
43
159
|
const isFirstLoad = useRef(true);
|
|
44
160
|
|
|
@@ -75,7 +191,8 @@ const App = () => {
|
|
|
75
191
|
// in the Lumera shell, same-origin /api with the app-session cookie when
|
|
76
192
|
// served standalone on the app's own hostname, and direct dev fetch when
|
|
77
193
|
// VITE_DEV_API_BASE_URL is set.
|
|
78
|
-
const cleanup = onInitMessage((
|
|
194
|
+
const cleanup = onInitMessage((rawPayload?: HostPayload) => {
|
|
195
|
+
const payload = rawPayload as ViewerHostPayload | undefined;
|
|
79
196
|
if (!payload) {
|
|
80
197
|
// No identity and no app context: authentication is required
|
|
81
198
|
// (payload.user alone being undefined means an anonymous visitor on
|
|
@@ -83,12 +200,15 @@ const App = () => {
|
|
|
83
200
|
setStatus('unauthenticated');
|
|
84
201
|
return;
|
|
85
202
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
203
|
+
void resolveViewer(payload).then((viewer) => {
|
|
204
|
+
setHostContext({
|
|
205
|
+
company: payload.company,
|
|
206
|
+
user: payload.user,
|
|
207
|
+
viewer,
|
|
208
|
+
sessionToken: payload.session?.token,
|
|
209
|
+
});
|
|
210
|
+
setStatus('ready');
|
|
90
211
|
});
|
|
91
|
-
setStatus('ready');
|
|
92
212
|
});
|
|
93
213
|
|
|
94
214
|
postReadyMessage();
|