cloudflare-next-intl 0.7.2 → 0.7.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
@@ -247,12 +247,23 @@ export async function generateMetadata({ params }) {
247
247
 
248
248
  ### Theme switcher
249
249
 
250
+ ### Firebase Auth
251
+
252
+ Set `firebaseAuth` on your `RoutingConfig` to enable — `IntlProvider` then
253
+ auto-wires `AuthUserProvider` and server-side `getAuthUser` session validation.
254
+
255
+ Features include:
256
+ - `createForgotPasswordAction(locale, actionCodeSettings?)`: Accepts optional Firebase `AuthActionCodeSettings` (e.g. `url` redirect link).
257
+ - `sendVerificationEmail(actionCodeSettings?)` on `useAuthUser()`: Custom action email settings when resending email verification.
258
+ - `followSameOriginContinueUrl`: Automatically forwards emailed action links with `continueUrl` to the specified path (or external URL) directly from `intlMiddleware` (default `true`; set `false` on `firebaseAuth` config to disable).
259
+
250
260
  ```tsx
251
261
  import ThemeSwitcher from "cloudflare-next-intl/ThemeSwitcher";
252
262
 
253
263
  <ThemeSwitcher lightLabelText="Light" darkLabelText="Dark" />
254
264
  ```
255
265
 
266
+
256
267
  ### Cookie consent
257
268
 
258
269
  Set `cookieConsent` on your `RoutingConfig` to enable — `IntlProvider` then
@@ -1,4 +1,4 @@
1
- import type { AuthActionMessages, AuthFormState } from '../types';
1
+ import type { AuthActionCodeSettings, AuthActionMessages, AuthFormState } from '../types';
2
2
  /**
3
3
  * Builds a login server action for React's `useActionState` form hook.
4
4
  * The returned function has the `(prevState, formData) => Promise<AuthFormState>`
@@ -37,10 +37,10 @@ export declare function createSignUpAction(locale: string, messages: AuthActionM
37
37
  * `formData` must contain an `email` field.
38
38
  *
39
39
  * @param locale Used to localize the returned error message.
40
- * @param messages Localized action messages (currently unused by this action).
40
+ * @param actionCodeSettings Optional settings for action email (continue URL, etc.).
41
41
  * @returns A form action: `{ success: true }` on success, `{ error }` on failure.
42
42
  * @example
43
- * const [state, action] = useActionState(createForgotPasswordAction(locale, messages), {});
43
+ * const [state, action] = useActionState(createForgotPasswordAction(locale), {});
44
44
  * <form action={action}>...</form>
45
45
  */
46
- export declare function createForgotPasswordAction(locale: string, messages: AuthActionMessages): (_prevState: AuthFormState, formData: FormData) => Promise<AuthFormState>;
46
+ export declare function createForgotPasswordAction(locale: string, actionCodeSettings?: AuthActionCodeSettings): (_prevState: AuthFormState, formData: FormData) => Promise<AuthFormState>;
@@ -79,20 +79,20 @@ export function createSignUpAction(locale, messages) {
79
79
  * `formData` must contain an `email` field.
80
80
  *
81
81
  * @param locale Used to localize the returned error message.
82
- * @param messages Localized action messages (currently unused by this action).
82
+ * @param actionCodeSettings Optional settings for action email (continue URL, etc.).
83
83
  * @returns A form action: `{ success: true }` on success, `{ error }` on failure.
84
84
  * @example
85
- * const [state, action] = useActionState(createForgotPasswordAction(locale, messages), {});
85
+ * const [state, action] = useActionState(createForgotPasswordAction(locale), {});
86
86
  * <form action={action}>...</form>
87
87
  */
88
- export function createForgotPasswordAction(locale, messages) {
88
+ export function createForgotPasswordAction(locale, actionCodeSettings) {
89
89
  return async function forgotPasswordAction(_prevState, formData) {
90
90
  requireFirebaseAuthConfig(config.firebaseAuth);
91
91
  const { auth } = await getFirebaseAuthClient();
92
92
  const { sendPasswordResetEmail } = await getFirebaseAuthModule();
93
93
  const email = (formData.get('email')?.toString() ?? '').trim();
94
94
  try {
95
- await sendPasswordResetEmail(auth, email);
95
+ await sendPasswordResetEmail(auth, email, actionCodeSettings);
96
96
  return { success: true };
97
97
  }
98
98
  catch (e) {
@@ -1,4 +1,4 @@
1
- import type { AuthUser, SerializedAuthUser } from '../types';
1
+ import type { AuthActionCodeSettings, AuthUser, SerializedAuthUser } from '../types';
2
2
  export interface AuthUserContextType {
3
3
  /** Current Firebase user, or `null` if signed out (or not yet resolved while `loading`). */
4
4
  user: AuthUser | null;
@@ -7,7 +7,7 @@ export interface AuthUserContextType {
7
7
  /** Force-refreshes the current user's ID token/claims and re-syncs the session cookie. */
8
8
  reloadUser: () => Promise<void>;
9
9
  /** Sends a verification email to the currently signed-in user. */
10
- sendVerificationEmail: () => Promise<void>;
10
+ sendVerificationEmail: (actionCodeSettings?: AuthActionCodeSettings) => Promise<void>;
11
11
  /** Signs out, clears the session cookie, and redirects to `firebaseAuth.redirectAuthPath`. */
12
12
  logout: () => Promise<void>;
13
13
  }
@@ -311,13 +311,13 @@ export default function AuthUserProvider({ initialUser = null, children }) {
311
311
  console.error('AuthUserProvider: reloadUser failed', e);
312
312
  }
313
313
  }, []);
314
- const sendVerificationEmail = useCallback(async () => {
314
+ const sendVerificationEmail = useCallback(async (actionCodeSettings) => {
315
315
  const { auth } = await getFirebaseAuthClient();
316
316
  const user = auth.currentUser;
317
317
  if (!user)
318
318
  return;
319
319
  const { sendEmailVerification } = await getFirebaseAuthModule();
320
- await sendEmailVerification(user);
320
+ await sendEmailVerification(user, actionCodeSettings);
321
321
  }, []);
322
322
  const logout = useCallback(async () => {
323
323
  try {
@@ -240,7 +240,37 @@ export default async function updateSession(request, baseResponse, locale, rebui
240
240
  if (fa.actionLinkRedirectEnabled !== false && isEligibleActionPath) {
241
241
  const mode = request.nextUrl.searchParams.get('mode');
242
242
  if (mode) {
243
- const target = resolveActionModePaths(fa)[mode];
243
+ let target = resolveActionModePaths(fa)[mode];
244
+ if (fa.followSameOriginContinueUrl !== false) {
245
+ const continueUrl = request.nextUrl.searchParams.get('continueUrl');
246
+ if (continueUrl) {
247
+ try {
248
+ const parsed = new URL(continueUrl, request.url);
249
+ if (parsed.origin === request.nextUrl.origin) {
250
+ let continuePath = parsed.pathname;
251
+ if (locale !== config.defaultLocale) {
252
+ if (continuePath.startsWith(`/${locale}/`)) {
253
+ continuePath = continuePath.slice(locale.length + 1);
254
+ }
255
+ else if (continuePath === `/${locale}`) {
256
+ continuePath = '/';
257
+ }
258
+ }
259
+ if (continuePath !== '/') {
260
+ target = continuePath;
261
+ }
262
+ }
263
+ else {
264
+ // External continueUrl: redirect directly to the full external URL
265
+ parsed.search = request.nextUrl.search;
266
+ return buildRedirect(baseResponse, parsed);
267
+ }
268
+ }
269
+ catch {
270
+ // Invalid continueUrl format — fall back to resolved mode target
271
+ }
272
+ }
273
+ }
244
274
  // Skip when already on the destination — the forward sets the same
245
275
  // `?mode=` it matched on, so redirecting again would loop forever.
246
276
  if (target && target !== path) {
@@ -20,3 +20,18 @@ export interface AuthActionMessages {
20
20
  mismatch?: string;
21
21
  }
22
22
  export type AuthUser = User | SerializedAuthUser;
23
+ /** Settings for configuring action code emails (password reset, email verification). */
24
+ export interface AuthActionCodeSettings {
25
+ url: string;
26
+ handleCodeInApp?: boolean;
27
+ iOS?: {
28
+ bundleId: string;
29
+ };
30
+ android?: {
31
+ packageName: string;
32
+ installApp?: boolean;
33
+ minimumVersion?: string;
34
+ };
35
+ dynamicLinkDomain?: string;
36
+ linkDomain?: string;
37
+ }
@@ -515,6 +515,12 @@ export interface FirebaseAuthRoutingConfig {
515
515
  * Defaults to `true`.
516
516
  */
517
517
  actionLinkRedirectEnabled?: boolean;
518
+ /**
519
+ * When `true` (the default), if an emailed-action-link carries a `continueUrl` query parameter
520
+ * matching the request's origin (same domain/app), the middleware will redirect to `continueUrl`'s
521
+ * path instead of the static mode path (e.g. `resetPasswordPath`). Set `false` to disable.
522
+ */
523
+ followSameOriginContinueUrl?: boolean;
518
524
  /**
519
525
  * Restricts the emailed-action-link forward (see {@link resetPasswordPath})
520
526
  * to this exact static path — set this to whatever path your Firebase
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
+
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "sideEffects": false,