@thunderid/browser 1.2.2 → 1.3.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
@@ -23,6 +23,7 @@ export { default as hasCalledForThisInstanceInUrl } from './utils/hasCalledForTh
23
23
  export { default as navigate } from './utils/navigate';
24
24
  export { default as http } from './utils/http';
25
25
  export { default as createHttpClientFetcher } from './utils/createHttpClientFetcher';
26
+ export { default as copyTextToClipboard } from './utils/copyTextToClipboard';
26
27
  export { default as handleWebAuthnAuthentication } from './utils/handleWebAuthnAuthentication';
27
28
  export { default as resolveEmojiUrisInHtml } from './utils/resolveEmojiUrisInHtml';
28
29
  export { default as isAvatarUri, AVATAR_URI_SCHEME } from './utils/isAvatarUri';
@@ -39,7 +40,7 @@ export type { AnonymousEntityIcon } from './utils/anonymousEntityIcons.generated
39
40
  export { default as resolveLogoUri } from './utils/resolveLogoUri';
40
41
  export type { ResolvedLogo, ResolvedLogoKind } from './utils/resolveLogoUri';
41
42
  export { detectThemeMode, createClassObserver, createMediaQueryListener } from './theme/themeDetection';
42
- export type { BrowserThemeDetection } from './theme/themeDetection';
43
+ export type { BrowserThemeDetection, MediaQueryListener } from './theme/themeDetection';
43
44
  export { default as getActiveTheme } from './theme/getActiveTheme';
44
45
  export { default as normalizeBorderRadius } from './theme/normalizeBorderRadius';
45
46
  export * from '@thunderid/javascript';
@@ -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":"AAGA;;GAEG;AAGH,OAAO,EAAC,OAAO,IAAI,sBAAsB,EAAC,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAC,OAAO,IAAI,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAC,OAAO,IAAI,KAAK,EAAE,KAAK,IAAI,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC1E,cAAc,0BAA0B,CAAC;AAGzC,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAC,mBAAmB,EAAE,iBAAiB,EAAC,MAAM,wBAAwB,CAAC;AACnF,YAAY,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACnE,YAAY,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACnE,YAAY,EAAC,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AACzE,YAAY,EAAC,sBAAsB,EAAC,MAAM,iBAAiB,CAAC;AAG5D,OAAO,EAAC,OAAO,IAAI,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAC,OAAO,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAC,OAAO,IAAI,WAAW,EAAC,MAAM,sBAAsB,CAAC;AAG5D,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAC,OAAO,IAAI,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,OAAO,IAAI,oBAAoB,EAAC,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAC,OAAO,IAAI,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAC,OAAO,IAAI,6BAA6B,EAAC,MAAM,uCAAuC,CAAC;AAC/F,OAAO,EAAC,OAAO,IAAI,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAC,OAAO,IAAI,IAAI,EAAC,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAC,OAAO,IAAI,uBAAuB,EAAC,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAC,OAAO,IAAI,mBAAmB,EAAC,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAC,OAAO,IAAI,4BAA4B,EAAC,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAC,OAAO,IAAI,sBAAsB,EAAC,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAAC,OAAO,IAAI,WAAW,EAAE,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAC,OAAO,IAAI,0BAA0B,EAAC,MAAM,oCAAoC,CAAC;AACzF,YAAY,EAAC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAC,MAAM,oCAAoC,CAAC;AACjG,OAAO,EAAC,OAAO,IAAI,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,OAAO,IAAI,qBAAqB,EAChC,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAC,OAAO,IAAI,uBAAuB,EAAC,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAC,OAAO,IAAI,uBAAuB,EAAC,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAC,sBAAsB,EAAC,MAAM,wCAAwC,CAAC;AAC9E,YAAY,EAAC,mBAAmB,EAAC,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAC,sBAAsB,EAAC,MAAM,wCAAwC,CAAC;AAC9E,YAAY,EAAC,mBAAmB,EAAC,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,wBAAwB,CAAC;AACjE,YAAY,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAG3E,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,wBAAwB,CAAC;AACtG,YAAY,EAAC,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAC,OAAO,IAAI,qBAAqB,EAAC,MAAM,+BAA+B,CAAC;AAG/E,cAAc,uBAAuB,CAAC"}
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, isEmojiUri } from "@thunderid/javascript";
3
+ import { DEFAULT_THEME, HttpClient, OIDCRequestConstants, ThunderIDAuthException, ThunderIDJavaScriptClient, ThunderIDRuntimeError, TokenConstants, arrayBufferToBase64url, base64urlToArrayBuffer, createPackageComponentLogger, extractEmojiFromUri, extractPkceStorageKeyFromState, generateFlattenedUserProfile, getUsersMe, isEmojiUri, resolveResourceEndpoint } from "@thunderid/javascript";
4
4
  import base64url from "base64url";
