@insureco/bio 0.5.0 → 0.8.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/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { B as BioAuthConfig, A as AuthorizeOptions, a as AuthorizeResult, T as TokenResponse, b as BioUser, I as IntrospectResult, c as BioAdminConfig, U as UserFilters, d as UpdateUserData, e as BioDepartment, C as CreateDepartmentData, f as BioRole, g as CreateRoleData, h as BioOAuthClient, i as CreateClientData, j as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-Dkb-drHZ.js';
2
- export { k as AdminResponse, l as BioAddress, m as BioClientTokenPayload, n as BioMessaging, o as BioUsersConfig, O as OrgMember, p as OrgMemberFilters, q as OrgMembersResult } from './types-Dkb-drHZ.js';
3
- export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, CarrierProfile, CarrierProgram, GraphClient, GraphClientConfig, ProgramProfile } from './graph.js';
1
+ import { b as BioAuthConfig, A as AuthorizeOptions, c as AuthorizeResult, S as SilentAuthOptions, T as TokenResponse, d as BioUser, I as IntrospectResult, e as BioAdminConfig, U as UserFilters, f as UpdateUserData, g as BioDepartment, C as CreateDepartmentData, h as BioRole, i as CreateRoleData, j as BioOAuthClient, k as CreateClientData, E as EmbedClientConfig, l as EmbedLoginParams, m as EmbedAuthResult, n as EmbedSignupParams, o as EmbedMagicLinkParams, p as EmbedVerifyParams, q as EmbedRefreshParams, r as EmbedLogoutParams, s as BioTokenPayload, V as VerifyOptions, J as JWKSVerifyOptions } from './types-DOpXwdF2.js';
2
+ export { t as AdminResponse, u as BioAddress, v as BioClientTokenPayload, w as BioMessaging, B as BioUsersConfig, x as EmbedBranding, y as EmbedUser, z as OrgMember, O as OrgMemberFilters, a as OrgMembersResult, D as ServiceRole } from './types-DOpXwdF2.js';
3
+ export { AgencyProfile, AgencyProgram, AgencyStaffMember, AgentEmployer, AgentProfile, AppetiteMatchInput, AppetiteMatchProgram, AppetiteMatchResult, AppointmentVerifyResult, CarrierProfile, CarrierProgram, GraphChainEdge, GraphChainNode, GraphChainResult, GraphClient, GraphClientConfig, GraphSearchOptions, GraphSearchResult, GraphSearchResultItem, LicenseVerifyResult, ProgramProfile, VerifyAppointmentInput, VerifyLicenseInput } from './graph.js';
4
+ export { PassportClient } from './passport.js';
5
+ export { P as Passport, b as PassportBranding, c as PassportClientConfig, d as PassportCrossOrgPermission, e as PassportEventType, f as PassportMessage, g as PassportServiceGrant, h as PassportSession, a as PassportStatus, i as PassportTokenRefresher, j as PassportVillage } from './passport-types-bPgjNxv-.js';
4
6
 
5
7
  /**
6
8
  * OAuth flow client for Bio-ID SSO.
@@ -28,6 +30,14 @@ declare class BioAuth {
28
30
  * Store the state and codeVerifier securely (e.g. in a cookie) for the callback.
29
31
  */
30
32
  getAuthorizationUrl(opts: AuthorizeOptions): AuthorizeResult;
