Skip to content

Commit 78113f7

Browse files
committed
Refactor portal module discovery flow
Consume module registry metadata from the portal LOGIN_SUCCESS response, route admin remotes to their new server topology, and remove the browser REST registry dependency.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent d60c712 commit 78113f7

23 files changed

Lines changed: 334 additions & 310 deletions

vuu-ui/docs/authentication-refactor-proposal.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ do not authenticate directly and do not contain identity-provider integration.
3838
authenticate or open websocket connections.
3939
10. Authentication failure for one remote does not terminate the portal or
4040
other remotes.
41+
11. The portal returns the module registry in
42+
`LOGIN_SUCCESS.moduleRegistry`; remote servers omit it.
4143

4244
## Terminology
4345

@@ -280,9 +282,10 @@ uses the explicit mode.
280282
5. Acquires the shared portal connection-registry entry.
281283
6. Opens the portal websocket with the portal VUU token when it is not already
282284
connected.
283-
7. Publishes the handler, authenticated identity, portal VUU session, and
285+
7. Captures the module registry returned by the portal `LOGIN_SUCCESS`.
286+
8. Publishes the handler, authenticated identity, portal VUU session, and
284287
active `connectionId`.
285-
8. Renders children only after authentication and portal connection succeed.
288+
9. Renders children only after authentication and portal connection succeed.
286289

287290
The portal is therefore authenticated before `PortalShell` or any remote
288291
feature is reachable.
@@ -516,28 +519,29 @@ required. Missing values produce a configuration error before token exchange.
516519

517520
### Registry example
518521

519-
Remote using the portal VUU server:
522+
Module admin uses the portal VUU server because module discovery is hosted by
523+
`vuu-portal`:
520524

521525
```json
522526
{
523-
"mfScope": "UserAdmin",
524-
"mfComponent": "UserAdmin",
525-
"mfUrl": "http://localhost:5007",
527+
"mfScope": "moduleAdmin",
528+
"mfComponent": "ModuleAdmin",
529+
"mfUrl": "http://localhost:5008",
526530
"vuu": {
527531
"connectionId": "portal"
528532
}
529533
}
530534
```
531535

532-
Remote using a separate VUU server:
536+
User admin uses its standalone VUU server:
533537

534538
```json
535539
{
536-
"mfScope": "moduleAdmin",
537-
"mfComponent": "ModuleAdmin",
538-
"mfUrl": "http://localhost:5008",
540+
"mfScope": "userAdmin",
541+
"mfComponent": "UserAdmin",
542+
"mfUrl": "http://localhost:5007",
539543
"vuu": {
540-
"connectionId": "module-admin",
544+
"connectionId": "user-admin",
541545
"restUrl": "https://localhost:8444/api/authn",
542546
"websocketUrl": "wss://localhost:8092/websocket"
543547
}
@@ -572,7 +576,7 @@ sequenceDiagram
572576
Registry->>PortalREST: exchange identity token
573577
PortalREST-->>Registry: portal VUU session
574578
Registry->>PortalWS: connect(VUU token)
575-
PortalWS-->>Registry: connected
579+
PortalWS-->>Registry: LOGIN_SUCCESS with moduleRegistry
576580
Registry-->>Identity: portal VUU session
577581
Identity-->>Browser: render portal runtime
578582
```
@@ -686,6 +690,8 @@ because both identity and VUU tokens may be used as bearer tokens.
686690
9. Identity failure blocks the portal.
687691
10. Remote connection failure is isolated to the affected feature.
688692
11. Logout terminates the identity session and all VUU connections.
693+
12. Portal module discovery does not make a separate module-registry REST
694+
request.
689695

690696
## Validation
691697

@@ -708,3 +714,4 @@ The implementation includes automated coverage for:
708714
- connection-aware data-source construction
709715
- remote failure isolation
710716
- logout of all VUU connections and the Keycloak session
717+
- module registry propagation from portal `LOGIN_SUCCESS`

vuu-ui/docs/multi-module-requirements.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This document is intended to be implementable without additional clarification.
2222
### Out of scope (this phase)
2323

2424
- Scala server implementation changes.
25-
- Replacing static `module-registry.json` with a dynamic registry service.
25+
- Persistent module-registry storage and administration.
2626
- Multi-server routing *within* a single remote module (assume one module -> one VUU server).
2727

2828
---
@@ -35,7 +35,6 @@ This document is intended to be implementable without additional clarification.
3535
4. Host config (`config.json`) currently includes:
3636
- `ssl`
3737
- `authUrl`
38-
- `moduleRegistryUrl`
3938
- `restUrl`
4039
- `websocketUrl`
4140

@@ -75,8 +74,9 @@ This document is intended to be implementable without additional clarification.
7574

7675
## 4.4 Discovery model
7776