5
5
  import sha256 from "fast-sha256";
6
6
  import { createLocalJWKSet, jwtVerify } from "jose";
@@ -32,6 +32,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  enumerable: true
33
33
  }) : target, mod));
34
34
 
35
+ //#endregion
36
+ //#region src/constants/Hooks.ts
37
+ /**
38
+ * Event hook names for attaching callbacks via `ThunderIDBrowserClient.on()`.
39
+ */
40
+ let Hooks = /* @__PURE__ */ function(Hooks$1) {
41
+ Hooks$1["SignIn"] = "sign-in";
42
+ Hooks$1["SignOut"] = "sign-out";
43
+ Hooks$1["Initialize"] = "initialize";
44
+ Hooks$1["HttpRequestStart"] = "http-request-start";
45
+ Hooks$1["HttpRequestFinish"] = "http-request-finish";
46
+ Hooks$1["HttpRequestError"] = "http-request-error";
47
+ Hooks$1["HttpRequestSuccess"] = "http-request-success";
48
+ Hooks$1["RevokeAccessToken"] = "revoke-access-token";
49
+ Hooks$1["CustomGrant"] = "custom-grant";
50
+ Hooks$1["SignOutFailed"] = "sign-out-failed";
51
+ return Hooks$1;
52
+ }({});
53
+ var Hooks_default = Hooks;
54
+
35
55
  //#endregion
36
56
  //#region src/constants/SPAConstants.ts
37
57
  const ERROR = "error";
@@ -89,9 +109,11 @@ var FetchHttpClient = class FetchHttpClient extends HttpClient {
89
109
  method: (method ?? "GET").toUpperCase()
90
110
  });
91
111
  } catch (networkError) {
112
+ const isAbort = networkError?.name === "AbortError";
92
113
  throw Object.assign(new Error(networkError.message), {
93
- code: "NETWORK_ERROR",
94
- config
114
+ code: isAbort ? "ABORT_ERROR" : "NETWORK_ERROR",
115
+ config,
116
+ name: isAbort ? "AbortError" : "Error"
95
117
  });
96
118
  }
97
119
  const responseData = (fetchResponse.headers.get("content-type") ?? "").includes("application/json") ? await fetchResponse.json() : await fetchResponse.text();
@@ -778,6 +800,57 @@ var AuthenticationHelper = class {
778
800
  };
779
801
  var AuthenticationHelper_default = AuthenticationHelper;
780
802
 
