@thunderid/browser 1.11.0 → 1.12.1

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
@@ -26,6 +26,7 @@ export { default as navigate } from './utils/navigate';
26
26
  export { default as http } from './utils/http';
27
27
  export { default as createHttpClientFetcher } from './utils/createHttpClientFetcher';
28
28
  export { default as copyTextToClipboard } from './utils/copyTextToClipboard';
29
+ export { getCurrentProjectStorageKey, readCurrentProject, writeCurrentProject } from './utils/currentProjectStorage';
29
30
  export { default as handleWebAuthnAuthentication } from './utils/handleWebAuthnAuthentication';
30
31
  export { default as resolveEmojiUrisInHtml } from './utils/resolveEmojiUrisInHtml';
31
32
  export { default as isAvatarUri, AVATAR_URI_SCHEME } from './utils/isAvatarUri';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'buffer/index.js';
2
2
  if (typeof window !== 'undefined' && !window.Buffer) { window.Buffer = Buffer; }
3
- import { DEFAULT_THEME, HttpClient, OIDCRequestConstants, ThunderIDAuthException, ThunderIDJavaScriptClient, ThunderIDRuntimeError, TokenConstants, arrayBufferToBase64url, base64urlToArrayBuffer, createPackageComponentLogger, extractEmojiFromUri, extractPkceStorageKeyFromState, generateFlattenedUserProfile, getTokenPermissions, getUsersMe, isEmojiUri, resolveBackendEndpoints, resolveRequestBaseUrl, resolveResourceEndpoint } from "@thunderid/javascript";
3
+ import { DEFAULT_THEME, EmbeddedFlowType, EmbeddedSignInFlowStatus, HttpClient, OIDCRequestConstants, OrganizationUnitSwitchErrorCodes, ThunderIDAPIError, ThunderIDAuthException, ThunderIDJavaScriptClient, ThunderIDRuntimeError, TokenConstants, arrayBufferToBase64url, base64urlToArrayBuffer, createPackageComponentLogger, executeEmbeddedSignInFlow, extractEmojiFromUri, extractPkceStorageKeyFromState, generateFlattenedUserProfile, getTokenPermissions, getUsersMe, getVendorPrefix, isEmojiUri, resolveBackendEndpoints, resolveRequestBaseUrl, resolveResourceEndpoint } from "@thunderid/javascript";
4
4
  import base64url from "base64url";
5
5
  import sha256 from "fast-sha256";
6
6
  import { createLocalJWKSet, jwtVerify } from "jose";
@@ -160,10 +160,14 @@ var BackendClient = class BackendClient {
160
160
  return this.refreshing;
161
161
  }
162
162
  /**
163
- * The URL that starts sign-in at the backend and comes back to `returnTo`.
163
+ * The URL that starts sign-in at the backend and comes back to `returnTo`. With `ouId`, the backend
164
+ * bounds the sign-in to that organization unit.
164
165
  */