78-
1. Host MUST discover remotes from `moduleRegistryUrl` (currently static JSON).
79-
2. The registry remains file-based in this phase.
77+
1. Host MUST discover remotes from the portal websocket
78+
`LOGIN_SUCCESS.moduleRegistry` payload.
79+
2. The registry is produced by the portal server's module-discovery module.
8080
3. Registry entries MUST include enough data to:
8181
- locate and load the remote module bundle
8282
- identify target VUU connection/server details for that module
@@ -118,7 +118,6 @@ Host config MUST continue to support:
118118
{
119119
"ssl": true,
120120
"authUrl": "http://localhost:5001",
121-
"moduleRegistryUrl": "/module-registry.json",
122121
"restUrl": "https://localhost:8443",
123122
"websocketUrl": "wss://localhost:8090/websocket"
124123
}
@@ -129,9 +128,8 @@ Semantics:
129128
- `authUrl`: IdP entry point (Keycloak target, simple-login-app dev).
130129
- `restUrl`: Portal REST base used for token exchange.
131130
- `websocketUrl`: Default portal websocket endpoint.
132-
- `moduleRegistryUrl`: location of remote module descriptor list.
133131

134-
## 6.2 Module registry schema (phase 1 file-based)
132+
## 6.2 Module registry schema
135133

136134
Each remote descriptor MUST provide:
137135

@@ -181,7 +179,7 @@ Notes:
181179

182180
## 7.3 Host + remote composition
183181

184-
1. Host loads module descriptors from `moduleRegistryUrl`.
182+
1. Host loads module descriptors from the authenticated portal VUU session.
185183
2. Host registers available remotes and presents them in navigation similarly to current feature model.
186184
3. On remote activation/load:
187185
- resolve module descriptor
@@ -215,7 +213,8 @@ Notes:
215213
3. At least two remote modules can be loaded where each uses a distinct websocket endpoint.
216214
4. A connection failure in one remote does not terminate other active module connections.
217215
5. Existing feature flows still work in legacy single-endpoint config with no remote VUU override.
218-
6. Module discovery is driven by `moduleRegistryUrl` JSON (no hard-coded module list in app code).
216+
6. Module discovery is driven by the portal `LOGIN_SUCCESS` registry (no
217+
hard-coded module list or browser REST request).
219218
7. Users can still drag a listed feature onto the workspace and instantiate a remote feature UI through the existing `Feature.tsx`-based layout flow.
220219

221220
---
@@ -226,10 +225,10 @@ Notes:
226225

227226
- Multi-connection registry + compatibility facade.
228227
- `RemoteModule` boundary and connection scoping.
229-
- Static file-based module registry ingestion.
228+
- Portal-login module registry ingestion.
230229
- Token exchange + reuse model wired in host runtime.
231230

232231
## Phase 2 (later session)
233232

234-
- Replace static `module-registry.json` with dynamic registry service.
233+
- Persist and administer module registry entries as needed.
235234
- Extend token refresh/re-auth and remote authorization policies as needed.
Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1 @@
1-
import type { RemoteModuleConnection } from "./auth";
2-
3-
export interface RemoteModuleDescriptor {
4-
description: string;
5-
id: string;
6-
location: string;
7-
name: string;
8-
/**
9-
* Module federation - the name of remote component to be imported
10-
*/
11-
mfComponent: string;
12-
/**
13-
* Module federation - the identifier of remote module
14-
*/
15-
mfScope: string;
16-
/**
17-
* Module federation - the url of remote module manifest
18-
*/
19-
mfUrl: string;
20-
moduleRegistryUrl?: string;
21-
path: string;
22-
title: string;
23-
vuu?: RemoteModuleConnection;
24-
version: number;
25-
}
1+
export type { VuuModuleDescriptor as RemoteModuleDescriptor } from "@vuu-ui/vuu-protocol-types";