803
+ //#endregion
804
+ //#region src/utils/createHttpClientFetcher.ts
805
+ /**
806
+ * Builds a `fetch`-shaped function backed by the shared `FetchHttpClient` singleton, for
807
+ * framework wrappers (`@thunderid/react`, `@thunderid/vue`) that need to hand a core
808
+ * `@thunderid/javascript` API function a `fetcher` carrying the access token automatically.
809
+ *
810
+ * Unlike a plain `fetch` call, `FetchHttpClient.request` throws on a non-2xx response instead
811
+ * of resolving it. This adapter catches that and reconstructs a `Response`-shaped object from
812
+ * the thrown error's `response`, so the core API function sees the real status and body instead
813
+ * of treating every non-2xx result as a network failure. A genuine network error (no response
814
+ * attached) still propagates.
815
+ *
816
+ * @param instanceId - Which `FetchHttpClient` instance to use, for multi-instance apps. Defaults to `0`.
817
+ * @returns A function matching the `(url, config) => Promise<Response>` shape core API functions expect.
818
+ * @example
819
+ * ```typescript
820
+ * await updateMeCredentials({
821
+ * ...config,
822
+ * fetcher: fetcher ?? createHttpClientFetcher(instanceId),
823
+ * });
824
+ * ```
825
+ */
826
+ const createHttpClientFetcher = (instanceId = 0) => {
827
+ return async (url, config) => {
828
+ const httpClient = FetchHttpClient_default.getInstance(instanceId);
829
+ const toResponse = (data, status, statusText) => ({
830
+ json: () => Promise.resolve(data),
831
+ ok: status >= 200 && status < 300,
832
+ status,
833
+ statusText,
834
+ text: () => Promise.resolve(typeof data === "string" ? data : JSON.stringify(data))
835
+ });
836
+ try {
837
+ const response = await httpClient.request({
838
+ data: config.body ? JSON.parse(config.body) : void 0,
839
+ headers: config.headers,
840
+ method: config.method || "POST",
841
+ signal: config.signal,
842
+ url
843
+ });
844
+ return toResponse(response.data, response.status, response.statusText || "");
845
+ } catch (error) {
846
+ const httpError = error;
847
+ if (httpError?.response) return toResponse(httpError.response.data, httpError.response.status, httpError.response.statusText ?? "");
848
+ throw error;
849
+ }
850
+ };
851
+ };
852
+ var createHttpClientFetcher_default = createHttpClientFetcher;
853
+
781
854
  //#endregion
782
855
  //#region src/utils/navigate.ts
783
856
  /**
@@ -2437,14 +2510,7 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2437
2510
  _storage = BrowserStorage_default.SessionStorage;
2438
2511
  _getSignOutURLFromSessionStorage = false;
2439
2512
  _isHttpHandlerEnabled = true;
2440
- _httpErrorCallback;
2441
- _httpFinishCallback;
2442
- _onSignInCallback = () => null;
2443
- _onSignOutCallback = () => null;
2444
- _onSignOutFailedCallback = () => null;
2445
- _onEndUserSession = () => null;
2446
- _onInitialize = () => null;
2447
- _onCustomGrant = /* @__PURE__ */ new Map();
2513
+ _hookCallbacks = /* @__PURE__ */ new Map();
2448
2514
  _initialized = false;
2449
2515
  _startedInitialize = false;
2450
2516
  constructor(instanceId = 0) {
@@ -2471,6 +2537,12 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2471
2537
  await this._authHelper?.attachTokenToRequestConfig(request);
2472
2538
  };
2473
2539
  this._httpClient = FetchHttpClient_default.getInstance(this._browserInstanceId, true, attachToken);
2540
+ this._httpClient.setHttpRequestStartCallback((request) => {
2541
+ this._emitHook(Hooks_default.HttpRequestStart, request);
2542
+ });
2543
+ this._httpClient.setHttpRequestSuccessCallback((response) => {
2544
+ this._emitHook(Hooks_default.HttpRequestSuccess, response);
2545
+ });
2474
2546
  this._authHelper = new AuthenticationHelper_default(sm, this._spaHelper, this._browserInstanceId, {
2475
2547
  exchangeToken: (cfg) => super.exchangeToken(cfg),
2476
2548
  getAccessToken: (sessionId) => this.getAccessToken(sessionId),
@@ -2485,7 +2557,7 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2485
2557
  setPKCECode: (pkceKey, state) => this.setPKCECode(pkceKey, state)
2486
2558
  });
2487
2559
  this._initialized = true;
2488
- if (this._onInitialize) this._onInitialize(true);
2560
+ this._emitHook(Hooks_default.Initialize, true);
2489
2561
  if (!merged.autoLogoutOnTokenRefreshError) return true;
