cloudflare-next-intl 0.6.5 → 0.6.6

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.
@@ -5,6 +5,7 @@ import { createContext, useMemo } from "react";
5
5
  import dynamic from "next/dynamic";
6
6
  import config from "@intl-config";
7
7
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override";
8
+ import installGlobalErrorOverride from "../../error_handling/install_global_error_override";
8
9
  export const LocaleContext = createContext(undefined);
9
10
  // Hoisted to module scope — calling `dynamic()` inside the component body
10
11
  // creates a brand-new component identity every render, forcing React to
@@ -22,6 +23,7 @@ export default function LocationzationClientProvider({ language, messages, initi
22
23
  setLocaleCache(language);
23
24
  setMessageForLocaleCache(language, messages);
24
25
  installConsoleErrorOverride(config, true);
26
+ installGlobalErrorOverride(config);
25
27
  // `LocaleContext.Provider` stays the outermost element here — the
26
28
  // client `AuthUserProvider` (and its descendants calling
27
29
  // usePathname()/useLocale()) must render as a CHILD of it, not a
@@ -3,6 +3,7 @@ export type { WithErrorHandlingOptions } from './with_error_handling';
3
3
  export { default as reportError } from './report_error';
4
4
  export type { ReportErrorConfig } from './report_error';
5
5
  export { default as installConsoleErrorOverride } from './install_console_error_override';
6
+ export { default as installGlobalErrorOverride } from './install_global_error_override';
6
7
  export { default as stringifyUnknown } from './stringify_unknown';
7
8
  export { default as formatErrorMessage } from './format_error_message';
8
9
  export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
@@ -1,6 +1,7 @@
1
1
  export { default as withErrorHandling } from './with_error_handling';
2
2
  export { default as reportError } from './report_error';
3
3
  export { default as installConsoleErrorOverride } from './install_console_error_override';
4
+ export { default as installGlobalErrorOverride } from './install_global_error_override';
4
5
  export { default as stringifyUnknown } from './stringify_unknown';
5
6
  export { default as formatErrorMessage } from './format_error_message';
6
7
  export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
@@ -0,0 +1,20 @@
1
+ import { type ReportErrorConfig } from './report_error';
2
+ /**
3
+ * Client-only: attaches `window.addEventListener('error'|'unhandledrejection', ...)`
4
+ * handlers that route through `config.errorHandling.onError`/`reportError` —
5
+ * catches uncaught exceptions and unhandled promise rejections that never go
6
+ * through `console.error` at all (unlike `installConsoleErrorOverride`),
7
+ * e.g. Next.js's own internal "Failed to fetch RSC payload" navigation
8
+ * fallback. Neither handler calls `event.preventDefault()` — the browser's
9
+ * own default handling (logging to the console) still happens, nothing is
10
+ * swallowed. Safe to call more than once (a no-op after the first call in
11
+ * this JS realm). Takes effect when `config.errorHandling.overrideWindowErrors`
12
+ * is `true`, or when it's omitted and `overrideConsoleError` is `true` (so
13
+ * enabling `overrideConsoleError` alone catches everything by default; pass
14
+ * `overrideWindowErrors: false` explicitly to opt out of just this part).
15
+ * No-op when `window` doesn't exist (server-side).
16
+ *
17
+ * @param config Pass the relevant slices of your `RoutingConfig` directly —
18
+ * `{ errorHandling: config.errorHandling, generate: config.generate }`.
19
+ */
20
+ export default function installGlobalErrorOverride(config: ReportErrorConfig | undefined): void;
@@ -0,0 +1,44 @@
1
+ import reportError from './report_error';
2
+ import stringifyUnknown from './stringify_unknown';
3
+ /**
4
+ * Client-only: attaches `window.addEventListener('error'|'unhandledrejection', ...)`
5
+ * handlers that route through `config.errorHandling.onError`/`reportError` —
6
+ * catches uncaught exceptions and unhandled promise rejections that never go
7
+ * through `console.error` at all (unlike `installConsoleErrorOverride`),
8
+ * e.g. Next.js's own internal "Failed to fetch RSC payload" navigation
9
+ * fallback. Neither handler calls `event.preventDefault()` — the browser's
10
+ * own default handling (logging to the console) still happens, nothing is
11
+ * swallowed. Safe to call more than once (a no-op after the first call in
12
+ * this JS realm). Takes effect when `config.errorHandling.overrideWindowErrors`
13
+ * is `true`, or when it's omitted and `overrideConsoleError` is `true` (so
14
+ * enabling `overrideConsoleError` alone catches everything by default; pass
15
+ * `overrideWindowErrors: false` explicitly to opt out of just this part).
16
+ * No-op when `window` doesn't exist (server-side).
17
+ *
18
+ * @param config Pass the relevant slices of your `RoutingConfig` directly —
19
+ * `{ errorHandling: config.errorHandling, generate: config.generate }`.
20
+ */
21
+ export default function installGlobalErrorOverride(config) {
22
+ const enabled = config?.errorHandling?.overrideWindowErrors ?? config?.errorHandling?.overrideConsoleError;
23
+ if (enabled !== true)
24
+ return;
25
+ if (typeof window === 'undefined')
26
+ return;
27
+ if (window.__isGlobalErrorOverrideInstalled)
28
+ return;
29
+ window.__isGlobalErrorOverrideInstalled = true;
30
+ window.addEventListener('error', (event) => {
31
+ void reportError(config, {
32
+ error: event.error ?? stringifyUnknown(event.message, true),
33
+ classOrMethodName: 'Global Window Error Handler',
34
+ isClient: true,
35
+ });
36
+ });
37
+ window.addEventListener('unhandledrejection', (event) => {
38
+ void reportError(config, {
39
+ error: event.reason,
40
+ classOrMethodName: 'Global Unhandled Rejection Handler',
41
+ isClient: true,
42
+ });
43
+ });
44
+ }
@@ -4,13 +4,17 @@ export interface ReportErrorConfig {
4
4
  generate?: GenerateRoutingConfig;
5
5
  }
6
6
  /**
7
- * Reports `params` via `config.errorHandling.onError` (default
8
- * `console.error(params.formattedMessage)`), unless
7
+ * Reports `params`: logs `params.formattedMessage` via the real
8
+ * `console.error` (unless `config.errorHandling.logToConsole` is `false`)
9
+ * AND calls `config.errorHandling.onError` when set — both run, not one
10
+ * instead of the other, so wiring `onError` (Sentry, Telegram, etc) never
11
+ * silently loses the console output. Skips reporting entirely when
9
12
  * `config.errorHandling.enable === false`, `params.consent` is set and not
10
13
  * `true` (reporting to a third party without cookie consent can itself be
11
14
  * GDPR-relevant), or dedup throttles it (on by default — see
12
15
  * `errorHandling.dedup`/`throttleMs`/`resetDedup`). Never throws — a broken
13
- * reporter must not mask the original error.
16
+ * `onError` must not mask the original error (falls back to logging via the
17
+ * real console instead).
14
18
  *
15
19
  * Always overwrites `params.formattedMessage` with a fresh
16
20
  * `formatErrorMessage(params)` before reporting — a human-readable one-line
@@ -1,6 +1,13 @@
1
1
  import formatErrorMessage from './format_error_message';
2
2
  import stringifyUnknown from './stringify_unknown';
3
3
  const DEFAULT_THROTTLE_MS = 5000;
4
+ // Captured once at module load — BEFORE `installConsoleErrorOverride` can
5
+ // ever patch `console.error` — so `callOnError`'s own fallback/always-log
6
+ // path calls the real console, never the override. Calling a possibly-
7
+ // patched `console.error` here would loop straight back into
8
+ // `reportError` (the override's job is to call `reportError`), producing
9
+ // a duplicate (or, if `onError` itself throws, an infinite) report.
10
+ const originalConsoleError = console.error.bind(console);
4
11
  // Module-scope dedup state — safe by default only because a fresh JS realm
5
12
  // (isolate/Worker instance) starts with it cleared. In a long-lived server
6
13
  // process reused across many requests, pass `resetDedup: true` on the first
@@ -13,26 +20,30 @@ function buildDedupKey(params) {
13
20
  }
14
21
  async function callOnError(config, params) {
15
22
  const paramsWithFormattedMessage = { ...params, formattedMessage: formatErrorMessage(params) };
16
- try {
17
- if (config?.onError) {
23
+ if (config?.logToConsole !== false) {
24
+ originalConsoleError(paramsWithFormattedMessage.formattedMessage);
25
+ }
26
+ if (config?.onError) {
27
+ try {
18
28
  await config.onError(paramsWithFormattedMessage);
19
29
  }
20
- else {
21
- console.error(paramsWithFormattedMessage.formattedMessage);
30
+ catch {
31
+ originalConsoleError(paramsWithFormattedMessage.formattedMessage);
22
32
  }
23
33
  }
24
- catch {
25
- console.error(paramsWithFormattedMessage.formattedMessage);
26
- }
27
34
  }
28
35
  /**
29
- * Reports `params` via `config.errorHandling.onError` (default
30
- * `console.error(params.formattedMessage)`), unless
36
+ * Reports `params`: logs `params.formattedMessage` via the real
37
+ * `console.error` (unless `config.errorHandling.logToConsole` is `false`)
38
+ * AND calls `config.errorHandling.onError` when set — both run, not one
39
+ * instead of the other, so wiring `onError` (Sentry, Telegram, etc) never
40
+ * silently loses the console output. Skips reporting entirely when
31
41
  * `config.errorHandling.enable === false`, `params.consent` is set and not
32
42
  * `true` (reporting to a third party without cookie consent can itself be
33
43
  * GDPR-relevant), or dedup throttles it (on by default — see
34
44
  * `errorHandling.dedup`/`throttleMs`/`resetDedup`). Never throws — a broken
35
- * reporter must not mask the original error.
45
+ * `onError` must not mask the original error (falls back to logging via the
46
+ * real console instead).
36
47
  *
37
48
  * Always overwrites `params.formattedMessage` with a fresh
38
49
  * `formatErrorMessage(params)` before reporting — a human-readable one-line
@@ -183,7 +183,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
183
183
  }
184
184
  finally {
185
185
  await clearSession(sessionCookieName, refreshTokenCookieName);
186
- window.location.assign(fa.redirectAuthPath);
186
+ router.push(fa.redirectAuthPath);
187
187
  }
188
188
  // eslint-disable-next-line react-hooks/exhaustive-deps
189
189
  }, [fa.redirectAuthPath, sessionCookieName, refreshTokenCookieName]);
@@ -167,11 +167,22 @@ export interface ErrorHandlingRoutingConfig {
167
167
  */
168
168
  enable?: boolean;
169
169
  /**
170
- * Called with the caught error whenever one is reported. Defaults to
171
- * `console.error`. Use this to wire your own error-tracking/logging
172
- * transport (Sentry, Telegram, etc).
170
+ * Called with the caught error whenever one is reported, in ADDITION to
171
+ * the always-on `console.error(params.formattedMessage)` (see
172
+ * `logToConsole` to disable that). Use this to wire your own
173
+ * error-tracking/logging transport (Sentry, Telegram, etc) alongside the
174
+ * console log, not instead of it. Omit to just log to the console.
173
175
  */
174
176
  onError?: (params: ErrorHandlingParams) => void | Promise<void>;
177
+ /**
178
+ * Whether `reportError` also logs `params.formattedMessage` via the
179
+ * real, unpatched `console.error` (captured at module load, before
180
+ * `installConsoleErrorOverride` can touch it — so this never loops back
181
+ * into `reportError` even when the override is installed). Defaults to
182
+ * `true`. Set `false` if `onError` is your only sink and you don't want
183
+ * console output at all.
184
+ */
185
+ logToConsole?: boolean;
175
186
  /**
176
187
  * Whether `reportError`/`withErrorHandling` replace the global
177
188
  * `console.error` so every `console.error(...)` call in your app is
@@ -201,6 +212,20 @@ export interface ErrorHandlingRoutingConfig {
201
212
  * substring match.
202
213
  */
203
214
  ignoreConsoleError?: (message: string) => boolean;
215
+ /**
216
+ * Whether the client `LocationzationClientProvider` also installs
217
+ * `window.addEventListener('error'|'unhandledrejection', ...)` handlers
218
+ * that route through `onError`/`reportError` the same way
219
+ * `overrideConsoleError` does for `console.error(...)` calls. Catches
220
+ * uncaught exceptions and unhandled promise rejections that never go
221
+ * through `console.error` at all — e.g. Next.js's own internal
222
+ * "Failed to fetch RSC payload" navigation-fallback error. Defaults to
223
+ * `overrideConsoleError`'s value (so setting just `overrideConsoleError:
224
+ * true` catches everything by default) — pass `false` explicitly to
225
+ * enable console overriding without the window listeners. No-op on the
226
+ * server (no `window` there).
227
+ */
228
+ overrideWindowErrors?: boolean;
204
229
  /**
205
230
  * Dedup: `reportError` skips reporting an error whose key (`dedupKey`,
206
231
  * or a built-in key derived from `classOrMethodName`/`error`/`params`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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",
@@ -148,6 +148,10 @@
148
148
  "types": "./dist/src/error_handling/install_console_error_override.d.ts",
149
149
  "import": "./dist/src/error_handling/install_console_error_override.js"
150
150
  },
151
+ "./installGlobalErrorOverride": {
152
+ "types": "./dist/src/error_handling/install_global_error_override.d.ts",
153
+ "import": "./dist/src/error_handling/install_global_error_override.js"
154
+ },
151
155
  "./stringifyUnknown": {
152
156
  "types": "./dist/src/error_handling/stringify_unknown.d.ts",
153
157
  "import": "./dist/src/error_handling/stringify_unknown.js"