openxiangda-cli 2.0.0-alpha.84 → 2.0.0-alpha.85
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.85",
|
|
4
4
|
"description": "Thin application-level CLI for OpenXiangda 2.0.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@oclif/core": "4.13.3",
|
|
26
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
27
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
28
|
-
"openxiangda-mcp": "2.0.0-alpha.
|
|
26
|
+
"openxiangda-contracts": "2.0.0-alpha.39",
|
|
27
|
+
"openxiangda-devkit-core": "2.0.0-alpha.52",
|
|
28
|
+
"openxiangda-mcp": "2.0.0-alpha.52"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"tsx": "4.23.12",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type DataTransactionResult,
|
|
14
14
|
type DirectoryEntryPage,
|
|
15
15
|
type DirectoryResolveRequest,
|
|
16
|
+
type CurrentUser,
|
|
16
17
|
} from 'openxiangda-contracts/browser';
|
|
17
18
|
import { applicationCode, runtimeMount } from './runtime-meta';
|
|
18
19
|
import { useSyncExternalStore } from 'react';
|
|
@@ -85,6 +86,13 @@ export interface RuntimeIdentity {
|
|
|
85
86
|
roleCodes: string[];
|
|
86
87
|
capabilityCodes: string[];
|
|
87
88
|
isAppSuperAdmin: boolean;
|
|
89
|
+
authorization?: {
|
|
90
|
+
mode: 'union' | 'focused_role';
|
|
91
|
+
focusedRoleCode: string | null;
|
|
92
|
+
availableRoles: Array<{ code: string; name: string }>;
|
|
93
|
+
hasAppSuperAdminGrant: boolean;
|
|
94
|
+
contextRevision: number;
|
|
95
|
+
};
|
|
88
96
|
environment: {
|
|
89
97
|
id: string;
|
|
90
98
|
key: 'preproduction' | 'production';
|
|
@@ -101,12 +109,7 @@ interface ConnectedCurrent {
|
|
|
101
109
|
principal: Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
|
|
105
|
-
schemaVersion: 'openxiangda.current-user/v2';
|
|
106
|
-
appCode: string;
|
|
107
|
-
environment: RuntimeIdentity['environment'];
|
|
108
|
-
principal: { type: 'user' } & Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
109
|
-
}
|
|
112
|
+
type DeployedCurrent = CurrentUser;
|
|
110
113
|
|
|
111
114
|
let activeIdentity: RuntimeIdentity | undefined;
|
|
112
115
|
|
|
@@ -117,14 +120,18 @@ export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
|
|
|
117
120
|
`${nativeBase()}/current-user?environmentKey=${encodeURIComponent(mount.environmentKey)}`
|
|
118
121
|
);
|
|
119
122
|
if (
|
|
120
|
-
current.schemaVersion !==
|
|
123
|
+
current.schemaVersion !== SCHEMA_VERSIONS.currentUser ||
|
|
121
124
|
current.appCode !== mount.appCode ||
|
|
122
125
|
current.environment.key !== mount.environmentKey ||
|
|
123
126
|
current.principal.type !== 'user'
|
|
124
127
|
) {
|
|
125
128
|
throw new Error('OPENXIANGDA_DEPLOYED_CURRENT_USER_INVALID');
|
|
126
129
|
}
|
|
127
|
-
activeIdentity = {
|
|
130
|
+
activeIdentity = {
|
|
131
|
+
...current.principal,
|
|
132
|
+
environment: current.environment,
|
|
133
|
+
authorization: current.authorization,
|
|
134
|
+
};
|
|
128
135
|
return activeIdentity;
|
|
129
136
|
}
|
|
130
137
|
const current = await request<ConnectedCurrent>(`/service/openxiangda-api/v2/applications/${applicationCode()}/dev-sessions/current`);
|
|
@@ -132,6 +139,68 @@ export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
|
|
|
132
139
|
return activeIdentity;
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
const AUTHORIZATION_CHANNEL = 'openxiangda.authorization-context/v2';
|
|
143
|
+
const AUTHORIZATION_STORAGE_KEY =
|
|
144
|
+
'openxiangda.authorization-context-invalidated/v2';
|
|
145
|
+
|
|
146
|
+
type AuthorizationInvalidation = {
|
|
147
|
+
type: 'authorization-context-invalidated';
|
|
148
|
+
appCode: string;
|
|
149
|
+
environmentKey: 'preproduction' | 'production';
|
|
150
|
+
contextRevision: number;
|
|
151
|
+
recordedAt: number;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function validAuthorizationInvalidation(
|
|
155
|
+
value: unknown
|
|
156
|
+
): value is AuthorizationInvalidation {
|
|
157
|
+
if (!value || typeof value !== 'object') return false;
|
|
158
|
+
const candidate = value as Partial<AuthorizationInvalidation>;
|
|
159
|
+
return (
|
|
160
|
+
candidate.type === 'authorization-context-invalidated' &&
|
|
161
|
+
typeof candidate.appCode === 'string' &&
|
|
162
|
+
(candidate.environmentKey === 'preproduction' ||
|
|
163
|
+
candidate.environmentKey === 'production') &&
|
|
164
|
+
Number.isSafeInteger(candidate.contextRevision) &&
|
|
165
|
+
typeof candidate.recordedAt === 'number'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function subscribeAuthorizationInvalidation(listener: () => void) {
|
|
170
|
+
const mount = runtimeMount();
|
|
171
|
+
if (!mount || typeof window === 'undefined') return () => undefined;
|
|
172
|
+
const receive = (value: unknown) => {
|
|
173
|
+
if (
|
|
174
|
+
validAuthorizationInvalidation(value) &&
|
|
175
|
+
value.appCode === mount.appCode &&
|
|
176
|
+
value.environmentKey === mount.environmentKey
|
|
177
|
+
) {
|
|
178
|
+
activeIdentity = undefined;
|
|
179
|
+
listener();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
let channel: BroadcastChannel | undefined;
|
|
183
|
+
try {
|
|
184
|
+
channel = new BroadcastChannel(AUTHORIZATION_CHANNEL);
|
|
185
|
+
channel.onmessage = event => receive(event.data);
|
|
186
|
+
} catch {
|
|
187
|
+
channel = undefined;
|
|
188
|
+
}
|
|
189
|
+
const onStorage = (event: StorageEvent) => {
|
|
190
|
+
if (event.key !== AUTHORIZATION_STORAGE_KEY || !event.newValue) return;
|
|
191
|
+
try {
|
|
192
|
+
receive(JSON.parse(event.newValue));
|
|
193
|
+
} catch {
|
|
194
|
+
// Invalid browser signals never alter the server-owned identity.
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
window.addEventListener('storage', onStorage);
|
|
198
|
+
return () => {
|
|
199
|
+
channel?.close();
|
|
200
|
+
window.removeEventListener('storage', onStorage);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
135
204
|
export async function logoutCurrentUser() {
|
|
136
205
|
await request<null>('/api/auth/logout', {
|
|
137
206
|
method: 'POST',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Alert, Button, Result, Spin } from 'antd';
|
|
2
2
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
loadCurrentIdentity,
|
|
5
|
+
subscribeAuthorizationInvalidation,
|
|
6
|
+
type RuntimeIdentity,
|
|
7
|
+
} from './platform-client';
|
|
4
8
|
import { runtimeMount } from './runtime-meta';
|
|
5
9
|
|
|
6
10
|
interface RuntimeValue {
|
|
@@ -14,6 +18,15 @@ export function RuntimeBoundary({ children }: { children: React.ReactNode }) {
|
|
|
14
18
|
const [identity, setIdentity] = useState<RuntimeIdentity>();
|
|
15
19
|
const [error, setError] = useState<Error>();
|
|
16
20
|
const [attempt, setAttempt] = useState(0);
|
|
21
|
+
useEffect(
|
|
22
|
+
() =>
|
|
23
|
+
subscribeAuthorizationInvalidation(() => {
|
|
24
|
+
setIdentity(undefined);
|
|
25
|
+
setError(undefined);
|
|
26
|
+
setAttempt(value => value + 1);
|
|
27
|
+
}),
|
|
28
|
+
[]
|
|
29
|
+
);
|
|
17
30
|
useEffect(() => {
|
|
18
31
|
let active = true;
|
|
19
32
|
setError(undefined);
|
package/template/package.json
CHANGED
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"build": "pnpm --recursive build"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"openxiangda-cli": "2.0.0-alpha.
|
|
26
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
27
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
25
|
+
"openxiangda-cli": "2.0.0-alpha.85",
|
|
26
|
+
"openxiangda-contracts": "2.0.0-alpha.39",
|
|
27
|
+
"openxiangda-devkit-core": "2.0.0-alpha.52",
|
|
28
28
|
"typescript": "5.9.3"
|
|
29
29
|
}
|
|
30
30
|
}
|