165
- getSignInUrl(returnTo) {
166
- return `${this.endpoints.signIn}?${new URLSearchParams({ returnTo }).toString()}`;
166
+ getSignInUrl(returnTo, ouId) {
167
+ return `${this.endpoints.signIn}?${new URLSearchParams({
168
+ returnTo,
169
+ ...ouId ? { ouId } : {}
170
+ }).toString()}`;
167
171
  }
168
172
  /**
169
173
  * Ends the backend session.
@@ -929,7 +933,7 @@ var AuthenticationHelper = class {
929
933
  * @param timeout - How long to wait for the iframe's response, in milliseconds.
930
934
  * @returns The user session, or `false` if the user is not signed in.
931
935
  */
932
- async signInSilently(constructSilentSignInUrl, requestAccessToken, additionalParams, tokenRequestConfig, timeout = SILENT_SIGN_IN_TIMEOUT) {
936
+ async signInSilently(constructSilentSignInUrl, requestAccessToken, additionalParams, tokenRequestConfig, timeout = SILENT_SIGN_IN_TIMEOUT, onSignedOut) {
933
937
  if (SPAUtils_default.isInitializedSilentSignIn()) {
934
938
  await this.handleSilentSignInResponse();
935
939
  return Promise.resolve({
@@ -964,6 +968,7 @@ var AuthenticationHelper = class {
964
968
  const data = e.data;
965
969
  if (data?.type === SILENT_SIGN_IN_SIGNED_OUT) {
966
970
  cleanUp();
971
+ onSignedOut?.(typeof data?.data?.error === "string" ? data.data.error : void 0);
967
972
  resolve(false);
968
973
  }
969
974
  if (data?.type === SILENT_SIGN_IN_SIGNED_IN && data?.data?.code) {
@@ -994,7 +999,14 @@ var AuthenticationHelper = class {
994
999
  state
995
1000
  },
996
1001
  type: SILENT_SIGN_IN_SIGNED_IN
997
- } : { type: SILENT_SIGN_IN_SIGNED_OUT };
1002
+ } : {
1003
+ data: url.searchParams.get("error") ? {
1004
+ code: "",
1005
+ error: url.searchParams.get("error") ?? "",
1006
+ state
1007
+ } : void 0,
1008
+ type: SILENT_SIGN_IN_SIGNED_OUT
1009
+ };
998
1010
  window.parent.postMessage(message, window.location.origin);
999
1011
  window.location.href = "about:blank";
1000
1012
  await SPAUtils_default.waitTillPageRedirect();
@@ -2785,6 +2797,24 @@ var SPAHelper_default = SPAHelper;
2785
2797
 
2786
2798
  //#endregion
2787
2799
  //#region src/ThunderIDBrowserClient.ts
2800
+ /**
2801
+ * OAuth errors that mean the server needs the user, so a silent switch falls back to a redirect.
2802
+ */
2803
+ const INTERACTION_ERRORS = [
2804
+ "interaction_required",
2805
+ "login_required",
2806
+ "consent_required",
2807
+ "account_selection_required"
2808
+ ];
2809
+ /**
2810
+ * OAuth errors for a device secret the server cannot use, which the SDK drops before signing in again.
2811
+ */
2812
+ const UNUSABLE_DEVICE_SECRET_ERRORS = ["invalid_request", "invalid_grant"];
2813
+ /**
2814
+ * Temporary-data key that marks a switch to another organization unit left the page, so its callback is
2815
+ * redeemed although the previous session is still signed in.
2816
+ */
2817
+ const ORGANIZATION_UNIT_SWITCH_KEY = "organization_unit_switch";
2788
2818
  const logger$2 = createPackageComponentLogger("@thunderid/browser", "ThunderIDBrowserClient");
2789
2819
  const BROWSER_DEFAULT_CONFIG = {
2790
2820
  autoLogoutOnTokenRefreshError: false,
@@ -2918,7 +2948,8 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2918
2948
  await this.isInitialized();
2919
2949
  if (this._backend) {
2920
2950
  if (config?.callOnlyOnRedirect) return;
2921
- location.href = this._backend.getSignInUrl(`${window.location.pathname}${window.location.search}`);
2951
+ const backendOuId = config?.["organizationUnitId"];
2952
+ location.href = this._backend.getSignInUrl(`${window.location.pathname}${window.location.search}`, typeof backendOuId === "string" && backendOuId !== "" ? backendOuId : void 0);
2922
2953
  await SPAUtils_default.waitTillPageRedirect();
2923
2954
  return;
2924
2955
  }
@@ -2931,6 +2962,8 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2931
2962
  async _signInInternal(signInConfig, authorizationCode, state, tokenRequestConfig) {
2932
2963
  const sm = this.getStorageManager();
2933
2964
  const config = await sm.getConfigData();
2965
+ const switchedUser = await this._completeOrganizationUnitSwitch(tokenRequestConfig);
2966
+ if (switchedUser) return switchedUser;
2934
2967
  const basicUserInfo = await this._authHelper.handleSignIn();
2935
2968
  if (basicUserInfo) return basicUserInfo;
2936
2969
  let resolvedAuthorizationCode;
@@ -2982,14 +3015,186 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2982
3015
  await this.setPKCECode(pkce, pkceKey);
2983
3016
  }
2984
3017
  await this.requestAccessToken(authorizationCode, state ?? "", void 0, tokenRequestConfig);
3018
+ return this._startSession(config.clientId);
3019
+ }
3020
+ /**
3021
+ * Prepares the sign-out URL and the token refresh for tokens just stored, and reads the user.
3022
+ */
3023
+ async _startSession(clientId) {
2985
3024
  try {
2986
3025
  const signOutUrl = await this.getSignOutUrl();
2987
- SPAUtils_default.setSignOutURL(signOutUrl, config.clientId, this._browserInstanceId);
3026
+ SPAUtils_default.setSignOutURL(signOutUrl, clientId, this._browserInstanceId);
2988
3027
  } catch {}
2989
3028
  await this._spaHelper.clearRefreshTokenTimeout();
2990
3029
  await this._spaHelper.refreshAccessTokenAutomatically(() => this.refreshAccessToken());
2991
3030
  return this.getUser();
2992
3031
  }
3032
+ /**
3033
+ * Signs in again bounded to the organization unit `organizationUnitId`, and replaces the session with
3034
+ * the one it gets. The server reuses the SSO session when the user can sign in there, so normally no
3035
+ * credentials are asked.
3036
+ *
3037
+ * - Redirect mode exchanges the session's device secret at `{baseUrl}/ou/{ouId}/oauth2/token` when it
3038
+ * holds one, which a sign-in with the `device_sso` scope returns. Otherwise it tries a `prompt=none`
3039
+ * request to `{baseUrl}/ou/{ouId}/oauth2/authorize` in a hidden iframe. When the server needs the
3040
+ * user, it redirects the page there (unless `redirect` is `false`), and the callback replaces the
3041
+ * session.
3042
+ * - Embedded mode starts an `AUTHENTICATION` flow with `ouId`, presenting the SSO session handles kept
3043
+ * from earlier flow steps. A flow that needs input fails with `interaction_required`; render the
3044
+ * sign-in for the unit instead.
3045
+ * - Backend mode sends the page to the backend's sign-in endpoint with `ouId`.
3046
+ *
3047
+ * A failed switch leaves the previous session as it was.
3048
+ *
3049
+ * @throws {ThunderIDAPIError} With a code from `OrganizationUnitSwitchErrorCodes`.
3050
+ */
3051
+ async switchOrganizationUnit(organizationUnitId, options = {}) {
3052
+ await this.isInitialized();
3053
+ if (typeof organizationUnitId !== "string" || organizationUnitId.trim() === "") throw new ThunderIDAPIError("An organization unit identifier is required.", OrganizationUnitSwitchErrorCodes.InvalidOrganizationUnit, "browser", 400);
3054
+ if (this._backend) {
3055
+ location.href = this._backend.getSignInUrl(`${window.location.pathname}${window.location.search}`, organizationUnitId);
3056
+ await SPAUtils_default.waitTillPageRedirect();
3057
+ return;
3058
+ }
3059
+ const { redirect = true,...params } = options;
3060
+ const sm = this.getStorageManager();
3061
+ const config = await sm.getConfigData();
3062
+ if (config?.mode === "embedded") return this._switchOrganizationUnitEmbedded(organizationUnitId, config);
3063
+ const exchangedUser = await this._switchOrganizationUnitWithDeviceSecret(organizationUnitId, config);
3064
+ if (exchangedUser) return exchangedUser;
3065
+ let silentError;
3066
+ let silentUser = false;
3067
+ if (exchangedUser === void 0) try {
3068
+ silentUser = await this._authHelper.signInSilently((additional) => this._constructSilentSignInUrl(additional), (code$1, s, trc) => this._exchangeCodeForTokens(code$1, s, trc), {
3069
+ ...params,
3070
+ organizationUnitId
3071
+ }, void 0, config?.session?.silentSignIn?.timeout, (error) => {
3072
+ silentError = error;
3073
+ });
3074
+ } catch (error) {
3075
+ throw ThunderIDBrowserClient._toSwitchError(error instanceof Error ? error.message : void 0, error);
3076
+ }
3077
+ if (silentUser && typeof silentUser === "object") {
3078
+ this._emitHook(Hooks_default.SignIn, silentUser);
3079
+ return silentUser;
3080
+ }
3081
+ if (silentError && !INTERACTION_ERRORS.includes(silentError)) throw ThunderIDBrowserClient._toSwitchError(silentError);
3082
+ if (!redirect) throw ThunderIDBrowserClient._toSwitchError("interaction_required");
3083
+ const url = await this.getSignInUrl({
3084
+ ...params,
3085
+ organizationUnitId
3086
+ });
3087
+ await sm.setTemporaryDataParameter(ORGANIZATION_UNIT_SWITCH_KEY, organizationUnitId);
3088
+ location.href = url;
3089
+ await SPAUtils_default.waitTillPageRedirect();
3090
+ }
3091
+ /**
3092
+ * Switches back-channel by exchanging the session's device secret at the unit's token endpoint.
3093
+ *
3094
+ * @returns The user, `false` when the server needs the user, or `undefined` when the session holds no
3095
+ * device secret.
3096
+ */
3097
+ async _switchOrganizationUnitWithDeviceSecret(organizationUnitId, config) {
3098
+ let result;
3099
+ try {
3100
+ result = await this.switchOrganizationUnitWithDeviceSecret(organizationUnitId);
3101
+ } catch (error) {
3102
+ throw ThunderIDBrowserClient._toSwitchError(void 0, error);
3103
+ }
3104
+ if (!result) return;
3105
+ if ("error" in result) {
3106
+ if (INTERACTION_ERRORS.includes(result.error) || UNUSABLE_DEVICE_SECRET_ERRORS.includes(result.error)) return false;
3107
+ throw ThunderIDBrowserClient._toSwitchError(result.error);
3108
+ }
3109
+ const user = await this._startSession(config.clientId ?? "");
3110
+ this._emitHook(Hooks_default.SignIn, user);
3111
+ return user;
3112
+ }
3113
+ /**
3114
+ * Maps an OAuth error from a bounded sign-in onto the switch's documented errors.
3115
+ */
3116
+ static _toSwitchError(oauthError, cause) {
3117
+ if (cause instanceof ThunderIDAPIError && Object.values(OrganizationUnitSwitchErrorCodes).includes(cause.code)) return cause;
3118
+ let code$1 = OrganizationUnitSwitchErrorCodes.Failed;
3119
+ if (oauthError === "unauthorized_client") code$1 = OrganizationUnitSwitchErrorCodes.UnauthorizedClient;
3120
+ else if (oauthError && INTERACTION_ERRORS.includes(oauthError)) code$1 = OrganizationUnitSwitchErrorCodes.InteractionRequired;
3121
+ const serverCode = oauthError ?? "switch_failed";
3122
+ return new ThunderIDAPIError(JSON.stringify({
3123
+ code: serverCode,
3124
+ message: { defaultValue: serverCode }
3125
+ }), code$1, "browser", oauthError === "unauthorized_client" ? 400 : void 0, void 0, "Switching the organization unit failed");
3126
+ }
3127
+ /**
3128
+ * Embedded mode: starts a sign-in flow bounded to the unit. When the SSO session is reused, the flow
3129
+ * completes at once and its assertion replaces the session.
3130
+ */
3131
+ async _switchOrganizationUnitEmbedded(organizationUnitId, config) {
3132
+ let response;
3133
+ const sessionData = await this.getFlowSessions();
3134
+ try {
3135
+ response = await executeEmbeddedSignInFlow({
3136
+ baseUrl: config?.baseUrl,
3137
+ payload: {
3138
+ applicationId: config?.applicationId ?? "",
3139
+ flowType: EmbeddedFlowType.Authentication,
3140
+ ouId: organizationUnitId,
3141
+ ...sessionData.length > 0 ? { sessionData } : {}
3142
+ },
3143
+ url: resolveResourceEndpoint("flowExecute", config)
3144
+ });
3145
+ } catch (error) {
3146
+ const serverCode = error instanceof ThunderIDAPIError ? error.serverError?.code : void 0;
3147
+ throw ThunderIDBrowserClient._toSwitchError(serverCode === "FES-1023" ? "unauthorized_client" : serverCode, error);
3148
+ }
3149
+ await this.storeFlowSessions(response);
3150
+ if (response?.flowStatus === EmbeddedSignInFlowStatus.Complete && response.assertion) {
3151
+ const claims = await this.decodeJwtToken(response.assertion);
3152
+ const iat = typeof claims["iat"] === "number" ? claims["iat"] : void 0;
3153
+ const exp = typeof claims["exp"] === "number" ? claims["exp"] : void 0;
3154
+ await this.setSession({
3155
+ access_token: response.assertion,
3156
+ created_at: iat ? iat * 1e3 : Date.now(),
3157
+ expires_in: exp && iat ? exp - iat : 3600,
3158
+ id_token: response.assertion,
3159
+ scope: claims["scope"],
3160
+ token_type: "Bearer"
3161
+ });
3162
+ const user = await this.getUser();
3163
+ this._emitHook(Hooks_default.SignIn, user);
3164
+ return user;
3165
+ }
3166
+ if (response?.flowStatus === EmbeddedSignInFlowStatus.Error) throw ThunderIDBrowserClient._toSwitchError("access_denied");
3167
+ throw ThunderIDBrowserClient._toSwitchError("interaction_required");
3168
+ }
3169
+ /**
3170
+ * Completes a switch that left the page for the unit's authorization endpoint: redeems the returned
3171
+ * code, or reports the returned error and keeps the previous session.
3172
+ */
3173
+ async _completeOrganizationUnitSwitch(tokenRequestConfig) {
3174
+ const sm = this.getStorageManager();
3175
+ if (!await sm.getTemporaryDataParameter(ORGANIZATION_UNIT_SWITCH_KEY) || SPAUtils_default.isInitializedSilentSignIn()) return;
3176
+ const url = new URL(window.location.href);
3177
+ const code$1 = url.searchParams.get(OIDCRequestConstants.Params.AUTHORIZATION_CODE);
3178
+ const state = url.searchParams.get(OIDCRequestConstants.Params.STATE);
3179
+ const error = url.searchParams.get("error");
3180
+ if (code$1 && state) {
3181
+ await sm.removeTemporaryDataParameter(ORGANIZATION_UNIT_SWITCH_KEY);
3182
+ SPAUtils_default.removeAuthorizationCode();
3183
+ try {
3184
+ return await this._exchangeCodeForTokens(code$1, state, tokenRequestConfig);
3185
+ } catch (exchangeError) {
3186
+ throw ThunderIDBrowserClient._toSwitchError(void 0, exchangeError);
3187
+ }
3188
+ }
3189
+ if (error) {
3190
+ await sm.removeTemporaryDataParameter(ORGANIZATION_UNIT_SWITCH_KEY);
3191
+ url.searchParams.delete("error");
3192
+ url.searchParams.delete("error_description");
3193
+ url.searchParams.delete(OIDCRequestConstants.Params.STATE);
3194
+ history.replaceState(null, document.title, url.toString());
3195
+ throw ThunderIDBrowserClient._toSwitchError(error);
3196
+ }
3197
+ }
2993
3198
  async signInSilently(additionalParams, tokenRequestConfig) {
2994
3199
  await this.isInitialized();
2995
3200
  if (this._backend) return await this.isSignedIn() ? this.getUser() : false;
@@ -3286,13 +3491,14 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
3286
3491
  const config = await this.getStorageManager().getConfigData();
3287
3492
  const urlString = await this.getSignInUrl({
3288
3493
  prompt: "none",
3494
+ response_mode: "query",
3289
3495
  state: SILENT_SIGN_IN_STATE,
3290
3496
  ...additionalParams
3291
3497
  });
3292
3498
  const urlObject = new URL(urlString);
3293
- urlObject.searchParams.set("response_mode", "query");
3499
+ if (!urlObject.searchParams.has("request_uri")) urlObject.searchParams.set("response_mode", "query");
3294
3500
  if (this._storage === BrowserStorage_default.BrowserMemory && config.enablePKCE) {
3295
- const state = urlObject.searchParams.get(OIDCRequestConstants.Params.STATE);
3501
+ const state = urlObject.searchParams.get(OIDCRequestConstants.Params.STATE) ?? this.lastAuthorizeRequestState;
3296
3502
  SPAUtils_default.setPKCE(extractPkceStorageKeyFromState(state ?? ""), await this.getPKCECode(state ?? ""));
3297
3503
  }
3298
3504
  return urlObject.toString();
@@ -3377,6 +3583,48 @@ const copyTextToClipboard = async (text) => {
3377
3583
  };
3378
3584
  var copyTextToClipboard_default = copyTextToClipboard;
3379
3585
 
3586
+ //#endregion
3587
+ //#region src/utils/currentProjectStorage.ts
3588
+ /**
3589
+ * The `localStorage` key the current project is kept under, scoped to the vendor like the SDK's
3590
+ * other UI preferences.
3591
+ *
3592
+ * @param vendor - The vendor prefix.
3593
+ * @returns The key.
3594
+ */
3595
+ const getCurrentProjectStorageKey = (vendor) => `${getVendorPrefix(vendor)}-current-project`;
3596
+ /**
3597
+ * Reads the project the user last chose. The current project is a client-side preference for the
3598
+ * management UI: it does not change the session, the tokens or what the server allows.
3599
+ *
3600
+ * @param vendor - The vendor prefix.
3601
+ * @returns The project, or `null` when none is kept or storage is unavailable.
3602
+ */
3603
+ const readCurrentProject = (vendor) => {
3604
+ try {
3605
+ if (typeof window === "undefined" || !window.localStorage) return null;
3606
+ const stored = window.localStorage.getItem(getCurrentProjectStorageKey(vendor));
3607
+ const parsed = stored ? JSON.parse(stored) : null;
3608
+ return parsed && typeof parsed === "object" && typeof parsed.id === "string" ? parsed : null;
3609
+ } catch {
3610
+ return null;
3611
+ }
3612
+ };
3613
+ /**
3614
+ * Keeps the project the user chose, or forgets it with `null`.
3615
+ *
3616
+ * @param vendor - The vendor prefix.
3617
+ * @param project - The project, or `null`.
3618
+ */
3619
+ const writeCurrentProject = (vendor, project) => {
3620
+ try {
3621
+ if (typeof window === "undefined" || !window.localStorage) return;
3622
+ const key = getCurrentProjectStorageKey(vendor);
3623
+ if (project) window.localStorage.setItem(key, JSON.stringify(project));
3624
+ else window.localStorage.removeItem(key);
3625
+ } catch {}
3626
+ };
3627
+
3380
3628
  //#endregion
3381
3629
  //#region src/utils/handleWebAuthnAuthentication.ts
3382
3630
  const logger$1 = createPackageComponentLogger("@thunderid/browser", "WebAuthn");
@@ -4228,5 +4476,5 @@ const normalizeBorderRadius = (raw) => {
4228
4476
  var normalizeBorderRadius_default = normalizeBorderRadius;
4229
4477
 
4230
4478
  //#endregion
4231
- export { ACCESS_TOKEN_INVALID, ANONYMOUS_ANIMAL_ICONS, ANONYMOUS_ENTITY_ICONS, AVATAR_GRADIENT_COUNT, AVATAR_URI_SCHEME, AuthenticationHelper_default as AuthenticationHelper, BACKEND_REQUEST_HEADER, BackendClient_default as BackendClient, BackendHttpClient_default as BackendHttpClient, Hooks as BrowserHooks, BrowserStorage_default as BrowserStorage, CUSTOM_GRANT_CONFIG, ERROR, ERROR_DESCRIPTION, FetchHttpClient_default as FetchHttpClient, Hooks_default as Hooks, LocalStore_default as LocalStore, MemoryStore_default as MemoryStore, REFRESH_ACCESS_TOKEN_ERR0R, SILENT_SIGN_IN_SIGNED_IN, SILENT_SIGN_IN_SIGNED_OUT, SILENT_SIGN_IN_STATE, SILENT_SIGN_IN_TIMEOUT, SPACryptoUtils_default as SPACryptoUtils, SPAHelper_default as SPAHelper, SPAUtils_default as SPAUtils, STATE_QUERY, SessionStore_default as SessionStore, TOKEN_REQUEST_CONFIG_KEY, ThunderIDBrowserClient_default as ThunderIDBrowserClient, buildAvatarSpec_default as buildAvatarSpec, copyTextToClipboard_default as copyTextToClipboard, createClassObserver, createHttpClientFetcher_default as createHttpClientFetcher, createMediaQueryListener, deriveAvatarContent, detectThemeMode, extractAvatarParamsFromUri_default as extractAvatarParamsFromUri, generateAvatarDataUri_default as generateAvatarDataUri, getActiveTheme_default as getActiveTheme, handleWebAuthnAuthentication_default as handleWebAuthnAuthentication, hasAuthParamsInUrl_default as hasAuthParamsInUrl, hasCalledForThisInstanceInUrl_default as hasCalledForThisInstanceInUrl, http_default as http, isAvatarUri_default as isAvatarUri, navigate_default as navigate, normalizeBorderRadius_default as normalizeBorderRadius, pickAnonymousAvatarName_default as pickAnonymousAvatarName, pickAnonymousEntityName_default as pickAnonymousEntityName, resolveEmojiUrisInHtml_default as resolveEmojiUrisInHtml, resolveLogoUri_default as resolveLogoUri };
4479
+ export { ACCESS_TOKEN_INVALID, ANONYMOUS_ANIMAL_ICONS, ANONYMOUS_ENTITY_ICONS, AVATAR_GRADIENT_COUNT, AVATAR_URI_SCHEME, AuthenticationHelper_default as AuthenticationHelper, BACKEND_REQUEST_HEADER, BackendClient_default as BackendClient, BackendHttpClient_default as BackendHttpClient, Hooks as BrowserHooks, BrowserStorage_default as BrowserStorage, CUSTOM_GRANT_CONFIG, ERROR, ERROR_DESCRIPTION, FetchHttpClient_default as FetchHttpClient, Hooks_default as Hooks, LocalStore_default as LocalStore, MemoryStore_default as MemoryStore, REFRESH_ACCESS_TOKEN_ERR0R, SILENT_SIGN_IN_SIGNED_IN, SILENT_SIGN_IN_SIGNED_OUT, SILENT_SIGN_IN_STATE, SILENT_SIGN_IN_TIMEOUT, SPACryptoUtils_default as SPACryptoUtils, SPAHelper_default as SPAHelper, SPAUtils_default as SPAUtils, STATE_QUERY, SessionStore_default as SessionStore, TOKEN_REQUEST_CONFIG_KEY, ThunderIDBrowserClient_default as ThunderIDBrowserClient, buildAvatarSpec_default as buildAvatarSpec, copyTextToClipboard_default as copyTextToClipboard, createClassObserver, createHttpClientFetcher_default as createHttpClientFetcher, createMediaQueryListener, deriveAvatarContent, detectThemeMode, extractAvatarParamsFromUri_default as extractAvatarParamsFromUri, generateAvatarDataUri_default as generateAvatarDataUri, getActiveTheme_default as getActiveTheme, getCurrentProjectStorageKey, handleWebAuthnAuthentication_default as handleWebAuthnAuthentication, hasAuthParamsInUrl_default as hasAuthParamsInUrl, hasCalledForThisInstanceInUrl_default as hasCalledForThisInstanceInUrl, http_default as http, isAvatarUri_default as isAvatarUri, navigate_default as navigate, normalizeBorderRadius_default as normalizeBorderRadius, pickAnonymousAvatarName_default as pickAnonymousAvatarName, pickAnonymousEntityName_default as pickAnonymousEntityName, readCurrentProject, resolveEmojiUrisInHtml_default as resolveEmojiUrisInHtml, resolveLogoUri_default as resolveLogoUri, writeCurrentProject };
4232
4480
  //# sourceMappingURL=index.js.map