azirid-react 0.10.1 → 0.10.3

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
@@ -81,11 +81,12 @@ AZIRID_API_URL=http://localhost:3000
81
81
  import { AziridProvider } from 'azirid-react'
82
82
 
83
83
  export default function RootLayout({ children }: { children: React.ReactNode }) {
84
+ const router = useRouter()
85
+
84
86
  return (
85
87
  <AziridProvider
86
88
  publishableKey={process.env.NEXT_PUBLIC_AZIRID_PK!}
87
- onLoginSuccess={(data) => console.log('Logged in:', data.user)}
88
- onLogoutSuccess={() => console.log('Logged out')}
89
+ onAuthStateChange={() => router.refresh()}
89
90
  onSessionExpired={() => (window.location.href = '/login')}
90
91
  >
91
92
  {children}
@@ -375,6 +376,8 @@ import { useBootstrap } from 'azirid-react'
375
376
  const { bootstrap, isBootstrapping } = useBootstrap()
376
377
  ```
377
378
 
379
+ > **Note:** The automatic bootstrap on mount runs exactly once, even in React 18 Strict Mode (which double-mounts components in development). Hooks like `usePayphoneCheckout` and `<PayphoneCallback>` wait for bootstrap to complete before making authenticated requests.
380
+
378
381
  ### `useRefresh`
379
382
 
380
383
  Manually refresh the access token.
@@ -717,6 +720,8 @@ The hook handles both phases automatically:
717
720
 
718
721
  > **Important:** The redirect URL after payment is configured in the **Payphone Developer dashboard** (not passed via code). Make sure your Response URL points to the page where `usePayphoneCheckout` or `<PayphoneCallback>` is rendered.
719
722
 
723
+ > **Session safety:** The confirmation request waits for the session bootstrap to complete before firing. This prevents 401 errors when Payphone redirects back and the page reloads — the SDK restores the session first, then confirms the payment.
724
+
720
725
  ### `usePayButton`
721
726
 
722
727
  Hook that returns **renderable components** and **state** for a complete payment flow. Supports all providers (Stripe, PayPal, Payphone, Manual Transfer, Nuvei). You control the layout.
@@ -926,7 +931,7 @@ import { InvoiceList } from 'azirid-react'
926
931
 
927
932
  #### `PayphoneCallback`
928
933
 
929
- Page component for handling Payphone payment callbacks. Reads `id` and `clientTransactionId` from URL query params automatically. Deploy this page at the **Response URL** configured in your Payphone Developer dashboard.
934
+ Page component for handling Payphone payment callbacks. Reads `id` and `clientTransactionId` from URL query params automatically. Deploy this page at the **Response URL** configured in your Payphone Developer dashboard. The component waits for the session bootstrap to complete before confirming, preventing 401 errors on page reload.
930
935
 
931
936
  ```tsx
932
937
  // app/payphone/callback/page.tsx
@@ -1294,9 +1299,10 @@ function createAccessClient(
1294
1299
  | `publishableKey` | `string` | — | Publishable key (e.g. `pk_live_...`) |
1295
1300
  | `tenantId` | `string` | — | Tenant ID for multi-tenant apps |
1296
1301
  | `fetchOptions` | `Record<string, string>` | — | Extra headers to send with every request |
1297
- | `autoBootstrap` | `boolean` | `true` | Auto-restore session on mount |
1302
+ | `autoBootstrap` | `boolean` | `true` | Auto-restore session on mount. Runs once (safe in React 18 Strict Mode) |
1298
1303
  | `refreshInterval` | `number` | `50000` | Token refresh interval in ms. `0` to disable |
1299
1304
  | `sessionSyncUrl` | `string \| false` | auto | URL for session cookie sync. Auto-activates in dev mode. Pass `false` to disable |
1305
+ | `onAuthStateChange`| `() => void` | — | Called after login, signup, or logout. **In Next.js, pass `router.refresh()`** to sync server actions with updated cookies |
1300
1306
  | `onLoginSuccess` | `(data) => void` | — | Called after successful login |
1301
1307
  | `onSignupSuccess` | `(data) => void` | — | Called after successful signup |
1302
1308
  | `onLogoutSuccess` | `() => void` | — | Called after logout |
@@ -1311,6 +1317,33 @@ function createAccessClient(
1311
1317
 
1312
1318
  `azirid-react` supports **Next.js 14, 15, and 16+** with full compatibility for each version's API conventions.
1313
1319
 
1320
+ ### Server Actions & `onAuthStateChange`
1321
+
1322
+ If you use **server actions** or **server components** that read the session token (via `createServerAccess`), you **must** pass `onAuthStateChange` to keep server-side cookies in sync:
1323
+
1324
+ ```tsx
1325
+ 'use client'
1326
+ import { useRouter } from 'next/navigation'
1327
+ import { AziridProvider } from 'azirid-react'
1328
+
1329
+ export function Providers({ children }: { children: React.ReactNode }) {
1330
+ const router = useRouter()
1331
+
1332
+ return (
1333
+ <AziridProvider
1334
+ publishableKey={process.env.NEXT_PUBLIC_AZIRID_PK!}
1335
+ onAuthStateChange={() => router.refresh()}
1336
+ >
1337
+ {children}
1338
+ </AziridProvider>
1339
+ )
1340
+ }
1341
+ ```
1342
+
1343
+ **Why?** After login/signup/logout, azirid-react updates the `__session` cookie in the browser. But Next.js server actions keep using the old cookies until `router.refresh()` forces a new server request. Without this, server actions may return 401 after login.
1344
+
1345
+ > **Not using server actions?** (e.g. pure client-side SPA with Vite) — you can skip `onAuthStateChange`.
1346
+
1314
1347
  ### Proxy Route Handler (all versions)
1315
1348
 
1316
1349
  Create the file `app/api/auth/[...path]/route.ts` — one line is all you need:
package/dist/index.cjs CHANGED
@@ -768,9 +768,15 @@ function AziridProviderInner({
768
768
  },
769
769
  [client]
770
770
  );
771
+ const bootstrapCalled = react.useRef(false);
771
772
  react.useEffect(() => {
772
773
  const autoBootstrap = props.autoBootstrap ?? true;
773
774
  if (!autoBootstrap) return;
775
+ if (bootstrapCalled.current) {
776
+ setIsBootstrapping(false);
777
+ return;
778
+ }
779
+ bootstrapCalled.current = true;
774
780
  let cancelled = false;
775
781
  async function bootstrap() {
776
782
  setIsBootstrapping(true);
@@ -3011,6 +3017,7 @@ function usePayButton({
3011
3017
  const [payphoneConfig, setPayphoneConfig] = react.useState(null);
3012
3018
  const [currentError, setCurrentError] = react.useState(null);
3013
3019
  const payphoneConfirmTriggered = react.useRef(false);
3020
+ const { isBootstrapping } = useAzirid();
3014
3021
  const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : new URLSearchParams();
3015
3022
  const callbackId = params.get("id");
3016
3023
  const callbackClientTxId = params.get("clientTransactionId");
@@ -3093,12 +3100,12 @@ function usePayButton({
3093
3100
  }
3094
3101
  });
3095
3102
  react.useEffect(() => {
3096
- if (!isPayphoneCallback || payphoneConfirmTriggered.current) return;
3103
+ if (!isPayphoneCallback || payphoneConfirmTriggered.current || isBootstrapping) return;
3097
3104
  payphoneConfirmTriggered.current = true;
3098
3105
  setSelectedProvider("PAYPHONE");
3099
3106
  setStatus("processing");
3100
3107
  confirmPayphone({ id: Number(callbackId), clientTransactionId: callbackClientTxId });
3101
- }, [isPayphoneCallback, callbackId, callbackClientTxId, confirmPayphone]);
3108
+ }, [isPayphoneCallback, isBootstrapping, callbackId, callbackClientTxId, confirmPayphone]);
3102
3109
  const handleSdkError = react.useCallback(
3103
3110
  (err) => {
3104
3111
  setCurrentError(err);
@@ -3532,16 +3539,17 @@ function PayphoneCallback({ onSuccess, onError, className, style }) {
3532
3539
  onSuccess,
3533
3540
  onError
3534
3541
  });
3542
+ const { isBootstrapping } = useAzirid();
3535
3543
  const called = react.useRef(false);
3536
3544
  react.useEffect(() => {
3537
- if (called.current) return;
3545
+ if (called.current || isBootstrapping) return;
3538
3546
  called.current = true;
3539
3547
  const params = new URLSearchParams(window.location.search);
3540
3548
  const id = params.get("id");
3541
3549
  const clientTransactionId = params.get("clientTransactionId");
3542
3550
  if (!id || !clientTransactionId) return;
3543
3551
  mutate({ id: Number(id), clientTransactionId });
3544
- }, [mutate]);
3552
+ }, [mutate, isBootstrapping]);
3545
3553
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { textAlign: "center", padding: "32px", ...style }, children: [
3546
3554
  isPending && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3547
3555
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: textStyle, children: "Confirming your payment..." }),
@@ -4043,6 +4051,7 @@ function usePayphoneCheckout({
4043
4051
  const [currentError, setCurrentError] = react.useState(null);
4044
4052
  const checkoutTriggered = react.useRef(false);
4045
4053
  const confirmTriggered = react.useRef(false);
4054
+ const { isBootstrapping } = useAzirid();
4046
4055
  const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : new URLSearchParams();
4047
4056
  const callbackId = params.get("id");
4048
4057
  const callbackClientTxId = params.get("clientTransactionId");
@@ -4084,11 +4093,11 @@ function usePayphoneCheckout({
4084
4093
  }
4085
4094
  });
4086
4095
  react.useEffect(() => {
4087
- if (!isCallback || confirmTriggered.current) return;
4096
+ if (!isCallback || confirmTriggered.current || isBootstrapping) return;
4088
4097
  confirmTriggered.current = true;
4089
4098
  setStatus("confirming");
4090
4099
  confirm({ id: Number(callbackId), clientTransactionId: callbackClientTxId });
4091
- }, [isCallback, callbackId, callbackClientTxId, confirm]);
4100
+ }, [isCallback, isBootstrapping, callbackId, callbackClientTxId, confirm]);
4092
4101
  const handleSdkError = react.useCallback(
4093
4102
  (err) => {
4094
4103
  setCurrentError(err);
@@ -4346,7 +4355,7 @@ function usePasswordToggle() {
4346
4355
  }
4347
4356
 
4348
4357
  // src/index.ts
4349
- var SDK_VERSION = "0.10.1";
4358
+ var SDK_VERSION = "0.10.3";
4350
4359
 
4351
4360
  exports.AuthForm = AuthForm;
4352
4361
  exports.AziridProvider = AziridProvider;