33
+ /**
34
+ * Build an authorization URL with prompt=none for silent authentication.
35
+ *
36
+ * Useful for checking if the user has an existing session without showing
37
+ * a login screen. If the user is not authenticated, Bio-ID redirects back
38
+ * with an `error=login_required` query parameter instead of showing UI.
39
+ */
40
+ silentAuth(opts: SilentAuthOptions): AuthorizeResult;
31
41
  /**
32
42
  * Exchange an authorization code for tokens.
33
43
  *
@@ -105,6 +115,91 @@ declare class BioAdmin {
105
115
  private request;
106
116
  }
107
117
 
118
+ /**
119
+ * Client for Bio-ID headless embed endpoints.
120
+ *
121
+ * Use this when building custom login/signup UIs that authenticate
122
+ * directly against Bio-ID without redirects (no OAuth flow).
123
+ *
124
+ * All endpoints use X-Client-Id / X-Client-Secret header auth.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * import { EmbedClient } from '@insureco/bio'
129
+ *
130
+ * const embed = EmbedClient.fromEnv()
131
+ *
132
+ * // Login
133
+ * const result = await embed.login({ email: 'user@example.com', password: 'secret' })
134
+ * console.log(result.accessToken, result.user.bioId)
135
+ *
136
+ * // Refresh
137
+ * const refreshed = await embed.refresh({ refreshToken: result.refreshToken })
138
+ *
139
+ * // Logout
140
+ * await embed.logout({ refreshToken: result.refreshToken })
141
+ * ```
142
+ */
143
+ declare class EmbedClient {
144
+ private readonly bioIdUrl;
145
+ private readonly clientId;
146
+ private readonly clientSecret;
147
+ private readonly retries;
148
+ private readonly timeoutMs;
149
+ constructor(config: EmbedClientConfig);
150
+ /**
151
+ * Create an EmbedClient from environment variables.
152
+ *
153
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
154
+ */
155
+ static fromEnv(overrides?: Partial<EmbedClientConfig>): EmbedClient;
156
+ /**
157
+ * Authenticate a user with email and password.
158
+ *
159
+ * @param params - Email and password
160
+ * @returns Access token, refresh token, user profile, and optional branding
161
+ */
162
+ login(params: EmbedLoginParams): Promise<EmbedAuthResult>;
163
+ /**
164
+ * Create a new user account.
165
+ *
166
+ * @param params - Email, password, name, and optional invite token
167
+ * @returns Access token, refresh token, user profile, and optional branding
168
+ */
169
+ signup(params: EmbedSignupParams): Promise<EmbedAuthResult>;
170
+ /**
171
+ * Send a magic link email to the user.
172
+ *
173
+ * The user clicks the link to authenticate without a password.
174
+ * After sending, use `verify()` with the token from the link.
175
+ *
176
+ * @param params - Email address to send the magic link to
177
+ */
178
+ sendMagicLink(params: EmbedMagicLinkParams): Promise<void>;
179
+ /**
180
+ * Verify a magic link token and exchange it for auth tokens.
181
+ *
182
+ * @param params - The token from the magic link
183
+ * @returns Access token, refresh token, user profile, and optional branding
184
+ */
185
+ verify(params: EmbedVerifyParams): Promise<EmbedAuthResult>;
186
+ /**
187
+ * Refresh an expired access token using a refresh token.
188
+ *
189
+ * @param params - The refresh token to exchange
190
+ * @returns New access token, rotated refresh token, user profile, and optional branding
191
+ */
192
+ refresh(params: EmbedRefreshParams): Promise<EmbedAuthResult>;
193
+ /**
194
+ * Revoke a refresh token (logout).
195
+ *
196
+ * @param params - The refresh token to revoke
197
+ */
198
+ logout(params: EmbedLogoutParams): Promise<void>;
199
+ private embedRequest;
200
+ private fetchWithRetry;
201
+ }
202
+
108
203
  /** Error thrown by Bio SDK operations */
