cloudflare-next-intl 0.7.1 → 0.7.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/client/components/client_provider.d.ts +4 -2
- package/dist/src/client/components/client_provider.js +5 -3
- package/dist/src/client/components/client_provider_static.d.ts +4 -2
- package/dist/src/client/components/client_provider_static.js +3 -2
- package/dist/src/cookie_consent/client/components/auto_analytics_events.d.ts +12 -0
- package/dist/src/cookie_consent/client/components/auto_analytics_events.js +58 -0
- package/dist/src/firebase_auth/client/components/auto_firebase_performance_events.d.ts +10 -0
- package/dist/src/firebase_auth/client/components/auto_firebase_performance_events.js +111 -0
- package/dist/src/firebase_auth/client/firebase_client.d.ts +3 -0
- package/dist/src/firebase_auth/client/firebase_client.js +14 -1
- package/dist/src/firebase_auth/index.d.ts +1 -1
- package/dist/src/firebase_auth/index.js +1 -1
- package/dist/src/server/components/server_provider.js +1 -1
- package/dist/src/server/components/server_provider_static.js +1 -1
- package/dist/src/types/types.d.ts +30 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TranslationObject } from "../../types/types";
|
|
2
2
|
import type { SerializedAuthUser } from "../../firebase_auth/types";
|
|
3
|
-
import type { CookieConsentAnalyticsConfig } from "../../types/types";
|
|
3
|
+
import type { CookieConsentAnalyticsConfig, AutoAnalyticsEventsConfig } from "../../types/types";
|
|
4
4
|
import type { CookieConsentDialogProps } from "../../cookie_consent/client/components/cookie_consent_dialog";
|
|
5
5
|
import type { PrivacyPolicyUpdateDialogProps } from "../../cookie_consent/client/components/privacy_policy_update_dialog";
|
|
6
6
|
interface LocaleContextType {
|
|
@@ -8,7 +8,7 @@ interface LocaleContextType {
|
|
|
8
8
|
messages: TranslationObject;
|
|
9
9
|
}
|
|
10
10
|
export declare const LocaleContext: import("react").Context<LocaleContextType | undefined>;
|
|
11
|
-
export default function LocationzationClientProvider({ language, messages, initialAuthUser, skipAuthProvider, analyticsConfig, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
|
|
11
|
+
export default function LocationzationClientProvider({ language, messages, initialAuthUser, skipAuthProvider, analyticsConfig, autoAnalyticsEventsConfig, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
|
|
12
12
|
language: string;
|
|
13
13
|
messages: TranslationObject;
|
|
14
14
|
initialAuthUser?: SerializedAuthUser | null;
|
|
@@ -16,6 +16,8 @@ export default function LocationzationClientProvider({ language, messages, initi
|
|
|
16
16
|
skipAuthProvider?: boolean;
|
|
17
17
|
/** Resolved server-side from `cookieConsent.analytics`/`getAnalytics` when `autoWireAnalytics` isn't `false`. */
|
|
18
18
|
analyticsConfig?: CookieConsentAnalyticsConfig;
|
|
19
|
+
/** From `cookieConsent.autoAnalyticsEvents` — forwarded as-is to the auto-wired `AutoAnalyticsEvents`. */
|
|
20
|
+
autoAnalyticsEventsConfig?: AutoAnalyticsEventsConfig;
|
|
19
21
|
/**
|
|
20
22
|
* Resolved server-side from `cookieConsent.getCountryCode`/`gdprCountries`.
|
|
21
23
|
* `false` means the visitor's country doesn't require the consent
|
|
@@ -15,11 +15,13 @@ export const LocaleContext = createContext(undefined);
|
|
|
15
15
|
// update (and a `getIdToken(true)` refresh) that causes another render —
|
|
16
16
|
// an infinite loop of session-cookie writes, one per render.
|
|
17
17
|
const AuthUserProvider = dynamic(() => import("../../firebase_auth/client/auth_user_provider"));
|
|
18
|
+
const AutoFirebasePerformanceEvents = dynamic(() => import("../../firebase_auth/client/components/auto_firebase_performance_events"));
|
|
18
19
|
const CookieConsentProvider = dynamic(() => import("../../cookie_consent/client/cookie_consent_provider"));
|
|
19
20
|
const CookieConsentAnalytics = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_analytics"));
|
|
21
|
+
const AutoAnalyticsEvents = dynamic(() => import("../../cookie_consent/client/components/auto_analytics_events"));
|
|
20
22
|
const CookieConsentDialog = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_dialog"));
|
|
21
23
|
const PrivacyPolicyUpdateDialog = dynamic(() => import("../../cookie_consent/client/components/privacy_policy_update_dialog"));
|
|
22
|
-
export default function LocationzationClientProvider({ language, messages, initialAuthUser = null, skipAuthProvider = false, analyticsConfig, requiresConsent = true, autoWireDialogs = true, dialogProps, updateDialogProps, children }) {
|
|
24
|
+
export default function LocationzationClientProvider({ language, messages, initialAuthUser = null, skipAuthProvider = false, analyticsConfig, autoAnalyticsEventsConfig, requiresConsent = true, autoWireDialogs = true, dialogProps, updateDialogProps, children }) {
|
|
23
25
|
setLocaleCache(language);
|
|
24
26
|
setMessageForLocaleCache(language, messages);
|
|
25
27
|
installConsoleErrorOverride(config, true);
|
|
@@ -31,10 +33,10 @@ export default function LocationzationClientProvider({ language, messages, initi
|
|
|
31
33
|
// the provider.
|
|
32
34
|
let providedChildren = children;
|
|
33
35
|
if (config.firebaseAuth && !skipAuthProvider) {
|
|
34
|
-
providedChildren =
|
|
36
|
+
providedChildren = _jsxs(AuthUserProvider, { initialUser: initialAuthUser, children: [children, config.firebaseAuth.performance !== false && _jsx(AutoFirebasePerformanceEvents, {})] });
|
|
35
37
|
}
|
|
36
38
|
if (config.cookieConsent) {
|
|
37
|
-
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
|
|
39
|
+
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
|
|
38
40
|
}
|
|
39
41
|
const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
|
|
40
42
|
return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TranslationObject } from "../../types/types";
|
|
2
|
-
import type { CookieConsentAnalyticsConfig } from "../../types/types";
|
|
2
|
+
import type { CookieConsentAnalyticsConfig, AutoAnalyticsEventsConfig } from "../../types/types";
|
|
3
3
|
import type { CookieConsentDialogProps } from "../../cookie_consent/client/components/cookie_consent_dialog";
|
|
4
4
|
import type { PrivacyPolicyUpdateDialogProps } from "../../cookie_consent/client/components/privacy_policy_update_dialog";
|
|
5
5
|
interface LocaleContextType {
|
|
@@ -7,11 +7,13 @@ interface LocaleContextType {
|
|
|
7
7
|
messages: TranslationObject;
|
|
8
8
|
}
|
|
9
9
|
export declare const LocaleContext: import("react").Context<LocaleContextType | undefined>;
|
|
10
|
-
export default function LocationzationClientProvider({ language, messages, analyticsConfig, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
|
|
10
|
+
export default function LocationzationClientProvider({ language, messages, analyticsConfig, autoAnalyticsEventsConfig, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
|
|
11
11
|
language: string;
|
|
12
12
|
messages: TranslationObject;
|
|
13
13
|
/** Resolved server-side from `cookieConsent.analytics`/`getAnalytics` when `autoWireAnalytics` isn't `false`. */
|
|
14
14
|
analyticsConfig?: CookieConsentAnalyticsConfig;
|
|
15
|
+
/** From `cookieConsent.autoAnalyticsEvents` — forwarded as-is to the auto-wired `AutoAnalyticsEvents`. */
|
|
16
|
+
autoAnalyticsEventsConfig?: AutoAnalyticsEventsConfig;
|
|
15
17
|
/**
|
|
16
18
|
* Resolved server-side from `cookieConsent.getCountryCode`/`gdprCountries`.
|
|
17
19
|
* `false` means the visitor's country doesn't require the consent
|
|
@@ -17,16 +17,17 @@ export const LocaleContext = createContext(undefined);
|
|
|
17
17
|
// for the full explanation.
|
|
18
18
|
const CookieConsentProvider = dynamic(() => import("../../cookie_consent/client/cookie_consent_provider"));
|
|
19
19
|
const CookieConsentAnalytics = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_analytics"));
|
|
20
|
+
const AutoAnalyticsEvents = dynamic(() => import("../../cookie_consent/client/components/auto_analytics_events"));
|
|
20
21
|
const CookieConsentDialog = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_dialog"));
|
|
21
22
|
const PrivacyPolicyUpdateDialog = dynamic(() => import("../../cookie_consent/client/components/privacy_policy_update_dialog"));
|
|
22
|
-
export default function LocationzationClientProvider({ language, messages, analyticsConfig, requiresConsent = true, autoWireDialogs = true, dialogProps, updateDialogProps, children }) {
|
|
23
|
+
export default function LocationzationClientProvider({ language, messages, analyticsConfig, autoAnalyticsEventsConfig, requiresConsent = true, autoWireDialogs = true, dialogProps, updateDialogProps, children }) {
|
|
23
24
|
setLocaleCache(language);
|
|
24
25
|
setMessageForLocaleCache(language, messages);
|
|
25
26
|
installConsoleErrorOverride(config, true);
|
|
26
27
|
installGlobalErrorOverride(config);
|
|
27
28
|
let providedChildren = children;
|
|
28
29
|
if (config.cookieConsent) {
|
|
29
|
-
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
|
|
30
|
+
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && _jsx(CookieConsentAnalytics, { config: analyticsConfig }), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
|
|
30
31
|
}
|
|
31
32
|
const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
|
|
32
33
|
return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AutoAnalyticsEventsConfig } from '../../../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* Auto-rendered alongside `CookieConsentAnalytics` when Google Analytics/Ads
|
|
4
|
+
* is configured — sends `screen_view` on route change and one
|
|
5
|
+
* `gtag('event', ...)` per Web Vitals metric. Gated on consent the same way
|
|
6
|
+
* as the other analytics scripts (no-ops until `gtag` exists on `window`).
|
|
7
|
+
* See `CookieConsentRoutingConfig.autoAnalyticsEvents` to disable specific
|
|
8
|
+
* events or customize `screen_name`.
|
|
9
|
+
*/
|
|
10
|
+
export default function AutoAnalyticsEvents({ config }: {
|
|
11
|
+
config?: AutoAnalyticsEventsConfig;
|
|
12
|
+
}): React.ReactElement | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { usePathname } from 'next/navigation';
|
|
3
|
+
import { useReportWebVitals } from 'next/web-vitals';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import useCookieConsent from '../use_cookie_consent';
|
|
6
|
+
const metricEventNames = {
|
|
7
|
+
CLS: 'web_cls',
|
|
8
|
+
FCP: 'web_fcp',
|
|
9
|
+
FID: 'web_fid',
|
|
10
|
+
LCP: 'web_lcp',
|
|
11
|
+
TTFB: 'web_ttfb',
|
|
12
|
+
INP: 'web_inp',
|
|
13
|
+
};
|
|
14
|
+
const formatMetricValue = (name, value) => Math.round(name === 'CLS' ? value * 1000 : value);
|
|
15
|
+
function gtagEvent(name, params) {
|
|
16
|
+
const w = window;
|
|
17
|
+
if (typeof w.gtag !== 'function')
|
|
18
|
+
return;
|
|
19
|
+
w.gtag('event', name, params);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Auto-rendered alongside `CookieConsentAnalytics` when Google Analytics/Ads
|
|
23
|
+
* is configured — sends `screen_view` on route change and one
|
|
24
|
+
* `gtag('event', ...)` per Web Vitals metric. Gated on consent the same way
|
|
25
|
+
* as the other analytics scripts (no-ops until `gtag` exists on `window`).
|
|
26
|
+
* See `CookieConsentRoutingConfig.autoAnalyticsEvents` to disable specific
|
|
27
|
+
* events or customize `screen_name`.
|
|
28
|
+
*/
|
|
29
|
+
export default function AutoAnalyticsEvents({ config }) {
|
|
30
|
+
const { consent, requiresConsent } = useCookieConsent();
|
|
31
|
+
const granted = consent === true || !requiresConsent;
|
|
32
|
+
const path = usePathname();
|
|
33
|
+
const screenName = config?.getScreenName ? config.getScreenName(path) : path;
|
|
34
|
+
const enabledEvents = config?.events;
|
|
35
|
+
const isEnabled = (event) => !enabledEvents || enabledEvents.includes(event);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!granted)
|
|
38
|
+
return;
|
|
39
|
+
if (!isEnabled('screen_view'))
|
|
40
|
+
return;
|
|
41
|
+
gtagEvent('screen_view', { screen_name: screenName, page_path: path });
|
|
42
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
|
+
}, [path, screenName, granted]);
|
|
44
|
+
useReportWebVitals((metric) => {
|
|
45
|
+
if (!granted)
|
|
46
|
+
return;
|
|
47
|
+
const eventName = metricEventNames[metric.name];
|
|
48
|
+
if (!isEnabled(eventName))
|
|
49
|
+
return;
|
|
50
|
+
gtagEvent(eventName, {
|
|
51
|
+
value: formatMetricValue(metric.name, metric.value),
|
|
52
|
+
metric_rating: metric.rating,
|
|
53
|
+
screen_name: screenName,
|
|
54
|
+
page_path: path,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-rendered alongside `FirebaseAuthClientProvider` when
|
|
3
|
+
* `firebaseAuth.performance` isn't `false` — records Firebase Performance
|
|
4
|
+
* custom traces for Web Vitals metrics (`web_cls`, `web_fcp`, `web_fid`,
|
|
5
|
+
* `web_lcp`, `web_ttfb`, `web_inp`) and SPA route changes (`route_change`),
|
|
6
|
+
* alongside Firebase Performance's own automatic page-load/network traces.
|
|
7
|
+
* No-ops if `firebaseAuth.performance` is disabled (`getFirebasePerformanceSync`
|
|
8
|
+
* never resolves an instance).
|
|
9
|
+
*/
|
|
10
|
+
export default function AutoFirebasePerformanceEvents(): null;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { usePathname } from 'next/navigation';
|
|
3
|
+
import { useReportWebVitals } from 'next/web-vitals';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
import reportError from '../../../error_handling/report_error';
|
|
6
|
+
import { getFirebaseAuthClient, getFirebasePerformanceSync } from '../firebase_client';
|
|
7
|
+
/**
|
|
8
|
+
* Records a Firebase Performance custom trace with a real duration, so
|
|
9
|
+
* Firebase's own duration histograms/percentiles are meaningful. No-ops if
|
|
10
|
+
* `firebaseAuth.performance` is disabled (`getFirebasePerformanceSync` never
|
|
11
|
+
* resolves an instance).
|
|
12
|
+
*/
|
|
13
|
+
async function recordFirebaseTrace(name, durationMs, attributes, metrics) {
|
|
14
|
+
try {
|
|
15
|
+
await getFirebaseAuthClient();
|
|
16
|
+
const performance = getFirebasePerformanceSync();
|
|
17
|
+
if (!performance)
|
|
18
|
+
return;
|
|
19
|
+
const { trace } = await import('firebase/performance');
|
|
20
|
+
const duration = Math.max(Math.round(durationMs), 1);
|
|
21
|
+
trace(performance, name).record(Date.now() - duration, duration, { attributes, metrics });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
void reportError(undefined, {
|
|
25
|
+
error,
|
|
26
|
+
classOrMethodName: 'recordFirebaseTrace',
|
|
27
|
+
isClient: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const SLOW_RESOURCE_THRESHOLD_MS = 1000;
|
|
32
|
+
/**
|
|
33
|
+
* Auto-rendered alongside `FirebaseAuthClientProvider` when
|
|
34
|
+
* `firebaseAuth.performance` isn't `false` — records Firebase Performance
|
|
35
|
+
* custom traces for Web Vitals metrics (`web_cls`, `web_fcp`, `web_fid`,
|
|
36
|
+
* `web_lcp`, `web_ttfb`, `web_inp`) and SPA route changes (`route_change`),
|
|
37
|
+
* alongside Firebase Performance's own automatic page-load/network traces.
|
|
38
|
+
* No-ops if `firebaseAuth.performance` is disabled (`getFirebasePerformanceSync`
|
|
39
|
+
* never resolves an instance).
|
|
40
|
+
*/
|
|
41
|
+
export default function AutoFirebasePerformanceEvents() {
|
|
42
|
+
useReportWebVitals((metric) => {
|
|
43
|
+
const value = Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value);
|
|
44
|
+
void recordFirebaseTrace(`web_${metric.name.toLowerCase()}`, value, { rating: metric.rating });
|
|
45
|
+
});
|
|
46
|
+
const path = usePathname();
|
|
47
|
+
const isFirstRoute = useRef(true);
|
|
48
|
+
const lastRouteChangeRef = useRef(Date.now());
|
|
49
|
+
const longTaskStats = useRef({ count: 0, totalDuration: 0 });
|
|
50
|
+
// Mount-only: registers a PerformanceObserver for long tasks (main-thread
|
|
51
|
+
// blocks >= 50ms). Not all browsers support the 'longtask' entry type, so
|
|
52
|
+
// this is a best-effort signal. The final route's tail of accumulated
|
|
53
|
+
// long tasks between the last route change and unmount is intentionally
|
|
54
|
+
// dropped rather than flushed here — this is a monitoring signal, not a
|
|
55
|
+
// billing metric, and an occasional dropped tail sample is acceptable.
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (typeof PerformanceObserver === 'undefined' || !PerformanceObserver.supportedEntryTypes?.includes('longtask'))
|
|
58
|
+
return;
|
|
59
|
+
const observer = new PerformanceObserver((list) => {
|
|
60
|
+
for (const entry of list.getEntries()) {
|
|
61
|
+
longTaskStats.current.count += 1;
|
|
62
|
+
longTaskStats.current.totalDuration += entry.duration;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
observer.observe({ type: 'longtask', buffered: true });
|
|
66
|
+
return () => observer.disconnect();
|
|
67
|
+
}, []);
|
|
68
|
+
// Mount-only: registers a PerformanceObserver for slow non-fetch/XHR
|
|
69
|
+
// resources (scripts, images, stylesheets, fonts, etc). Firebase
|
|
70
|
+
// Performance's own automatic network monitoring already instruments
|
|
71
|
+
// fetch/XHR, so those are skipped here to avoid duplicate signals.
|
|
72
|
+
// `buffered: true` also catches resources that loaded before this
|
|
73
|
+
// component mounted (it only mounts after `AuthUserProvider`).
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (typeof PerformanceObserver === 'undefined' || !PerformanceObserver.supportedEntryTypes?.includes('resource'))
|
|
76
|
+
return;
|
|
77
|
+
const observer = new PerformanceObserver((list) => {
|
|
78
|
+
for (const entry of list.getEntries()) {
|
|
79
|
+
if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest')
|
|
80
|
+
continue;
|
|
81
|
+
if (entry.duration < SLOW_RESOURCE_THRESHOLD_MS)
|
|
82
|
+
continue;
|
|
83
|
+
void recordFirebaseTrace('slow_resource', entry.duration, {
|
|
84
|
+
initiator_type: entry.initiatorType,
|
|
85
|
+
resource: entry.name.slice(-100),
|
|
86
|
+
}, { transfer_size_bytes: Math.round(entry.transferSize ?? 0) });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
observer.observe({ type: 'resource', buffered: true });
|
|
90
|
+
return () => observer.disconnect();
|
|
91
|
+
}, []);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const duration = now - lastRouteChangeRef.current;
|
|
95
|
+
lastRouteChangeRef.current = now;
|
|
96
|
+
if (isFirstRoute.current) {
|
|
97
|
+
isFirstRoute.current = false;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Approximates navigation duration as time between path-change commits —
|
|
101
|
+
// App Router exposes no public "navigation start" event this package
|
|
102
|
+
// can hook into, so this is not a precise navigation timing.
|
|
103
|
+
void recordFirebaseTrace('route_change', duration, { path: path.slice(-100) });
|
|
104
|
+
const { count, totalDuration } = longTaskStats.current;
|
|
105
|
+
longTaskStats.current = { count: 0, totalDuration: 0 };
|
|
106
|
+
if (count > 0) {
|
|
107
|
+
void recordFirebaseTrace('route_long_tasks', totalDuration, { path: path.slice(-100) }, { long_task_count: count });
|
|
108
|
+
}
|
|
109
|
+
}, [path]);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FirebaseApp } from 'firebase/app';
|
|
2
2
|
import type { Auth } from 'firebase/auth';
|
|
3
|
+
import type { FirebasePerformance } from 'firebase/performance';
|
|
3
4
|
/**
|
|
4
5
|
* Current App Check token, or `undefined` if `appCheck` isn't configured or
|
|
5
6
|
* hasn't initialized yet. Forces a refresh only when the cached token is
|
|
@@ -24,5 +25,7 @@ export declare function getFirebaseAuthClientSync(): {
|
|
|
24
25
|
app: FirebaseApp;
|
|
25
26
|
auth: Auth;
|
|
26
27
|
} | undefined;
|
|
28
|
+
/** Synchronous read of the cached `FirebasePerformance` instance, or `undefined` if `performance` isn't enabled or hasn't initialized yet. */
|
|
29
|
+
export declare function getFirebasePerformanceSync(): FirebasePerformance | undefined;
|
|
27
30
|
/** Memoized `import('firebase/auth')` — see {@link getFirebaseAuthClient} for why this is worth caching. */
|
|
28
31
|
export declare function getFirebaseAuthModule(): Promise<typeof import('firebase/auth')>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import config from '@intl-config';
|
|
3
3
|
import requireFirebaseAuthConfig from '../require_config';
|
|
4
4
|
let cachedAppCheck;
|
|
5
|
+
let cachedPerformance;
|
|
5
6
|
async function initializeFirebaseAppCheck(app, appCheckConfig) {
|
|
6
7
|
const { initializeAppCheck, ReCaptchaV3Provider, ReCaptchaEnterpriseProvider } = await import('firebase/app-check');
|
|
7
8
|
if (appCheckConfig.debugToken) {
|
|
@@ -45,7 +46,12 @@ export async function getFirebaseAuthClient() {
|
|
|
45
46
|
return cached;
|
|
46
47
|
if (!cachedPromise) {
|
|
47
48
|
const fa = config.firebaseAuth;
|
|
48
|
-
|
|
49
|
+
const isPerformanceEnabled = fa.performance !== false && typeof window !== 'undefined';
|
|
50
|
+
cachedPromise = Promise.all([
|
|
51
|
+
import('firebase/app'),
|
|
52
|
+
import('firebase/auth'),
|
|
53
|
+
isPerformanceEnabled ? import('firebase/performance') : Promise.resolve(null),
|
|
54
|
+
]).then(async ([{ getApp, getApps, initializeApp }, { getAuth }, perfModule]) => {
|
|
49
55
|
const firebaseConfig = {
|
|
50
56
|
apiKey: fa.apiKey,
|
|
51
57
|
authDomain: fa.authDomain,
|
|
@@ -59,6 +65,9 @@ export async function getFirebaseAuthClient() {
|
|
|
59
65
|
if (fa.appCheck) {
|
|
60
66
|
cachedAppCheck = await initializeFirebaseAppCheck(app, fa.appCheck);
|
|
61
67
|
}
|
|
68
|
+
if (perfModule) {
|
|
69
|
+
cachedPerformance = perfModule.getPerformance(app);
|
|
70
|
+
}
|
|
62
71
|
const auth = getAuth(app);
|
|
63
72
|
cached = { app, auth };
|
|
64
73
|
return cached;
|
|
@@ -70,6 +79,10 @@ export async function getFirebaseAuthClient() {
|
|
|
70
79
|
export function getFirebaseAuthClientSync() {
|
|
71
80
|
return cached;
|
|
72
81
|
}
|
|
82
|
+
/** Synchronous read of the cached `FirebasePerformance` instance, or `undefined` if `performance` isn't enabled or hasn't initialized yet. */
|
|
83
|
+
export function getFirebasePerformanceSync() {
|
|
84
|
+
return cachedPerformance;
|
|
85
|
+
}
|
|
73
86
|
let cachedAuthModule;
|
|
74
87
|
/** Memoized `import('firebase/auth')` — see {@link getFirebaseAuthClient} for why this is worth caching. */
|
|
75
88
|
export function getFirebaseAuthModule() {
|
|
@@ -5,6 +5,6 @@ export { default as useFirebaseAuthUserServer } from './server/use_auth_user_ser
|
|
|
5
5
|
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
6
6
|
export { default as clearFirebaseAuthSession } from './server/clear_session_action';
|
|
7
7
|
export { default as updateFirebaseAuthSession, defaultSessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
8
|
-
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
8
|
+
export { getFirebaseAuthClient, getFirebasePerformanceSync } from './client/firebase_client';
|
|
9
9
|
export type { SerializedAuthUser, AuthFormState, AuthActionMessages, AuthUser } from './types';
|
|
10
10
|
export type { FirebaseAuthRoutingConfig } from '../types/types';
|
|
@@ -11,4 +11,4 @@ export { default as useFirebaseAuthUserServer } from './server/use_auth_user_ser
|
|
|
11
11
|
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
12
12
|
export { default as clearFirebaseAuthSession } from './server/clear_session_action';
|
|
13
13
|
export { default as updateFirebaseAuthSession, defaultSessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
14
|
-
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
14
|
+
export { getFirebaseAuthClient, getFirebasePerformanceSync } from './client/firebase_client';
|
|
@@ -161,5 +161,5 @@ export default async function LocationzationProvider({ language, messages, stati
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children });
|
|
164
|
+
return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children });
|
|
165
165
|
}
|
|
@@ -104,5 +104,5 @@ export default async function LocationzationProvider({ language, messages, child
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, analyticsConfig: analyticsConfig, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children });
|
|
107
|
+
return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, analyticsConfig: analyticsConfig, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children });
|
|
108
108
|
}
|
|
@@ -308,6 +308,16 @@ export interface CookieConsentRoutingConfig {
|
|
|
308
308
|
* `undefined` in the returned object disables that provider's script.
|
|
309
309
|
*/
|
|
310
310
|
getAnalytics?: () => CookieConsentAnalyticsConfig | Promise<CookieConsentAnalyticsConfig>;
|
|
311
|
+
/**
|
|
312
|
+
* Configures automatic `screen_view` (on route change) and Web Vitals
|
|
313
|
+
* (`web_cls`/`web_fcp`/`web_fid`/`web_lcp`/`web_ttfb`/`web_inp`)
|
|
314
|
+
* `gtag('event', ...)` tracking. Auto-rendered alongside
|
|
315
|
+
* `CookieConsentAnalytics` whenever Google Analytics/Ads is configured
|
|
316
|
+
* and `autoWireAnalytics` isn't `false` — no manual wiring needed. Omit
|
|
317
|
+
* to keep all events enabled with default screen names (raw pathname);
|
|
318
|
+
* set `events` to a narrower array to disable specific ones.
|
|
319
|
+
*/
|
|
320
|
+
autoAnalyticsEvents?: AutoAnalyticsEventsConfig;
|
|
311
321
|
/**
|
|
312
322
|
* Resolves the visitor's country code directly (ISO 3166-1 alpha-2,
|
|
313
323
|
* e.g. `"DE"`) — the simplest option when you already have it from
|
|
@@ -389,6 +399,20 @@ export interface CookieConsentGetCloudflareContext {
|
|
|
389
399
|
async: false;
|
|
390
400
|
}): CookieConsentCloudflareContext | null;
|
|
391
401
|
}
|
|
402
|
+
/** Auto-tracked event names — `screen_view` on route change, plus one per Web Vitals metric. All enabled by default. */
|
|
403
|
+
export type AutoAnalyticsEventName = 'screen_view' | 'web_cls' | 'web_fcp' | 'web_fid' | 'web_lcp' | 'web_ttfb' | 'web_inp';
|
|
404
|
+
export interface AutoAnalyticsEventsConfig {
|
|
405
|
+
/**
|
|
406
|
+
* Which auto-tracked events to send via `gtag('event', ...)`. Defaults
|
|
407
|
+
* to all of {@link AutoAnalyticsEventName}. Pass a narrower array to
|
|
408
|
+
* disable specific events (e.g. omit `'web_cls'`) while keeping the
|
|
409
|
+
* rest. Only takes effect once `gtag` is available (i.e. Google
|
|
410
|
+
* Analytics/Ads is configured and consent is granted).
|
|
411
|
+
*/
|
|
412
|
+
events?: readonly AutoAnalyticsEventName[];
|
|
413
|
+
/** Maps a locale-stripped pathname to a human-readable screen name for `screen_view`'s `screen_name` param. Defaults to the raw pathname. */
|
|
414
|
+
getScreenName?: (path: string) => string;
|
|
415
|
+
}
|
|
392
416
|
export interface CookieConsentAnalyticsConfig {
|
|
393
417
|
/** Cloudflare Web Analytics beacon token, e.g. `'{"token": "..."}'` (the raw `data-cf-beacon` attribute value). */
|
|
394
418
|
cloudflareBeaconToken?: string;
|
|
@@ -440,6 +464,12 @@ export interface FirebaseAuthRoutingConfig {
|
|
|
440
464
|
appId: string;
|
|
441
465
|
/** Firebase Analytics measurement ID. */
|
|
442
466
|
measurementId?: string;
|
|
467
|
+
/**
|
|
468
|
+
* Whether Firebase Performance Monitoring is initialized on the client.
|
|
469
|
+
* Enabled (`true`) by default when `firebaseAuth` is configured.
|
|
470
|
+
* Set `false` to disable Performance Monitoring initialization.
|
|
471
|
+
*/
|
|
472
|
+
performance?: boolean;
|
|
443
473
|
/**
|
|
444
474
|
* Enables Firebase App Check on the client. Omit to leave App Check
|
|
445
475
|
* uninitialized — required if App Check enforcement is turned on for
|