@flow-industries/id 0.12.0 → 0.13.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.
@@ -151,6 +151,22 @@ export function createFlow(options = {}) {
151
151
  return false;
152
152
  }
153
153
  }
154
+ /**
155
+ * One-time delivery of any additional-audience sessions a mint returned
156
+ * (AUTH-39). Handed straight to the consumer's callback and dropped — the
157
+ * SDK retains nothing; the consumer forwards each to its audience's own
158
+ * origin (the embed bootstrap) to be sealed HttpOnly there.
159
+ */
160
+ function deliverAdditionalSessions(sessions) {
161
+ if (!sessions?.length || !options.onAdditionalSessions)
162
+ return;
163
+ try {
164
+ options.onAdditionalSessions(sessions);
165
+ }
166
+ catch (err) {
167
+ console.warn("Flow ID: onAdditionalSessions handler threw", err);
168
+ }
169
+ }
154
170
  /**
155
171
  * One-time handoff of a freshly minted session (dialog login or guest
156
172
  * mint) to the app's server, which verifies the JWT against the issuer's
@@ -247,7 +263,9 @@ export function createFlow(options = {}) {
247
263
  if (await doRefresh())
248
264
  return true;
249
265
  try {
250
- const result = await getDialog().requestSilent(METHODS.guest, []);
266
+ const result = await getDialog().requestSilent(METHODS.guest, options.additionalAudiences?.length
267
+ ? [{ additionalAudiences: options.additionalAudiences }]
268
+ : []);
251
269
  if (!result.user)
252
270
  return false;
253
271
  store.setState({
@@ -259,6 +277,7 @@ export function createFlow(options = {}) {
259
277
  ...(result.user.isGuest ? { credential: null, address: null } : {}),
260
278
  });
261
279
  await installSession(result.refreshToken, result.jwt);
280
+ deliverAdditionalSessions(result.additionalSessions);
262
281
  return true;
263
282
  }
264
283
  catch {
@@ -336,13 +355,20 @@ export function createFlow(options = {}) {
336
355
  extraCapabilities = { ...(extraCapabilities ?? {}), guestToken };
337
356
  }
338
357
  }
339
- const { session, webauthn, refreshToken } = await runLogin({
358
+ if (options.additionalAudiences?.length) {
359
+ extraCapabilities = {
360
+ ...(extraCapabilities ?? {}),
361
+ additionalAudiences: options.additionalAudiences,
362
+ };
363
+ }
364
+ const { session, webauthn, refreshToken, additionalSessions } = await runLogin({
340
365
  dialog: dialogHost,
341
366
  store,
342
367
  options: loginOpts,
343
368
  ...(extraCapabilities ? { extraCapabilities } : {}),
344
369
  });
345
370
  await installSession(refreshToken, session.jwt);
371
+ deliverAdditionalSessions(additionalSessions);
346
372
  if (accessKeyModule && accessKeyPrep && webauthn) {
347
373
  await accessKeyModule.finalizeAccessKey({
348
374
  address: session.address,
@@ -1,5 +1,5 @@
1
1
  export { defaultIdHost, isLocalHostname } from "../id-host";
2
- export type { AccessKeyOptions, Address, ConnectCapabilities, ConnectResponse, CreateFlowOptions, DialogHost, Flow, FlowCredential, FlowSessionState, FlowState, FlowUser, LoginOptions, MethodName, MountProfileOptions, ProfileButtonHandle, ProfilePosition, Session, } from "../types";
2
+ export type { AccessKeyOptions, AdditionalSession, Address, ConnectCapabilities, ConnectResponse, CreateFlowOptions, DialogHost, Flow, FlowCredential, FlowSessionState, FlowState, FlowUser, LoginOptions, MethodName, MountProfileOptions, ProfileButtonHandle, ProfilePosition, Session, } from "../types";
3
3
  export { createFlow, getFlow, requireFlow, resetFlow } from "./create-flow";
4
4
  export { createDialogHost } from "./dialog-host";
5
5
  export { METHODS } from "./methods";
@@ -40,7 +40,7 @@ export async function runLogin(params) {
40
40
  const result = (await dialog.request(METHODS.connect, [
41
41
  { capabilities },
42
42
  ]));
43
- const { jwt, refreshToken, user, credential, webauthn } = result;
43
+ const { jwt, refreshToken, user, credential, webauthn, additionalSessions } = result;
44
44
  const address = credentialToAddress(credential);
45
45
  await idb.set(CREDENTIAL_KEY, credential);
46
46
  store.setState({ user, jwt, credential, address });
@@ -48,6 +48,7 @@ export async function runLogin(params) {
48
48
  session: { user, jwt, credential, address },
49
49
  refreshToken,
50
50
  webauthn,
51
+ ...(additionalSessions ? { additionalSessions } : {}),
51
52
  };
52
53
  }
53
54
  /**
@@ -21,7 +21,7 @@
21
21
  * into the server bundle there; everything it does lives here.
22
22
  */