2490
2562
  window.addEventListener("message", (event) => {
2491
2563
  if (event?.data?.type === REFRESH_ACCESS_TOKEN_ERR0R) this.signOut();
@@ -2516,7 +2588,7 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2516
2588
  if (!SPAUtils_default.canContinueSignIn(Boolean(config?.callOnlyOnRedirect), authorizationCode)) return;
2517
2589
  delete config?.callOnlyOnRedirect;
2518
2590
  const user = await this._signInInternal(config, authorizationCode, state, tokenRequestConfig);
2519
- if (user && this._onSignInCallback) this._onSignInCallback(user);
2591
+ if (user) this._emitHook(Hooks_default.SignIn, user);
2520
2592
  return user;
2521
2593
  }
2522
2594
  async _signInInternal(signInConfig, authorizationCode, state, tokenRequestConfig) {
@@ -2585,7 +2657,7 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2585
2657
  await this.isInitialized();
2586
2658
  if (SPAUtils_default.wasSignInCalled()) return;
2587
2659
  const response = await this._signInSilentlyInternal(additionalParams, tokenRequestConfig);
2588
- if (this._onSignInCallback && response) this._onSignInCallback(response);
2660
+ if (response) this._emitHook(Hooks_default.SignIn, response);
2589
2661
  return response;
2590
2662
  }
2591
2663
  async _signInSilentlyInternal(additionalParams, tokenRequestConfig) {
@@ -2631,16 +2703,40 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2631
2703
  }
2632
2704
  async httpRequest(requestConfig) {
2633
2705
  await this._validateMethod(false);
2634
- return this._authHelper.httpRequest(this._httpClient, requestConfig, this._isHttpHandlerEnabled, this._httpErrorCallback, this._httpFinishCallback);
2706
+ const response = await this._authHelper.httpRequest(this._httpClient, requestConfig, this._isHttpHandlerEnabled, this._emitHttpError, this._emitHttpFinish);
2707
+ if (this._isHttpHandlerEnabled) this._emitHttpFinish();
2708
+ return response;
2635
2709
  }
2636
2710
  async httpRequestAll(configs) {
2637
2711
  await this._validateMethod(false);
2638
- return this._authHelper.httpRequestAll(configs, this._httpClient, this._isHttpHandlerEnabled, this._httpErrorCallback, this._httpFinishCallback);
2712
+ const responses = await this._authHelper.httpRequestAll(configs, this._httpClient, this._isHttpHandlerEnabled, this._emitHttpError, this._emitHttpFinish);
2713
+ if (this._isHttpHandlerEnabled) this._emitHttpFinish();
2714
+ return responses;
2639
2715
  }
2640
2716
  async getUser(userId) {
2641
2717
  await this._validateMethod();
2642
2718
  return super.getUser(userId);
2643
2719
  }
2720
+ /**
2721
+ * Fetches the signed-in user's profile from the `/users/me` endpoint (or `endpoints.usersMe` when
2722
+ * configured), sending the request through the client's HTTP client so the access token is attached.
2723
+ *
2724
+ * @param options - Optional `baseUrl` or full `url` overrides for the profile endpoint.
2725
+ * @returns The user's profile.
2726
+ */
2727
+ async getUserProfile(options) {
2728
+ await this._validateMethod();
2729
+ const configData = await this.getStorageManager().getConfigData();
2730
+ const profile = await getUsersMe({
2731
+ baseUrl: options?.baseUrl ?? configData?.baseUrl,
2732
+ fetcher: createHttpClientFetcher_default(this._browserInstanceId),
2733
+ url: resolveResourceEndpoint("usersMe", configData, options?.url)
2734
+ });
2735
+ return {
2736
+ flattenedProfile: generateFlattenedUserProfile(profile),
2737
+ profile
2738
+ };
2739
+ }
2644
2740
  async getAccessToken(sessionId) {
2645
2741
  return super.getAccessToken(sessionId);
2646
2742
  }
@@ -2669,7 +2765,7 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2669
2765
  return super.isSignedIn(userId);
2670
2766
  }
2671
2767
  notifySignIn(user) {
2672
- this._onSignInCallback(user);
2768
+ this._emitHook(Hooks_default.SignIn, user);
2673
2769
  }
2674
2770
  async isSessionActive() {
2675
2771
  await this.isInitialized();
@@ -2688,14 +2784,14 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2688
2784
  const timer = await this._spaHelper.getRefreshTimeoutTimer();
2689
2785
  await super.revokeAccessToken(userId);
2690
2786
  await this._spaHelper.clearRefreshTokenTimeout(timer);
2691
- this._onEndUserSession && await this._onEndUserSession(true);
2787
+ await this._emitHook(Hooks_default.RevokeAccessToken, true);
2692
2788
  return true;
2693
2789
  }
