cloudflare-next-intl 0.3.0 → 0.3.2
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/src/config/middleware.js +5 -1
- package/dist/src/firebase_auth/client/auth_user_provider.js +11 -10
- package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.js +3 -0
- package/dist/src/firebase_auth/index.d.ts +1 -1
- package/dist/src/firebase_auth/index.js +1 -1
- package/dist/src/firebase_auth/middleware/update_session.d.ts +2 -2
- package/dist/src/firebase_auth/middleware/update_session.js +59 -11
- package/dist/src/firebase_auth/server/firebase_server.js +2 -1
- package/dist/src/firebase_auth/server/use_auth_user_server.d.ts +31 -11
- package/dist/src/firebase_auth/server/use_auth_user_server.js +31 -13
- package/dist/src/general/general_functions.js +11 -2
- package/dist/src/types/types.d.ts +4 -0
- package/llms.txt +1 -0
- package/package.json +7 -3
|
@@ -15,6 +15,8 @@ async function getIsBotValue(userAgent) {
|
|
|
15
15
|
if (userAgent === null)
|
|
16
16
|
return false;
|
|
17
17
|
const { isBot } = await import('next/dist/server/web/spec-extension/user-agent');
|
|
18
|
+
// Unreachable: userAgent is already narrowed to non-null string above,
|
|
19
|
+
// so the ?? '' fallback never triggers.
|
|
18
20
|
return isBot(userAgent ?? '');
|
|
19
21
|
}
|
|
20
22
|
const getIsBotValueCache = cache(getIsBotValue);
|
|
@@ -57,7 +59,9 @@ export default async function intlMiddleware(request, options) {
|
|
|
57
59
|
let urlLocale;
|
|
58
60
|
let pathWithoutLocale;
|
|
59
61
|
// Avoids split('/').filter(Boolean) array allocation on every request:
|
|
60
|
-
// scan for the first segment's bounds directly.
|
|
62
|
+
// scan for the first segment's bounds directly. Unreachable:
|
|
63
|
+
// Next.js guarantees pathname always starts with '/', so the else
|
|
64
|
+
// branch (segmentStart = 0) never runs.
|
|
61
65
|
const segmentStart = pathname.charCodeAt(0) === 47 /* '/' */ ? 1 : 0;
|
|
62
66
|
let segmentEnd = pathname.indexOf('/', segmentStart);
|
|
63
67
|
if (segmentEnd === -1)
|
|
@@ -7,15 +7,15 @@ import config from '@intl-config';
|
|
|
7
7
|
import requireFirebaseAuthConfig from '../require_config';
|
|
8
8
|
import { getFirebaseAuthClient } from './firebase_client';
|
|
9
9
|
import { setAuthUserCache } from './auth_user_cache';
|
|
10
|
-
import {
|
|
10
|
+
import { defaultSessionCookieName } from '../middleware/update_session';
|
|
11
11
|
// `null` default (instead of a `{ loading: true, ... }` stand-in) lets
|
|
12
12
|
// `useAuthUser` distinguish "not wrapped in AuthUserProvider" (throw) from
|
|
13
13
|
// "wrapped, still loading" (`loading: true`).
|
|
14
14
|
export const AuthUserContext = createContext(null);
|
|
15
|
-
function writeSessionCookie(idToken, maxAge) {
|
|
15
|
+
function writeSessionCookie(sessionCookieName, idToken, maxAge) {
|
|
16
16
|
document.cookie = `${sessionCookieName}=${idToken}; path=/; max-age=${maxAge}`;
|
|
17
17
|
}
|
|
18
|
-
function clearSessionCookie() {
|
|
18
|
+
function clearSessionCookie(sessionCookieName) {
|
|
19
19
|
document.cookie = `${sessionCookieName}=; path=/; max-age=0`;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
@@ -44,6 +44,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
44
44
|
const isAuthPage = fa.isAuthPath(pathname);
|
|
45
45
|
const isWhiteListed = fa.whiteListPaths?.includes(pathname) ?? false;
|
|
46
46
|
const maxAge = fa.sessionCookieMaxAge ?? 60 * 60 * 24 * 5;
|
|
47
|
+
const sessionCookieName = fa.sessionCookieName ?? defaultSessionCookieName;
|
|
47
48
|
const [state, setState] = useState({
|
|
48
49
|
user: initialUser,
|
|
49
50
|
loading: initialUser === null,
|
|
@@ -76,10 +77,10 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
76
77
|
const previous = syncedSignedIn.current;
|
|
77
78
|
try {
|
|
78
79
|
if (user) {
|
|
79
|
-
writeSessionCookie(await user.getIdToken(true), maxAge);
|
|
80
|
+
writeSessionCookie(sessionCookieName, await user.getIdToken(true), maxAge);
|
|
80
81
|
}
|
|
81
82
|
else if (previous) {
|
|
82
|
-
clearSessionCookie();
|
|
83
|
+
clearSessionCookie(sessionCookieName);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
catch (e) {
|
|
@@ -112,7 +113,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
112
113
|
unsubscribe?.();
|
|
113
114
|
};
|
|
114
115
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
-
}, [router, isAuthPage, maxAge]);
|
|
116
|
+
}, [router, isAuthPage, maxAge, sessionCookieName]);
|
|
116
117
|
const reloadUser = useCallback(async () => {
|
|
117
118
|
const { auth } = await getFirebaseAuthClient();
|
|
118
119
|
const user = auth.currentUser;
|
|
@@ -120,10 +121,10 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
120
121
|
return;
|
|
121
122
|
const { reload } = await import('firebase/auth');
|
|
122
123
|
await reload(user);
|
|
123
|
-
writeSessionCookie(await user.getIdToken(true), maxAge);
|
|
124
|
+
writeSessionCookie(sessionCookieName, await user.getIdToken(true), maxAge);
|
|
124
125
|
setAuthUserCache(user);
|
|
125
126
|
setState({ user, loading: false });
|
|
126
|
-
}, [maxAge]);
|
|
127
|
+
}, [maxAge, sessionCookieName]);
|
|
127
128
|
const sendVerificationEmail = useCallback(async () => {
|
|
128
129
|
const { auth } = await getFirebaseAuthClient();
|
|
129
130
|
const user = auth.currentUser;
|
|
@@ -139,10 +140,10 @@ export default function AuthUserProvider({ initialUser = null, children }) {
|
|
|
139
140
|
await signOut(auth);
|
|
140
141
|
}
|
|
141
142
|
finally {
|
|
142
|
-
clearSessionCookie();
|
|
143
|
+
clearSessionCookie(sessionCookieName);
|
|
143
144
|
window.location.assign(fa.redirectAuthPath);
|
|
144
145
|
}
|
|
145
146
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
-
}, [fa.redirectAuthPath]);
|
|
147
|
+
}, [fa.redirectAuthPath, sessionCookieName]);
|
|
147
148
|
return _jsx(AuthUserContext.Provider, { value: { ...state, reloadUser, sendVerificationEmail, logout }, children: children });
|
|
148
149
|
}
|
|
@@ -38,5 +38,8 @@ export default function firebaseAuthErrorMessage(locale, error) {
|
|
|
38
38
|
// fall through to English default
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
// Unreachable: key is always either a value from ERROR_CODE_TO_KEY (all
|
|
42
|
+
// valid DEFAULT_MESSAGES_EN keys) or the literal 'unknown' fallback
|
|
43
|
+
// above, so DEFAULT_MESSAGES_EN[key] never misses.
|
|
41
44
|
return DEFAULT_MESSAGES_EN[key] ?? DEFAULT_MESSAGES_EN.unknown;
|
|
42
45
|
}
|
|
@@ -3,7 +3,7 @@ export { default as FirebaseAuthServerProvider } from './server/auth_user_server
|
|
|
3
3
|
export { default as useFirebaseAuthUserClient } from './client/use_auth_user';
|
|
4
4
|
export { default as useFirebaseAuthUserServer } from './server/use_auth_user_server';
|
|
5
5
|
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
6
|
-
export { default as updateFirebaseAuthSession,
|
|
6
|
+
export { default as updateFirebaseAuthSession, defaultSessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
7
7
|
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
8
8
|
export type { SerializedAuthUser, AuthFormState, AuthActionMessages, AuthUser } from './types';
|
|
9
9
|
export type { FirebaseAuthRoutingConfig } from '../types/types';
|
|
@@ -9,5 +9,5 @@ export { default as FirebaseAuthServerProvider } from './server/auth_user_server
|
|
|
9
9
|
export { default as useFirebaseAuthUserClient } from './client/use_auth_user';
|
|
10
10
|
export { default as useFirebaseAuthUserServer } from './server/use_auth_user_server';
|
|
11
11
|
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
12
|
-
export { default as updateFirebaseAuthSession,
|
|
12
|
+
export { default as updateFirebaseAuthSession, defaultSessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
13
13
|
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const defaultSessionCookieName = "__fa_session__";
|
|
3
|
+
export declare const defaultRefreshTokenCookieName = "__fa_refresh_token__";
|
|
4
4
|
/**
|
|
5
5
|
* Layers Firebase session-cookie validation/refresh and auth redirects onto
|
|
6
6
|
* an already-built middleware response. Called internally by `intlMiddleware`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import config from '@intl-config';
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
3
|
+
export const defaultSessionCookieName = '__fa_session__';
|
|
4
|
+
export const defaultRefreshTokenCookieName = '__fa_refresh_token__';
|
|
5
5
|
const DEFAULT_SESSION_MAX_AGE = 60 * 60 * 24 * 5;
|
|
6
6
|
const DEFAULT_REFRESH_MAX_AGE = 60 * 60 * 24 * 365;
|
|
7
7
|
// Refresh slightly before the real expiry — treating a token as expired
|
|
@@ -77,6 +77,21 @@ async function setCachedRefresh(refreshToken, refreshed) {
|
|
|
77
77
|
// refresh result already returned to the caller.
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
// Google's Secure Token API returns 400 with one of these error codes when
|
|
81
|
+
// the refresh token itself is the problem (expired/revoked/malformed/the
|
|
82
|
+
// associated user no longer exists) — this is the ONLY case that should
|
|
83
|
+
// sign the user out. Any other failure (5xx, network error, timeout,
|
|
84
|
+
// unrecognized 400 body) is transient/unexpected and must NOT clear the
|
|
85
|
+
// refresh-token cookie or redirect to login: doing so previously caused a
|
|
86
|
+
// signed-in user with a perfectly valid refresh token to flash to /login
|
|
87
|
+
// and bounce back home the moment their ID token merely expired and a
|
|
88
|
+
// single refresh attempt happened to fail.
|
|
89
|
+
const INVALID_REFRESH_TOKEN_ERRORS = new Set([
|
|
90
|
+
'INVALID_REFRESH_TOKEN',
|
|
91
|
+
'TOKEN_EXPIRED',
|
|
92
|
+
'USER_DISABLED',
|
|
93
|
+
'USER_NOT_FOUND',
|
|
94
|
+
]);
|
|
80
95
|
/**
|
|
81
96
|
* Mints a fresh ID token from a stored refresh token via Google's Secure
|
|
82
97
|
* Token API. No `firebase/auth` import: this runs in the Edge middleware
|
|
@@ -86,15 +101,28 @@ async function setCachedRefresh(refreshToken, refreshed) {
|
|
|
86
101
|
async function refreshIdToken(apiKey, refreshToken) {
|
|
87
102
|
const cached = await getCachedRefresh(refreshToken);
|
|
88
103
|
if (cached)
|
|
89
|
-
return cached;
|
|
104
|
+
return { status: 'refreshed', ...cached };
|
|
90
105
|
try {
|
|
91
106
|
const res = await fetch(`https://securetoken.googleapis.com/v1/token?key=${apiKey}`, {
|
|
92
107
|
method: 'POST',
|
|
93
108
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
94
109
|
body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`,
|
|
95
110
|
});
|
|
96
|
-
if (!res.ok)
|
|
97
|
-
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
if (res.status === 400) {
|
|
113
|
+
try {
|
|
114
|
+
const errorBody = await res.json();
|
|
115
|
+
if (errorBody.error?.message && INVALID_REFRESH_TOKEN_ERRORS.has(errorBody.error.message)) {
|
|
116
|
+
return { status: 'invalid' };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Unparseable body on a 400 — treat as transient rather
|
|
121
|
+
// than assuming the token is invalid.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { status: 'transient-failure' };
|
|
125
|
+
}
|
|
98
126
|
const data = await res.json();
|
|
99
127
|
const refreshed = { idToken: data.id_token, refreshToken: data.refresh_token };
|
|
100
128
|
// Not awaited: the cache write is a pure optimization for FUTURE
|
|
@@ -103,10 +131,10 @@ async function refreshIdToken(apiKey, refreshToken) {
|
|
|
103
131
|
// setCachedRefresh already swallows its own errors, so a rejected
|
|
104
132
|
// write here would otherwise surface as an unhandled rejection.
|
|
105
133
|
void setCachedRefresh(refreshToken, refreshed);
|
|
106
|
-
return refreshed;
|
|
134
|
+
return { status: 'refreshed', ...refreshed };
|
|
107
135
|
}
|
|
108
136
|
catch {
|
|
109
|
-
return
|
|
137
|
+
return { status: 'transient-failure' };
|
|
110
138
|
}
|
|
111
139
|
}
|
|
112
140
|
/**
|
|
@@ -131,6 +159,8 @@ export default async function updateSession(request, baseResponse, locale) {
|
|
|
131
159
|
const fa = config.firebaseAuth;
|
|
132
160
|
if (!fa || fa.middlewareEnabled === false)
|
|
133
161
|
return baseResponse;
|
|
162
|
+
const sessionCookieName = fa.sessionCookieName ?? defaultSessionCookieName;
|
|
163
|
+
const refreshTokenCookieName = fa.refreshTokenCookieName ?? defaultRefreshTokenCookieName;
|
|
134
164
|
const rawPath = request.nextUrl.pathname;
|
|
135
165
|
const requestPrefix = `/${locale}`;
|
|
136
166
|
const path = rawPath === requestPrefix || rawPath.startsWith(`${requestPrefix}/`)
|
|
@@ -149,19 +179,30 @@ export default async function updateSession(request, baseResponse, locale) {
|
|
|
149
179
|
let token = request.cookies.get(sessionCookieName)?.value;
|
|
150
180
|
let refreshedToken = null;
|
|
151
181
|
let clearInvalidSession = false;
|
|
182
|
+
// A transient refresh failure (network blip, Google 5xx, timeout) means
|
|
183
|
+
// "couldn't confirm the session right now" — NOT "this user is signed
|
|
184
|
+
// out". Redirecting to login in that case is the bug this guards
|
|
185
|
+
// against: it signs a still-valid user out for a one-off hiccup, and
|
|
186
|
+
// the client SDK (which still has a live session independent of these
|
|
187
|
+
// cookies) then bounces them straight back, producing a login flash.
|
|
188
|
+
let refreshWasTransientFailure = false;
|
|
152
189
|
if (token && isJwtExpired(token)) {
|
|
153
190
|
token = undefined;
|
|
154
191
|
}
|
|
155
192
|
if (!token) {
|
|
156
193
|
const refreshToken = request.cookies.get(refreshTokenCookieName)?.value;
|
|
157
194
|
if (refreshToken) {
|
|
158
|
-
|
|
159
|
-
if (
|
|
195
|
+
const result = await refreshIdToken(fa.apiKey, refreshToken);
|
|
196
|
+
if (result.status === 'refreshed') {
|
|
197
|
+
refreshedToken = { idToken: result.idToken, refreshToken: result.refreshToken };
|
|
160
198
|
token = refreshedToken.idToken;
|
|
161
199
|
}
|
|
162
|
-
else {
|
|
200
|
+
else if (result.status === 'invalid') {
|
|
163
201
|
clearInvalidSession = true;
|
|
164
202
|
}
|
|
203
|
+
else {
|
|
204
|
+
refreshWasTransientFailure = true;
|
|
205
|
+
}
|
|
165
206
|
}
|
|
166
207
|
else if (request.cookies.get(sessionCookieName)) {
|
|
167
208
|
clearInvalidSession = true;
|
|
@@ -169,7 +210,14 @@ export default async function updateSession(request, baseResponse, locale) {
|
|
|
169
210
|
}
|
|
170
211
|
const hasSession = !!token;
|
|
171
212
|
let response;
|
|
172
|
-
if (
|
|
213
|
+
if (refreshWasTransientFailure) {
|
|
214
|
+
// Couldn't confirm the session either way — pass through without
|
|
215
|
+
// forcing a redirect in either direction. The next request (or the
|
|
216
|
+
// client SDK's own session, independent of these cookies) gets a
|
|
217
|
+
// chance to resolve this correctly instead of guessing wrong.
|
|
218
|
+
response = baseResponse;
|
|
219
|
+
}
|
|
220
|
+
else if (!hasSession) {
|
|
173
221
|
response = isAuthPage ? baseResponse : buildRedirect(baseResponse, localeUrl(fa.redirectAuthPath));
|
|
174
222
|
}
|
|
175
223
|
else if (isAuthPage) {
|
|
@@ -2,7 +2,7 @@ import { cookies } from 'next/headers';
|
|
|
2
2
|
import { cache } from 'react';
|
|
3
3
|
import config from '@intl-config';
|
|
4
4
|
import requireFirebaseAuthConfig from '../require_config';
|
|
5
|
-
import {
|
|
5
|
+
import { defaultSessionCookieName } from '../middleware/update_session';
|
|
6
6
|
let baseApp;
|
|
7
7
|
/**
|
|
8
8
|
* Resolves the signed-in user on the server from the session cookie.
|
|
@@ -16,6 +16,7 @@ let baseApp;
|
|
|
16
16
|
export const getAuthenticatedAppForUser = cache(async function getAuthenticatedAppForUser() {
|
|
17
17
|
const fa = config.firebaseAuth;
|
|
18
18
|
requireFirebaseAuthConfig(fa);
|
|
19
|
+
const sessionCookieName = fa.sessionCookieName ?? defaultSessionCookieName;
|
|
19
20
|
const authIdToken = (await cookies()).get(sessionCookieName)?.value;
|
|
20
21
|
if (!authIdToken) {
|
|
21
22
|
return { firebaseServerApp: null, currentUser: null };
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
import type { User } from 'firebase/auth';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
* Resolves the current request's authenticated Firebase user.
|
|
4
|
+
* @returns `{ user, loading: false }` — `loading` is always `false` here;
|
|
5
|
+
* server resolution is synchronous with respect to the awaited call.
|
|
6
|
+
*/
|
|
7
|
+
declare function iGetAuthUser(): Promise<{
|
|
8
|
+
user: User | null;
|
|
9
|
+
loading: false;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Server Component/Action only: resolves the current request's
|
|
13
|
+
* authenticated Firebase user, same style as {@link getLocale}/
|
|
14
|
+
* {@link getTranslations} — an unconditional, always-`async` export
|
|
15
|
+
* (`cloudflare-next-intl/getFirebaseAuthUser`), so the `await` requirement
|
|
16
|
+
* is visible from the type itself in every editor.
|
|
17
|
+
*
|
|
18
|
+
* Returns the same `{ user, loading }` shape the client `useAuthUser()`
|
|
19
|
+
* hook's context exposes, so `const { user } = await getAuthUser()`
|
|
20
|
+
* generalizes correctly from `const { user } = useAuthUser()` on the client.
|
|
9
21
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const { user } = await getAuthUser();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const getAuthUser: typeof iGetAuthUser;
|
|
28
|
+
/**
|
|
29
|
+
* Server Component counterpart of the client `useAuthUser()` hook, reached
|
|
30
|
+
* via `cloudflare-next-intl/useFirebaseAuthUser`'s `react-server` condition
|
|
31
|
+
* (resolved automatically — not meant to be imported directly by name).
|
|
32
|
+
* Prefer {@link getAuthUser} for an unconditional, editor-typed-as-async
|
|
33
|
+
* equivalent.
|
|
15
34
|
*/
|
|
16
35
|
export default function useAuthUser(): Promise<{
|
|
17
36
|
user: User | null;
|
|
18
37
|
loading: false;
|
|
19
38
|
}>;
|
|
39
|
+
export {};
|
|
@@ -1,19 +1,37 @@
|
|
|
1
1
|
import { getAuthenticatedAppForUser } from './firebase_server';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
* automatically, not a separately-imported function). Reads through the
|
|
7
|
-
* same `cache()`-wrapped `getAuthenticatedAppForUser`, so every server
|
|
8
|
-
* component calling this within one request shares one lookup.
|
|
9
|
-
*
|
|
10
|
-
* Returns the same `{ user, loading }` shape the client variant's context
|
|
11
|
-
* exposes (`loading` is always `false` here — server resolution is
|
|
12
|
-
* synchronous with respect to the awaited call), so code reading
|
|
13
|
-
* `const { user } = await useAuthUser()` generalizes correctly from
|
|
14
|
-
* `const { user } = useAuthUser()` on the client side.
|
|
3
|
+
* Resolves the current request's authenticated Firebase user.
|
|
4
|
+
* @returns `{ user, loading: false }` — `loading` is always `false` here;
|
|
5
|
+
* server resolution is synchronous with respect to the awaited call.
|
|
15
6
|
*/
|
|
16
|
-
|
|
7
|
+
async function iGetAuthUser() {
|
|
17
8
|
const { currentUser } = await getAuthenticatedAppForUser();
|
|
18
9
|
return { user: currentUser, loading: false };
|
|
19
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Server Component/Action only: resolves the current request's
|
|
13
|
+
* authenticated Firebase user, same style as {@link getLocale}/
|
|
14
|
+
* {@link getTranslations} — an unconditional, always-`async` export
|
|
15
|
+
* (`cloudflare-next-intl/getFirebaseAuthUser`), so the `await` requirement
|
|
16
|
+
* is visible from the type itself in every editor.
|
|
17
|
+
*
|
|
18
|
+
* Returns the same `{ user, loading }` shape the client `useAuthUser()`
|
|
19
|
+
* hook's context exposes, so `const { user } = await getAuthUser()`
|
|
20
|
+
* generalizes correctly from `const { user } = useAuthUser()` on the client.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const { user } = await getAuthUser();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const getAuthUser = iGetAuthUser;
|
|
28
|
+
/**
|
|
29
|
+
* Server Component counterpart of the client `useAuthUser()` hook, reached
|
|
30
|
+
* via `cloudflare-next-intl/useFirebaseAuthUser`'s `react-server` condition
|
|
31
|
+
* (resolved automatically — not meant to be imported directly by name).
|
|
32
|
+
* Prefer {@link getAuthUser} for an unconditional, editor-typed-as-async
|
|
33
|
+
* equivalent.
|
|
34
|
+
*/
|
|
35
|
+
export default async function useAuthUser() {
|
|
36
|
+
return getAuthUser();
|
|
37
|
+
}
|
|
@@ -51,7 +51,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
// If after traversal, no base translations object was found.
|
|
54
|
+
// If after traversal, no base translations object was found. Unreachable:
|
|
55
|
+
// the loop above always either sets translationsBase or returns early on
|
|
56
|
+
// its final iteration, since namespaceParts always has length >= 1
|
|
57
|
+
// (''.split('.') yields ['']).
|
|
55
58
|
if (!translationsBase) {
|
|
56
59
|
return errorAndReturnFallback(`Translations for namespace "${namespace}" could not be found.`, cacheKeyValue, locale, namespace);
|
|
57
60
|
}
|
|
@@ -66,6 +69,9 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
66
69
|
// Traverse the resolved translations base using the key parts.
|
|
67
70
|
for (let i = 0; i < keyParts.length; i++) {
|
|
68
71
|
const part = keyParts[i];
|
|
72
|
+
// Unreachable: currentTranslation only ever becomes a string via
|
|
73
|
+
// the reassignment below, which is guarded to only assign
|
|
74
|
+
// non-null objects.
|
|
69
75
|
if (typeof currentTranslation === 'string') {
|
|
70
76
|
// Translation key path prematurely leads to a string.
|
|
71
77
|
console.warn(`Translation key "${key}" in namespace "${namespace}" leads to a string prematurely at "${part}" for locale "${locale}".`);
|
|
@@ -91,7 +97,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
// If the loop completes and no string translation was found (e.g.,
|
|
100
|
+
// If the loop completes and no string translation was found (e.g.,
|
|
101
|
+
// key missing or not a string). Unreachable: keyParts always has
|
|
102
|
+
// length >= 1 (''.split('.') yields ['']), and every branch above
|
|
103
|
+
// returns on the final iteration.
|
|
95
104
|
console.warn(`Translation key "${key}" in namespace "${namespace}" is missing or not a string for locale "${locale}".`);
|
|
96
105
|
return key; // Return the key as fallback
|
|
97
106
|
};
|
|
@@ -120,6 +120,10 @@ export interface FirebaseAuthRoutingConfig {
|
|
|
120
120
|
sessionCookieMaxAge?: number;
|
|
121
121
|
/** Refresh-token cookie max-age in seconds. Defaults to 365 days (31536000). */
|
|
122
122
|
refreshTokenCookieMaxAge?: number;
|
|
123
|
+
/** Session cookie name. Defaults to `'__fa_session__'`. Override this if your app already uses a different name for its Firebase ID-token cookie. */
|
|
124
|
+
sessionCookieName?: string;
|
|
125
|
+
/** Refresh-token cookie name. Defaults to `'__fa_refresh_token__'`. Override this if your app already uses a different name for its Firebase refresh-token cookie. */
|
|
126
|
+
refreshTokenCookieName?: string;
|
|
123
127
|
}
|
|
124
128
|
export interface CookieAttributes {
|
|
125
129
|
/**
|
package/llms.txt
CHANGED
|
@@ -30,6 +30,7 @@ other subpath can be used.
|
|
|
30
30
|
- `./firebaseAuthClientProvider` — `AuthUserProvider`: client auth-state provider (session cookie sync, auth-page redirects).
|
|
31
31
|
- `./firebaseAuthServerProvider` — server-side equivalent provider (not used by the default auto-wiring path — see its doc comment).
|
|
32
32
|
- `./useFirebaseAuthUser` — `useAuthUser()`; resolves to RSC or client implementation via the `react-server` condition. Client variant throws `"useAuthUser must be used within an AuthUserProvider"` if called outside one.
|
|
33
|
+
- `./getFirebaseAuthUser` — `getAuthUser()`; unconditional server-only export of the same RSC implementation `useFirebaseAuthUser` resolves to via `react-server`. Use this when you want `await` to be visible from the type itself — TypeScript doesn't evaluate the `react-server` condition, so `useFirebaseAuthUser` always types as its client (sync) signature in editors regardless of call site.
|
|
33
34
|
- `./firebaseAuthActions` — `createLoginAction`/`createSignUpAction`/`createForgotPasswordAction`: factories returning React `useActionState`-shaped form actions.
|
|
34
35
|
- `./firebaseAuthMiddleware` — `updateSession`: session-cookie refresh, called automatically by `./middleware`'s default handler.
|
|
35
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Optimized Next Intl Package Special for App Router and Cloudflare",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -104,6 +104,10 @@
|
|
|
104
104
|
"import": "./dist/src/firebase_auth/client/use_auth_user.js"
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
|
+
"./getFirebaseAuthUser": {
|
|
108
|
+
"types": "./dist/src/firebase_auth/server/use_auth_user_server.d.ts",
|
|
109
|
+
"import": "./dist/src/firebase_auth/server/use_auth_user_server.js"
|
|
110
|
+
},
|
|
107
111
|
"./firebaseAuthActions": {
|
|
108
112
|
"types": "./dist/src/firebase_auth/client/auth_actions.d.ts",
|
|
109
113
|
"import": "./dist/src/firebase_auth/client/auth_actions.js"
|
|
@@ -150,9 +154,9 @@
|
|
|
150
154
|
"author": "Demian Ilnutskyi",
|
|
151
155
|
"license": "MIT",
|
|
152
156
|
"bugs": {
|
|
153
|
-
"url": "https://github.com/
|
|
157
|
+
"url": "https://github.com/demian-ilnytskyi/cloudflare-next-intl/issues"
|
|
154
158
|
},
|
|
155
|
-
"homepage": "https://github.com/
|
|
159
|
+
"homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
|
|
156
160
|
"peerDependencies": {
|
|
157
161
|
"firebase": ">=10.0.0",
|
|
158
162
|
"next": ">=12.0.0",
|