109
204
  declare class BioError extends Error {
110
205
  /** HTTP status code (if from an API response) */
@@ -155,4 +250,4 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean;
155
250
  */
156
251
  declare function verifyTokenJWKS(token: string, options?: JWKSVerifyOptions): Promise<BioTokenPayload>;
157
252
 
158
- export { AuthorizeOptions, AuthorizeResult, BioAdmin, BioAdminConfig, BioAuth, BioAuthConfig, BioDepartment, BioError, BioOAuthClient, BioRole, BioTokenPayload, BioUser, CreateClientData, CreateDepartmentData, CreateRoleData, IntrospectResult, JWKSVerifyOptions, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
253
+ export { AuthorizeOptions, AuthorizeResult, BioAdmin, BioAdminConfig, BioAuth, BioAuthConfig, BioDepartment, BioError, BioOAuthClient, BioRole, BioTokenPayload, BioUser, CreateClientData, CreateDepartmentData, CreateRoleData, EmbedAuthResult, EmbedClient, EmbedClientConfig, EmbedLoginParams, EmbedLogoutParams, EmbedMagicLinkParams, EmbedRefreshParams, EmbedSignupParams, EmbedVerifyParams, IntrospectResult, JWKSVerifyOptions, SilentAuthOptions, TokenResponse, UpdateUserData, UserFilters, VerifyOptions, decodeToken, generatePKCE, isTokenExpired, verifyToken, verifyTokenJWKS };
package/dist/index.js CHANGED
@@ -33,7 +33,9 @@ __export(index_exports, {
33
33
  BioAdmin: () => BioAdmin,
34
34
  BioAuth: () => BioAuth,
35
35
  BioError: () => BioError,
36
+ EmbedClient: () => EmbedClient,
36
37
  GraphClient: () => GraphClient,
38
+ PassportClient: () => PassportClient,
37
39
  decodeToken: () => decodeToken,
38
40
  generatePKCE: () => generatePKCE,
39
41
  isTokenExpired: () => isTokenExpired,
@@ -171,6 +173,23 @@ var BioAuth = class _BioAuth {
171
173
  codeChallenge
172
174
  };
173
175
  }
176
+ /**
177
+ * Build an authorization URL with prompt=none for silent authentication.
178
+ *
179
+ * Useful for checking if the user has an existing session without showing
180
+ * a login screen. If the user is not authenticated, Bio-ID redirects back
181
+ * with an `error=login_required` query parameter instead of showing UI.
182
+ */
183
+ silentAuth(opts) {
184
+ const result = this.getAuthorizationUrl({
185
+ redirectUri: opts.redirectUri,
186
+ scopes: opts.scopes,
187
+ state: opts.state
188
+ });
189
+ const url = new URL(result.url);
190
+ url.searchParams.set("prompt", "none");
191
+ return { ...result, url: url.toString() };
192
+ }
174
193
  /**
175
194
  * Exchange an authorization code for tokens.
176
195
  *
@@ -586,6 +605,257 @@ var BioAdmin = class _BioAdmin {
586
605
  }
587
606
  };
588
607
 
608
+ // src/embed.ts
609
+ var DEFAULT_BIO_URL = "https://bio.tawa.pro";
610
+ var DEFAULT_TIMEOUT_MS3 = 1e4;
611
+ var EmbedClient = class _EmbedClient {
612
+ bioIdUrl;
613
+ clientId;
614
+ clientSecret;
615
+ retries;
616
+ timeoutMs;
617
+ constructor(config) {
618
+ if (!config.clientId) {
619
+ throw new BioError("clientId is required", "config_error");
620
+ }
621
+ if (!config.clientSecret) {
622
+ throw new BioError("clientSecret is required", "config_error");
623
+ }
624
+ this.clientId = config.clientId;
625
+ this.clientSecret = config.clientSecret;
626
+ this.bioIdUrl = (config.bioIdUrl ?? DEFAULT_BIO_URL).replace(/\/$/, "");
627
+ this.retries = config.retries ?? 2;
628
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
629
+ }
630
+ /**
631
+ * Create an EmbedClient from environment variables.
632
+ *
633
+ * Reads: BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL
634
+ */
635
+ static fromEnv(overrides) {
636
+ const clientId = overrides?.clientId ?? process.env.BIO_CLIENT_ID;
637
+ const clientSecret = overrides?.clientSecret ?? process.env.BIO_CLIENT_SECRET;
638
+ if (!clientId) {
639
+ throw new BioError(
640
+ "BIO_CLIENT_ID environment variable is required",
641
+ "config_error"
642
+ );
643
+ }
644
+ if (!clientSecret) {
645
+ throw new BioError(
646
+ "BIO_CLIENT_SECRET environment variable is required",
647
+ "config_error"
648
+ );
649
+ }
650
+ return new _EmbedClient({
651
+ clientId,
652
+ clientSecret,
653
+ bioIdUrl: overrides?.bioIdUrl ?? process.env.BIO_ID_URL,
654
+ retries: overrides?.retries,
655
+ timeoutMs: overrides?.timeoutMs
656
+ });
657
+ }
658
+ /**
659
+ * Authenticate a user with email and password.
660
+ *
661
+ * @param params - Email and password
662
+ * @returns Access token, refresh token, user profile, and optional branding
663
+ */
664
+ async login(params) {
665
+ if (!params.email) throw new BioError("email is required", "validation_error");
666
+ if (!params.password) throw new BioError("password is required", "validation_error");
667
+ return this.embedRequest("/api/embed/login", {
668
+ email: params.email,
669
+ password: params.password
670
+ });
671
+ }
672
+ /**
673
+ * Create a new user account.
674
+ *
675
+ * @param params - Email, password, name, and optional invite token
676
+ * @returns Access token, refresh token, user profile, and optional branding
677
+ */
678
+ async signup(params) {
679
+ if (!params.email) throw new BioError("email is required", "validation_error");
680
+ if (!params.password) throw new BioError("password is required", "validation_error");
681
+ if (!params.name) throw new BioError("name is required", "validation_error");
682
+ const body = {
683
+ email: params.email,
684
+ password: params.password,
685
+ name: params.name
686
+ };
687
+ if (params.inviteToken) {
688
+ body.inviteToken = params.inviteToken;
689
+ }
690
+ return this.embedRequest("/api/embed/signup", body);
691
+ }
692
+ /**
693
+ * Send a magic link email to the user.
694
+ *
695
+ * The user clicks the link to authenticate without a password.
696
+ * After sending, use `verify()` with the token from the link.
697
+ *
698
+ * @param params - Email address to send the magic link to
699
+ */
700
+ async sendMagicLink(params) {
701
+ if (!params.email) throw new BioError("email is required", "validation_error");
702
+ const response = await this.fetchWithRetry(
703
+ "POST",
704
+ `${this.bioIdUrl}/api/embed/magic-link`,
705
+ JSON.stringify({ email: params.email })
706
+ );
707
+ const json = await parseJsonResponse(response);
708
+ if (!response.ok) {
709
+ throw new BioError(
710
+ extractErrorMessage(json, response.status),
711
+ extractErrorCode(json),
712
+ response.status,
713
+ json
714
+ );
715
+ }
716
+ }
717
+ /**
718
+ * Verify a magic link token and exchange it for auth tokens.
719
+ *
720
+ * @param params - The token from the magic link
721
+ * @returns Access token, refresh token, user profile, and optional branding
722
+ */
723
+ async verify(params) {
724
+ if (!params.token) throw new BioError("token is required", "validation_error");
725
+ return this.embedRequest("/api/embed/verify", {
726
+ token: params.token
727
+ });
728
+ }
729
+ /**
730
+ * Refresh an expired access token using a refresh token.
731
+ *
732
+ * @param params - The refresh token to exchange
733
+ * @returns New access token, rotated refresh token, user profile, and optional branding
734
+ */
735
+ async refresh(params) {
736
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
737
+ return this.embedRequest("/api/embed/refresh", {
738
+ refreshToken: params.refreshToken
739
+ });
740
+ }
741
+ /**
742
+ * Revoke a refresh token (logout).
743
+ *
744
+ * @param params - The refresh token to revoke
745
+ */
746
+ async logout(params) {
747
+ if (!params.refreshToken) throw new BioError("refreshToken is required", "validation_error");
748
+ const response = await this.fetchWithRetry(
749
+ "POST",
750
+ `${this.bioIdUrl}/api/embed/logout`,
751
+ JSON.stringify({ refreshToken: params.refreshToken })
752
+ );
753
+ const json = await parseJsonResponse(response);
754
+ if (!response.ok) {
755
+ throw new BioError(
756
+ extractErrorMessage(json, response.status),
757
+ extractErrorCode(json),
758
+ response.status,
759
+ json
760
+ );
761
+ }
762
+ }
763
+ // ── Private helpers ──────────────────────────────────────────────────────
764
+ async embedRequest(path, body) {
765
+ const response = await this.fetchWithRetry(
766
+ "POST",
767
+ `${this.bioIdUrl}${path}`,
768
+ JSON.stringify(body)
769
+ );
770
+ const json = await parseJsonResponse(response);
771
+ if (!response.ok) {
772
+ throw new BioError(
773
+ extractErrorMessage(json, response.status),
774
+ extractErrorCode(json),
775
+ response.status,
776
+ json
777
+ );
778
+ }
779
+ return mapEmbedResponse(json);
780
+ }
781
+ async fetchWithRetry(method, url, body, attempt = 0) {
782
+ try {
783
+ const response = await fetch(url, {
784
+ method,
785
+ headers: {
786
+ "Content-Type": "application/json",
787
+ "X-Client-Id": this.clientId,
788
+ "X-Client-Secret": this.clientSecret
789
+ },
790
+ body,
791
+ signal: AbortSignal.timeout(this.timeoutMs)
792
+ });
793
+ if (response.status >= 500 && attempt < this.retries) {
794
+ await sleep(retryDelay(attempt));
795
+ return this.fetchWithRetry(method, url, body, attempt + 1);
796
+ }
797
+ return response;
798
+ } catch (err) {
799
+ if (attempt < this.retries) {
800
+ await sleep(retryDelay(attempt));
801
+ return this.fetchWithRetry(method, url, body, attempt + 1);
802
+ }
803
+ const isTimeout = err instanceof DOMException && err.name === "TimeoutError";
804
+ throw new BioError(
805
+ isTimeout ? `Request timed out after ${this.timeoutMs}ms` : err instanceof Error ? err.message : "Network error",
806
+ isTimeout ? "timeout" : "network_error"
807
+ );
808
+ }
809
+ }
810
+ };
811
+ function mapEmbedResponse(raw) {
812
+ const data = raw.data ?? raw;
813
+ const rawUser = data.user ?? {};
814
+ const rawBranding = data.branding;
815
+ const user = {
816
+ bioId: rawUser.bioId,
817
+ email: rawUser.email,
818
+ name: rawUser.name,
819
+ orgSlug: rawUser.orgSlug
820
+ };
821
+ const result = {
822
+ accessToken: data.access_token,
823
+ refreshToken: data.refresh_token,
824
+ tokenType: data.token_type ?? "Bearer",
825
+ expiresIn: data.expires_in,
826
+ user
827
+ };
828
+ if (rawBranding) {
829
+ result.branding = {
830
+ displayName: rawBranding.displayName,
831
+ logoUrl: rawBranding.logoUrl,
832
+ logoMarkUrl: rawBranding.logoMarkUrl,
833
+ primaryColor: rawBranding.primaryColor,
834
+ secondaryColor: rawBranding.secondaryColor,
835
+ verified: rawBranding.verified,
836
+ whiteLabelApproved: rawBranding.whiteLabelApproved
837
+ };
838
+ }
839
+ return result;
840
+ }
841
+ function extractErrorMessage(json, status) {
842
+ const error = json.error;
843
+ if (typeof error === "object" && error !== null) {
844
+ return error.message ?? `Embed API returned ${status}`;
845
+ }
846
+ if (typeof error === "string") {
847
+ return error;
848
+ }
849
+ return `Embed API returned ${status}`;
850
+ }
851
+ function extractErrorCode(json) {
852
+ const error = json.error;
853
+ if (typeof error === "object" && error !== null) {
854
+ return error.code ?? "embed_error";
855
+ }
856
+ return "embed_error";
857
+ }
858
+
589
859
  // src/jwt.ts
590
860
  var import_node_crypto3 = __toESM(require("crypto"));
591
861
  var DEFAULT_ISSUERS = [
@@ -740,7 +1010,7 @@ async function verifyTokenJWKS(token, options) {
740
1010
  }
741
1011
 
742
1012
  // src/graph.ts
743
- var DEFAULT_TIMEOUT_MS3 = 1e4;
1013
+ var DEFAULT_TIMEOUT_MS4 = 1e4;
744
1014
  var BIO_GRAPH_URL = "https://bio-graph.tawa.pro";
745
1015
  var GraphClient = class _GraphClient {
746
1016
  graphUrl;
@@ -749,7 +1019,7 @@ var GraphClient = class _GraphClient {
749
1019
  constructor(config = {}) {
750
1020
  this.graphUrl = (config.graphUrl ?? process.env.BIO_GRAPH_URL ?? BIO_GRAPH_URL).replace(/\/$/, "");
751
1021
  this.accessToken = config.accessToken;
752
- this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
1022
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
753
1023
  }
754
1024
  /**
755
1025
  * Create a GraphClient from environment variables.
@@ -798,7 +1068,48 @@ var GraphClient = class _GraphClient {
798
1068
  async matchAppetite(input) {
799
1069
  return this.post("/api/graph/appetite/match", input, { requiresAuth: true });
800
1070
  }
1071
+ // ─── Verification Routes (auth required, gas-metered) ─────────────────────
1072
+ /**
1073
+ * Verify an agent's license for a given state and line of business.
1074
+ * Returns validity, license number, expiry, and a Septor proof hash.
1075
+ */
1076
+ async verifyLicense(input) {
1077
+ return this.post("/api/graph/verify/license", input, { requiresAuth: true });
1078
+ }
1079
+ /**
1080
+ * Verify an agent's appointment with a carrier.
1081
+ * Returns validity, appointed date, states, and a Septor proof hash.
1082
+ */
1083
+ async verifyAppointment(input) {
1084
+ return this.post("/api/graph/verify/appointment", input, { requiresAuth: true });
1085
+ }
1086
+ /**
1087
+ * Get the distribution chain for an entity (agent → agency → MGA → carrier).
1088
+ * Requires an accessToken (auth: required).
1089
+ */
1090
+ async getChain(entityId) {
1091
+ return this.getAuth(`/api/graph/chain/${encodeURIComponent(entityId)}`);
1092
+ }
1093
+ /**
1094
+ * Search across Agent, Agency, MGA, and Carrier nodes.
1095
+ * Public route — no auth required.
1096
+ */
1097
+ async search(query, options) {
1098
+ const params = new URLSearchParams({ q: query });
1099
+ if (options?.limit) params.set("limit", String(options.limit));
1100
+ if (options?.types?.length) params.set("types", options.types.join(","));
1101
+ return this.get(`/api/graph/search?${params.toString()}`);
1102
+ }
801
1103
  // ─── Internal ─────────────────────────────────────────────────────────────
1104
+ async getAuth(path) {
1105
+ if (!this.accessToken) {
1106
+ throw new BioError(
1107
+ `bio-graph ${path} requires an accessToken \u2014 pass it in the GraphClient constructor`,
1108
+ "config_error"
1109
+ );
1110
+ }
1111
+ return this.get(path);
1112
+ }
802
1113
  async get(path, attempt = 0) {
803
1114
  const url = `${this.graphUrl}${path}`;
804
1115
  const controller = new AbortController();
@@ -897,12 +1208,165 @@ var GraphClient = class _GraphClient {
897
1208
  return body;
898
1209
  }
899
1210
  };
1211
+
1212
+ // src/passport-client.ts
1213
+ var PassportClient = class {
1214
+ config;
1215
+ ws = null;
1216
+ _passport = null;
1217
+ _status = "disconnected";
1218
+ reconnectAttempt = 0;
1219
+ reconnectTimer = null;
1220
+ listeners = /* @__PURE__ */ new Map();
1221
+ closed = false;
1222
+ constructor(config) {
1223
+ this.config = {
1224
+ autoReconnect: true,
1225
+ maxReconnectDelay: 3e4,
1226
+ ...config
1227
+ };
1228
+ }
1229
+ /** Current passport object (null until first identity message) */
1230
+ get passport() {
1231
+ return this._passport;
1232
+ }
1233
+ /** Current connection status */
1234
+ get status() {
1235
+ return this._status;
1236
+ }
1237
+ /** Connect to the passport WebSocket */
1238
+ connect() {
1239
+ if (this.closed) return;
1240
+ this.setStatus("connecting");
1241
+ const url = new URL("/passport", this.config.bioIdUrl.replace(/^http/, "ws"));
1242
+ url.searchParams.set("token", this.config.accessToken);
1243
+ url.searchParams.set("version", "1");
1244
+ if (this.config.service) {
1245
+ url.searchParams.set("service", this.config.service);
1246
+ }
1247
+ this.ws = new WebSocket(url.toString());
1248
+ this.ws.onopen = () => {
1249
+ this.reconnectAttempt = 0;
1250
+ this.setStatus("connected");
1251
+ this.emit("connected", this._status);
1252
+ };
1253
+ this.ws.onmessage = (event) => {
1254
+ try {
1255
+ const data = JSON.parse(
1256
+ typeof event.data === "string" ? event.data : ""
1257
+ );
1258
+ if (data.type === "identity" || data.type === "passport_updated") {
1259
+ this._passport = data.passport ?? null;
1260
+ this.emit(data.type, this._passport);
1261
+ } else if (data.type === "revoked") {
1262
+ this._passport = null;
1263
+ this.emit("revoked");
1264
+ }
1265
+ } catch {
1266
+ }
1267
+ };
1268
+ this.ws.onclose = (event) => {
1269
+ this.setStatus("disconnected");
1270
+ this.emit("disconnected", this._status);
1271
+ if (event?.code === 4003 && this.config.refreshToken && this.config.bioAuth) {
1272
+ this.handleTokenRefresh();
1273
+ return;
1274
+ }
1275
+ this.scheduleReconnect();
1276
+ };
1277
+ this.ws.onerror = () => {
1278
+ const error = new Error("Passport WebSocket error");
1279
+ this.emit("error", error);
1280
+ };
1281
+ }
1282
+ /** Subscribe to an event */
1283
+ on(event, handler) {
1284
+ if (!this.listeners.has(event)) {
1285
+ this.listeners.set(event, /* @__PURE__ */ new Set());
1286
+ }
1287
+ this.listeners.get(event).add(handler);
1288
+ }
1289
+ /** Unsubscribe from an event */
1290
+ off(event, handler) {
1291
+ this.listeners.get(event)?.delete(handler);
1292
+ }
1293
+ /** Disconnect and stop reconnecting */
1294
+ close() {
1295
+ this.closed = true;
1296
+ if (this.reconnectTimer) {
1297
+ clearTimeout(this.reconnectTimer);
1298
+ this.reconnectTimer = null;
1299
+ }
1300
+ if (this.ws) {
1301
+ this.ws.onclose = null;
1302
+ this.ws.close();
1303
+ this.ws = null;
1304
+ }
1305
+ this.setStatus("disconnected");
1306
+ }
1307
+ /** Update access token (e.g. after refresh) and reconnect */
1308
+ updateToken(accessToken) {
1309
+ this.config.accessToken = accessToken;
1310
+ if (this.ws) {
1311
+ this.ws.onclose = null;
1312
+ this.ws.close();
1313
+ this.ws = null;
1314
+ }
1315
+ this.reconnectAttempt = 0;
1316
+ this.connect();
1317
+ }
1318
+ async handleTokenRefresh() {
1319
+ if (!this.config.refreshToken || !this.config.bioAuth) return;
1320
+ try {
1321
+ const tokens = await this.config.bioAuth.refreshToken(this.config.refreshToken);
1322
+ this.config.accessToken = tokens.access_token;
1323
+ if (tokens.refresh_token) {
1324
+ this.config.refreshToken = tokens.refresh_token;
1325
+ }
1326
+ if (this.config.onTokenRefresh) {
1327
+ this.config.onTokenRefresh(tokens);
1328
+ }
1329
+ this.reconnectAttempt = 0;
1330
+ this.connect();
1331
+ } catch {
1332
+ this.emit("error", new Error("Token refresh failed"));
1333
+ }
1334
+ }
1335
+ setStatus(status) {
1336
+ this._status = status;
1337
+ }
1338
+ emit(event, ...args) {
1339
+ const handlers = this.listeners.get(event);
1340
+ if (handlers) {
1341
+ for (const handler of handlers) {
1342
+ try {
1343
+ handler(...args);
1344
+ } catch {
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ scheduleReconnect() {
1350
+ if (this.closed || !this.config.autoReconnect) return;
1351
+ const delay = Math.min(
1352
+ 1e3 * Math.pow(2, this.reconnectAttempt),
1353
+ this.config.maxReconnectDelay ?? 3e4
1354
+ );
1355
+ this.reconnectAttempt++;
1356
+ this.reconnectTimer = setTimeout(() => {
1357
+ this.reconnectTimer = null;
1358
+ this.connect();
1359
+ }, delay);
1360
+ }
1361
+ };
900
1362
  // Annotate the CommonJS export names for ESM import in node:
901
1363
  0 && (module.exports = {
902
1364
  BioAdmin,
903
1365
  BioAuth,
904
1366
  BioError,
1367
+ EmbedClient,
905
1368
  GraphClient,
1369
+ PassportClient,
906
1370
  decodeToken,
907
1371
  generatePKCE,
908
1372
  isTokenExpired,