23
23
  import { type ReactNode } from "react";
24
- import type { FlowSessionState } from "../types";
24
+ import type { AdditionalSession, FlowSessionState } from "../types";
25
25
  /**
26
26
  * The `beforeLoad` body: during SSR resolve the session server-side (via the
27
27
  * app's `getFlowSession` server fn, which appends rotated Set-Cookie values);
@@ -37,6 +37,10 @@ export type FlowRootProps = {
37
37
  autoGuest?: boolean;
38
38
  /** Override the Flow ID origin; defaults per `defaultIdHost`. */
39
39
  host?: string;
40
+ /** Extra audiences every mint requests (see `CreateFlowOptions`). */
41
+ additionalAudiences?: readonly string[];
42
+ /** One-time delivery of the extra sessions (see `CreateFlowOptions`). */
43
+ onAdditionalSessions?: (sessions: AdditionalSession[]) => void;
40
44
  children: ReactNode;
41
45
  };
42
46
  /**
@@ -45,4 +49,4 @@ export type FlowRootProps = {
45
49
  * matches the SSR HTML and no bootstrap refresh runs while the hydrated JWT
46
50
  * is fresh.
47
51
  */
48
- export declare function FlowRoot({ session, autoGuest, host, children, }: FlowRootProps): import("react/jsx-runtime").JSX.Element;
52
+ export declare function FlowRoot({ session, autoGuest, host, additionalAudiences, onAdditionalSessions, children, }: FlowRootProps): import("react/jsx-runtime").JSX.Element;
@@ -44,9 +44,15 @@ export async function flowSessionContext(getSession) {
44
44
  * matches the SSR HTML and no bootstrap refresh runs while the hydrated JWT
45
45
  * is fresh.
46
46
  */
47
- export function FlowRoot({ session, autoGuest, host, children, }) {
47
+ export function FlowRoot({ session, autoGuest, host, additionalAudiences, onAdditionalSessions, children, }) {
48
48
  const flow = useMemo(() => typeof window === "undefined"
49
49
  ? createStaticFlow(session, { host })
50
- : createFlow({ initialState: session, autoGuest, host }), [session, autoGuest, host]);
50
+ : createFlow({
51
+ initialState: session,
52
+ autoGuest,
53
+ host,
54
+ additionalAudiences,
55
+ onAdditionalSessions,
56
+ }), [session, autoGuest, host, additionalAudiences, onAdditionalSessions]);
51
57
  return _jsx(FlowIdProvider, { flow: flow, children: children });
52
58
  }
@@ -14,11 +14,24 @@ export type FlowCredential = {
14
14
  id: string;
15
15
  publicKey: string;
16
16
  };
17
+ /**
18
+ * A session minted for one of the ADDITIONAL audiences a login requested
19
+ * (AUTH-39): its own refresh-token lineage and access JWT, both bound to
20
+ * `audience`. Installed once into that origin's own HttpOnly cookies via its
21
+ * `/flow/session` route (the embed bootstrap) — never held by page JS beyond
22
+ * the one-time handoff.
23
+ */
24
+ export type AdditionalSession = {
25
+ audience: string;
26
+ jwt: string;
27
+ refreshToken: string;
28
+ };
17
29
  export type AuthResponse = {
18
30
  jwt: string;
19
31
  refreshToken: string;
20
32
  user: FlowUser;
21
33
  credential: FlowCredential;
34
+ additionalSessions?: AdditionalSession[];
22
35
  };
23
36
  export type AuthConfig = {
24
37
  rpId: string;
@@ -8,7 +8,7 @@ export type AuthOutcome = "success" | "failure" | "info";
8
8
  export type AuthMode = "sign-up" | "sign-in";
9
9
  /** Client-only funnel steps reported via the `/api/events` beacon. */
10
10
  export type FunnelStep = "mode_selected" | "email_entered" | "ceremony_started" | "done_shown";
11
- export type AuthEventName = "auth.username.checked" | "auth.signup.succeeded" | "auth.signup.failed" | "auth.otp.sent" | "auth.otp.verified" | "auth.otp.failed" | "auth.email.verify.sent" | "auth.email.verify.succeeded" | "auth.email.verify.failed" | "auth.email.changed" | "auth.avatar.upload.succeeded" | "auth.avatar.upload.failed" | "auth.challenge.issued" | "auth.signin.succeeded" | "auth.signin.failed" | "auth.restore.succeeded" | "auth.restore.failed" | "auth.restore.no_session" | "auth.refresh.succeeded" | "auth.refresh.failed" | "auth.refresh.reuse" | "auth.refresh.grace" | "auth.jwt.verified" | "auth.jwt.rejected" | "auth.signout" | "auth.audience.rejected" | "auth.guest.created" | "auth.guest.restored" | "auth.guest.upgraded" | "auth.guest.failed" | "auth.funnel.mode_selected" | "auth.funnel.email_entered" | "auth.funnel.ceremony_started" | "auth.funnel.done_shown";
11
+ export type AuthEventName = "auth.username.checked" | "auth.signup.succeeded" | "auth.signup.failed" | "auth.otp.sent" | "auth.otp.verified" | "auth.otp.failed" | "auth.email.verify.sent" | "auth.email.verify.succeeded" | "auth.email.verify.failed" | "auth.email.changed" | "auth.avatar.upload.succeeded" | "auth.avatar.upload.failed" | "auth.challenge.issued" | "auth.signin.succeeded" | "auth.signin.failed" | "auth.restore.succeeded" | "auth.restore.failed" | "auth.restore.no_session" | "auth.refresh.succeeded" | "auth.refresh.failed" | "auth.refresh.reuse" | "auth.refresh.grace" | "auth.jwt.verified" | "auth.jwt.rejected" | "auth.signout" | "auth.audience.rejected" | "auth.audience.minted" | "auth.guest.created" | "auth.guest.restored" | "auth.guest.upgraded" | "auth.guest.failed" | "auth.funnel.mode_selected" | "auth.funnel.email_entered" | "auth.funnel.ceremony_started" | "auth.funnel.done_shown";
12
12
  export type AuthErrorCode = "username_taken" | "credential_taken" | "email_taken" | "email_not_verified" | "no_email" | "image_upload_forbidden" | "image_upload_rate_limited" | "invalid_image_type" | "image_too_large" | "image_upload_failed" | "otp_invalid" | "otp_expired" | "otp_attempts_exceeded" | "otp_resend_cooldown" | "otp_resend_limit" | "otp_global_limit" | "otp_send_failed" | "challenge_expired" | "unknown_credential" | "invalid_assertion_type" | "invalid_assertion_origin" | "user_verification_required" | "invalid_signature" | "user_not_found" | "missing_audience" | "audience_not_allowed" | "audience_mismatch" | "no_session" | "no_passkey" | "refresh_token_invalid" | "refresh_token_expired" | "refresh_reuse_detected" | "guest_rate_limited" | "guest_global_limit" | "guest_username_exhausted" | "already_upgraded" | "malformed_token" | "unknown_key" | "verification_failed" | "internal_error";
13
13
  /** One flat record per event = one row in the `auth_events` stream. */
14
14
  export interface AuthEventRecord {
@@ -1,4 +1,4 @@
1
- export type { AuthConfig, AuthResponse, AuthResponseWithWebAuthn, FlowCredential, FlowUser, PasskeyPluginOptions, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
1
+ export type { AdditionalSession, AuthConfig, AuthResponse, AuthResponseWithWebAuthn, FlowCredential, FlowUser, PasskeyPluginOptions, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
2
2
  export type { BoundaryError, DialogCustomFeatures, DialogCustomLabels, DialogError, DialogReferrer, DialogState, ProfileData, ProfileIdentity, } from "./dialog";
3
3
  export type { AuthErrorCode, AuthEventName, AuthEventRecord, AuthMode, AuthOutcome, BeaconBody, FunnelStep, } from "./events";
4
4
  export type { Bridge, BridgeParameters, FlowAccount, FlowRemote, FlowRemoteConfig, FromWindowOptions, MessageResponse, Messenger, OneOf, Payload, QueuedRequest, ReadyOptions, RemoteFlowState, RemoteState, Schema, Storage, Topic, WithReady, } from "./messenger";
@@ -1,4 +1,4 @@
1
- import type { FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
1
+ import type { AdditionalSession, FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
2
2
  import type { Address } from "./sdk";
3
3
  export type MethodName = "wallet_connect" | "personal_sign" | "eth_signTypedData" | "eth_sendTransaction" | "wallet_sendCalls" | "wallet_guest" | "wallet_signout";
4
4
  export type ConnectCapabilities = {
@@ -14,6 +14,12 @@ export type ConnectCapabilities = {
14
14
  * cookie session; see the register endpoint.
15
15
  */
16
16
  guestToken?: string;
17
+ /**
18
+ * Extra audiences to mint refresh tokens for in the same login (AUTH-39).
19
+ * The server refuses any entry outside its explicit origin -> audiences
20
+ * allowlist; allowed entries come back as `additionalSessions`.
21
+ */
22
+ additionalAudiences?: readonly string[];
17
23
  };
18
24
  export type ConnectRequest = [{
19
25
  capabilities?: ConnectCapabilities;
@@ -25,6 +31,7 @@ export type ConnectResponse = {
25
31
  user: FlowUser;
26
32
  credential: FlowCredential;
27
33
  webauthn?: WebAuthnSignature;
34
+ additionalSessions?: AdditionalSession[];
28
35
  };
29
36
  export type SignMessageRequest = [message: string, address: string];
30
37
  export type SignMessageResponse = `0x${string}`;
@@ -74,7 +81,9 @@ export type RestoreResponse = {
74
81
  credential: FlowCredential | null;
75
82
  address: Address | null;
76
83
  };
77
- export type GuestRequest = [];
84
+ export type GuestRequest = [] | [{
85
+ additionalAudiences?: readonly string[];
86
+ }];
78
87
  /**
79
88
  * A guest has no passkey and no derivable wallet address yet, so `credential`
80
89
  * and `address` are always null — the shape mirrors RestoreResponse so the SDK
@@ -86,6 +95,7 @@ export type GuestResponse = {
86
95
  user: FlowUser;
87
96
  credential: null;
88
97
  address: null;
98
+ additionalSessions?: AdditionalSession[];
89
99
  };
90
100
  export type SignOutRequest = [];
91
101
  export type SignOutResponse = {
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Chain, Hex, PrepareTransactionRequestParameters, SendTransactionParameters, SignTypedDataParameters, Transport, WalletClient } from "viem";
3
3
  import type { SendCallsParameters } from "viem/actions";
4
- import type { FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
4
+ import type { AdditionalSession, FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
5
5
  import type { ConnectCapabilities, ConnectResponse } from "./protocol";
6
6
  import type { RoomsApi } from "./rooms";
7
7
  export type Address = `0x${string}`;
@@ -88,6 +88,22 @@ export type CreateFlowOptions = {
88
88
  * bootstrap.
89
89
  */
90
90
  initialState?: FlowSessionState | null;
91
+ /**
92
+ * Extra audiences every dialog mint (login, guest) requests refresh tokens
93
+ * for, on top of the app's own (AUTH-39). Only audiences the auth server
94
+ * explicitly allowlists for this origin succeed — anything else fails the
95
+ * mint. The minted sessions are delivered once via `onAdditionalSessions`
96
+ * and never stored; the app forwards each to its own origin (e.g. the
97
+ * same-site chat embed's `/flow/session`) to be sealed HttpOnly.
98
+ */
99
+ additionalAudiences?: readonly string[];
100
+ /**
101
+ * One-time delivery of the additional-audience sessions minted at login or
102
+ * guest creation. Fired after the app's own session installs; the receiver
103
+ * must hand each session to its audience's origin (the embed bootstrap) and
104
+ * drop it — nothing here is retained by the SDK.
105
+ */
106
+ onAdditionalSessions?: (sessions: AdditionalSession[]) => void;
91
107
  };
92
108
  export type FlowState = {
93
109
  user: FlowUser | null;
@@ -297,6 +313,7 @@ export type RunLoginResult = {
297
313
  session: Session;
298
314
  refreshToken: string;
299
315
  webauthn: ConnectResponse["webauthn"];
316
+ additionalSessions?: AdditionalSession[];
300
317
  };
301
318
  export type FinalizeAccessKeyParams = {
302
319
  address: Address;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow-industries/id",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "main": "./dist/sdk/client/index.js",
5
5
  "module": "./dist/sdk/client/index.js",
6
6
  "types": "./dist/sdk/client/index.d.ts",