@xray-analytics/analytics-react 0.0.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/index.d.mts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +360 -0
- package/dist/index.mjs +330 -0
- package/package.json +39 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
|
|
4
|
+
type Transport = 'auto' | 'bff' | 'direct';
|
|
5
|
+
type AnalyticsEnvironment = 'local' | 'dev' | 'production';
|
|
6
|
+
type TrackProps = Record<string, unknown>;
|
|
7
|
+
type TrackTags = string[];
|
|
8
|
+
type TrackEventName = 'page_view' | 'click_link' | 'redirect' | 'click_button' | 'scroll' | 'element_view' | (string & {});
|
|
9
|
+
type TrackCatalogEntry = {
|
|
10
|
+
trackName: string;
|
|
11
|
+
validateOn?: 'props' | 'event';
|
|
12
|
+
version?: number;
|
|
13
|
+
description?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
deprecated?: boolean;
|
|
16
|
+
schema?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
type AnalyticsTrackMetadataConfig = {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
includeIp?: boolean;
|
|
21
|
+
includeUserAgent?: boolean;
|
|
22
|
+
includeDevice?: boolean;
|
|
23
|
+
includeLanguage?: boolean;
|
|
24
|
+
includeScreen?: boolean;
|
|
25
|
+
staticIp?: string;
|
|
26
|
+
resolveIp?: () => Promise<string | undefined>;
|
|
27
|
+
};
|
|
28
|
+
type AnalyticsTrackClientMetadata = {
|
|
29
|
+
ip?: string;
|
|
30
|
+
userAgent?: string;
|
|
31
|
+
isMobile?: boolean;
|
|
32
|
+
os?: 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'unknown';
|
|
33
|
+
platform?: string;
|
|
34
|
+
language?: string;
|
|
35
|
+
screen?: {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
type TrackOptions = {
|
|
41
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
42
|
+
};
|
|
43
|
+
type AnalyticsProviderProps = PropsWithChildren<{
|
|
44
|
+
appId: string;
|
|
45
|
+
transport?: Transport;
|
|
46
|
+
bffEndpoint?: string;
|
|
47
|
+
directEndpoint?: string;
|
|
48
|
+
writeKey?: string;
|
|
49
|
+
environment?: AnalyticsEnvironment | string;
|
|
50
|
+
autoPageViews?: boolean;
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
preferSendBeacon?: boolean;
|
|
53
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
54
|
+
catalog?: TrackCatalogEntry[];
|
|
55
|
+
catalogEndpoint?: string;
|
|
56
|
+
strictCatalog?: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
type SendTrack = (name: TrackEventName, props?: TrackProps, tags?: TrackTags, options?: TrackOptions) => void;
|
|
59
|
+
type AnalyticsContextValue = {
|
|
60
|
+
track: SendTrack;
|
|
61
|
+
sendTrack: SendTrack;
|
|
62
|
+
trackPageView: (props?: TrackProps, tags?: TrackTags) => void;
|
|
63
|
+
trackClickLink: (props?: TrackProps, tags?: TrackTags) => void;
|
|
64
|
+
trackRedirect: (props?: TrackProps, tags?: TrackTags) => void;
|
|
65
|
+
trackClickButton: (props?: TrackProps, tags?: TrackTags) => void;
|
|
66
|
+
trackScroll: (props?: TrackProps, tags?: TrackTags) => void;
|
|
67
|
+
trackElementView: (props?: TrackProps, tags?: TrackTags) => void;
|
|
68
|
+
};
|
|
69
|
+
type TrackPageViewProps = {
|
|
70
|
+
props?: TrackProps;
|
|
71
|
+
trackOnPopState?: boolean;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
declare function AnalyticsProvider({ children, appId, transport, bffEndpoint, directEndpoint, writeKey, environment, autoPageViews, debug, preferSendBeacon, metadata, catalog, catalogEndpoint, strictCatalog, }: AnalyticsProviderProps): react_jsx_runtime.JSX.Element;
|
|
75
|
+
|
|
76
|
+
declare function useTrackPageView(props?: TrackProps, trackOnPopState?: boolean): void;
|
|
77
|
+
declare function TrackPageView({ props, trackOnPopState }: TrackPageViewProps): null;
|
|
78
|
+
|
|
79
|
+
type UseAnalyticsOptions = {
|
|
80
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
81
|
+
};
|
|
82
|
+
declare function useAnalytics(options?: UseAnalyticsOptions): AnalyticsContextValue;
|
|
83
|
+
|
|
84
|
+
export { type AnalyticsEnvironment, AnalyticsProvider, type AnalyticsProviderProps, type AnalyticsTrackClientMetadata, type AnalyticsTrackMetadataConfig, type SendTrack, type TrackCatalogEntry, type TrackEventName, type TrackOptions, TrackPageView, type TrackPageViewProps, type TrackProps, type Transport, useAnalytics, useTrackPageView };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
|
|
4
|
+
type Transport = 'auto' | 'bff' | 'direct';
|
|
5
|
+
type AnalyticsEnvironment = 'local' | 'dev' | 'production';
|
|
6
|
+
type TrackProps = Record<string, unknown>;
|
|
7
|
+
type TrackTags = string[];
|
|
8
|
+
type TrackEventName = 'page_view' | 'click_link' | 'redirect' | 'click_button' | 'scroll' | 'element_view' | (string & {});
|
|
9
|
+
type TrackCatalogEntry = {
|
|
10
|
+
trackName: string;
|
|
11
|
+
validateOn?: 'props' | 'event';
|
|
12
|
+
version?: number;
|
|
13
|
+
description?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
deprecated?: boolean;
|
|
16
|
+
schema?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
type AnalyticsTrackMetadataConfig = {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
includeIp?: boolean;
|
|
21
|
+
includeUserAgent?: boolean;
|
|
22
|
+
includeDevice?: boolean;
|
|
23
|
+
includeLanguage?: boolean;
|
|
24
|
+
includeScreen?: boolean;
|
|
25
|
+
staticIp?: string;
|
|
26
|
+
resolveIp?: () => Promise<string | undefined>;
|
|
27
|
+
};
|
|
28
|
+
type AnalyticsTrackClientMetadata = {
|
|
29
|
+
ip?: string;
|
|
30
|
+
userAgent?: string;
|
|
31
|
+
isMobile?: boolean;
|
|
32
|
+
os?: 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'unknown';
|
|
33
|
+
platform?: string;
|
|
34
|
+
language?: string;
|
|
35
|
+
screen?: {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
type TrackOptions = {
|
|
41
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
42
|
+
};
|
|
43
|
+
type AnalyticsProviderProps = PropsWithChildren<{
|
|
44
|
+
appId: string;
|
|
45
|
+
transport?: Transport;
|
|
46
|
+
bffEndpoint?: string;
|
|
47
|
+
directEndpoint?: string;
|
|
48
|
+
writeKey?: string;
|
|
49
|
+
environment?: AnalyticsEnvironment | string;
|
|
50
|
+
autoPageViews?: boolean;
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
preferSendBeacon?: boolean;
|
|
53
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
54
|
+
catalog?: TrackCatalogEntry[];
|
|
55
|
+
catalogEndpoint?: string;
|
|
56
|
+
strictCatalog?: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
type SendTrack = (name: TrackEventName, props?: TrackProps, tags?: TrackTags, options?: TrackOptions) => void;
|
|
59
|
+
type AnalyticsContextValue = {
|
|
60
|
+
track: SendTrack;
|
|
61
|
+
sendTrack: SendTrack;
|
|
62
|
+
trackPageView: (props?: TrackProps, tags?: TrackTags) => void;
|
|
63
|
+
trackClickLink: (props?: TrackProps, tags?: TrackTags) => void;
|
|
64
|
+
trackRedirect: (props?: TrackProps, tags?: TrackTags) => void;
|
|
65
|
+
trackClickButton: (props?: TrackProps, tags?: TrackTags) => void;
|
|
66
|
+
trackScroll: (props?: TrackProps, tags?: TrackTags) => void;
|
|
67
|
+
trackElementView: (props?: TrackProps, tags?: TrackTags) => void;
|
|
68
|
+
};
|
|
69
|
+
type TrackPageViewProps = {
|
|
70
|
+
props?: TrackProps;
|
|
71
|
+
trackOnPopState?: boolean;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
declare function AnalyticsProvider({ children, appId, transport, bffEndpoint, directEndpoint, writeKey, environment, autoPageViews, debug, preferSendBeacon, metadata, catalog, catalogEndpoint, strictCatalog, }: AnalyticsProviderProps): react_jsx_runtime.JSX.Element;
|
|
75
|
+
|
|
76
|
+
declare function useTrackPageView(props?: TrackProps, trackOnPopState?: boolean): void;
|
|
77
|
+
declare function TrackPageView({ props, trackOnPopState }: TrackPageViewProps): null;
|
|
78
|
+
|
|
79
|
+
type UseAnalyticsOptions = {
|
|
80
|
+
metadata?: boolean | AnalyticsTrackMetadataConfig;
|
|
81
|
+
};
|
|
82
|
+
declare function useAnalytics(options?: UseAnalyticsOptions): AnalyticsContextValue;
|
|
83
|
+
|
|
84
|
+
export { type AnalyticsEnvironment, AnalyticsProvider, type AnalyticsProviderProps, type AnalyticsTrackClientMetadata, type AnalyticsTrackMetadataConfig, type SendTrack, type TrackCatalogEntry, type TrackEventName, type TrackOptions, TrackPageView, type TrackPageViewProps, type TrackProps, type Transport, useAnalytics, useTrackPageView };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.tsx
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AnalyticsProvider: () => AnalyticsProvider,
|
|
24
|
+
TrackPageView: () => TrackPageView,
|
|
25
|
+
useAnalytics: () => useAnalytics,
|
|
26
|
+
useTrackPageView: () => useTrackPageView
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/core/provider.tsx
|
|
31
|
+
var import_react2 = require("react");
|
|
32
|
+
|
|
33
|
+
// src/core/context.ts
|
|
34
|
+
var import_react = require("react");
|
|
35
|
+
var AnalyticsContext = (0, import_react.createContext)(null);
|
|
36
|
+
|
|
37
|
+
// src/runtime/catalog.ts
|
|
38
|
+
function createCatalogMap(catalog) {
|
|
39
|
+
return new Map(catalog.map((entry) => [entry.trackName, entry]));
|
|
40
|
+
}
|
|
41
|
+
async function fetchCatalog(endpoint) {
|
|
42
|
+
const response = await fetch(endpoint, { method: "GET", cache: "no-store" });
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(`Failed to fetch track catalog (${response.status})`);
|
|
45
|
+
}
|
|
46
|
+
const payload = await response.json();
|
|
47
|
+
if (!payload.ok || !payload.data?.tracks) {
|
|
48
|
+
throw new Error("Invalid track catalog response");
|
|
49
|
+
}
|
|
50
|
+
return payload.data.tracks;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/runtime/environment.ts
|
|
54
|
+
function normalizeEnvironment(environment) {
|
|
55
|
+
if (!environment) return "production";
|
|
56
|
+
const env = environment.toLowerCase();
|
|
57
|
+
if (env === "production" || env === "prod") return "production";
|
|
58
|
+
if (env === "development" || env === "dev") return "dev";
|
|
59
|
+
return "local";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/runtime/metadata.ts
|
|
63
|
+
function detectOs(userAgent) {
|
|
64
|
+
const ua = userAgent.toLowerCase();
|
|
65
|
+
if (/android/.test(ua)) return "android";
|
|
66
|
+
if (/iphone|ipad|ipod/.test(ua)) return "ios";
|
|
67
|
+
if (/mac os x|macintosh/.test(ua)) return "macos";
|
|
68
|
+
if (/windows nt/.test(ua)) return "windows";
|
|
69
|
+
if (/linux/.test(ua)) return "linux";
|
|
70
|
+
return "unknown";
|
|
71
|
+
}
|
|
72
|
+
function normalizeMetadataConfig(metadata) {
|
|
73
|
+
if (!metadata) return null;
|
|
74
|
+
if (metadata === true) {
|
|
75
|
+
return {
|
|
76
|
+
enabled: true,
|
|
77
|
+
includeIp: true,
|
|
78
|
+
includeUserAgent: true,
|
|
79
|
+
includeDevice: true,
|
|
80
|
+
includeLanguage: true,
|
|
81
|
+
includeScreen: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
enabled: metadata.enabled ?? true,
|
|
86
|
+
includeIp: metadata.includeIp ?? true,
|
|
87
|
+
includeUserAgent: metadata.includeUserAgent ?? true,
|
|
88
|
+
includeDevice: metadata.includeDevice ?? true,
|
|
89
|
+
includeLanguage: metadata.includeLanguage ?? true,
|
|
90
|
+
includeScreen: metadata.includeScreen ?? true,
|
|
91
|
+
staticIp: metadata.staticIp,
|
|
92
|
+
resolveIp: metadata.resolveIp
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function collectTrackClientMetadata(metadata) {
|
|
96
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
|
|
97
|
+
const config = normalizeMetadataConfig(metadata);
|
|
98
|
+
if (!config || !config.enabled) return void 0;
|
|
99
|
+
const userAgent = navigator.userAgent;
|
|
100
|
+
const os = detectOs(userAgent);
|
|
101
|
+
const isMobile = /iphone|ipad|ipod|android|mobile/.test(userAgent.toLowerCase());
|
|
102
|
+
let ip;
|
|
103
|
+
if (config.includeIp) {
|
|
104
|
+
if (config.staticIp) {
|
|
105
|
+
ip = config.staticIp;
|
|
106
|
+
} else if (config.resolveIp) {
|
|
107
|
+
ip = await config.resolveIp().catch(() => void 0);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const metadataPayload = {
|
|
111
|
+
ip,
|
|
112
|
+
userAgent: config.includeUserAgent ? userAgent : void 0,
|
|
113
|
+
isMobile: config.includeDevice ? isMobile : void 0,
|
|
114
|
+
os: config.includeDevice ? os : void 0,
|
|
115
|
+
platform: config.includeDevice ? navigator.platform : void 0,
|
|
116
|
+
language: config.includeLanguage ? navigator.language : void 0,
|
|
117
|
+
screen: config.includeScreen && window.screen ? {
|
|
118
|
+
width: window.screen.width,
|
|
119
|
+
height: window.screen.height
|
|
120
|
+
} : void 0
|
|
121
|
+
};
|
|
122
|
+
const hasValue = Object.values(metadataPayload).some((value) => value !== void 0);
|
|
123
|
+
return hasValue ? metadataPayload : void 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/runtime/session.ts
|
|
127
|
+
function getOrCreateSessionId() {
|
|
128
|
+
const key = "xray_session_id";
|
|
129
|
+
let id = localStorage.getItem(key);
|
|
130
|
+
if (!id) {
|
|
131
|
+
id = crypto.randomUUID();
|
|
132
|
+
localStorage.setItem(key, id);
|
|
133
|
+
}
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/runtime/transport.ts
|
|
138
|
+
async function sendBeaconFirst(url, payload, options) {
|
|
139
|
+
const preferBeacon = options?.preferBeacon ?? true;
|
|
140
|
+
if (preferBeacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
141
|
+
try {
|
|
142
|
+
const ok = navigator.sendBeacon(url, new Blob([payload], { type: "application/json" }));
|
|
143
|
+
if (ok) return { ok: true, status: 204 };
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "content-type": "application/json" },
|
|
151
|
+
body: payload,
|
|
152
|
+
keepalive: true
|
|
153
|
+
});
|
|
154
|
+
return { ok: res.ok, status: res.status };
|
|
155
|
+
} catch {
|
|
156
|
+
return { ok: false, status: 0 };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/core/provider.tsx
|
|
161
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
162
|
+
function AnalyticsProvider({
|
|
163
|
+
children,
|
|
164
|
+
appId,
|
|
165
|
+
transport = "auto",
|
|
166
|
+
bffEndpoint = "/api/track",
|
|
167
|
+
directEndpoint,
|
|
168
|
+
writeKey,
|
|
169
|
+
environment = "production",
|
|
170
|
+
autoPageViews = true,
|
|
171
|
+
debug = false,
|
|
172
|
+
preferSendBeacon = true,
|
|
173
|
+
metadata,
|
|
174
|
+
catalog,
|
|
175
|
+
catalogEndpoint,
|
|
176
|
+
strictCatalog = false
|
|
177
|
+
}) {
|
|
178
|
+
const sessionIdRef = (0, import_react2.useRef)(null);
|
|
179
|
+
const catalogMapRef = (0, import_react2.useRef)(
|
|
180
|
+
catalog ? createCatalogMap(catalog) : null
|
|
181
|
+
);
|
|
182
|
+
if (typeof window !== "undefined" && !sessionIdRef.current) {
|
|
183
|
+
sessionIdRef.current = getOrCreateSessionId();
|
|
184
|
+
}
|
|
185
|
+
(0, import_react2.useEffect)(() => {
|
|
186
|
+
if (!catalog) return;
|
|
187
|
+
catalogMapRef.current = createCatalogMap(catalog);
|
|
188
|
+
}, [catalog]);
|
|
189
|
+
(0, import_react2.useEffect)(() => {
|
|
190
|
+
if (!catalogEndpoint || typeof window === "undefined") return;
|
|
191
|
+
let cancelled = false;
|
|
192
|
+
fetchCatalog(catalogEndpoint).then((tracks) => {
|
|
193
|
+
if (cancelled) return;
|
|
194
|
+
catalogMapRef.current = createCatalogMap(tracks);
|
|
195
|
+
}).catch((error) => {
|
|
196
|
+
if (!debug) return;
|
|
197
|
+
console.warn(
|
|
198
|
+
"[xray] failed to load track catalog from endpoint",
|
|
199
|
+
error instanceof Error ? error.message : error
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
return () => {
|
|
203
|
+
cancelled = true;
|
|
204
|
+
};
|
|
205
|
+
}, [catalogEndpoint, debug]);
|
|
206
|
+
const base = (0, import_react2.useMemo)(
|
|
207
|
+
() => ({
|
|
208
|
+
appId,
|
|
209
|
+
sessionId: sessionIdRef.current ?? "unknown"
|
|
210
|
+
}),
|
|
211
|
+
[appId]
|
|
212
|
+
);
|
|
213
|
+
const sendTrack = (0, import_react2.useCallback)(
|
|
214
|
+
(name, props, tags, options) => {
|
|
215
|
+
if (typeof window === "undefined") return;
|
|
216
|
+
const resolvedEnvironment = normalizeEnvironment(environment);
|
|
217
|
+
const catalogMap = catalogMapRef.current;
|
|
218
|
+
if (catalogMap) {
|
|
219
|
+
const entry = catalogMap.get(name);
|
|
220
|
+
if (!entry) {
|
|
221
|
+
if (debug) {
|
|
222
|
+
console.warn(`[xray] track '${name}' does not exist in the loaded catalog`);
|
|
223
|
+
}
|
|
224
|
+
if (strictCatalog) return;
|
|
225
|
+
} else if (entry.deprecated && debug) {
|
|
226
|
+
console.warn(`[xray] track '${name}' is marked as deprecated in the catalog`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
(async () => {
|
|
230
|
+
const metadataConfig = (() => {
|
|
231
|
+
if (options?.metadata === void 0) return metadata;
|
|
232
|
+
if (options.metadata === true || options.metadata === false) return options.metadata;
|
|
233
|
+
const baseMetadataConfig = typeof metadata === "object" && metadata ? metadata : {};
|
|
234
|
+
return {
|
|
235
|
+
...baseMetadataConfig,
|
|
236
|
+
...options.metadata
|
|
237
|
+
};
|
|
238
|
+
})();
|
|
239
|
+
const clientMeta = await collectTrackClientMetadata(metadataConfig);
|
|
240
|
+
const event = {
|
|
241
|
+
name,
|
|
242
|
+
ts: Date.now(),
|
|
243
|
+
url: window.location.href,
|
|
244
|
+
path: window.location.pathname,
|
|
245
|
+
ref: document.referrer || void 0,
|
|
246
|
+
environment: resolvedEnvironment,
|
|
247
|
+
...base,
|
|
248
|
+
props: props ?? void 0,
|
|
249
|
+
tags: tags ?? void 0,
|
|
250
|
+
clientMeta: clientMeta ?? void 0,
|
|
251
|
+
writeKey: writeKey ?? void 0
|
|
252
|
+
};
|
|
253
|
+
if (resolvedEnvironment !== "production") {
|
|
254
|
+
console.log("[xray][track]", event);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const payload = JSON.stringify(event);
|
|
258
|
+
const sendWithTransport = (url) => preferSendBeacon ? sendBeaconFirst(url, payload) : sendBeaconFirst(url, payload, { preferBeacon: false });
|
|
259
|
+
if (transport === "bff") {
|
|
260
|
+
await sendWithTransport(bffEndpoint);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (transport === "direct") {
|
|
264
|
+
if (!directEndpoint) return;
|
|
265
|
+
await sendWithTransport(directEndpoint);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const r1 = await sendWithTransport(bffEndpoint).catch(() => null);
|
|
269
|
+
if (r1?.ok) return;
|
|
270
|
+
if (directEndpoint) {
|
|
271
|
+
await sendWithTransport(directEndpoint).catch(() => null);
|
|
272
|
+
}
|
|
273
|
+
})().catch(() => {
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
[
|
|
277
|
+
base,
|
|
278
|
+
transport,
|
|
279
|
+
bffEndpoint,
|
|
280
|
+
directEndpoint,
|
|
281
|
+
writeKey,
|
|
282
|
+
debug,
|
|
283
|
+
environment,
|
|
284
|
+
strictCatalog,
|
|
285
|
+
preferSendBeacon,
|
|
286
|
+
metadata
|
|
287
|
+
]
|
|
288
|
+
);
|
|
289
|
+
(0, import_react2.useEffect)(() => {
|
|
290
|
+
if (!autoPageViews || typeof window === "undefined") return;
|
|
291
|
+
sendTrack("page_view");
|
|
292
|
+
const notify = () => sendTrack("page_view");
|
|
293
|
+
window.addEventListener("popstate", notify);
|
|
294
|
+
return () => window.removeEventListener("popstate", notify);
|
|
295
|
+
}, [autoPageViews, sendTrack]);
|
|
296
|
+
const value = (0, import_react2.useMemo)(
|
|
297
|
+
() => ({
|
|
298
|
+
track: sendTrack,
|
|
299
|
+
sendTrack,
|
|
300
|
+
trackPageView: (props, tags) => sendTrack("page_view", props, tags),
|
|
301
|
+
trackClickLink: (props, tags) => sendTrack("click_link", props, tags),
|
|
302
|
+
trackRedirect: (props, tags) => sendTrack("redirect", props, tags),
|
|
303
|
+
trackClickButton: (props, tags) => sendTrack("click_button", props, tags),
|
|
304
|
+
trackScroll: (props, tags) => sendTrack("scroll", props, tags),
|
|
305
|
+
trackElementView: (props, tags) => sendTrack("element_view", props, tags)
|
|
306
|
+
}),
|
|
307
|
+
[sendTrack]
|
|
308
|
+
);
|
|
309
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnalyticsContext.Provider, { value, children });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/hooks/track-page-view.tsx
|
|
313
|
+
var import_react4 = require("react");
|
|
314
|
+
|
|
315
|
+
// src/hooks/use-analytics.ts
|
|
316
|
+
var import_react3 = require("react");
|
|
317
|
+
function useAnalytics(options) {
|
|
318
|
+
const ctx = (0, import_react3.useContext)(AnalyticsContext);
|
|
319
|
+
if (!ctx) throw new Error("useAnalytics must be used within AnalyticsProvider");
|
|
320
|
+
const metadataOption = options?.metadata;
|
|
321
|
+
const wrapped = (0, import_react3.useMemo)(
|
|
322
|
+
() => ({
|
|
323
|
+
...ctx,
|
|
324
|
+
track: (name, props, tags) => ctx.track(name, props, tags, { metadata: metadataOption }),
|
|
325
|
+
sendTrack: (name, props, tags) => ctx.sendTrack(name, props, tags, { metadata: metadataOption }),
|
|
326
|
+
trackPageView: (props, tags) => ctx.track("page_view", props, tags, { metadata: metadataOption }),
|
|
327
|
+
trackClickLink: (props, tags) => ctx.track("click_link", props, tags, { metadata: metadataOption }),
|
|
328
|
+
trackRedirect: (props, tags) => ctx.track("redirect", props, tags, { metadata: metadataOption }),
|
|
329
|
+
trackClickButton: (props, tags) => ctx.track("click_button", props, tags, { metadata: metadataOption }),
|
|
330
|
+
trackScroll: (props, tags) => ctx.track("scroll", props, tags, { metadata: metadataOption }),
|
|
331
|
+
trackElementView: (props, tags) => ctx.track("element_view", props, tags, { metadata: metadataOption })
|
|
332
|
+
}),
|
|
333
|
+
[ctx, metadataOption]
|
|
334
|
+
);
|
|
335
|
+
return metadataOption ? wrapped : ctx;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/hooks/track-page-view.tsx
|
|
339
|
+
function useTrackPageView(props, trackOnPopState = true) {
|
|
340
|
+
const { trackPageView } = useAnalytics();
|
|
341
|
+
(0, import_react4.useEffect)(() => {
|
|
342
|
+
if (typeof window === "undefined") return;
|
|
343
|
+
trackPageView(props);
|
|
344
|
+
if (!trackOnPopState) return;
|
|
345
|
+
const notify = () => trackPageView(props);
|
|
346
|
+
window.addEventListener("popstate", notify);
|
|
347
|
+
return () => window.removeEventListener("popstate", notify);
|
|
348
|
+
}, [trackPageView, props, trackOnPopState]);
|
|
349
|
+
}
|
|
350
|
+
function TrackPageView({ props, trackOnPopState = true }) {
|
|
351
|
+
useTrackPageView(props, trackOnPopState);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
355
|
+
0 && (module.exports = {
|
|
356
|
+
AnalyticsProvider,
|
|
357
|
+
TrackPageView,
|
|
358
|
+
useAnalytics,
|
|
359
|
+
useTrackPageView
|
|
360
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// src/core/provider.tsx
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
// src/core/context.ts
|
|
5
|
+
import { createContext } from "react";
|
|
6
|
+
var AnalyticsContext = createContext(null);
|
|
7
|
+
|
|
8
|
+
// src/runtime/catalog.ts
|
|
9
|
+
function createCatalogMap(catalog) {
|
|
10
|
+
return new Map(catalog.map((entry) => [entry.trackName, entry]));
|
|
11
|
+
}
|
|
12
|
+
async function fetchCatalog(endpoint) {
|
|
13
|
+
const response = await fetch(endpoint, { method: "GET", cache: "no-store" });
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Failed to fetch track catalog (${response.status})`);
|
|
16
|
+
}
|
|
17
|
+
const payload = await response.json();
|
|
18
|
+
if (!payload.ok || !payload.data?.tracks) {
|
|
19
|
+
throw new Error("Invalid track catalog response");
|
|
20
|
+
}
|
|
21
|
+
return payload.data.tracks;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/runtime/environment.ts
|
|
25
|
+
function normalizeEnvironment(environment) {
|
|
26
|
+
if (!environment) return "production";
|
|
27
|
+
const env = environment.toLowerCase();
|
|
28
|
+
if (env === "production" || env === "prod") return "production";
|
|
29
|
+
if (env === "development" || env === "dev") return "dev";
|
|
30
|
+
return "local";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/runtime/metadata.ts
|
|
34
|
+
function detectOs(userAgent) {
|
|
35
|
+
const ua = userAgent.toLowerCase();
|
|
36
|
+
if (/android/.test(ua)) return "android";
|
|
37
|
+
if (/iphone|ipad|ipod/.test(ua)) return "ios";
|
|
38
|
+
if (/mac os x|macintosh/.test(ua)) return "macos";
|
|
39
|
+
if (/windows nt/.test(ua)) return "windows";
|
|
40
|
+
if (/linux/.test(ua)) return "linux";
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
function normalizeMetadataConfig(metadata) {
|
|
44
|
+
if (!metadata) return null;
|
|
45
|
+
if (metadata === true) {
|
|
46
|
+
return {
|
|
47
|
+
enabled: true,
|
|
48
|
+
includeIp: true,
|
|
49
|
+
includeUserAgent: true,
|
|
50
|
+
includeDevice: true,
|
|
51
|
+
includeLanguage: true,
|
|
52
|
+
includeScreen: true
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
enabled: metadata.enabled ?? true,
|
|
57
|
+
includeIp: metadata.includeIp ?? true,
|
|
58
|
+
includeUserAgent: metadata.includeUserAgent ?? true,
|
|
59
|
+
includeDevice: metadata.includeDevice ?? true,
|
|
60
|
+
includeLanguage: metadata.includeLanguage ?? true,
|
|
61
|
+
includeScreen: metadata.includeScreen ?? true,
|
|
62
|
+
staticIp: metadata.staticIp,
|
|
63
|
+
resolveIp: metadata.resolveIp
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function collectTrackClientMetadata(metadata) {
|
|
67
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
|
|
68
|
+
const config = normalizeMetadataConfig(metadata);
|
|
69
|
+
if (!config || !config.enabled) return void 0;
|
|
70
|
+
const userAgent = navigator.userAgent;
|
|
71
|
+
const os = detectOs(userAgent);
|
|
72
|
+
const isMobile = /iphone|ipad|ipod|android|mobile/.test(userAgent.toLowerCase());
|
|
73
|
+
let ip;
|
|
74
|
+
if (config.includeIp) {
|
|
75
|
+
if (config.staticIp) {
|
|
76
|
+
ip = config.staticIp;
|
|
77
|
+
} else if (config.resolveIp) {
|
|
78
|
+
ip = await config.resolveIp().catch(() => void 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const metadataPayload = {
|
|
82
|
+
ip,
|
|
83
|
+
userAgent: config.includeUserAgent ? userAgent : void 0,
|
|
84
|
+
isMobile: config.includeDevice ? isMobile : void 0,
|
|
85
|
+
os: config.includeDevice ? os : void 0,
|
|
86
|
+
platform: config.includeDevice ? navigator.platform : void 0,
|
|
87
|
+
language: config.includeLanguage ? navigator.language : void 0,
|
|
88
|
+
screen: config.includeScreen && window.screen ? {
|
|
89
|
+
width: window.screen.width,
|
|
90
|
+
height: window.screen.height
|
|
91
|
+
} : void 0
|
|
92
|
+
};
|
|
93
|
+
const hasValue = Object.values(metadataPayload).some((value) => value !== void 0);
|
|
94
|
+
return hasValue ? metadataPayload : void 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/runtime/session.ts
|
|
98
|
+
function getOrCreateSessionId() {
|
|
99
|
+
const key = "xray_session_id";
|
|
100
|
+
let id = localStorage.getItem(key);
|
|
101
|
+
if (!id) {
|
|
102
|
+
id = crypto.randomUUID();
|
|
103
|
+
localStorage.setItem(key, id);
|
|
104
|
+
}
|
|
105
|
+
return id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/runtime/transport.ts
|
|
109
|
+
async function sendBeaconFirst(url, payload, options) {
|
|
110
|
+
const preferBeacon = options?.preferBeacon ?? true;
|
|
111
|
+
if (preferBeacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
112
|
+
try {
|
|
113
|
+
const ok = navigator.sendBeacon(url, new Blob([payload], { type: "application/json" }));
|
|
114
|
+
if (ok) return { ok: true, status: 204 };
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(url, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "content-type": "application/json" },
|
|
122
|
+
body: payload,
|
|
123
|
+
keepalive: true
|
|
124
|
+
});
|
|
125
|
+
return { ok: res.ok, status: res.status };
|
|
126
|
+
} catch {
|
|
127
|
+
return { ok: false, status: 0 };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/core/provider.tsx
|
|
132
|
+
import { jsx } from "react/jsx-runtime";
|
|
133
|
+
function AnalyticsProvider({
|
|
134
|
+
children,
|
|
135
|
+
appId,
|
|
136
|
+
transport = "auto",
|
|
137
|
+
bffEndpoint = "/api/track",
|
|
138
|
+
directEndpoint,
|
|
139
|
+
writeKey,
|
|
140
|
+
environment = "production",
|
|
141
|
+
autoPageViews = true,
|
|
142
|
+
debug = false,
|
|
143
|
+
preferSendBeacon = true,
|
|
144
|
+
metadata,
|
|
145
|
+
catalog,
|
|
146
|
+
catalogEndpoint,
|
|
147
|
+
strictCatalog = false
|
|
148
|
+
}) {
|
|
149
|
+
const sessionIdRef = useRef(null);
|
|
150
|
+
const catalogMapRef = useRef(
|
|
151
|
+
catalog ? createCatalogMap(catalog) : null
|
|
152
|
+
);
|
|
153
|
+
if (typeof window !== "undefined" && !sessionIdRef.current) {
|
|
154
|
+
sessionIdRef.current = getOrCreateSessionId();
|
|
155
|
+
}
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!catalog) return;
|
|
158
|
+
catalogMapRef.current = createCatalogMap(catalog);
|
|
159
|
+
}, [catalog]);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!catalogEndpoint || typeof window === "undefined") return;
|
|
162
|
+
let cancelled = false;
|
|
163
|
+
fetchCatalog(catalogEndpoint).then((tracks) => {
|
|
164
|
+
if (cancelled) return;
|
|
165
|
+
catalogMapRef.current = createCatalogMap(tracks);
|
|
166
|
+
}).catch((error) => {
|
|
167
|
+
if (!debug) return;
|
|
168
|
+
console.warn(
|
|
169
|
+
"[xray] failed to load track catalog from endpoint",
|
|
170
|
+
error instanceof Error ? error.message : error
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
return () => {
|
|
174
|
+
cancelled = true;
|
|
175
|
+
};
|
|
176
|
+
}, [catalogEndpoint, debug]);
|
|
177
|
+
const base = useMemo(
|
|
178
|
+
() => ({
|
|
179
|
+
appId,
|
|
180
|
+
sessionId: sessionIdRef.current ?? "unknown"
|
|
181
|
+
}),
|
|
182
|
+
[appId]
|
|
183
|
+
);
|
|
184
|
+
const sendTrack = useCallback(
|
|
185
|
+
(name, props, tags, options) => {
|
|
186
|
+
if (typeof window === "undefined") return;
|
|
187
|
+
const resolvedEnvironment = normalizeEnvironment(environment);
|
|
188
|
+
const catalogMap = catalogMapRef.current;
|
|
189
|
+
if (catalogMap) {
|
|
190
|
+
const entry = catalogMap.get(name);
|
|
191
|
+
if (!entry) {
|
|
192
|
+
if (debug) {
|
|
193
|
+
console.warn(`[xray] track '${name}' does not exist in the loaded catalog`);
|
|
194
|
+
}
|
|
195
|
+
if (strictCatalog) return;
|
|
196
|
+
} else if (entry.deprecated && debug) {
|
|
197
|
+
console.warn(`[xray] track '${name}' is marked as deprecated in the catalog`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
(async () => {
|
|
201
|
+
const metadataConfig = (() => {
|
|
202
|
+
if (options?.metadata === void 0) return metadata;
|
|
203
|
+
if (options.metadata === true || options.metadata === false) return options.metadata;
|
|
204
|
+
const baseMetadataConfig = typeof metadata === "object" && metadata ? metadata : {};
|
|
205
|
+
return {
|
|
206
|
+
...baseMetadataConfig,
|
|
207
|
+
...options.metadata
|
|
208
|
+
};
|
|
209
|
+
})();
|
|
210
|
+
const clientMeta = await collectTrackClientMetadata(metadataConfig);
|
|
211
|
+
const event = {
|
|
212
|
+
name,
|
|
213
|
+
ts: Date.now(),
|
|
214
|
+
url: window.location.href,
|
|
215
|
+
path: window.location.pathname,
|
|
216
|
+
ref: document.referrer || void 0,
|
|
217
|
+
environment: resolvedEnvironment,
|
|
218
|
+
...base,
|
|
219
|
+
props: props ?? void 0,
|
|
220
|
+
tags: tags ?? void 0,
|
|
221
|
+
clientMeta: clientMeta ?? void 0,
|
|
222
|
+
writeKey: writeKey ?? void 0
|
|
223
|
+
};
|
|
224
|
+
if (resolvedEnvironment !== "production") {
|
|
225
|
+
console.log("[xray][track]", event);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const payload = JSON.stringify(event);
|
|
229
|
+
const sendWithTransport = (url) => preferSendBeacon ? sendBeaconFirst(url, payload) : sendBeaconFirst(url, payload, { preferBeacon: false });
|
|
230
|
+
if (transport === "bff") {
|
|
231
|
+
await sendWithTransport(bffEndpoint);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (transport === "direct") {
|
|
235
|
+
if (!directEndpoint) return;
|
|
236
|
+
await sendWithTransport(directEndpoint);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const r1 = await sendWithTransport(bffEndpoint).catch(() => null);
|
|
240
|
+
if (r1?.ok) return;
|
|
241
|
+
if (directEndpoint) {
|
|
242
|
+
await sendWithTransport(directEndpoint).catch(() => null);
|
|
243
|
+
}
|
|
244
|
+
})().catch(() => {
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
[
|
|
248
|
+
base,
|
|
249
|
+
transport,
|
|
250
|
+
bffEndpoint,
|
|
251
|
+
directEndpoint,
|
|
252
|
+
writeKey,
|
|
253
|
+
debug,
|
|
254
|
+
environment,
|
|
255
|
+
strictCatalog,
|
|
256
|
+
preferSendBeacon,
|
|
257
|
+
metadata
|
|
258
|
+
]
|
|
259
|
+
);
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if (!autoPageViews || typeof window === "undefined") return;
|
|
262
|
+
sendTrack("page_view");
|
|
263
|
+
const notify = () => sendTrack("page_view");
|
|
264
|
+
window.addEventListener("popstate", notify);
|
|
265
|
+
return () => window.removeEventListener("popstate", notify);
|
|
266
|
+
}, [autoPageViews, sendTrack]);
|
|
267
|
+
const value = useMemo(
|
|
268
|
+
() => ({
|
|
269
|
+
track: sendTrack,
|
|
270
|
+
sendTrack,
|
|
271
|
+
trackPageView: (props, tags) => sendTrack("page_view", props, tags),
|
|
272
|
+
trackClickLink: (props, tags) => sendTrack("click_link", props, tags),
|
|
273
|
+
trackRedirect: (props, tags) => sendTrack("redirect", props, tags),
|
|
274
|
+
trackClickButton: (props, tags) => sendTrack("click_button", props, tags),
|
|
275
|
+
trackScroll: (props, tags) => sendTrack("scroll", props, tags),
|
|
276
|
+
trackElementView: (props, tags) => sendTrack("element_view", props, tags)
|
|
277
|
+
}),
|
|
278
|
+
[sendTrack]
|
|
279
|
+
);
|
|
280
|
+
return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value, children });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/hooks/track-page-view.tsx
|
|
284
|
+
import { useEffect as useEffect2 } from "react";
|
|
285
|
+
|
|
286
|
+
// src/hooks/use-analytics.ts
|
|
287
|
+
import { useContext, useMemo as useMemo2 } from "react";
|
|
288
|
+
function useAnalytics(options) {
|
|
289
|
+
const ctx = useContext(AnalyticsContext);
|
|
290
|
+
if (!ctx) throw new Error("useAnalytics must be used within AnalyticsProvider");
|
|
291
|
+
const metadataOption = options?.metadata;
|
|
292
|
+
const wrapped = useMemo2(
|
|
293
|
+
() => ({
|
|
294
|
+
...ctx,
|
|
295
|
+
track: (name, props, tags) => ctx.track(name, props, tags, { metadata: metadataOption }),
|
|
296
|
+
sendTrack: (name, props, tags) => ctx.sendTrack(name, props, tags, { metadata: metadataOption }),
|
|
297
|
+
trackPageView: (props, tags) => ctx.track("page_view", props, tags, { metadata: metadataOption }),
|
|
298
|
+
trackClickLink: (props, tags) => ctx.track("click_link", props, tags, { metadata: metadataOption }),
|
|
299
|
+
trackRedirect: (props, tags) => ctx.track("redirect", props, tags, { metadata: metadataOption }),
|
|
300
|
+
trackClickButton: (props, tags) => ctx.track("click_button", props, tags, { metadata: metadataOption }),
|
|
301
|
+
trackScroll: (props, tags) => ctx.track("scroll", props, tags, { metadata: metadataOption }),
|
|
302
|
+
trackElementView: (props, tags) => ctx.track("element_view", props, tags, { metadata: metadataOption })
|
|
303
|
+
}),
|
|
304
|
+
[ctx, metadataOption]
|
|
305
|
+
);
|
|
306
|
+
return metadataOption ? wrapped : ctx;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/hooks/track-page-view.tsx
|
|
310
|
+
function useTrackPageView(props, trackOnPopState = true) {
|
|
311
|
+
const { trackPageView } = useAnalytics();
|
|
312
|
+
useEffect2(() => {
|
|
313
|
+
if (typeof window === "undefined") return;
|
|
314
|
+
trackPageView(props);
|
|
315
|
+
if (!trackOnPopState) return;
|
|
316
|
+
const notify = () => trackPageView(props);
|
|
317
|
+
window.addEventListener("popstate", notify);
|
|
318
|
+
return () => window.removeEventListener("popstate", notify);
|
|
319
|
+
}, [trackPageView, props, trackOnPopState]);
|
|
320
|
+
}
|
|
321
|
+
function TrackPageView({ props, trackOnPopState = true }) {
|
|
322
|
+
useTrackPageView(props, trackOnPopState);
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
export {
|
|
326
|
+
AnalyticsProvider,
|
|
327
|
+
TrackPageView,
|
|
328
|
+
useAnalytics,
|
|
329
|
+
useTrackPageView
|
|
330
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xray-analytics/analytics-react",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": ">=19 <20"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.tsx --format cjs,esm --dts",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test": "vitest run"
|
|
31
|
+
},
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react": "^19.2.14",
|
|
35
|
+
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"jsdom": "^28.0.0",
|
|
37
|
+
"react-dom": "^19.2.4"
|
|
38
|
+
}
|
|
39
|
+
}
|