2694
2790
  async exchangeToken(config) {
2695
2791
  if (config.signInRequired) await this._validateMethod();
2696
2792
  if (!config.id) return Promise.reject(new ThunderIDAuthException("SPA-AUTH_CLIENT-RCG-NF01", "The custom grant request id not found.", "Set the `id` attribute on the token exchange config."));
2697
2793
  const response = await this._authHelper.exchangeToken(config, (cfg) => this._enableRetrievingSignOutURLFromSession(cfg));
2698
- this._onCustomGrant.get(config.id)?.(response);
2794
+ this._emitHook(`${Hooks_default.CustomGrant}:${config.id}`, response);
2699
2795
  return response;
2700
2796
  }
2701
2797
  async reInitialize(config) {
@@ -2722,47 +2818,56 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2722
2818
  async decodeJwtToken(token) {
2723
2819
  return this.getCryptoHelper().decodeJwtToken(token);
2724
2820
  }
2821
+ /**
2822
+ * Subscribes a callback to a lifecycle hook. Every callback registered for a hook is called when it
2823
+ * fires; registering the same callback again for the same hook has no further effect. Hooks can be
2824
+ * registered before or after `initialize()`.
2825
+ *
2826
+ * @param hook - The hook to subscribe to (see `Hooks`).
2827
+ * @param callback - Called when the hook fires.
2828
+ * @param id - The custom grant id. Required for `custom-grant`.
2829
+ */
2725
2830
  async on(hook, callback, id) {
2726
2831
  await this.isInitialized();
2727
2832
  if (!callback || typeof callback !== "function") throw new ThunderIDAuthException("SPA-AUTH_CLIENT-ON-IV02", "Invalid callback function.", "The provided callback must be a function.");
2728
2833
  switch (hook) {
2729
- case "sign-in":
2730
- this._onSignInCallback = callback;
2731
- break;
2732
- case "sign-out":
2733
- this._onSignOutCallback = callback;
2734
- if (await SPAUtils_default.isSignOutSuccessful(ThunderIDBrowserClient.isSignOutSuccessful.bind(ThunderIDBrowserClient), () => ThunderIDBrowserClient.clearSession())) this._onSignOutCallback();
2735
- break;
2736
- case "revoke-access-token":
2737
- this._onEndUserSession = callback;
2738
- break;
2739
- case "initialize":
2740
- this._onInitialize = callback;
2741
- break;
2742
- case "http-request-error":
2743
- this._httpErrorCallback = callback;
2744
- break;
2745
- case "http-request-finish":
2746
- this._httpFinishCallback = callback;
2834
+ case Hooks_default.SignIn:
2835
+ case Hooks_default.RevokeAccessToken:
2836
+ case Hooks_default.Initialize:
2837
+ case Hooks_default.HttpRequestError:
2838
+ case Hooks_default.HttpRequestFinish:
2839
+ case Hooks_default.HttpRequestStart:
2840
+ case Hooks_default.HttpRequestSuccess:
2841
+ this._addHookCallback(hook, callback);
2747
2842
  break;
2748
- case "http-request-start":
2749
- this._httpClient?.setHttpRequestStartCallback?.(callback);
2843
+ case Hooks_default.SignOut:
2844
+ if (await SPAUtils_default.isSignOutSuccessful(ThunderIDBrowserClient.isSignOutSuccessful.bind(ThunderIDBrowserClient), () => ThunderIDBrowserClient.clearSession())) callback();
2750
2845
  break;
2751
- case "http-request-success":
2752
- this._httpClient?.setHttpRequestSuccessCallback?.(callback);
2846
+ case Hooks_default.CustomGrant:
2847
+ id && this._addHookCallback(`${Hooks_default.CustomGrant}:${id}`, callback);
2753
2848
  break;
2754
- case "custom-grant":
2755
- id && this._onCustomGrant.set(id, callback);
2756
- break;
2757
- case "sign-out-failed": {
2758
- this._onSignOutFailedCallback = callback;
2849
+ case Hooks_default.SignOutFailed: {
2759
2850
  const signOutFail = SPAUtils_default.didSignOutFail(ThunderIDBrowserClient.didSignOutFail.bind(ThunderIDBrowserClient));
2760
- if (signOutFail) this._onSignOutFailedCallback(signOutFail);
2851
+ if (signOutFail) callback(signOutFail);
2761
2852
  break;
2762
2853
  }
2763
2854
  default: throw new ThunderIDAuthException("SPA-AUTH_CLIENT-ON-IV01", "Invalid hook.", `Unknown hook: "${hook}"`);
2764
2855
  }
2765
2856
  }
2857
+ _addHookCallback(hook, callback) {
2858
+ const callbacks = this._hookCallbacks.get(hook) ?? /* @__PURE__ */ new Set();
2859
+ callbacks.add(callback);
2860
+ this._hookCallbacks.set(hook, callbacks);
2861
+ }
2862
+ _emitHook(hook, response) {
2863
+ return Promise.all([...this._hookCallbacks.get(hook) ?? []].map((callback) => Promise.resolve(callback(response))));
2864
+ }
2865
+ _emitHttpError = async (error) => {
2866
+ await this._emitHook(Hooks_default.HttpRequestError, error);
2867
+ };
2868
+ _emitHttpFinish = () => {
2869
+ this._emitHook(Hooks_default.HttpRequestFinish);
2870
+ };
2766
2871
  _enableRetrievingSignOutURLFromSession(config) {
2767
2872
  if (config.preventSignOutURLUpdate) this._getSignOutURLFromSessionStorage = true;
2768
2873
  }
@@ -2788,26 +2893,6 @@ var ThunderIDBrowserClient = class ThunderIDBrowserClient extends ThunderIDJavaS
2788
2893
  };
2789
2894
  var ThunderIDBrowserClient_default = ThunderIDBrowserClient;
2790
2895
 
2791
- //#endregion
2792
- //#region src/constants/Hooks.ts
2793
- /**
2794
- * Event hook names for attaching callbacks via `ThunderIDBrowserClient.on()`.
2795
- */
2796
- let Hooks = /* @__PURE__ */ function(Hooks$1) {
2797
- Hooks$1["SignIn"] = "sign-in";
2798
- Hooks$1["SignOut"] = "sign-out";
2799
- Hooks$1["Initialize"] = "initialize";
2800
- Hooks$1["HttpRequestStart"] = "http-request-start";
2801
- Hooks$1["HttpRequestFinish"] = "http-request-finish";
2802
- Hooks$1["HttpRequestError"] = "http-request-error";
2803
- Hooks$1["HttpRequestSuccess"] = "http-request-success";
2804
- Hooks$1["RevokeAccessToken"] = "revoke-access-token";
2805
- Hooks$1["CustomGrant"] = "custom-grant";
2806
- Hooks$1["SignOutFailed"] = "sign-out-failed";
2807
- return Hooks$1;
2808
- }({});
2809
- var Hooks_default = Hooks;
2810
-
2811
2896
  //#endregion
2812
2897
  //#region src/utils/hasAuthParamsInUrl.ts
2813
2898
  /**
@@ -2860,54 +2945,26 @@ const http = (client) => {
2860
2945
  var http_default = http;
2861
2946
 
2862
2947
  //#endregion
2863
- //#region src/utils/createHttpClientFetcher.ts
2948
+ //#region src/utils/copyTextToClipboard.ts
2864
2949
  /**
2865
- * Builds a `fetch`-shaped function backed by the shared `FetchHttpClient` singleton, for
2866
- * framework wrappers (`@thunderid/react`, `@thunderid/vue`) that need to hand a core
2867
- * `@thunderid/javascript` API function a `fetcher` carrying the access token automatically.
2868
- *
2869
- * Unlike a plain `fetch` call, `FetchHttpClient.request` throws on a non-2xx response instead
2870
- * of resolving it. This adapter catches that and reconstructs a `Response`-shaped object from
2871
- * the thrown error's `response`, so the core API function sees the real status and body instead
2872
- * of treating every non-2xx result as a network failure. A genuine network error (no response
2873
- * attached) still propagates.
2950
+ * Copies text to the clipboard. It uses the Clipboard API, and falls back to a hidden textarea and
2951
+ * `document.execCommand('copy')` where the API is missing or refuses, such as on a page without a secure context.
2874
2952
  *
2875
- * @param instanceId - Which `FetchHttpClient` instance to use, for multi-instance apps. Defaults to `0`.
2876
- * @returns A function matching the `(url, config) => Promise<Response>` shape core API functions expect.
2877
- * @example
2878
- * ```typescript
2879
- * await updateMeCredentials({
2880
- * ...config,
2881
- * fetcher: fetcher ?? createHttpClientFetcher(instanceId),
2882
- * });
2883
- * ```
2953
+ * @param text - The text to copy.
2884
2954
  */
2885
- const createHttpClientFetcher = (instanceId = 0) => {
2886
- return async (url, config) => {
2887
- const httpClient = FetchHttpClient_default.getInstance(instanceId);
2888
- const toResponse = (data, status, statusText) => ({
2889
- json: () => Promise.resolve(data),
2890
- ok: status >= 200 && status < 300,
2891
- status,
2892
- statusText,
2893
- text: () => Promise.resolve(typeof data === "string" ? data : JSON.stringify(data))
2894
- });
2895
- try {
2896
- const response = await httpClient.request({
2897
- data: config.body ? JSON.parse(config.body) : void 0,
2898
- headers: config.headers,
2899
- method: config.method || "POST",
2900
- url
2901
- });
2902
- return toResponse(response.data, response.status, response.statusText || "");
2903
- } catch (error) {
2904
- const httpError = error;
2905
- if (httpError?.response) return toResponse(httpError.response.data, httpError.response.status, httpError.response.statusText ?? "");
2906
- throw error;
2907
- }
2908
- };
2955
+ const copyTextToClipboard = async (text) => {
2956
+ try {
2957
+ await navigator.clipboard.writeText(text);
2958
+ } catch {
2959
+ const textArea = document.createElement("textarea");
2960
+ textArea.value = text;
2961
+ document.body.appendChild(textArea);
2962
+ textArea.select();
2963
+ document.execCommand("copy");
2964
+ document.body.removeChild(textArea);
2965
+ }
2909
2966
  };
2910
- var createHttpClientFetcher_default = createHttpClientFetcher;
2967
+ var copyTextToClipboard_default = copyTextToClipboard;
2911
2968
 
2912
2969
  //#endregion
2913
2970
  //#region src/utils/handleWebAuthnAuthentication.ts
@@ -3480,6 +3537,23 @@ const AVATAR_PALETTES = [
3480
3537
  * without needing the actual palette values.
3481
3538
  */
3482
3539
  const AVATAR_GRADIENT_COUNT = AVATAR_PALETTES.length;
3540
+ const XML_ESCAPES = {
3541
+ "\"": "&quot;",
3542
+ "&": "&amp;",
3543
+ "'": "&apos;",
3544
+ "<": "&lt;",
3545
+ ">": "&gt;"
3546
+ };
3547
+ /**
3548
+ * Escapes a caller-supplied value for interpolation into SVG text.
3549
+ */
3550
+ const escapeXml = (value) => value.replace(/["&'<>]/g, (ch) => XML_ESCAPES[ch]);
3551
+ /**
3552
+ * Encodes an SVG document as a data URI. Lone UTF-16 surrogates are replaced with U+FFFD
3553
+ * first, since `encodeURIComponent` throws a `URIError` on them. `Array.from` iterates by
3554
+ * code point, so a surrogate it yields on its own is unpaired.
3555
+ */
3556
+ const toSvgDataUri = (svg) => `data:image/svg+xml,${encodeURIComponent(Array.from(svg, (ch) => ch.length === 1 && ch >= "\ud800" && ch <= "\udfff" ? "�" : ch).join(""))}`;
3483
3557
  /**
3484
3558
  * Fills in `params.content` when the spec itself didn't carry one, deriving it from a raw
3485
3559
  * seed name (e.g. an app or user display name) — extracting initials for letter variants,
@@ -3530,11 +3604,10 @@ const generateGradientAvatar = ({ shape, variant, content, colors, bg }) => {
3530
3604
  let text = "";
3531
3605
  if (variant !== "blank") {
3532
3606
  const letterCount = variant === "one_letter" ? 1 : 2;
3533
- const initials = (content || "A").toUpperCase().slice(0, letterCount);
3607
+ const initials = escapeXml(Array.from((content || "A").toUpperCase()).slice(0, letterCount).join(""));
3534
3608
  text = `<text x="50" y="64" font-family="Helvetica,Arial,sans-serif" font-weight="700" font-size="${letterCount === 1 ? 46 : 34}" text-anchor="middle" fill="#fff">${initials}</text>`;
3535
3609
  }
3536
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${defs}${shapeMarkup(shape, background)}${text}</svg>`;
3537
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
3610
+ return toSvgDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${defs}${shapeMarkup(shape, background)}${text}</svg>`);
3538
3611
  };
3539
3612
  /**
3540
3613
  * Renders one of the curated anonymous-animal icons, applying the requested background
@@ -3546,8 +3619,7 @@ const generateAnonymousAnimalAvatar = ({ shape, content, bg }) => {
3546
3619
  const icon = ANONYMOUS_ANIMAL_ICONS[Object.prototype.hasOwnProperty.call(ANONYMOUS_ANIMAL_ICONS, content) ? content : pickAnonymousAvatarName_default()];
3547
3620
  const color = bg ?? icon.color;
3548
3621
  const markup = icon.markup.replaceAll("%COLOR%", color);
3549
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${shapeMarkup(shape, color)}${markup}</svg>`;
3550
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
3622
+ return toSvgDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${shapeMarkup(shape, color)}${markup}</svg>`);
3551
3623
  };
3552
3624
  /**
3553
3625
  * Renders one of the curated anonymous-entity icons (application/organization/resource-server
@@ -3559,8 +3631,7 @@ const generateAnonymousEntityAvatar = ({ shape, content, bg }) => {
3559
3631
  const icon = ANONYMOUS_ENTITY_ICONS[Object.prototype.hasOwnProperty.call(ANONYMOUS_ENTITY_ICONS, content) ? content : pickAnonymousEntityName_default()];
3560
3632
  const color = bg ?? icon.color;
3561
3633
  const markup = icon.markup.replaceAll("%COLOR%", color);
3562
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${shapeMarkup(shape, color)}${markup}</svg>`;
3563
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
3634
+ return toSvgDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">${shapeMarkup(shape, color)}${markup}</svg>`);
3564
3635
  };
3565
3636
  /**
3566
3637
  * Generates an avatar image as an `svg+xml` data URI from a fully-resolved {@link AvatarParams}.
@@ -3677,7 +3748,8 @@ const createClassObserver = (targetElement, callback, config = {}) => {
3677
3748
  return observer;
3678
3749
  };
3679
3750
  /**
3680
- * Creates a media query listener for system theme changes
3751
+ * Creates a media query listener for system theme changes. Call `disconnect()` on the returned
3752
+ * object to remove the listener.
3681
3753
  */
3682
3754
  const createMediaQueryListener = (callback) => {
3683
3755
  if (typeof window === "undefined" || !window.matchMedia) return null;
@@ -3687,7 +3759,10 @@ const createMediaQueryListener = (callback) => {
3687
3759
  };
3688
3760
  if (mediaQuery.addEventListener) mediaQuery.addEventListener("change", handleChange);
3689
3761
  else mediaQuery.addListener(handleChange);
3690
- return mediaQuery;
3762
+ return Object.assign(mediaQuery, { disconnect: () => {
3763
+ if (mediaQuery.removeEventListener) mediaQuery.removeEventListener("change", handleChange);
3764
+ else mediaQuery.removeListener(handleChange);
3765
+ } });
3691
3766
  };
3692
3767
 
3693
3768
  //#endregion
@@ -3742,5 +3817,5 @@ const normalizeBorderRadius = (raw) => {
3742
3817
  var normalizeBorderRadius_default = normalizeBorderRadius;
3743
3818
 
3744
3819
  //#endregion
3745
- export { ACCESS_TOKEN_INVALID, ANONYMOUS_ANIMAL_ICONS, ANONYMOUS_ENTITY_ICONS, AVATAR_GRADIENT_COUNT, AVATAR_URI_SCHEME, AuthenticationHelper_default as AuthenticationHelper, 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, 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 };
3820
+ export { ACCESS_TOKEN_INVALID, ANONYMOUS_ANIMAL_ICONS, ANONYMOUS_ENTITY_ICONS, AVATAR_GRADIENT_COUNT, AVATAR_URI_SCHEME, AuthenticationHelper_default as AuthenticationHelper, 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 };
3746
3821
  //# sourceMappingURL=index.js.map