vuu-ui/packages/core/src/auth/AuthenticationProvider.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
DataSourceConstructorProps,
44
RemoteModuleConnection,
55
} from "@vuu-ui/vuu-data-types";
6+
import type { VuuModuleRegistry } from "@vuu-ui/vuu-protocol-types";
67
import { DataProvider } from "../context-definitions/DataProvider";
78
import {
89
createContext,
@@ -72,6 +73,7 @@ interface IdentityContextValue {
7273
authHandler: AuthHandler;
7374
getIdentityToken: () => Promise<string>;
7475
logout: () => Promise<void>;
76+
moduleRegistry?: VuuModuleRegistry;
7577
portalTarget: VuuAuthTarget;
7678
registry: VuuConnectionRegistry;
7779
user: User;
@@ -299,6 +301,7 @@ const AuthenticatedIdentityProvider = ({
299301
authHandler,
300302
getIdentityToken,
301303
logout,
304+
moduleRegistry: session?.moduleRegistry,
302305
portalTarget,
303306
registry,
304307
user: identity.user,
@@ -310,6 +313,7 @@ const AuthenticatedIdentityProvider = ({
310313
logout,
311314
portalTarget,
312315
registry,
316+
session?.moduleRegistry,
313317
],
314318
);
315319

@@ -392,6 +396,26 @@ export const useIdentityToken = () => {
392396
return identity.getIdentityToken;
393397
};
394398

399+
export const useModuleRegistry = () => {
400+
const identity = useContext(IdentityContext);
401+
if (!identity) {
402+
throw new AuthenticationConfigurationError(
403+
"No identity AuthenticationProvider has been installed",
404+
);
405+
}
406+
if (!identity.moduleRegistry) {
407+
throw new AuthenticationConfigurationError(
408+
"Portal LOGIN_SUCCESS did not include a module registry",
409+
);
410+
}
411+
if (!Array.isArray(identity.moduleRegistry.modules)) {
412+
throw new AuthenticationConfigurationError(
413+
"Portal LOGIN_SUCCESS module registry did not include a modules array",
414+
);
415+
}
416+
return identity.moduleRegistry;
417+
};
418+
395419
export const usePortalVuuAuthTarget = () => {
396420
const identity = useContext(IdentityContext);
397421
if (!identity) {

vuu-ui/packages/core/src/auth/VuuConnectionRegistry.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ConnectionManager,
33
type ConnectionStatus,
4+
type VuuConnectionResult,
45
} from "@vuu-ui/vuu-data-remote";
56
import type { AuthHandler } from "./AuthHandler";
67
import {
@@ -39,11 +40,11 @@ type ConnectionRegistryEntry = {
3940
};
4041

4142
export interface VuuConnectionClient {
42-
connectTo(
43+
connectWithLoginResponseTo(
4344
connectionId: string,
4445
options: { token: string; url: string },
4546
throwOnRejected?: boolean,
46-
): Promise<ConnectionStatus | "rejected">;
47+
): Promise<VuuConnectionResult>;
4748
connectedFor(connectionId: string): boolean;
4849
destroyConnection(connectionId: string): Promise<void>;
4950
onConnectionStatus(
@@ -204,22 +205,28 @@ export class VuuConnectionRegistry {
204205
entry.target,
205206
);
206207
entry.state = "connecting";
207-
const status = await this.#connectionClient.connectTo(
208-
entry.target.connectionId,
209-
{
210-
token: session.token,
211-
url: entry.target.websocketUrl,
212-
},
213-
true,
214-
);
208+
const connectionResult =
209+
await this.#connectionClient.connectWithLoginResponseTo(
210+
entry.target.connectionId,
211+
{
212+
token: session.token,
213+
url: entry.target.websocketUrl,
214+
},
215+
true,
216+
);
217+
const { status } = connectionResult;
215218
if (status !== "connected" && status !== "reconnected") {
216219
entry.state = "failed";
217220
throw Error(
218221
`VUU websocket connection ${entry.target.connectionId} failed with ${status}`,
219222
);
220223
}
221224

222-
entry.session = session;
225+
const connectedSession = {
226+
...session,
227+
moduleRegistry: connectionResult.loginResponse.moduleRegistry,
228+
};
229+
entry.session = connectedSession;
223230
entry.state = "connected";
224231
entry.unsubscribeStatus ??= this.#connectionClient.onConnectionStatus(
225232
entry.target.connectionId,
@@ -234,9 +241,9 @@ export class VuuConnectionRegistry {
234241
},
235242
);
236243
entry.listeners.forEach((listener) => {
237-
listener(session);
244+
listener(connectedSession);
238245
});
239-
return session;
246+
return connectedSession;
240247
}
241248

242249
#reconnect(entry: ConnectionRegistryEntry) {

vuu-ui/packages/core/src/auth/VuuTokenExchange.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { User } from "./AuthHandler";
2+
import type { VuuModuleRegistry } from "@vuu-ui/vuu-protocol-types";
23
import { parseVuuUserFromToken } from "./VuuUser";
34

45
export interface VuuAuthTarget {
@@ -9,6 +10,7 @@ export interface VuuAuthTarget {
910

1011
export interface VuuSession {
1112
authorizations: string[];
13+
moduleRegistry?: VuuModuleRegistry;
1214
token: string;
1315
user: User;
1416
}

vuu-ui/packages/core/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
export {
88
AuthenticationConfigurationError, AuthenticationProvider, normalizeVuuAuthTarget, useAuthenticatedUser,
99
useIdentityToken,
10+
useModuleRegistry,
1011
useLogout,
1112
useOptionalVuuConnectionId,
1213
usePortalVuuAuthTarget,

0 commit comments

Comments
 (0)