azirid-react 0.13.0 → 0.13.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/README.md CHANGED
@@ -1381,29 +1381,31 @@ The Azirid dashboard lets admins click "Sign in as user" to impersonate any end
1381
1381
 
1382
1382
  **How it works:**
1383
1383
  1. Admin clicks "Sign in as user" in the Azirid dashboard
1384
- 2. Dashboard generates a one-time code (valid for 60 seconds) and redirects to your app at `/auth/handoff?code=<code>`
1385
- 3. Your app renders `<HandoffCallback>`, which calls `client.exchangeHandoff(code)` to exchange the code for session tokens
1386
- 4. The user is signed in as the impersonated user access token, refresh token, and CSRF token are all stored as normal
1384
+ 2. Dashboard generates a one-time handoff code (valid for 5 minutes) and redirects to your app at `/auth/handoff?code=<code>&api=<apiUrl>`
1385
+ 3. Your app renders `<HandoffCallback>`, which exchanges the code for session tokens directly with the API
1386
+ 4. Tokens are stored in `sessionStorage`on redirect to `/`, `AziridProvider` picks them up automatically
1387
+
1388
+ > **`<HandoffCallback>` is fully standalone** — it does NOT depend on `AziridProvider`. It works even when the provider wraps the entire layout and its bootstrap would redirect to `/login`. No special route groups or layout changes needed.
1387
1389
 
1388
1390
  **Setup — create the handoff page:**
1389
1391
 
1390
1392
  ```tsx
1391
1393
  // app/auth/handoff/page.tsx
1392
1394
  'use client'
1393
- import { useRouter } from 'next/navigation'
1394
1395
  import { HandoffCallback } from 'azirid-react'
1395
1396
 
1396
1397
  export default function HandoffPage() {
1397
- const router = useRouter()
1398
1398
  return (
1399
1399
  <HandoffCallback
1400
- onSuccess={() => router.push('/')}
1401
- onError={() => router.push('/login')}
1400
+ onSuccess={() => window.location.href = '/'}
1401
+ onError={() => window.location.href = '/login'}
1402
1402
  />
1403
1403
  )
1404
1404
  }
1405
1405
  ```
1406
1406
 
1407
+ > **Important:** Use `window.location.href` (not `router.push()`) for redirects. This triggers a full page reload so `AziridProvider` re-bootstraps with the new tokens.
1408
+
1407
1409
  **Props:**
1408
1410
 
1409
1411
  | Prop | Type | Default | Description |
@@ -1413,10 +1415,10 @@ export default function HandoffPage() {
1413
1415
  | `loadingText` | `string` | `"Signing you in..."` | Text shown while the exchange is in progress |
1414
1416
  | `errorText` | `string` | `"Failed to complete sign-in. The link may have expired."` | Fallback error text |
1415
1417
 
1416
- You can also call `client.exchangeHandoff(code)` directly if you prefer a headless approach:
1418
+ You can also call `client.exchangeHandoff(code, apiUrl)` directly if you prefer a headless approach:
1417
1419
 
1418
1420
  ```ts
1419
- const { user } = await client.exchangeHandoff(code)
1421
+ const { user } = await client.exchangeHandoff(code, 'https://api.azirid.com/v1')
1420
1422
  ```
1421
1423
 
1422
1424
  ---
package/dist/index.cjs CHANGED
@@ -386,7 +386,8 @@ function createAccessClient(config, appContext) {
386
386
  }
387
387
  return json;
388
388
  }
