@structured-id/ui-core 4.3.0 → 4.4.0

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": "@structured-id/ui-core",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -42,7 +42,7 @@
42
42
  "@protobuf-ts/grpcweb-transport": "^2.11.1",
43
43
  "@protobuf-ts/runtime": "^2.11.1",
44
44
  "@protobuf-ts/runtime-rpc": "^2.11.1",
45
- "@structured-id/proto": "^0.3.0",
45
+ "@structured-id/proto": "^0.4.1",
46
46
  "@structured-world/structured-email-address-ts": "^0.0.22",
47
47
  "@structured-world/structured-public-domains": "0.0.x",
48
48
  "libphonenumber-js": "^1.13.15"
@@ -1,11 +1,4 @@
1
- export {
2
- useGrpc,
3
- initGrpc,
4
- getTransport,
5
- closeTransport,
6
- getTransportType,
7
- type TransportType,
8
- } from "./useGrpc";
1
+ export { useGrpc, initGrpc, getTransport, closeTransport } from "./useGrpc";
9
2
  export {
10
3
  initAccountApi,
11
4
  getAccountTransport,
@@ -4,28 +4,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
4
4
  // Mock modules BEFORE importing the module under test
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
- const mockAdaptiveInit = vi
8
- .fn<() => Promise<void>>()
9
- .mockResolvedValue(undefined);
10
- const mockAdaptiveClose = vi.fn();
11
- const mockAdaptiveActiveTransport = vi
12
- .fn<() => string>()
13
- .mockReturnValue("grpc-web");
14
-
15
- vi.mock("@structured-id/proto/transport", () => ({
16
- // Constructor mocks are `function`s: they are called with `new`.
17
- AdaptiveRpcTransport: vi.fn().mockImplementation(function () {
18
- return {
19
- init: mockAdaptiveInit,
20
- close: mockAdaptiveClose,
21
- get activeTransport() {
22
- return mockAdaptiveActiveTransport();
23
- },
24
- };
25
- }),
26
- }));
27
-
28
7
  vi.mock("@protobuf-ts/grpcweb-transport", () => ({
8
+ // Constructor mocks are `function`s: they are called with `new`.
29
9
  GrpcWebFetchTransport: vi.fn().mockImplementation(function () {
30
10
  return { type: "grpc-web-transport" };
31
11
  }),
@@ -33,14 +13,7 @@ vi.mock("@protobuf-ts/grpcweb-transport", () => ({
33
13
 
34
14
  // Import AFTER mocks
35
15
  import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
36
- import { AdaptiveRpcTransport } from "@structured-id/proto/transport";
37
- import {
38
- initGrpc,
39
- getTransport,
40
- closeTransport,
41
- getTransportType,
42
- useGrpc,
43
- } from "./useGrpc";
16
+ import { initGrpc, getTransport, closeTransport, useGrpc } from "./useGrpc";
44
17
  import type { GrpcConfig } from "../types/auth";
45
18
 
46
19
  // ---------------------------------------------------------------------------
@@ -50,11 +23,12 @@ import type { GrpcConfig } from "../types/auth";
50
23
  describe("useGrpc", () => {
51
24
  beforeEach(() => {
52
25
  vi.clearAllMocks();
53
- // Reset transport state by calling close between tests
54
26
  closeTransport();
55
27
  });
56
28
 
57
- describe("initGrpc — without WebTransport URL", () => {
29
+ describe("initGrpc", () => {
30
+ // The BFF session cookie rides on every call, so credentials are always
31
+ // included; nothing else configures the transport.
58
32
  it("creates GrpcWebFetchTransport with baseUrl and credentials", () => {
59
33
  const config: GrpcConfig = { baseUrl: "https://api.sid.example.com" };
60
34
  initGrpc(config);
@@ -63,75 +37,6 @@ describe("useGrpc", () => {
63
37
  baseUrl: "https://api.sid.example.com",
64
38
  fetchInit: { credentials: "include" },
65
39
  });
66
- expect(AdaptiveRpcTransport).not.toHaveBeenCalled();
67
- });
68
-
69
- it("getTransportType returns grpc-web", () => {
70
- initGrpc({ baseUrl: "https://api.sid.example.com" });
71
- expect(getTransportType()).toBe("grpc-web");
72
- });
73
- });
74
-
75
- describe("initGrpc — with WebTransport URL", () => {
76
- it("creates AdaptiveRpcTransport with fetchInit credentials", () => {
77
- const config: GrpcConfig = {
78
- baseUrl: "https://api.sid.example.com",
79
- webTransportUrl: "https://wt.api.sid.example.com",
80
- };
81
- initGrpc(config);
82
-
83
- expect(AdaptiveRpcTransport).toHaveBeenCalledWith(
84
- expect.objectContaining({
85
- grpcWebUrl: "https://api.sid.example.com",
86
- webTransportUrl: "https://wt.api.sid.example.com",
87
- fetchInit: { credentials: "include" },
88
- }),
89
- );
90
- expect(GrpcWebFetchTransport).not.toHaveBeenCalled();
91
- });
92
-
93
- it("calls adaptive.init() non-blocking on creation", async () => {
94
- initGrpc({
95
- baseUrl: "https://api.sid.example.com",
96
- webTransportUrl: "https://wt.api.sid.example.com",
97
- });
98
-
99
- // init() should be called (non-blocking fire-and-forget)
100
- // Give microtasks time to run
101
- await Promise.resolve();
102
- expect(mockAdaptiveInit).toHaveBeenCalled();
103
- });
104
-
105
- it("passes all WebTransport options to AdaptiveRpcTransport", () => {
106
- const onChange = vi.fn();
107
- initGrpc({
108
- baseUrl: "https://api.sid.example.com",
109
- webTransportUrl: "https://wt.api.sid.example.com",
110
- webTransportCertHash: "abcdef1234567890",
111
- webTransportReconnectDelay: 3000,
112
- onTransportChange: onChange,
113
- });
114
-
115
- expect(AdaptiveRpcTransport).toHaveBeenCalledWith(
116
- expect.objectContaining({
117
- grpcWebUrl: "https://api.sid.example.com",
118
- webTransportUrl: "https://wt.api.sid.example.com",
119
- webTransportCertHash: "abcdef1234567890",
120
- reconnectDelay: 3000,
121
- onTransportChange: onChange,
122
- fetchInit: { credentials: "include" },
123
- }),
124
- );
125
- });
126
-
127
- it("getTransportType delegates to AdaptiveRpcTransport.activeTransport", () => {
128
- mockAdaptiveActiveTransport.mockReturnValue("webtransport");
129
- initGrpc({
130
- baseUrl: "https://api.sid.example.com",
131
- webTransportUrl: "https://wt.api.sid.example.com",
132
- });
133
-
134
- expect(getTransportType()).toBe("webtransport");
135
40
  });
136
41
  });
137
42
 
@@ -142,8 +47,7 @@ describe("useGrpc", () => {
142
47
 
143
48
  it("returns transport after initGrpc", () => {
144
49
  initGrpc({ baseUrl: "https://api.sid.example.com" });
145
- expect(() => getTransport()).not.toThrow();
146
- expect(getTransport()).toBeDefined();
50
+ expect(getTransport()).toEqual({ type: "grpc-web-transport" });
147
51
  });
148
52
  });
149
53
 
@@ -164,36 +68,16 @@ describe("useGrpc", () => {
164
68
  expect(typeof composable.getTransport).toBe("function");
165
69
  expect(typeof composable.initGrpc).toBe("function");
166
70
  });
167
-
168
- it("transport.value is null after closeTransport", () => {
169
- initGrpc({ baseUrl: "https://api.sid.example.com" });
170
- closeTransport();
171
- const { transport } = useGrpc();
172
- expect(transport.value).toBeNull();
173
- });
174
71
  });
175
72
 
176
73
  describe("closeTransport", () => {
177
- it("calls close() on AdaptiveRpcTransport", () => {
178
- initGrpc({
179
- baseUrl: "https://api.sid.example.com",
180
- webTransportUrl: "https://wt.api.sid.example.com",
181
- });
182
-
183
- closeTransport();
184
- expect(mockAdaptiveClose).toHaveBeenCalled();
185
- });
186
-
74
+ // After close, the transport is gone: a later call must initialize
75
+ // again rather than reuse the old one.
187
76
  it("resets transport to null (getTransport throws after close)", () => {
188
77
  initGrpc({ baseUrl: "https://api.sid.example.com" });
189
78
  closeTransport();
190
79
  expect(() => getTransport()).toThrow("gRPC transport not initialized");
191
- });
192
-
193
- it("does not call close() on plain GrpcWebFetchTransport (no close method)", () => {
194
- initGrpc({ baseUrl: "https://api.sid.example.com" });
195
- // Should not throw even though GrpcWebFetchTransport has no close()
196
- expect(() => closeTransport()).not.toThrow();
80
+ expect(useGrpc().transport.value).toBeNull();
197
81
  });
198
82
  });
199
83
  });
@@ -2,67 +2,28 @@ import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
2
2
  import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
3
3
  import { ref, type Ref } from "vue";
4
4
  import type { GrpcConfig } from "../types/auth";
5
- import {
6
- AdaptiveRpcTransport,
7
- type TransportType,
8
- } from "@structured-id/proto/transport";
9
5
 
10
- /** Shared RPC transport instance (gRPC-web or AdaptiveRpcTransport). */
6
+ /** Shared gRPC-web transport instance. */
11
7
  let _transport: RpcTransport | null = null;
12
- /** Separate reference to AdaptiveRpcTransport when active (avoids instanceof). */
13
- let _adaptive: AdaptiveRpcTransport | null = null;
14
8
 
15
9
  /**
16
- * Initialize the gRPC transport. Call once at app startup.
10
+ * Initialize the gRPC-web transport. Call once at app startup.
17
11
  *
18
- * When `config.webTransportUrl` is provided, creates an AdaptiveRpcTransport
19
- * with WebTransport as the primary and gRPC-web as the fallback. The initial
20
- * WebTransport connection is attempted asynchronously (non-blocking).
21
- *
22
- * Without `config.webTransportUrl`, falls back to plain gRPC-web (same as before).
23
- *
24
- * After BFF migration (#536i-3): `credentials: "include"` is always set
25
- * so the httpOnly BFF session cookie is sent with every gRPC-web request.
12
+ * `credentials: "include"` is always set so the httpOnly BFF session cookie
13
+ * is sent with every request.
26
14
  *
27
15
  * @example
28
16
  * ```ts
29
- * // gRPC-web only
30
17
  * initGrpc({ baseUrl: 'https://api.example.com' })
31
- *
32
- * // With WebTransport failover
33
- * initGrpc({
34
- * baseUrl: 'https://api.example.com',
35
- * webTransportUrl: 'https://wt.api.example.com',
36
- * })
37
18
  * ```
38
19
  */
39
20
  export function initGrpc(config: GrpcConfig): void {
40
- if (config.webTransportUrl) {
41
- const adaptive = new AdaptiveRpcTransport({
42
- grpcWebUrl: config.baseUrl,
43
- webTransportUrl: config.webTransportUrl,
44
- webTransportCertHash: config.webTransportCertHash,
45
- reconnectDelay: config.webTransportReconnectDelay,
46
- onTransportChange: config.onTransportChange,
47
- // Always include credentials so the BFF session cookie is attached.
48
- // The BFF proxy exchanges the cookie for a Bearer token server-side.
49
- fetchInit: { credentials: "include" },
50
- });
51
- // Attempt WebTransport connection in background — non-blocking.
52
- adaptive.init().catch(() => {
53
- // Silently falls back to gRPC-web if WebTransport is unavailable.
54
- });
55
- _adaptive = adaptive;
56
- _transport = adaptive;
57
- } else {
58
- _adaptive = null;
59
- _transport = new GrpcWebFetchTransport({
60
- baseUrl: config.baseUrl,
61
- // Always include credentials so the BFF session cookie is attached.
62
- // The BFF proxy exchanges the cookie for a Bearer token server-side.
63
- fetchInit: { credentials: "include" },
64
- });
65
- }
21
+ _transport = new GrpcWebFetchTransport({
22
+ baseUrl: config.baseUrl,
23
+ // The BFF proxy exchanges the session cookie for a Bearer token
24
+ // server-side.
25
+ fetchInit: { credentials: "include" },
26
+ });
66
27
  }
67
28
 
68
29
  /**
@@ -78,24 +39,12 @@ export function getTransport(): RpcTransport {
78
39
  }
79
40
 
80
41
  /**
81
- * Close the transport and release resources (timers, connections).
82
- * Call during app teardown if using WebTransport.
42
+ * Drop the shared transport; the next use needs `initGrpc()` again.
83
43
  */
84
44
  export function closeTransport(): void {
85
- _adaptive?.close();
86
- _adaptive = null;
87
45
  _transport = null;
88
46
  }
89
47
 
90
- /**
91
- * Get the currently active transport type.
92
- * Returns "grpc-web" if AdaptiveRpcTransport is not used or WebTransport
93
- * is not available.
94
- */
95
- export function getTransportType(): TransportType {
96
- return _adaptive?.activeTransport ?? "grpc-web";
97
- }
98
-
99
48
  /**
100
49
  * Vue composable for gRPC transport.
101
50
  *
@@ -118,5 +67,3 @@ export function useGrpc() {
118
67
  initGrpc,
119
68
  };
120
69
  }
121
-
122
- export type { TransportType };
package/src/index.ts CHANGED
@@ -4,8 +4,6 @@ export {
4
4
  initGrpc,
5
5
  getTransport,
6
6
  closeTransport,
7
- getTransportType,
8
- type TransportType,
9
7
  } from "./composables/useGrpc";
10
8
  export { useSession, type SessionCallbacks } from "./composables/useSession";
11
9
  export {
@@ -37,7 +37,6 @@ const quasarStubs = {
37
37
  };
38
38
 
39
39
  const loginFn = vi.fn().mockResolvedValue({
40
- accessToken: "tok",
41
40
  expiresIn: 3600,
42
41
  sessionId: "sess-1",
43
42
  });
@@ -175,7 +175,6 @@ const props = withDefaults(
175
175
  password: string,
176
176
  onStep?: (step: number) => void,
177
177
  ) => Promise<{
178
- accessToken: string;
179
178
  expiresIn: number;
180
179
  sessionId?: string;
181
180
  }>;
@@ -207,9 +206,7 @@ const props = withDefaults(
207
206
  );
208
207
 
209
208
  const emit = defineEmits<{
210
- success: [
211
- result: { accessToken: string; expiresIn: number; sessionId?: string },
212
- ];
209
+ success: [result: { expiresIn: number; sessionId?: string }];
213
210
  error: [error: Error];
214
211
  "forgot-password": [];
215
212
  }>();
@@ -125,7 +125,6 @@ describe("createAuth prewarm", () => {
125
125
  });
126
126
  api.loginFinish.mockResolvedValue({
127
127
  sessionId: "s",
128
- accessToken: "t",
129
128
  expiresIn: 60n,
130
129
  });
131
130
  });
@@ -173,10 +172,13 @@ describe("createAuth register", () => {
173
172
  registrationResponse: RESPONSE,
174
173
  history: wireContext(),
175
174
  });
176
- api.regFinish.mockResolvedValue({
175
+ // An older server's answer, still carrying a Profile identifier the
176
+ // current message no longer has.
177
+ const fromOlderServer = {
177
178
  profileId: "profile-uuid",
178
179
  credentialId: "cred-uuid",
179
- });
180
+ };
181
+ api.regFinish.mockResolvedValue(fromOlderServer);
180
182
  });
181
183
 
182
184
  it("runs the operation: start, evaluate, prove, finish with the proof", async () => {
@@ -208,10 +210,9 @@ describe("createAuth register", () => {
208
210
  RESPONSE,
209
211
  );
210
212
  expect(api.regFinish).toHaveBeenCalledWith(OPERATION, RECORD, WIRE_PROOF);
211
- expect(result).toEqual({
212
- profileId: "profile-uuid",
213
- credentialId: "cred-uuid",
214
- });
213
+ // Regression: the result passed the server's Profile identifier on to
214
+ // the application; even a server still sending one is not forwarded.
215
+ expect(result).toEqual({ credentialId: "cred-uuid" });
215
216
  });
216
217
 
217
218
  // A password the circuit cannot hold has no history request: nothing is
@@ -651,7 +652,6 @@ describe("createAuth resetPassword", () => {
651
652
  api.executeReset.mockResolvedValue({ registrationResponse: RESPONSE });
652
653
  api.completeReset.mockResolvedValue({
653
654
  sessionId: "sess-1",
654
- accessToken: "jwt",
655
655
  expiresIn: 3600,
656
656
  });
657
657
  });
@@ -687,9 +687,8 @@ describe("createAuth login", () => {
687
687
  });
688
688
  api.loginFinish.mockResolvedValue({
689
689
  sessionId: "sess-1",
690
- accessToken: "jwt",
691
690
  expiresIn: BigInt(3600),
692
- } as never);
691
+ });
693
692
  });
694
693
 
695
694
  it("signs in with the Pallas client in two round trips", async () => {
@@ -714,9 +713,10 @@ describe("createAuth login", () => {
714
713
  "server-state",
715
714
  );
716
715
  expect(steps).toEqual([1, 2, 3]);
716
+ // The result carries no credential: the browser holds the session
717
+ // cookie the answer set.
717
718
  expect(result).toEqual({
718
719
  sessionId: "sess-1",
719
- accessToken: "jwt",
720
720
  expiresIn: 3600,
721
721
  });
722
722
  });
@@ -33,16 +33,21 @@ import {
33
33
 
34
34
  // ── Types ──
35
35
 
36
- /** A sign-in's session, as the server issued it. */
36
+ /**
37
+ * A sign-in's session, as the server issued it. The credential itself never
38
+ * reaches page code: a browser holds the session cookie the answer set.
39
+ */
37
40
  export interface LoginResult {
38
41
  sessionId: string;
39
- accessToken: string;
42
+ /** Seconds until the delivered credential expires. */
40
43
  expiresIn: number;
41
44
  }
42
45
 
43
- /** A registration's profile and its password credential. */
46
+ /**
47
+ * A registration's password credential. No Profile identifier: the client
48
+ * signs in after registering and works through its session.
49
+ */
44
50
  export interface RegisterResult {
45
- profileId: string;
46
51
  credentialId: string;
47
52
  }
48
53
 
@@ -394,10 +399,7 @@ export function createAuth(loadClient: ZkppClientLoader) {
394
399
  done.proof,
395
400
  );
396
401
  plan.finish();
397
- return {
398
- profileId: finished.profileId,
399
- credentialId: finished.credentialId,
400
- };
402
+ return { credentialId: finished.credentialId };
401
403
  }
402
404
 
403
405
  /**
@@ -541,7 +543,6 @@ export function createAuth(loadClient: ZkppClientLoader) {
541
543
  );
542
544
  return {
543
545
  sessionId: finished.sessionId,
544
- accessToken: finished.accessToken,
545
546
  expiresIn: Number(finished.expiresIn),
546
547
  };
547
548
  }
@@ -71,7 +71,6 @@ describe("OPAQUE gRPC wrappers", () => {
71
71
  it("sends principal, finalization, and serverLoginState", async () => {
72
72
  const expected = {
73
73
  sessionId: "sess-1",
74
- accessToken: "jwt",
75
74
  expiresIn: BigInt(3600),
76
75
  };
77
76
  mockOpaqueLoginFinish.mockResolvedValue({ response: expected });
package/src/types/auth.ts CHANGED
@@ -29,30 +29,4 @@ export interface SessionInfo {
29
29
  export interface GrpcConfig {
30
30
  /** Base URL for gRPC-web transport (e.g., "https://api.example.com"). */
31
31
  baseUrl: string;
32
- /**
33
- * @deprecated BFF mode always uses credentials: "include".
34
- * This field is kept for API compatibility but has no effect.
35
- */
36
- withCredentials?: boolean;
37
- /**
38
- * WebTransport server URL (e.g., "https://wt.api.example.com").
39
- * When provided, AdaptiveRpcTransport is used with WebTransport as primary
40
- * and gRPC-web as fallback. Omit to use gRPC-web only.
41
- */
42
- webTransportUrl?: string;
43
- /**
44
- * SHA-256 certificate hash (hex string) for dev self-signed WebTransport certs.
45
- * Only needed when connecting to servers with self-signed certificates.
46
- */
47
- webTransportCertHash?: string;
48
- /**
49
- * Delay in milliseconds before attempting to reconnect WebTransport after failover.
50
- * Defaults to 5000ms.
51
- */
52
- webTransportReconnectDelay?: number;
53
- /**
54
- * Callback fired when the active transport changes between "webtransport" and "grpc-web".
55
- * Use this to show transport status indicators in the UI.
56
- */
57
- onTransportChange?: (transport: "webtransport" | "grpc-web") => void;
58
32
  }