@rixl/sdk 0.9.0 → 0.9.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.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { atom } from "nanostores";
2
2
  import { decodeJwt } from "jose";
3
3
  import * as v from "valibot";
4
- import "ky";
5
4
 
6
5
  //#region src/generated/core/bodySerializer.gen.ts
7
6
  const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
@@ -352,9 +351,7 @@ const setAuthParams = async ({ security, ...options }) => {
352
351
  case "cookie":
353
352
  options.headers.append("Cookie", `${name}=${token}`);
354
353
  break;
355
- default:
356
- options.headers.set(name, token);
357
- break;
354
+ default: options.headers.set(name, token);
358
355
  }
359
356
  }
360
357
  };
@@ -515,9 +512,7 @@ const createClient = (config = {}) => {
515
512
  case "stream":
516
513
  emptyData = response.body;
517
514
  break;
518
- default:
519
- emptyData = {};
520
- break;
515
+ default: emptyData = {};
521
516
  }
522
517
  return opts.responseStyle === "data" ? emptyData : {
523
518
  data: emptyData,
@@ -2324,6 +2319,15 @@ function validateOAuthState(provider, state) {
2324
2319
  return sessionStorage.getItem(storagePath(provider)) === state;
2325
2320
  }
2326
2321
  /**
2322
+ * Discards the stored state for a provider so the next login round-trip mints a
2323
+ * fresh one. The state doubles as the OAuth `nonce`, so reusing it across logins
2324
+ * would replay a nonce the provider has already issued a token for.
2325
+ * @param provider The provider identifier
2326
+ */
2327
+ function clearOauthState(provider) {
2328
+ sessionStorage.removeItem(storagePath(provider));
2329
+ }
2330
+ /**
2327
2331
  * Gets or Generates a complete state parameter for OAuth requests
2328
2332
  * @param provider The identifier for the provider (e.g., 'google', 'apple')
2329
2333
  * @returns A state string containing the provider identifier and random data
@@ -2347,9 +2351,10 @@ function generateOauthState(provider) {
2347
2351
  * Builds an OAuth URL from configuration and metadata
2348
2352
  */
2349
2353
  const buildOAuthUrl = (config, metadata, state) => {
2354
+ const redirectUri = config.redirectUri ?? window.location.origin;
2350
2355
  const params = new URLSearchParams({
2351
2356
  client_id: config.clientId,
2352
- redirect_uri: window.location.origin,
2357
+ redirect_uri: redirectUri,
2353
2358
  response_type: metadata.responseType,
2354
2359
  scope: [...metadata.defaultScopes, ...config.scope ? [config.scope] : []].join(" "),
2355
2360
  state
@@ -2372,8 +2377,8 @@ const warnProviderNotConfigured = (providerName) => {
2372
2377
  * This factory reduces code duplication across OAuth providers
2373
2378
  */
2374
2379
  const createOAuthProvider = ({ provider, metadata }) => {
2375
- const config = atom(null);
2376
- const authUrl = atom(null);
2380
+ const config = shared(`provider.${provider}.config`, () => atom(null));
2381
+ const authUrl = shared(`provider.${provider}.authUrl`, () => atom(null));
2377
2382
  const updateAuthUrl = () => {
2378
2383
  const currentConfig = config.get();
2379
2384
  if (!currentConfig) {
@@ -2449,8 +2454,8 @@ const updateAppleAuthUrl = appleProvider.updateAuthUrl;
2449
2454
 
2450
2455
  //#endregion
2451
2456
  //#region src/auth/providers/telegram.ts
2452
- const telegramConfig = atom(null);
2453
- const telegramAuthUrl = atom(null);
2457
+ const telegramConfig = shared("provider.telegram.config", () => atom(null));
2458
+ const telegramAuthUrl = shared("provider.telegram.authUrl", () => atom(null));
2454
2459
  /**
2455
2460
  * Updates the Telegram authentication URL using the configured settings
2456
2461
  */
@@ -2483,7 +2488,7 @@ function extractProviderFromState(state) {
2483
2488
  const parts = state.split("_");
2484
2489
  if (parts.length >= 1) return parts[0];
2485
2490
  }
2486
- const OAUTH_PROVIDERS = [
2491
+ const OAUTH_PROVIDERS$1 = [
2487
2492
  "google",
2488
2493
  "apple",
2489
2494
  "microsoft"
@@ -2499,7 +2504,7 @@ function detectProvider() {
2499
2504
  const state = urlParams.get("state");
2500
2505
  if (id_token && state) {
2501
2506
  const providerFromState = extractProviderFromState(state);
2502
- return OAUTH_PROVIDERS.find((p) => providerFromState === p && validateOAuthState(p, state));
2507
+ return OAUTH_PROVIDERS$1.find((p) => providerFromState === p && validateOAuthState(p, state));
2503
2508
  }
2504
2509
  }
2505
2510
  /**
@@ -2513,20 +2518,210 @@ function getProviderToken(provider) {
2513
2518
  }
2514
2519
 
2515
2520
  //#endregion
2516
- //#region src/auth/api/refresh-tokens.ts
2517
- const refreshTokens = async (provider, token, options) => {
2518
- const { data } = await authV1TokenServiceRefreshToken({
2519
- body: {
2521
+ //#region src/auth/providers/callback.ts
2522
+ /** Params that carry the credential itself their presence marks a callback response. */
2523
+ const CREDENTIAL_PARAMS = [
2524
+ "id_token",
2525
+ "code",
2526
+ "access_token",
2527
+ "tgAuthResult",
2528
+ "tgWebAppData"
2529
+ ];
2530
+ /** Everything the providers append to the redirect URL, credential and bookkeeping alike. */
2531
+ const OAUTH_RESPONSE_PARAMS = [
2532
+ ...CREDENTIAL_PARAMS,
2533
+ "state",
2534
+ "token_type",
2535
+ "expires_in",
2536
+ "scope",
2537
+ "session_state",
2538
+ "authuser",
2539
+ "prompt",
2540
+ "hd"
2541
+ ];
2542
+ /**
2543
+ * Whether the URL this page loaded with carries a provider response at all —
2544
+ * unlike `detectProvider()`, this does not require the state to validate. The
2545
+ * gap between the two is the case where a login round-trip came back but cannot
2546
+ * be used, which otherwise ends the flow in silence.
2547
+ */
2548
+ const hasProviderResponse = () => CREDENTIAL_PARAMS.some((key) => urlParams.has(key));
2549
+ const AUTH_URL_UPDATERS = {
2550
+ ["google"]: updateGoogleAuthUrl,
2551
+ ["apple"]: updateAppleAuthUrl,
2552
+ ["microsoft"]: updateMicrosoftAuthUrl
2553
+ };
2554
+ /** Returns undefined when the half holds no credential, meaning it is the app's own and must not be touched. */
2555
+ const stripped = (query) => {
2556
+ const params = new URLSearchParams(query);
2557
+ if (!CREDENTIAL_PARAMS.some((key) => params.has(key))) return void 0;
2558
+ OAUTH_RESPONSE_PARAMS.forEach((key) => params.delete(key));
2559
+ return params.toString();
2560
+ };
2561
+ /**
2562
+ * Providers put their response in the query string or the fragment depending on
2563
+ * `response_mode`, and either half may also hold params the app itself put
2564
+ * there. Only the half actually carrying the credential is rewritten.
2565
+ */
2566
+ const withoutOAuthResponse = (href) => {
2567
+ const url = new URL(href);
2568
+ url.search = stripped(url.search) ?? url.search;
2569
+ url.hash = stripped(url.hash.slice(1)) ?? url.hash;
2570
+ return url.toString();
2571
+ };
2572
+ /**
2573
+ * Retires the provider response after `initClient` is done with it.
2574
+ *
2575
+ * Left in place, the credential stays in the address bar and every reload — or
2576
+ * Vite's HMR full reload — replays an already-consumed `id_token`, which the
2577
+ * gateway rejects. Clearing the stored state on the way out also stops the next
2578
+ * login from reusing this round-trip's `nonce`.
2579
+ *
2580
+ * The `urlParams` snapshot is taken at import time and is intentionally left
2581
+ * untouched, so callers that already read the credential out of it keep working
2582
+ * for the rest of this page load.
2583
+ */
2584
+ const completeOAuthCallback = () => {
2585
+ const provider = detectProvider();
2586
+ if (!provider) return;
2587
+ clearOauthState(provider);
2588
+ AUTH_URL_UPDATERS[provider]?.();
2589
+ window.history.replaceState(window.history.state, "", withoutOAuthResponse(window.location.href));
2590
+ };
2591
+
2592
+ //#endregion
2593
+ //#region src/auth/api/types.ts
2594
+ /**
2595
+ * Generic API error class - wraps ky's HTTPError for consistency
2596
+ */
2597
+ var ApiError = class extends Error {
2598
+ status;
2599
+ endpoint;
2600
+ data;
2601
+ constructor(message, ...rest) {
2602
+ super(message);
2603
+ this.name = "ApiError";
2604
+ const firstArg = rest[0];
2605
+ if (typeof firstArg === "number") {
2606
+ this.status = firstArg;
2607
+ this.endpoint = rest[1] ?? "";
2608
+ this.data = rest[2];
2609
+ return;
2610
+ }
2611
+ this.status = firstArg.status;
2612
+ this.endpoint = firstArg.endpoint;
2613
+ this.data = firstArg.data;
2614
+ }
2615
+ };
2616
+
2617
+ //#endregion
2618
+ //#region src/auth/providers/diagnostics.ts
2619
+ const claimsOf = (token) => {
2620
+ try {
2621
+ const { iss, aud, exp, nonce, email } = decodeJwt(token);
2622
+ return {
2623
+ iss: String(iss ?? ""),
2624
+ aud: String(aud ?? ""),
2625
+ exp: exp ? (/* @__PURE__ */ new Date(Number(exp) * 1e3)).toISOString() : void 0,
2626
+ expired: typeof exp === "number" && Date.now() >= exp * 1e3,
2627
+ nonce: String(nonce ?? ""),
2628
+ email: String(email ?? "")
2629
+ };
2630
+ } catch {
2631
+ return { note: "not a JWT — expected for Telegram, unexpected for Google/Apple/Microsoft" };
2632
+ }
2633
+ };
2634
+ const verdictFor = (error) => {
2635
+ if ((typeof error.data === "object" && error.data !== null ? error.data : void 0)?.error === "invalid token type") return "CLIENT: the gateway does not accept this token_type. Expected one of Bearer, google, apple, microsoft, tgAuthResult, tgWebAppData.";
2636
+ if (error.status === HTTP_STATUS.UNAUTHORIZED) return "BACKEND or CONFIG: the gateway could not verify the credential. Compare `aud` above with the client ID the gateway is configured with, and check `expired`.";
2637
+ if (error.status === HTTP_STATUS.CONFLICT) return "EXPECTED: this email already belongs to an account created with a different provider.";
2638
+ if (error.status === HTTP_STATUS.BAD_REQUEST) return "BACKEND: the gateway rejected the request body. Read `response.body` for its reason.";
2639
+ return "UNKNOWN: see `response` below.";
2640
+ };
2641
+ const describe = (error) => {
2642
+ if (!(error instanceof ApiError)) return {
2643
+ response: {
2644
+ status: "none — the request never completed",
2645
+ detail: String(error)
2646
+ },
2647
+ verdict: "NETWORK or CORS: the browser never got a response. Check the gateway is reachable and its CORS headers allow this origin."
2648
+ };
2649
+ return {
2650
+ response: {
2651
+ status: error.status,
2652
+ body: error.data,
2653
+ message: error.message
2654
+ },
2655
+ verdict: verdictFor(error)
2656
+ };
2657
+ };
2658
+ const OAUTH_PROVIDERS = [
2659
+ "google",
2660
+ "apple",
2661
+ "microsoft"
2662
+ ];
2663
+ const storedStates = () => Object.fromEntries(OAUTH_PROVIDERS.map((p) => [p, sessionStorage.getItem("__rixl_auth_state_" + p) ?? "(none)"]));
2664
+ /**
2665
+ * Reports a provider response that arrived but could not be used, which ends the
2666
+ * login without a request ever being sent — the case that otherwise looks like
2667
+ * "it just went back to the login page".
2668
+ *
2669
+ * `detectProvider()` requires the `state` in the URL to equal the one stored when
2670
+ * the login started, so the two states below are the thing to compare.
2671
+ */
2672
+ const logUnusableProviderResponse = () => {
2673
+ const state = urlParams.get("state") ?? "(none)";
2674
+ console.error("[@rixl/sdk] a provider response is in the URL but no login was attempted", {
2675
+ urlState: state,
2676
+ storedStates: storedStates(),
2677
+ credentialParams: [
2678
+ "id_token",
2679
+ "code",
2680
+ "access_token"
2681
+ ].filter((key) => urlParams.has(key)),
2682
+ verdict: state === "(none)" ? "CLIENT: the provider returned no `state`, so the response cannot be matched to a login attempt." : "CLIENT: `urlState` does not match the stored state for its provider. sessionStorage was cleared, the login started in a different tab, or the response was replayed after the state had already been retired."
2683
+ });
2684
+ };
2685
+ /**
2686
+ * Reports a failed provider token exchange. `provider` doubles as the `token_type`
2687
+ * sent on the wire, so it is printed as-is.
2688
+ */
2689
+ const logProviderExchangeFailure = (provider, credential, error) => {
2690
+ console.error("[@rixl/sdk] provider login failed at POST /auth/v1/token", {
2691
+ request: {
2520
2692
  token_type: provider,
2521
- refresh_token: token,
2522
- country_code: options?.countryCode,
2523
- origin: options?.origin
2693
+ credential: claimsOf(credential)
2524
2694
  },
2525
- throwOnError: true
2695
+ ...describe(error)
2526
2696
  });
2527
- return data;
2528
2697
  };
2529
2698
 
2699
+ //#endregion
2700
+ //#region src/auth/social/socialState.ts
2701
+ const socialStoragePath = (provider) => SOCIAL_CONNECT_KEY_PREFIX + provider;
2702
+ /**
2703
+ * Sets a flag indicating that a social provider connection is being attempted
2704
+ * @param provider The provider identifier
2705
+ */
2706
+ function setSocialConnectAttempt(provider) {
2707
+ sessionStorage.setItem(socialStoragePath(provider), "true");
2708
+ }
2709
+ /**
2710
+ * Checks if there's a pending social provider connection attempt
2711
+ * @param provider The provider identifier
2712
+ * @returns True if there's a pending connection attempt, false otherwise
2713
+ */
2714
+ function hasSocialConnectAttempt(provider) {
2715
+ return sessionStorage.getItem(socialStoragePath(provider)) === "true";
2716
+ }
2717
+ /**
2718
+ * Clears the social provider connection attempt flag
2719
+ * @param provider The provider identifier
2720
+ */
2721
+ function clearSocialConnectAttempt(provider) {
2722
+ sessionStorage.removeItem(socialStoragePath(provider));
2723
+ }
2724
+
2530
2725
  //#endregion
2531
2726
  //#region src/auth/initialization.ts
2532
2727
  /**
@@ -2546,534 +2741,94 @@ function createDeferred() {
2546
2741
  };
2547
2742
  }
2548
2743
  /**
2549
- * Global deferred promise that tracks the initialization status of the auth library
2550
- * This is resolved when initClient is called
2744
+ * Global deferred promise that tracks the initialization status of the auth library.
2745
+ * This is resolved when initClient is called.
2746
+ *
2747
+ * Shared across copies of this package. `connect()` runs in exactly one copy, so
2748
+ * a per-copy deferred leaves every other copy awaiting a promise nothing will
2749
+ * ever resolve — and because getToken() and the request interceptor both await
2750
+ * it, that is not a slow path but a permanent hang.
2551
2751
  */
2552
- const initDeferred = createDeferred();
2752
+ const initDeferred = shared("initDeferred", createDeferred);
2553
2753
 
2554
2754
  //#endregion
2555
- //#region src/auth/cookie/util.ts
2556
- const getAllCookiesStartWith = (startWithKey) => {
2557
- if (typeof document === "undefined") return {};
2558
- return document.cookie.split(";").map((cv) => cv.split("=").map((v) => v.trim())).filter(([key]) => key && key !== startWithKey && key.startsWith(startWithKey)).reduce((ac, [key, value]) => Object.assign(ac, { [key.slice(startWithKey.length + 1)]: value }), {});
2755
+ //#region src/auth/api/error-handlers.ts
2756
+ /**
2757
+ * Helper to create error functions - reduces bundle size by reusing error creation logic
2758
+ */
2759
+ const err = (message) => () => new Error(message);
2760
+ /** Reusable error handlers for common cases - reduces repetitive error messages */
2761
+ const commonErrors = {
2762
+ unauthorized: err("User is not authorized"),
2763
+ badRequest: err("Bad request"),
2764
+ notFound: err("Not found"),
2765
+ conflict: err("Resource already exists"),
2766
+ forbidden: err("Forbidden"),
2767
+ tooManyRequests: err("Too many requests")
2559
2768
  };
2560
- function setCookie(name, value, options) {
2561
- if (typeof document === "undefined") return;
2562
- let cookieString = `${encodeName(name)}=${encodeValue(value)}`;
2563
- if (options) {
2564
- if (options.expires) if (typeof options.expires === "number") {
2565
- const date = /* @__PURE__ */ new Date();
2566
- date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1e3);
2567
- cookieString += `; expires=${date.toUTCString()}`;
2568
- } else cookieString += `; expires=${options.expires.toUTCString()}`;
2569
- if (options.path) cookieString += `; path=${options.path}`;
2570
- if (options.domain) cookieString += `; domain=${options.domain}`;
2571
- if (options.secure) cookieString += `; secure`;
2572
- if (options.sameSite) cookieString += `; samesite=${options.sameSite}`;
2769
+ /**
2770
+ * Standard error handler for API errors with custom status code handling
2771
+ */
2772
+ const handleApiError = (error, statusHandlers) => {
2773
+ if (error instanceof ApiError) {
2774
+ const handler = statusHandlers[error.status];
2775
+ if (handler) throw new ApiError(handler().message, error.status, error.endpoint, error.data);
2776
+ throw error;
2573
2777
  }
2574
- document.cookie = cookieString;
2575
- }
2576
- function deleteCookie(name) {
2577
- if (typeof document === "undefined") return;
2578
- document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
2579
- }
2580
- const encodeName = (name) => encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent);
2581
- const encodeValue = (value) => encodeURIComponent(value).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent);
2778
+ throw error;
2779
+ };
2582
2780
 
2583
2781
  //#endregion
2584
- //#region src/auth/cookie/index.ts
2585
- const initVals = getAllCookiesStartWith(GLOBAL_PREFIX);
2586
- const setStoreCookie = (key, value) => {
2587
- const expires = /* @__PURE__ */ new Date();
2588
- expires.setTime(expires.getTime() + 30 * 24 * 60 * 60 * 1e3);
2589
- const cookieKey = `${GLOBAL_PREFIX}_${key}`;
2590
- if (!value || value === "") {
2591
- deleteCookie(cookieKey);
2592
- return;
2782
+ //#region src/auth/api/utils.ts
2783
+ /**
2784
+ * Generic helper for API calls to handle initialization and error handling
2785
+ * @param fn The async function to execute
2786
+ * @param errorMap A map of status codes to error functions
2787
+ * @returns The result of the function execution
2788
+ */
2789
+ const apiCall = async (fn, errorMap = {}) => {
2790
+ await initDeferred.promise;
2791
+ try {
2792
+ return await fn();
2793
+ } catch (error) {
2794
+ return handleApiError(error, errorMap);
2593
2795
  }
2594
- const stringValue = typeof value === "string" ? value : JSON.stringify(value);
2595
- setCookie(`${GLOBAL_PREFIX}_${key}`, stringValue, {
2596
- expires,
2597
- path: "/",
2598
- sameSite: "Lax"
2599
- });
2600
2796
  };
2601
2797
 
2602
2798
  //#endregion
2603
- //#region src/auth/userStore.ts
2604
- const userPath = GLOBAL_PREFIX + "_user";
2605
- const parseUser = () => {
2606
- if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.getItem !== "function") return;
2607
- const value = localStorage.getItem(userPath);
2608
- if (value && value != "undefined") try {
2609
- return JSON.parse(value);
2610
- } catch (err) {
2611
- console.warn("Can't parse user data, error: ", err);
2612
- return;
2799
+ //#region src/auth/api/tokens.ts
2800
+ function persistTokens(data) {
2801
+ const tokens = data ?? {};
2802
+ if (tokens.access_token && tokens.refresh_token && tokens.expires_in) {
2803
+ setTokens(tokens.access_token, tokens.refresh_token, Number(tokens.expires_in));
2804
+ return true;
2613
2805
  }
2614
- };
2615
- const user = shared("user", () => {
2616
- const store = atom(parseUser());
2617
- store.subscribe((value) => {
2618
- if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.setItem !== "function") return;
2619
- if (value) localStorage.setItem(userPath, JSON.stringify(value));
2620
- else localStorage.removeItem(userPath);
2621
- });
2622
- return store;
2623
- });
2806
+ return false;
2807
+ }
2624
2808
 
2625
2809
  //#endregion
2626
- //#region src/auth/utils/jwt.ts
2810
+ //#region src/auth/validation/base.ts
2627
2811
  /**
2628
- * Decodes a JWT token and extracts user information
2629
- * Uses jose library for modern, secure JWT handling
2630
- * @param token The JWT token to decode
2631
- * @returns The decoded user data or undefined if decoding fails
2812
+ * 🛡️ INPUT VALIDATION UTILITY
2813
+ * Validates data before API calls and throws user-friendly errors
2632
2814
  */
2633
- const decodeToken = (token) => {
2815
+ const validateInput = (schema, data) => {
2634
2816
  try {
2635
- const decodedUser = decodeJwt(token);
2636
- return {
2637
- id: decodedUser.id,
2638
- email: decodedUser.email,
2639
- first_name: decodedUser.first_name,
2640
- last_name: decodedUser.last_name,
2641
- username: decodedUser.username,
2642
- image_url: decodedUser.image_url,
2643
- language_code: decodedUser.language_code,
2644
- org_id: decodedUser.org_id
2645
- };
2817
+ return v.parse(schema, data);
2646
2818
  } catch (error) {
2647
- console.warn("Failed to decode JWT token. Error: ", error);
2648
- return;
2819
+ if (error instanceof v.ValiError) {
2820
+ const firstError = error.issues[0];
2821
+ throw new Error(firstError.message);
2822
+ }
2823
+ throw error;
2649
2824
  }
2650
2825
  };
2826
+ const EmailSchema = v.pipe(v.string("Email must be text"), v.email("Please enter a valid email address"), v.minLength(1, "Email is required"));
2827
+ const PasswordSchema = v.pipe(v.string("Password must be text"), v.minLength(8, "Password must be at least 8 characters long"), v.regex(/[A-Z]/, "Password must contain at least one uppercase letter"), v.regex(/[a-z]/, "Password must contain at least one lowercase letter"), v.regex(/[0-9]/, "Password must contain at least one number"));
2651
2828
  /**
2652
- * Decodes a JWT token and sets the user in the store
2653
- * @param token The JWT token to decode
2654
- * @returns True if user was successfully decoded and set, false otherwise
2655
- */
2656
- const decodeAndSetUser = (token) => {
2657
- const userData = decodeToken(token);
2658
- if (userData) {
2659
- user.set(userData);
2660
- return true;
2661
- }
2662
- return false;
2663
- };
2664
- /**
2665
- * Checks if a token is expired based on the expiration timestamp
2666
- * @param expireAt The expiration timestamp in milliseconds
2667
- * @returns True if the token is expired, false otherwise
2668
- */
2669
- const isTokenExpired = (expireAt) => {
2670
- if (!expireAt) return true;
2671
- return Date.now() >= expireAt;
2672
- };
2673
-
2674
- //#endregion
2675
- //#region src/auth/authStore.ts
2676
- const isLogged = shared("isLogged", () => atom(initVals["isLogged"] === "true" || detectProvider() !== void 0));
2677
- const accessToken = shared("accessToken", () => atom(initVals["accessToken"]));
2678
- const refreshToken = shared("refreshToken", () => atom(initVals["refreshToken"]));
2679
- const expireAt = shared("expireAt", () => atom(Number(initVals["expireAt"])));
2680
- const authError = shared("authError", () => atom(null));
2681
- const requiresAction = shared("requiresAction", () => atom(initVals["requiresAction"] || null));
2682
- const limitedAccessToken = shared("limitedAccessToken", () => atom(initVals["limitedAccessToken"] || null));
2683
- let currentTokenPromise = null;
2684
- const PROVIDER_URL_MAP = {
2685
- google: googleAuthUrl,
2686
- apple: appleAuthUrl,
2687
- microsoft: microsoftAuthUrl,
2688
- telegram: telegramAuthUrl
2689
- };
2690
- const login = async (provider) => {
2691
- await initDeferred.promise;
2692
- const authUrlAtom = PROVIDER_URL_MAP[provider];
2693
- if (!authUrlAtom) throw new Error(`Unsupported provider: ${provider}`);
2694
- const authUrl = authUrlAtom.get();
2695
- if (authUrl) window.location.href = authUrl;
2696
- else throw new Error(`${provider} provider is not configured. Please check your initClient configuration.`);
2697
- };
2698
- const refreshAccessToken = async (refresh) => {
2699
- const result = await refreshTokens("Bearer", refresh);
2700
- if (!("requires_action" in result)) setTokens(result.access_token, result.refresh_token, result.expires_in);
2701
- };
2702
- const ensureValidAccessToken = async (refresh) => {
2703
- if (accessToken.get() && !isTokenExpired(expireAt.get())) return;
2704
- try {
2705
- await refreshAccessToken(refresh);
2706
- } catch (refreshError) {
2707
- console.error("Token refresh failed in getToken:", refreshError);
2708
- removeTokens();
2709
- throw refreshError;
2710
- }
2711
- };
2712
- const getToken = async () => {
2713
- if (currentTokenPromise) return currentTokenPromise;
2714
- currentTokenPromise = (async () => {
2715
- try {
2716
- await initDeferred.promise;
2717
- if (requiresAction.get()) return void 0;
2718
- const currentRefreshToken = refreshToken.get();
2719
- if (!currentRefreshToken) return void 0;
2720
- await ensureValidAccessToken(currentRefreshToken);
2721
- const token = accessToken.get();
2722
- if (token) decodeAndSetUser(token);
2723
- return token;
2724
- } catch (error) {
2725
- console.warn("Failed to getToken(). Error: ", error);
2726
- throw error;
2727
- } finally {
2728
- currentTokenPromise = null;
2729
- }
2730
- })();
2731
- return currentTokenPromise;
2732
- };
2733
- /**
2734
- * Sets authentication tokens in the store
2735
- * @param access The access token
2736
- * @param refresh The refresh token
2737
- * @param expiresIn The token expiration time in seconds
2738
- */
2739
- const setTokens = (access, refresh, expiresIn) => {
2740
- accessToken.set(access);
2741
- refreshToken.set(refresh);
2742
- expireAt.set(Date.now() + expiresIn * 1e3);
2743
- isLogged.set(true);
2744
- limitedAccessToken.set(null);
2745
- requiresAction.set(null);
2746
- authError.set(null);
2747
- decodeAndSetUser(access);
2748
- };
2749
- /**
2750
- * Removes authentication tokens from the store
2751
- */
2752
- const removeTokens = () => {
2753
- accessToken.set("");
2754
- refreshToken.set("");
2755
- expireAt.set(0);
2756
- isLogged.set(false);
2757
- user.set(void 0);
2758
- limitedAccessToken.set(null);
2759
- requiresAction.set(null);
2760
- authError.set(null);
2761
- };
2762
- /**
2763
- * Clears the auth error from the store
2764
- */
2765
- const clearAuthError = () => {
2766
- authError.set(null);
2767
- };
2768
- /**
2769
- * Sets limited access state for users requiring additional action (e.g., Telegram users without email)
2770
- * @param token The limited scope access token
2771
- * @param action The required action (e.g., "add_email")
2772
- */
2773
- const setLimitedAccessState = (token, action) => {
2774
- limitedAccessToken.set(token);
2775
- requiresAction.set(action);
2776
- authError.set(null);
2777
- isLogged.set(true);
2778
- };
2779
- /**
2780
- * Clears the limited access state (after email verification completes or user logs out)
2781
- */
2782
- const clearLimitedAccessState = () => {
2783
- limitedAccessToken.set(null);
2784
- requiresAction.set(null);
2785
- if (!(!!accessToken.get() && !!refreshToken.get())) isLogged.set(false);
2786
- };
2787
- isLogged.subscribe((value) => setStoreCookie("isLogged", value));
2788
- accessToken.subscribe((value) => setStoreCookie("accessToken", value));
2789
- refreshToken.subscribe((value) => setStoreCookie("refreshToken", value));
2790
- expireAt.subscribe((value) => setStoreCookie("expireAt", value));
2791
- requiresAction.subscribe((value) => setStoreCookie("requiresAction", value));
2792
- limitedAccessToken.subscribe((value) => setStoreCookie("limitedAccessToken", value));
2793
-
2794
- //#endregion
2795
- //#region src/auth/api/types.ts
2796
- /**
2797
- * Generic API error class - wraps ky's HTTPError for consistency
2798
- */
2799
- var ApiError = class extends Error {
2800
- status;
2801
- endpoint;
2802
- data;
2803
- constructor(message, ...rest) {
2804
- super(message);
2805
- this.name = "ApiError";
2806
- const firstArg = rest[0];
2807
- if (typeof firstArg === "number") {
2808
- this.status = firstArg;
2809
- this.endpoint = rest[1] ?? "";
2810
- this.data = rest[2];
2811
- return;
2812
- }
2813
- this.status = firstArg.status;
2814
- this.endpoint = firstArg.endpoint;
2815
- this.data = firstArg.data;
2816
- }
2817
- };
2818
-
2819
- //#endregion
2820
- //#region src/auth/api-url.ts
2821
- /**
2822
- * Global API base URL store
2823
- */
2824
- const apiURL = shared("apiURL", () => atom(""));
2825
-
2826
- //#endregion
2827
- //#region src/auth/api/sdk-client.ts
2828
- function isWireErrorBody(error) {
2829
- return typeof error === "object" && error !== null;
2830
- }
2831
- const state = shared("sdkClientState", () => ({
2832
- configured: false,
2833
- tokenResolver: getToken
2834
- }));
2835
- function setTokenResolver(resolver) {
2836
- state.tokenResolver = resolver;
2837
- }
2838
- /**
2839
- * Routes that do not require a Bearer token at the gateway edge. These
2840
- * authenticate via credentials in the request body (or are webhooks verified by
2841
- * signature), so attaching an Authorization header here would make the gateway
2842
- * attempt to validate a stale/absent token and reject the request with 401.
2843
- * Mirrors `publicRoutes` in backend/gateway/internal/routes/routes.go.
2844
- */
2845
- const publicRoutes = [
2846
- {
2847
- method: "POST",
2848
- path: "/auth/v1/token"
2849
- },
2850
- {
2851
- method: "POST",
2852
- path: "/auth/v1/register"
2853
- },
2854
- {
2855
- method: "POST",
2856
- path: "/auth/v1/login"
2857
- },
2858
- {
2859
- method: "POST",
2860
- path: "/auth/v1/email/verify"
2861
- },
2862
- {
2863
- method: "POST",
2864
- path: "/auth/v1/email/verify/resend"
2865
- },
2866
- {
2867
- method: "POST",
2868
- path: "/auth/v1/password/reset"
2869
- },
2870
- {
2871
- method: "POST",
2872
- path: "/auth/v1/password/reset/confirm"
2873
- },
2874
- {
2875
- method: "POST",
2876
- path: "/auth/v1/verify-totp"
2877
- },
2878
- {
2879
- method: "POST",
2880
- path: "/auth/v1/verify-passkey"
2881
- },
2882
- {
2883
- method: "POST",
2884
- path: "/auth/v1/invitations/",
2885
- prefix: true
2886
- },
2887
- {
2888
- method: "POST",
2889
- path: "/auth/v1/passkey/login/begin"
2890
- },
2891
- {
2892
- method: "POST",
2893
- path: "/auth/v1/passkey/login/finish"
2894
- },
2895
- {
2896
- method: "POST",
2897
- path: "/auth/v1/logout"
2898
- },
2899
- {
2900
- method: "POST",
2901
- path: "/auth/v1/blog/unsubscribe/email"
2902
- },
2903
- {
2904
- method: "POST",
2905
- path: "/auth/v1/blog/broadcast"
2906
- },
2907
- {
2908
- method: "GET",
2909
- path: "/media/v1/videos/",
2910
- prefix: true
2911
- },
2912
- {
2913
- method: "GET",
2914
- path: "/media/v1/images/",
2915
- prefix: true
2916
- },
2917
- {
2918
- method: "GET",
2919
- path: "/media/v1/languages"
2920
- },
2921
- {
2922
- method: "GET",
2923
- path: "/posts/v1/feeds/",
2924
- prefix: true
2925
- },
2926
- {
2927
- method: "POST",
2928
- path: "/billing/webhooks/stripe"
2929
- },
2930
- {
2931
- method: "POST",
2932
- path: "/webhooks/storage"
2933
- },
2934
- {
2935
- method: "POST",
2936
- path: "/platform/auth/v1/token"
2937
- },
2938
- {
2939
- method: "POST",
2940
- path: "/platform/auth/v1/refresh"
2941
- }
2942
- ];
2943
- function isPublicRoute(method, pathname) {
2944
- const match = method.toUpperCase();
2945
- return publicRoutes.some(({ method: m, path, prefix }) => match === m && (prefix ? pathname.startsWith(path) : pathname === path));
2946
- }
2947
- function configureSdkClient() {
2948
- if (state.configured) return;
2949
- state.configured = true;
2950
- configureAllClients(apiURL.get());
2951
- apiURL.subscribe((url) => {
2952
- configureAllClients(url);
2953
- });
2954
- addClientInitializer((client) => {
2955
- client.interceptors.request.use(async (request) => {
2956
- if (request.headers.has("Authorization")) return request;
2957
- const { pathname } = new URL(request.url);
2958
- if (isPublicRoute(request.method, pathname)) return request;
2959
- const token = await state.tokenResolver();
2960
- if (!token) throw new ApiError("No access token available for an authenticated request", HTTP_STATUS.UNAUTHORIZED, pathname);
2961
- request.headers.set("Authorization", `Bearer ${token}`);
2962
- return request;
2963
- });
2964
- client.interceptors.error.use((error, response, request) => {
2965
- if (error instanceof Error) return error;
2966
- const body = isWireErrorBody(error) ? error : void 0;
2967
- const status = response?.status ?? body?.code ?? 0;
2968
- return new ApiError(body?.error || body?.details || (typeof error === "string" ? error : "Request failed"), status, request ? new URL(request.url).pathname : "", error);
2969
- });
2970
- });
2971
- }
2972
-
2973
- //#endregion
2974
- //#region src/auth/social/socialState.ts
2975
- const socialStoragePath = (provider) => SOCIAL_CONNECT_KEY_PREFIX + provider;
2976
- /**
2977
- * Sets a flag indicating that a social provider connection is being attempted
2978
- * @param provider The provider identifier
2979
- */
2980
- function setSocialConnectAttempt(provider) {
2981
- sessionStorage.setItem(socialStoragePath(provider), "true");
2982
- }
2983
- /**
2984
- * Checks if there's a pending social provider connection attempt
2985
- * @param provider The provider identifier
2986
- * @returns True if there's a pending connection attempt, false otherwise
2987
- */
2988
- function hasSocialConnectAttempt(provider) {
2989
- return sessionStorage.getItem(socialStoragePath(provider)) === "true";
2990
- }
2991
- /**
2992
- * Clears the social provider connection attempt flag
2993
- * @param provider The provider identifier
2994
- */
2995
- function clearSocialConnectAttempt(provider) {
2996
- sessionStorage.removeItem(socialStoragePath(provider));
2997
- }
2998
-
2999
- //#endregion
3000
- //#region src/auth/api/error-handlers.ts
3001
- /**
3002
- * Helper to create error functions - reduces bundle size by reusing error creation logic
3003
- */
3004
- const err = (message) => () => new Error(message);
3005
- /** Reusable error handlers for common cases - reduces repetitive error messages */
3006
- const commonErrors = {
3007
- unauthorized: err("User is not authorized"),
3008
- badRequest: err("Bad request"),
3009
- notFound: err("Not found"),
3010
- conflict: err("Resource already exists"),
3011
- forbidden: err("Forbidden"),
3012
- tooManyRequests: err("Too many requests")
3013
- };
3014
- /**
3015
- * Standard error handler for API errors with custom status code handling
3016
- */
3017
- const handleApiError = (error, statusHandlers) => {
3018
- if (error instanceof ApiError) {
3019
- const handler = statusHandlers[error.status];
3020
- if (handler) throw new ApiError(handler().message, error.status, error.endpoint, error.data);
3021
- throw error;
3022
- }
3023
- throw error;
3024
- };
3025
-
3026
- //#endregion
3027
- //#region src/auth/api/utils.ts
3028
- /**
3029
- * Generic helper for API calls to handle initialization and error handling
3030
- * @param fn The async function to execute
3031
- * @param errorMap A map of status codes to error functions
3032
- * @returns The result of the function execution
3033
- */
3034
- const apiCall = async (fn, errorMap = {}) => {
3035
- await initDeferred.promise;
3036
- try {
3037
- return await fn();
3038
- } catch (error) {
3039
- return handleApiError(error, errorMap);
3040
- }
3041
- };
3042
-
3043
- //#endregion
3044
- //#region src/auth/api/tokens.ts
3045
- function persistTokens(data) {
3046
- const tokens = data ?? {};
3047
- if (tokens.access_token && tokens.refresh_token && tokens.expires_in) {
3048
- setTokens(tokens.access_token, tokens.refresh_token, Number(tokens.expires_in));
3049
- return true;
3050
- }
3051
- return false;
3052
- }
3053
-
3054
- //#endregion
3055
- //#region src/auth/validation/base.ts
3056
- /**
3057
- * 🛡️ INPUT VALIDATION UTILITY
3058
- * Validates data before API calls and throws user-friendly errors
3059
- */
3060
- const validateInput = (schema, data) => {
3061
- try {
3062
- return v.parse(schema, data);
3063
- } catch (error) {
3064
- if (error instanceof v.ValiError) {
3065
- const firstError = error.issues[0];
3066
- throw new Error(firstError.message);
3067
- }
3068
- throw error;
3069
- }
3070
- };
3071
- const EmailSchema = v.pipe(v.string("Email must be text"), v.email("Please enter a valid email address"), v.minLength(1, "Email is required"));
3072
- const PasswordSchema = v.pipe(v.string("Password must be text"), v.minLength(8, "Password must be at least 8 characters long"), v.regex(/[A-Z]/, "Password must contain at least one uppercase letter"), v.regex(/[a-z]/, "Password must contain at least one lowercase letter"), v.regex(/[0-9]/, "Password must contain at least one number"));
3073
- /**
3074
- * Username validation schema
3075
- * - Must be 4-24 characters
3076
- * - Only letters, numbers, underscores, and periods
2829
+ * Username validation schema
2830
+ * - Must be 4-24 characters
2831
+ * - Only letters, numbers, underscores, and periods
3077
2832
  */
3078
2833
  const UsernameSchema = v.pipe(v.string("Username must be text"), v.minLength(4, "Username must be 4-24 characters long"), v.maxLength(24, "Username must be 4-24 characters long"), v.regex(/^[a-z0-9_.]+$/, "Username can only contain lowercase letters, numbers, dots and underscores"));
3079
2834
  /**
@@ -3169,55 +2924,491 @@ const listSocials = async () => {
3169
2924
  });
3170
2925
  }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to list providers!") });
3171
2926
  };
3172
- const connectSocialInternal = async (provider, token) => {
3173
- return apiCall(async () => {
3174
- const requestBody = validateInput(ConnectProviderSchema, {
3175
- provider,
3176
- token
3177
- });
3178
- const { data } = await authV1ProvidersServiceConnectProvider({
3179
- body: {
3180
- provider: toProtoProvider(normalizeProviderType(requestBody.provider)),
3181
- token: requestBody.token
3182
- },
3183
- throwOnError: true
3184
- });
3185
- persistTokens(data);
3186
- }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to connect provider!") });
2927
+ const connectSocialInternal = async (provider, token) => {
2928
+ return apiCall(async () => {
2929
+ const requestBody = validateInput(ConnectProviderSchema, {
2930
+ provider,
2931
+ token
2932
+ });
2933
+ const sessionToken = await getToken();
2934
+ const headers = sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {};
2935
+ const { data } = await authV1ProvidersServiceConnectProvider({
2936
+ body: {
2937
+ provider: toProtoProvider(normalizeProviderType(requestBody.provider)),
2938
+ token: requestBody.token
2939
+ },
2940
+ headers,
2941
+ throwOnError: true
2942
+ });
2943
+ persistTokens(data);
2944
+ }, { [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to connect provider!") });
2945
+ };
2946
+ const disconnectSocial = async (provider) => {
2947
+ return apiCall(async () => {
2948
+ await authV1ProvidersServiceDisconnectProvider({
2949
+ path: { provider: toProtoProvider(provider) },
2950
+ throwOnError: true
2951
+ });
2952
+ }, {
2953
+ [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to disconnect provider!"),
2954
+ [HTTP_STATUS.NOT_FOUND]: () => /* @__PURE__ */ new Error("Provider not found!"),
2955
+ [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Cannot disconnect the last social provider!")
2956
+ });
2957
+ };
2958
+ const connectSocial = (provider) => {
2959
+ setSocialConnectAttempt(provider);
2960
+ login(provider);
2961
+ };
2962
+
2963
+ //#endregion
2964
+ //#region src/auth/api/refresh-tokens.ts
2965
+ /**
2966
+ * Exchange a provider credential for a Rixl session, or refresh an existing
2967
+ * Rixl session with a Bearer refresh token.
2968
+ *
2969
+ * - For `AuthProvider.BEARER`, the call hits `/auth/v1/token` with the stored
2970
+ * Rixl refresh token.
2971
+ * - For OAuth/Telegram providers, the call hits `/auth/v1/providers/connect`
2972
+ * with the provider's `id_token` or Telegram payload. The wire `provider`
2973
+ * value is the `auth.v1.ExternalAccountProvider` enum (e.g.
2974
+ * `EXTERNAL_ACCOUNT_PROVIDER_GOOGLE`), not the short SDK name.
2975
+ */
2976
+ const refreshTokens = async (provider, token, options) => {
2977
+ if (provider === "Bearer") {
2978
+ const { data } = await authV1TokenServiceRefreshToken({
2979
+ body: {
2980
+ token_type: "Bearer",
2981
+ refresh_token: token,
2982
+ country_code: options?.countryCode,
2983
+ origin: options?.origin
2984
+ },
2985
+ throwOnError: true
2986
+ });
2987
+ return data;
2988
+ }
2989
+ const requestProvider = normalizeProviderType(provider);
2990
+ const { data } = await authV1ProvidersServiceConnectProvider({
2991
+ body: {
2992
+ provider: toProtoProvider(requestProvider),
2993
+ token,
2994
+ country_code: options?.countryCode,
2995
+ origin: options?.origin
2996
+ },
2997
+ throwOnError: true
2998
+ });
2999
+ return data;
3000
+ };
3001
+
3002
+ //#endregion
3003
+ //#region src/auth/cookie/util.ts
3004
+ const splitOnFirstEquals = (pair) => {
3005
+ const trimmed = pair.trim();
3006
+ const separator = trimmed.indexOf("=");
3007
+ return separator === -1 ? [trimmed, ""] : [trimmed.slice(0, separator), trimmed.slice(separator + 1).trim()];
3008
+ };
3009
+ const getAllCookiesStartWith = (startWithKey) => {
3010
+ if (typeof document === "undefined") return {};
3011
+ return document.cookie.split(";").map(splitOnFirstEquals).filter(([key]) => key && key !== startWithKey && key.startsWith(startWithKey)).reduce((ac, [key, value]) => Object.assign(ac, { [key.slice(startWithKey.length + 1)]: value }), {});
3012
+ };
3013
+ function setCookie(name, value, options) {
3014
+ if (typeof document === "undefined") return;
3015
+ let cookieString = `${encodeName(name)}=${encodeValue(value)}`;
3016
+ if (options) {
3017
+ if (options.expires) if (typeof options.expires === "number") {
3018
+ const date = /* @__PURE__ */ new Date();
3019
+ date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1e3);
3020
+ cookieString += `; expires=${date.toUTCString()}`;
3021
+ } else cookieString += `; expires=${options.expires.toUTCString()}`;
3022
+ if (options.path) cookieString += `; path=${options.path}`;
3023
+ if (options.domain) cookieString += `; domain=${options.domain}`;
3024
+ if (options.secure) cookieString += `; secure`;
3025
+ if (options.sameSite) cookieString += `; samesite=${options.sameSite}`;
3026
+ }
3027
+ document.cookie = cookieString;
3028
+ }
3029
+ function deleteCookie(name) {
3030
+ if (typeof document === "undefined") return;
3031
+ document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
3032
+ }
3033
+ const encodeName = (name) => encodeURIComponent(name).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent);
3034
+ const encodeValue = (value) => encodeURIComponent(value).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent);
3035
+
3036
+ //#endregion
3037
+ //#region src/auth/cookie/index.ts
3038
+ const initVals = getAllCookiesStartWith(GLOBAL_PREFIX);
3039
+ const setStoreCookie = (key, value) => {
3040
+ const expires = /* @__PURE__ */ new Date();
3041
+ expires.setTime(expires.getTime() + 30 * 24 * 60 * 60 * 1e3);
3042
+ const cookieKey = `${GLOBAL_PREFIX}_${key}`;
3043
+ if (!value || value === "") {
3044
+ deleteCookie(cookieKey);
3045
+ return;
3046
+ }
3047
+ const stringValue = typeof value === "string" ? value : JSON.stringify(value);
3048
+ setCookie(`${GLOBAL_PREFIX}_${key}`, stringValue, {
3049
+ expires,
3050
+ path: "/",
3051
+ sameSite: "Lax"
3052
+ });
3053
+ };
3054
+
3055
+ //#endregion
3056
+ //#region src/auth/userStore.ts
3057
+ const userPath = GLOBAL_PREFIX + "_user";
3058
+ const parseUser = () => {
3059
+ if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.getItem !== "function") return;
3060
+ const value = localStorage.getItem(userPath);
3061
+ if (value && value != "undefined") try {
3062
+ return JSON.parse(value);
3063
+ } catch (err) {
3064
+ console.warn("Can't parse user data, error: ", err);
3065
+ return;
3066
+ }
3067
+ };
3068
+ const user = shared("user", () => {
3069
+ const store = atom(parseUser());
3070
+ store.subscribe((value) => {
3071
+ if (typeof localStorage === "undefined" || !localStorage || typeof localStorage.setItem !== "function") return;
3072
+ if (value) localStorage.setItem(userPath, JSON.stringify(value));
3073
+ else localStorage.removeItem(userPath);
3074
+ });
3075
+ return store;
3076
+ });
3077
+
3078
+ //#endregion
3079
+ //#region src/auth/utils/jwt.ts
3080
+ /**
3081
+ * Decodes a JWT token and extracts user information
3082
+ * Uses jose library for modern, secure JWT handling
3083
+ * @param token The JWT token to decode
3084
+ * @returns The decoded user data or undefined if decoding fails
3085
+ */
3086
+ const decodeToken = (token) => {
3087
+ try {
3088
+ const decodedUser = decodeJwt(token);
3089
+ return {
3090
+ id: decodedUser.id,
3091
+ email: decodedUser.email,
3092
+ first_name: decodedUser.first_name,
3093
+ last_name: decodedUser.last_name,
3094
+ username: decodedUser.username,
3095
+ image_url: decodedUser.image_url,
3096
+ language_code: decodedUser.language_code,
3097
+ org_id: decodedUser.org_id
3098
+ };
3099
+ } catch (error) {
3100
+ console.warn("Failed to decode JWT token. Error: ", error);
3101
+ return;
3102
+ }
3103
+ };
3104
+ /**
3105
+ * Decodes a JWT token and sets the user in the store
3106
+ * @param token The JWT token to decode
3107
+ * @returns True if user was successfully decoded and set, false otherwise
3108
+ */
3109
+ const decodeAndSetUser = (token) => {
3110
+ const userData = decodeToken(token);
3111
+ if (userData) {
3112
+ user.set(userData);
3113
+ return true;
3114
+ }
3115
+ return false;
3116
+ };
3117
+ /**
3118
+ * Checks if a token is expired based on the expiration timestamp
3119
+ * @param expireAt The expiration timestamp in milliseconds
3120
+ * @returns True if the token is expired, false otherwise
3121
+ */
3122
+ const isTokenExpired = (expireAt) => {
3123
+ if (!expireAt) return true;
3124
+ return Date.now() >= expireAt;
3125
+ };
3126
+
3127
+ //#endregion
3128
+ //#region src/auth/authStore.ts
3129
+ const isLogged = shared("isLogged", () => atom(initVals["isLogged"] === "true"));
3130
+ const accessToken = shared("accessToken", () => atom(initVals["accessToken"]));
3131
+ const refreshToken = shared("refreshToken", () => atom(initVals["refreshToken"]));
3132
+ const expireAt = shared("expireAt", () => atom(Number(initVals["expireAt"])));
3133
+ const authError = shared("authError", () => atom(null));
3134
+ const requiresAction = shared("requiresAction", () => atom(initVals["requiresAction"] || null));
3135
+ const limitedAccessToken = shared("limitedAccessToken", () => atom(initVals["limitedAccessToken"] || null));
3136
+ const inFlight = shared("getTokenPromise", () => ({ promise: null }));
3137
+ const PROVIDER_URL_MAP = {
3138
+ google: googleAuthUrl,
3139
+ apple: appleAuthUrl,
3140
+ microsoft: microsoftAuthUrl,
3141
+ telegram: telegramAuthUrl
3142
+ };
3143
+ const login = async (provider) => {
3144
+ await initDeferred.promise;
3145
+ const authUrlAtom = PROVIDER_URL_MAP[provider];
3146
+ if (!authUrlAtom) throw new Error(`Unsupported provider: ${provider}`);
3147
+ const authUrl = authUrlAtom.get();
3148
+ if (authUrl) window.location.href = authUrl;
3149
+ else throw new Error(`${provider} provider is not configured. Please check your initClient configuration.`);
3150
+ };
3151
+ const refreshAccessToken = async (refresh) => {
3152
+ const result = await refreshTokens("Bearer", refresh);
3153
+ if (!("requires_action" in result)) setTokens(result.access_token, result.refresh_token, result.expires_in);
3154
+ };
3155
+ const ensureValidAccessToken = async (refresh) => {
3156
+ if (accessToken.get() && !isTokenExpired(expireAt.get())) return;
3157
+ try {
3158
+ await refreshAccessToken(refresh);
3159
+ } catch (refreshError) {
3160
+ console.error("Token refresh failed in getToken:", refreshError);
3161
+ removeTokens();
3162
+ throw refreshError;
3163
+ }
3164
+ };
3165
+ const getToken = async () => {
3166
+ if (inFlight.promise) return inFlight.promise;
3167
+ inFlight.promise = (async () => {
3168
+ try {
3169
+ await initDeferred.promise;
3170
+ if (requiresAction.get()) return void 0;
3171
+ const currentRefreshToken = refreshToken.get();
3172
+ if (!currentRefreshToken) return void 0;
3173
+ await ensureValidAccessToken(currentRefreshToken);
3174
+ const token = accessToken.get();
3175
+ if (token) decodeAndSetUser(token);
3176
+ return token;
3177
+ } catch (error) {
3178
+ console.warn("Failed to getToken(). Error: ", error);
3179
+ throw error;
3180
+ } finally {
3181
+ inFlight.promise = null;
3182
+ }
3183
+ })();
3184
+ return inFlight.promise;
3185
+ };
3186
+ /**
3187
+ * Sets authentication tokens in the store
3188
+ * @param access The access token
3189
+ * @param refresh The refresh token
3190
+ * @param expiresIn The token expiration time in seconds
3191
+ */
3192
+ const setTokens = (access, refresh, expiresIn) => {
3193
+ accessToken.set(access);
3194
+ refreshToken.set(refresh);
3195
+ expireAt.set(Date.now() + expiresIn * 1e3);
3196
+ isLogged.set(true);
3197
+ limitedAccessToken.set(null);
3198
+ requiresAction.set(null);
3199
+ authError.set(null);
3200
+ decodeAndSetUser(access);
3201
+ };
3202
+ /**
3203
+ * Removes authentication tokens from the store
3204
+ */
3205
+ const removeTokens = () => {
3206
+ accessToken.set("");
3207
+ refreshToken.set("");
3208
+ expireAt.set(0);
3209
+ isLogged.set(false);
3210
+ user.set(void 0);
3211
+ limitedAccessToken.set(null);
3212
+ requiresAction.set(null);
3213
+ authError.set(null);
3187
3214
  };
3188
- const disconnectSocial = async (provider) => {
3189
- return apiCall(async () => {
3190
- await authV1ProvidersServiceDisconnectProvider({
3191
- path: { provider: toProtoProvider(provider) },
3192
- throwOnError: true
3193
- });
3194
- }, {
3195
- [HTTP_STATUS.UNAUTHORIZED]: () => /* @__PURE__ */ new Error("User is not authorized to disconnect provider!"),
3196
- [HTTP_STATUS.NOT_FOUND]: () => /* @__PURE__ */ new Error("Provider not found!"),
3197
- [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Cannot disconnect the last social provider!")
3198
- });
3215
+ /**
3216
+ * Clears the auth error from the store
3217
+ */
3218
+ const clearAuthError = () => {
3219
+ authError.set(null);
3199
3220
  };
3200
- const connectSocial = (provider) => {
3201
- setSocialConnectAttempt(provider);
3202
- login(provider);
3221
+ /**
3222
+ * Sets limited access state for users requiring additional action (e.g., Telegram users without email)
3223
+ * @param token The limited scope access token
3224
+ * @param action The required action (e.g., "add_email")
3225
+ */
3226
+ const setLimitedAccessState = (token, action) => {
3227
+ limitedAccessToken.set(token);
3228
+ requiresAction.set(action);
3229
+ authError.set(null);
3230
+ isLogged.set(true);
3203
3231
  };
3232
+ /**
3233
+ * Clears the limited access state (after email verification completes or user logs out)
3234
+ */
3235
+ const clearLimitedAccessState = () => {
3236
+ limitedAccessToken.set(null);
3237
+ requiresAction.set(null);
3238
+ if (!(!!accessToken.get() && !!refreshToken.get())) isLogged.set(false);
3239
+ };
3240
+ isLogged.subscribe((value) => setStoreCookie("isLogged", value));
3241
+ accessToken.subscribe((value) => setStoreCookie("accessToken", value));
3242
+ refreshToken.subscribe((value) => setStoreCookie("refreshToken", value));
3243
+ expireAt.subscribe((value) => setStoreCookie("expireAt", value));
3244
+ requiresAction.subscribe((value) => setStoreCookie("requiresAction", value));
3245
+ limitedAccessToken.subscribe((value) => setStoreCookie("limitedAccessToken", value));
3204
3246
 
3205
3247
  //#endregion
3206
- //#region src/auth/api/client-core.ts
3207
- let tokenRefreshFunction = null;
3248
+ //#region src/auth/api-url.ts
3208
3249
  /**
3209
- * Sets the token refresh function used by the API client
3210
- * This should be called during initialization
3250
+ * Global API base URL store
3211
3251
  */
3212
- const setTokenRefreshFunction = (fn) => {
3213
- tokenRefreshFunction = fn ?? null;
3214
- };
3252
+ const apiURL = shared("apiURL", () => atom(""));
3253
+
3254
+ //#endregion
3255
+ //#region src/auth/api/sdk-client.ts
3256
+ function isWireErrorBody(error) {
3257
+ return typeof error === "object" && error !== null;
3258
+ }
3259
+ const state = shared("sdkClientState", () => ({
3260
+ configured: false,
3261
+ tokenResolver: getToken
3262
+ }));
3263
+ function setTokenResolver(resolver) {
3264
+ state.tokenResolver = resolver;
3265
+ }
3266
+ /**
3267
+ * Routes that do not require a Bearer token at the gateway edge. These
3268
+ * authenticate via credentials in the request body (or are webhooks verified by
3269
+ * signature), so attaching an Authorization header here would make the gateway
3270
+ * attempt to validate a stale/absent token and reject the request with 401.
3271
+ * Mirrors `publicRoutes` in backend/gateway/internal/routes/routes.go.
3272
+ */
3273
+ const publicRoutes = [
3274
+ {
3275
+ method: "POST",
3276
+ path: "/auth/v1/token"
3277
+ },
3278
+ {
3279
+ method: "POST",
3280
+ path: "/auth/v1/register"
3281
+ },
3282
+ {
3283
+ method: "POST",
3284
+ path: "/auth/v1/login"
3285
+ },
3286
+ {
3287
+ method: "POST",
3288
+ path: "/auth/v1/email/verify"
3289
+ },
3290
+ {
3291
+ method: "POST",
3292
+ path: "/auth/v1/email/verify/resend"
3293
+ },
3294
+ {
3295
+ method: "POST",
3296
+ path: "/auth/v1/password/reset"
3297
+ },
3298
+ {
3299
+ method: "POST",
3300
+ path: "/auth/v1/password/reset/confirm"
3301
+ },
3302
+ {
3303
+ method: "POST",
3304
+ path: "/auth/v1/verify-totp"
3305
+ },
3306
+ {
3307
+ method: "POST",
3308
+ path: "/auth/v1/verify-passkey"
3309
+ },
3310
+ {
3311
+ method: "POST",
3312
+ path: "/auth/v1/invitations/",
3313
+ prefix: true
3314
+ },
3315
+ {
3316
+ method: "POST",
3317
+ path: "/auth/v1/passkey/login/begin"
3318
+ },
3319
+ {
3320
+ method: "POST",
3321
+ path: "/auth/v1/passkey/login/finish"
3322
+ },
3323
+ {
3324
+ method: "POST",
3325
+ path: "/auth/v1/logout"
3326
+ },
3327
+ {
3328
+ method: "POST",
3329
+ path: "/auth/v1/providers/connect"
3330
+ },
3331
+ {
3332
+ method: "POST",
3333
+ path: "/auth/v1/blog/unsubscribe/email"
3334
+ },
3335
+ {
3336
+ method: "POST",
3337
+ path: "/auth/v1/blog/broadcast"
3338
+ },
3339
+ {
3340
+ method: "GET",
3341
+ path: "/media/v1/videos/",
3342
+ prefix: true
3343
+ },
3344
+ {
3345
+ method: "GET",
3346
+ path: "/media/v1/images/",
3347
+ prefix: true
3348
+ },
3349
+ {
3350
+ method: "GET",
3351
+ path: "/media/v1/languages"
3352
+ },
3353
+ {
3354
+ method: "GET",
3355
+ path: "/posts/v1/feeds/",
3356
+ prefix: true
3357
+ },
3358
+ {
3359
+ method: "POST",
3360
+ path: "/billing/webhooks/stripe"
3361
+ },
3362
+ {
3363
+ method: "POST",
3364
+ path: "/webhooks/storage"
3365
+ },
3366
+ {
3367
+ method: "POST",
3368
+ path: "/platform/auth/v1/token"
3369
+ },
3370
+ {
3371
+ method: "POST",
3372
+ path: "/platform/auth/v1/refresh"
3373
+ }
3374
+ ];
3375
+ function isPublicRoute(method, pathname) {
3376
+ const match = method.toUpperCase();
3377
+ return publicRoutes.some(({ method: m, path, prefix }) => match === m && (prefix ? pathname.startsWith(path) : pathname === path));
3378
+ }
3379
+ function configureSdkClient() {
3380
+ if (state.configured) return;
3381
+ state.configured = true;
3382
+ configureAllClients(apiURL.get());
3383
+ apiURL.subscribe((url) => {
3384
+ configureAllClients(url);
3385
+ });
3386
+ addClientInitializer((client) => {
3387
+ client.interceptors.request.use(async (request) => {
3388
+ if (request.headers.has("Authorization")) return request;
3389
+ const { pathname } = new URL(request.url);
3390
+ if (isPublicRoute(request.method, pathname)) return request;
3391
+ const token = await state.tokenResolver();
3392
+ if (!token) throw new ApiError("No access token available for an authenticated request", HTTP_STATUS.UNAUTHORIZED, pathname);
3393
+ request.headers.set("Authorization", `Bearer ${token}`);
3394
+ return request;
3395
+ });
3396
+ client.interceptors.error.use((error, response, request) => {
3397
+ if (error instanceof Error) return error;
3398
+ const body = isWireErrorBody(error) ? error : void 0;
3399
+ const status = response?.status ?? body?.code ?? 0;
3400
+ const message = body?.error || body?.details || (typeof error === "string" ? error : "Request failed");
3401
+ const endpoint = request ? new URL(request.url).pathname : "";
3402
+ return new ApiError(message, status, endpoint, error);
3403
+ });
3404
+ });
3405
+ }
3215
3406
 
3216
3407
  //#endregion
3217
3408
  //#region src/auth/authConfig.ts
3218
- let loginRedirectUrl;
3409
+ const config = shared("authConfig", () => ({ loginRedirectUrl: void 0 }));
3219
3410
  const setLoginRedirectUrl = (url) => {
3220
- loginRedirectUrl = url;
3411
+ config.loginRedirectUrl = url;
3221
3412
  };
3222
3413
 
3223
3414
  //#endregion
@@ -3228,24 +3419,26 @@ const setLoginRedirectUrl = (url) => {
3228
3419
  * @returns A promise that resolves to the current access token (if available)
3229
3420
  */
3230
3421
  const initClient = async (config) => {
3231
- await initConfig(config);
3232
- await initPage();
3233
- initDeferred.resolve();
3234
- await initSocials();
3422
+ try {
3423
+ await runInitSequence(config);
3424
+ } finally {
3425
+ completeOAuthCallback();
3426
+ }
3235
3427
  return getToken();
3236
3428
  };
3429
+ const runInitSequence = async (config) => {
3430
+ try {
3431
+ await initConfig(config);
3432
+ await initPage();
3433
+ } finally {
3434
+ initDeferred.resolve();
3435
+ }
3436
+ await initSocials();
3437
+ };
3237
3438
  const initConfig = async (config) => {
3238
3439
  apiURL.set(config.apiUrl);
3239
3440
  configureSdkClient();
3240
3441
  setLoginRedirectUrl(config.loginRedirectUrl);
3241
- setTokenRefreshFunction(async () => {
3242
- const currentRefreshToken = refreshToken.get();
3243
- if (currentRefreshToken) {
3244
- const result = await refreshTokens("Bearer", currentRefreshToken);
3245
- if (result && !("requires_action" in result)) setTokens(result.access_token, result.refresh_token, result.expires_in);
3246
- return getToken();
3247
- }
3248
- });
3249
3442
  if (config.googleProvider) {
3250
3443
  googleConfig.set(config.googleProvider);
3251
3444
  updateGoogleAuthUrl();
@@ -3293,9 +3486,15 @@ const extractAuthErrorInfo = (error) => {
3293
3486
  };
3294
3487
  const handleInitPageApiError = (error) => {
3295
3488
  const info = extractAuthErrorInfo(error);
3296
- if (error.status === HTTP_STATUS.BAD_REQUEST && info.error === "invalid_grant") return setAuthErrorAndClear("email_not_verified", info.description, info.email);
3297
- if (error.status === HTTP_STATUS.CONFLICT) return setAuthErrorAndClear("provider_conflict", info.description, info.email);
3298
- return false;
3489
+ if (error.status === HTTP_STATUS.BAD_REQUEST && info.error === "invalid_grant") {
3490
+ setAuthErrorAndClear("email_not_verified", info.description, info.email);
3491
+ return;
3492
+ }
3493
+ if (error.status === HTTP_STATUS.CONFLICT) {
3494
+ setAuthErrorAndClear("provider_conflict", info.description, info.email);
3495
+ return;
3496
+ }
3497
+ setAuthErrorAndClear("provider_exchange_failed", info.error || info.description, info.email);
3299
3498
  };
3300
3499
  const exchangeProviderToken = async (provider, token) => {
3301
3500
  const result = await refreshTokens(provider, token);
@@ -3303,14 +3502,18 @@ const exchangeProviderToken = async (provider, token) => {
3303
3502
  };
3304
3503
  const initPage = async () => {
3305
3504
  const provider = detectProvider();
3306
- if (!provider) return void 0;
3505
+ if (!provider) {
3506
+ if (hasProviderResponse()) logUnusableProviderResponse();
3507
+ return;
3508
+ }
3307
3509
  const token = getProviderToken(provider);
3308
3510
  if (!token) return void 0;
3309
3511
  try {
3310
3512
  await exchangeProviderToken(provider, token);
3311
3513
  } catch (error) {
3312
- if (error instanceof ApiError && handleInitPageApiError(error)) return;
3313
- throw error;
3514
+ logProviderExchangeFailure(provider, token, error);
3515
+ if (!(error instanceof ApiError)) throw error;
3516
+ handleInitPageApiError(error);
3314
3517
  }
3315
3518
  };
3316
3519
  const initSocials = async () => {
@@ -3734,8 +3937,9 @@ const getUserInfo = async (userId) => {
3734
3937
  };
3735
3938
  const updateFullName = async (fullName) => {
3736
3939
  return apiCall(async () => {
3940
+ const validatedInput = validateInput(UpdateNameSchema, { full_name: fullName });
3737
3941
  await authV1UserServiceUpdateName({
3738
- body: validateInput(UpdateNameSchema, { full_name: fullName }),
3942
+ body: validatedInput,
3739
3943
  throwOnError: true
3740
3944
  });
3741
3945
  }, {
@@ -3746,8 +3950,9 @@ const updateFullName = async (fullName) => {
3746
3950
  };
3747
3951
  const updateUsername = async (username) => {
3748
3952
  return apiCall(async () => {
3953
+ const validatedInput = validateInput(UpdateUsernameSchema, { username });
3749
3954
  await authV1UserServiceUpdateUsername({
3750
- body: validateInput(UpdateUsernameSchema, { username }),
3955
+ body: validatedInput,
3751
3956
  throwOnError: true
3752
3957
  });
3753
3958
  }, {
@@ -3777,8 +3982,9 @@ const setupUserOTP = async () => {
3777
3982
  };
3778
3983
  const verifyUserOTP = async (code) => {
3779
3984
  return apiCall(async () => {
3985
+ const validatedBody = validateInput(VerifyOTPCodeSchema, { code });
3780
3986
  const { data } = await authV1OtpServiceVerifyOtp({
3781
- body: validateInput(VerifyOTPCodeSchema, { code }),
3987
+ body: validatedBody,
3782
3988
  throwOnError: true
3783
3989
  });
3784
3990
  persistTokens(data);
@@ -3861,11 +4067,12 @@ const loginWithEmail = async (email, password) => {
3861
4067
  };
3862
4068
  const verifyTOTPForLogin = async (code, session_id) => {
3863
4069
  return apiCall(async () => {
4070
+ const validatedInput = validateInput(LoginOTPVerifyRequestSchema, {
4071
+ code,
4072
+ session_id
4073
+ });
3864
4074
  const { data } = await authV1OtpServiceVerifyTotpForLogin({
3865
- body: validateInput(LoginOTPVerifyRequestSchema, {
3866
- code,
3867
- session_id
3868
- }),
4075
+ body: validatedInput,
3869
4076
  throwOnError: true
3870
4077
  });
3871
4078
  persistTokens(data);
@@ -3916,13 +4123,14 @@ function isApiErrorBody(error) {
3916
4123
  //#region src/auth/auth/register.ts
3917
4124
  const registerWithEmail = async (email, password, subscribeToBlog, countryCode) => {
3918
4125
  return apiCall(async () => {
4126
+ const validatedInput = validateInput(RegisterRequestSchema, {
4127
+ email,
4128
+ password,
4129
+ country_code: countryCode,
4130
+ subscribe_to_blog: subscribeToBlog
4131
+ });
3919
4132
  const { data } = await authV1EmailServiceRegister({
3920
- body: validateInput(RegisterRequestSchema, {
3921
- email,
3922
- password,
3923
- country_code: countryCode,
3924
- subscribe_to_blog: subscribeToBlog
3925
- }),
4133
+ body: validatedInput,
3926
4134
  throwOnError: true
3927
4135
  });
3928
4136
  if (data.verification_id) return {
@@ -3935,8 +4143,9 @@ const registerWithEmail = async (email, password, subscribeToBlog, countryCode)
3935
4143
  };
3936
4144
  const resendEmailVerificationCode = async (email) => {
3937
4145
  return apiCall(async () => {
4146
+ const validatedInput = validateInput(ResendEmailRequestSchema, { email });
3938
4147
  const { data } = await authV1EmailServiceResendVerification({
3939
- body: validateInput(ResendEmailRequestSchema, { email }),
4148
+ body: validatedInput,
3940
4149
  throwOnError: true
3941
4150
  });
3942
4151
  if (data.verification_id) return {
@@ -3954,19 +4163,21 @@ const resendEmailVerificationCode = async (email) => {
3954
4163
  //#region src/auth/auth/password.ts
3955
4164
  const sendPasswordResetEmail = async (email) => {
3956
4165
  return apiCall(async () => {
4166
+ const validatedInput = validateInput(ResendEmailRequestSchema, { email });
3957
4167
  await authV1EmailServiceSendPasswordReset({
3958
- body: validateInput(ResendEmailRequestSchema, { email }),
4168
+ body: validatedInput,
3959
4169
  throwOnError: true
3960
4170
  });
3961
4171
  }, { [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Bad request - invalid email or validation error") });
3962
4172
  };
3963
4173
  const confirmPasswordReset = async (token, password) => {
3964
4174
  return apiCall(async () => {
4175
+ const validatedInput = validateInput(ResetPasswordRequestSchema, {
4176
+ token,
4177
+ new_password: password
4178
+ });
3965
4179
  await authV1EmailServiceResetPassword({
3966
- body: validateInput(ResetPasswordRequestSchema, {
3967
- token,
3968
- new_password: password
3969
- }),
4180
+ body: validatedInput,
3970
4181
  throwOnError: true
3971
4182
  });
3972
4183
  }, { [HTTP_STATUS.BAD_REQUEST]: () => /* @__PURE__ */ new Error("Bad request - invalid token or password") });
@@ -3976,8 +4187,9 @@ const confirmPasswordReset = async (token, password) => {
3976
4187
  //#region src/auth/auth/email.ts
3977
4188
  const initiateEmailChange = async (email) => {
3978
4189
  return apiCall(async () => {
4190
+ const validatedInput = validateInput(ChangeEmailRequestSchema, { new_email: email });
3979
4191
  const { data } = await authV1EmailServiceInitiateEmailChange({
3980
- body: validateInput(ChangeEmailRequestSchema, { new_email: email }),
4192
+ body: validatedInput,
3981
4193
  throwOnError: true
3982
4194
  });
3983
4195
  if (data.verification_id) return {
@@ -3994,8 +4206,9 @@ const initiateEmailChange = async (email) => {
3994
4206
  };
3995
4207
  const addEmail = async (email) => {
3996
4208
  return apiCall(async () => {
4209
+ const validatedInput = validateInput(ResendEmailRequestSchema, { email });
3997
4210
  const { data } = await authV1EmailServiceAddEmail({
3998
- body: validateInput(ResendEmailRequestSchema, { email }),
4211
+ body: validatedInput,
3999
4212
  throwOnError: true
4000
4213
  });
4001
4214
  if (data.verification_id) return {
@@ -4289,10 +4502,11 @@ function serializeRegistrationCredential(cred) {
4289
4502
  }
4290
4503
  const finishPasskeyLogin = async (session_id, credential) => {
4291
4504
  return apiCall(async () => {
4505
+ const serialized = serializeLoginCredential(credential);
4292
4506
  const { data } = await authV1PasskeyServicePasskeyLoginFinish({
4293
4507
  body: {
4294
4508
  session_id,
4295
- credential: serializeLoginCredential(credential)
4509
+ credential: serialized
4296
4510
  },
4297
4511
  throwOnError: true
4298
4512
  });
@@ -4332,11 +4546,12 @@ const beginPasskeyRegistration = async () => {
4332
4546
  };
4333
4547
  const finishPasskeyRegistration = async (session_id, name, credential) => {
4334
4548
  return apiCall(async () => {
4549
+ const serialized = serializeRegistrationCredential(credential);
4335
4550
  const { data } = await authV1PasskeyServicePasskeyRegisterFinish({
4336
4551
  body: {
4337
4552
  session_id,
4338
4553
  name,
4339
- credential: serializeRegistrationCredential(credential)
4554
+ credential: serialized
4340
4555
  },
4341
4556
  throwOnError: true
4342
4557
  });
@@ -4383,10 +4598,11 @@ const listPasskeys = async () => {
4383
4598
  };
4384
4599
  const verifyPasskeyForLogin = async (session_id, credential) => {
4385
4600
  return apiCall(async () => {
4601
+ const serialized = serializeLoginCredential(credential);
4386
4602
  const { data } = await authV1PasskeyServiceVerifyPasskeyForLogin({
4387
4603
  body: {
4388
4604
  session_id,
4389
- credential: serializeLoginCredential(credential)
4605
+ credential: serialized
4390
4606
  },
4391
4607
  throwOnError: true
4392
4608
  });