389
- async function exchangeHandoff(code) {
389
+ async function exchangeHandoff(code, apiUrl) {
390
+ const exchangeBase = apiUrl?.replace(/\/+$/, "") ?? baseUrl;
390
391
  const headers = {
391
392
  "Content-Type": "application/json",
392
393
  ...config.headers
@@ -396,7 +397,7 @@ function createAccessClient(config, appContext) {
396
397
  }
397
398
  const devId = getOrCreateDeviceId();
398
399
  if (devId) headers["X-Device-Id"] = devId;
399
- const res = await fetch(`${baseUrl}/v1/users/auth/handoff/exchange`, {
400
+ const res = await fetch(`${exchangeBase}/users/auth/handoff/exchange`, {
400
401
  method: "POST",
401
402
  headers,
402
403
  credentials: "include",
@@ -4007,7 +4008,6 @@ function HandoffCallback({
4007
4008
  loadingText = "Signing you in...",
4008
4009
  errorText = "Failed to complete sign-in. The link may have expired."
4009
4010
  }) {
4010
- const client = useAccessClient();
4011
4011
  const [status, setStatus] = react.useState("loading");
4012
4012
  const [errorMessage, setErrorMessage] = react.useState(null);
4013
4013
  const attempted = react.useRef(false);
@@ -4016,21 +4016,49 @@ function HandoffCallback({
4016
4016
  attempted.current = true;
4017
4017
  const params = new URLSearchParams(window.location.search);
4018
4018
  const code = params.get("code");
4019
+ const apiUrl = params.get("api");
4019
4020
  if (!code) {
4020
4021
  setStatus("error");
4021
4022
  setErrorMessage("No handoff code provided");
4022
4023
  onError?.(new Error("No handoff code provided"));
4023
4024
  return;
4024
4025
  }
4025
- client.exchangeHandoff(code).then((result) => {
4026
- onSuccess?.(result.user);
4026
+ if (!apiUrl) {
4027
+ setStatus("error");
4028
+ setErrorMessage("No API URL provided");
4029
+ onError?.(new Error("No API URL in handoff link"));
4030
+ return;
4031
+ }
4032
+ const exchangeUrl = `${apiUrl.replace(/\/+$/, "")}/users/auth/handoff/exchange`;
4033
+ fetch(exchangeUrl, {
4034
+ method: "POST",
4035
+ headers: { "Content-Type": "application/json" },
4036
+ credentials: "include",
4037
+ body: JSON.stringify({ code })
4038
+ }).then(async (res) => {
4039
+ if (!res.ok) {
4040
+ const body = await res.json().catch(() => null);
4041
+ throw new Error(body?.error?.message ?? body?.message ?? "Handoff exchange failed");
4042
+ }
4043
+ return res.json();
4044
+ }).then((raw) => {
4045
+ const json = raw && typeof raw === "object" && "data" in raw && "meta" in raw ? raw.data : raw;
4046
+ json.at ?? json.accessToken;
4047
+ const refreshToken = json.rt ?? json.refreshToken;
4048
+ const csrfToken = json.xc ?? json.csrfToken;
4049
+ try {
4050
+ if (refreshToken) sessionStorage.setItem("__azrt", refreshToken);
4051
+ if (csrfToken) sessionStorage.setItem("__azxc", csrfToken);
4052
+ } catch {
4053
+ }
4054
+ onSuccess?.(json.user);
4027
4055
  }).catch((err) => {
4028
4056
  setStatus("error");
4029
4057
  const error = err instanceof Error ? err : new Error("Handoff exchange failed");
4030
4058
  setErrorMessage(error.message);
4031
4059
  onError?.(error);
4032
4060
  });
4033
- }, [client, onSuccess, onError]);
4061
+ }, [onSuccess, onError]);
4034
4062
  if (status === "error") {
4035
4063
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: wrapperStyle, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...messageStyle2, color: "#ef4444" }, children: errorMessage || errorText }) });
4036
4064
  }
@@ -4847,7 +4875,7 @@ function usePasswordToggle() {
4847
4875
  }
4848
4876
 
4849
4877
  // src/index.ts
4850
- var SDK_VERSION = "0.13.0";
4878
+ var SDK_VERSION = "0.13.1";
4851
4879
 
4852
4880
  exports.AuthForm = AuthForm;
4853
4881
  exports.AziridProvider = AziridProvider;