@jwiedeman/gtm-kit-next 1.1.6 → 1.2.0
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.cjs +274 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +272 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5,10 +5,280 @@ var navigation = require('next/navigation');
|
|
|
5
5
|
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var DEFAULT_EVENT_NAME = "page_view";
|
|
9
|
+
var defaultBuildPayload = ({ pagePath, url, title }) => {
|
|
10
|
+
const payload = {
|
|
11
|
+
page_path: pagePath,
|
|
12
|
+
page_location: url
|
|
13
|
+
};
|
|
14
|
+
if (title) {
|
|
15
|
+
payload.page_title = title;
|
|
16
|
+
}
|
|
17
|
+
return payload;
|
|
18
|
+
};
|
|
19
|
+
var buildUrl = (pagePath, hash) => {
|
|
20
|
+
var _a;
|
|
21
|
+
const origin = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.origin) ? window.location.origin : "";
|
|
22
|
+
if (!origin) {
|
|
23
|
+
return `${pagePath}${hash}`;
|
|
24
|
+
}
|
|
25
|
+
return `${origin}${pagePath}${hash}`;
|
|
26
|
+
};
|
|
27
|
+
var sanitizeHash = (hash) => hash && hash.startsWith("#") ? hash : hash ? `#${hash}` : "";
|
|
28
|
+
var useTrackPageViews = ({
|
|
29
|
+
client,
|
|
30
|
+
eventName = DEFAULT_EVENT_NAME,
|
|
31
|
+
buildPayload = defaultBuildPayload,
|
|
32
|
+
includeSearchParams = true,
|
|
33
|
+
trackHash = false,
|
|
34
|
+
trackOnMount = true,
|
|
35
|
+
skipSamePath = true,
|
|
36
|
+
pushEventFn = gtmKit.pushEvent,
|
|
37
|
+
waitForReady = false,
|
|
38
|
+
readyPromise
|
|
39
|
+
}) => {
|
|
40
|
+
if (!client) {
|
|
41
|
+
throw new Error("A GTM client is required to track page views.");
|
|
42
|
+
}
|
|
43
|
+
const pathname = navigation.usePathname();
|
|
44
|
+
const searchParams = navigation.useSearchParams();
|
|
45
|
+
const search = react.useMemo(() => {
|
|
46
|
+
if (!includeSearchParams || !searchParams) {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
return searchParams.toString();
|
|
50
|
+
}, [includeSearchParams, searchParams]);
|
|
51
|
+
const previousRef = react.useRef(null);
|
|
52
|
+
const hasTrackedRef = react.useRef(false);
|
|
53
|
+
const pendingKeyRef = react.useRef(null);
|
|
54
|
+
const readinessRef = react.useRef(
|
|
55
|
+
waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
|
|
56
|
+
);
|
|
57
|
+
const isMountedRef = react.useRef(true);
|
|
58
|
+
react.useEffect(() => {
|
|
59
|
+
isMountedRef.current = true;
|
|
60
|
+
return () => {
|
|
61
|
+
isMountedRef.current = false;
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
react.useEffect(() => {
|
|
65
|
+
readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
|
|
66
|
+
}, [client, readyPromise, waitForReady]);
|
|
67
|
+
const logFailures = react.useCallback((states) => {
|
|
68
|
+
const failed = states.filter((state) => state.status === "failed");
|
|
69
|
+
if (!failed.length) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const details = failed.map((state) => state.containerId).join(", ");
|
|
73
|
+
console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);
|
|
74
|
+
}, []);
|
|
75
|
+
const handleRouteChange = react.useCallback(
|
|
76
|
+
(nextPathname, searchValue, rawHash) => {
|
|
77
|
+
if (!nextPathname) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const normalizedHash = trackHash ? sanitizeHash(rawHash) : "";
|
|
81
|
+
const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;
|
|
82
|
+
const key = `${pagePath}${normalizedHash}`;
|
|
83
|
+
const url = buildUrl(pagePath, normalizedHash);
|
|
84
|
+
if (!trackOnMount && !hasTrackedRef.current) {
|
|
85
|
+
previousRef.current = {
|
|
86
|
+
key,
|
|
87
|
+
pathname: nextPathname,
|
|
88
|
+
search: searchValue,
|
|
89
|
+
hash: normalizedHash,
|
|
90
|
+
pagePath,
|
|
91
|
+
url
|
|
92
|
+
};
|
|
93
|
+
hasTrackedRef.current = true;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (skipSamePath && previousRef.current && previousRef.current.key === key) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const title = typeof document !== "undefined" ? document.title : void 0;
|
|
100
|
+
const previous = previousRef.current ? {
|
|
101
|
+
pathname: previousRef.current.pathname,
|
|
102
|
+
search: previousRef.current.search,
|
|
103
|
+
hash: previousRef.current.hash,
|
|
104
|
+
pagePath: previousRef.current.pagePath,
|
|
105
|
+
url: previousRef.current.url
|
|
106
|
+
} : void 0;
|
|
107
|
+
const details = {
|
|
108
|
+
pathname: nextPathname,
|
|
109
|
+
search: searchValue,
|
|
110
|
+
hash: normalizedHash,
|
|
111
|
+
pagePath,
|
|
112
|
+
url,
|
|
113
|
+
title,
|
|
114
|
+
previous
|
|
115
|
+
};
|
|
116
|
+
const pushPayload = () => {
|
|
117
|
+
const payload = buildPayload(details);
|
|
118
|
+
pushEventFn(client, eventName, payload);
|
|
119
|
+
previousRef.current = {
|
|
120
|
+
key,
|
|
121
|
+
pathname: nextPathname,
|
|
122
|
+
search: searchValue,
|
|
123
|
+
hash: normalizedHash,
|
|
124
|
+
pagePath,
|
|
125
|
+
url
|
|
126
|
+
};
|
|
127
|
+
hasTrackedRef.current = true;
|
|
128
|
+
};
|
|
129
|
+
if (waitForReady && readinessRef.current) {
|
|
130
|
+
pendingKeyRef.current = key;
|
|
131
|
+
readinessRef.current.then((states) => {
|
|
132
|
+
if (!isMountedRef.current || pendingKeyRef.current !== key) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
logFailures(states);
|
|
136
|
+
pushPayload();
|
|
137
|
+
}).catch((error) => {
|
|
138
|
+
if (!isMountedRef.current || pendingKeyRef.current !== key) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.error("[gtm-kit/next] Error while waiting for GTM readiness.", error);
|
|
142
|
+
pushPayload();
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
pushPayload();
|
|
147
|
+
},
|
|
148
|
+
[buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
|
|
149
|
+
);
|
|
150
|
+
react.useEffect(() => {
|
|
151
|
+
var _a;
|
|
152
|
+
if (typeof window === "undefined") {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const hash = trackHash ? (_a = window.location.hash) != null ? _a : "" : "";
|
|
156
|
+
handleRouteChange(pathname, search, hash);
|
|
157
|
+
}, [handleRouteChange, pathname, search, trackHash]);
|
|
158
|
+
react.useEffect(() => {
|
|
159
|
+
if (!trackHash || typeof window === "undefined") {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const listener = () => {
|
|
163
|
+
var _a;
|
|
164
|
+
handleRouteChange(pathname, search, (_a = window.location.hash) != null ? _a : "");
|
|
165
|
+
};
|
|
166
|
+
window.addEventListener("hashchange", listener);
|
|
167
|
+
return () => {
|
|
168
|
+
window.removeEventListener("hashchange", listener);
|
|
169
|
+
};
|
|
170
|
+
}, [handleRouteChange, pathname, search, trackHash]);
|
|
171
|
+
};
|
|
172
|
+
var DEFAULT_ASYNC = true;
|
|
173
|
+
var GtmHeadScript = ({
|
|
174
|
+
containers,
|
|
175
|
+
host = gtmKit.DEFAULT_GTM_HOST,
|
|
176
|
+
defaultQueryParams,
|
|
177
|
+
scriptAttributes,
|
|
178
|
+
dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
|
|
179
|
+
}) => {
|
|
180
|
+
const normalized = gtmKit.normalizeContainers(containers);
|
|
181
|
+
if (!normalized.length) {
|
|
182
|
+
throw new Error("At least one GTM container is required to render script tags.");
|
|
183
|
+
}
|
|
184
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: normalized.map((container) => {
|
|
185
|
+
if (!container.id) {
|
|
186
|
+
throw new Error("Container id is required to render GTM script tags.");
|
|
187
|
+
}
|
|
188
|
+
const params = {
|
|
189
|
+
...defaultQueryParams,
|
|
190
|
+
...container.queryParams
|
|
191
|
+
};
|
|
192
|
+
const src = gtmKit.buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
193
|
+
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
194
|
+
const scriptProps = {
|
|
195
|
+
src,
|
|
196
|
+
async: asyncAttr != null ? asyncAttr : DEFAULT_ASYNC
|
|
197
|
+
};
|
|
198
|
+
if (defer !== void 0) {
|
|
199
|
+
scriptProps.defer = defer;
|
|
200
|
+
}
|
|
201
|
+
if (nonce) {
|
|
202
|
+
scriptProps.nonce = nonce;
|
|
203
|
+
}
|
|
204
|
+
for (const [attribute, value] of Object.entries(restAttributes)) {
|
|
205
|
+
if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (value === void 0 || value === null) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
scriptProps[attribute] = value;
|
|
212
|
+
}
|
|
213
|
+
return /* @__PURE__ */ jsxRuntime.jsx("script", { "data-gtm-container-id": container.id, ...scriptProps }, container.id);
|
|
214
|
+
}) });
|
|
215
|
+
};
|
|
216
|
+
var toStringValue = (value) => String(value);
|
|
217
|
+
var parseStyle = (style) => {
|
|
218
|
+
return style.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, declaration) => {
|
|
219
|
+
const [property, rawValue] = declaration.split(":");
|
|
220
|
+
if (!property || rawValue === void 0) {
|
|
221
|
+
return acc;
|
|
222
|
+
}
|
|
223
|
+
const key = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
224
|
+
const value = rawValue.trim();
|
|
225
|
+
if (!key || !value) {
|
|
226
|
+
return acc;
|
|
227
|
+
}
|
|
228
|
+
acc[key] = value;
|
|
229
|
+
return acc;
|
|
230
|
+
}, {});
|
|
231
|
+
};
|
|
232
|
+
var GtmNoScript = ({
|
|
233
|
+
containers,
|
|
234
|
+
host = gtmKit.DEFAULT_GTM_HOST,
|
|
235
|
+
defaultQueryParams,
|
|
236
|
+
iframeAttributes,
|
|
237
|
+
dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
|
|
238
|
+
}) => {
|
|
239
|
+
const normalized = gtmKit.normalizeContainers(containers);
|
|
240
|
+
if (!normalized.length) {
|
|
241
|
+
throw new Error("At least one GTM container is required to render noscript markup.");
|
|
242
|
+
}
|
|
243
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: normalized.map((container) => {
|
|
244
|
+
if (!container.id) {
|
|
245
|
+
throw new Error("Container id is required to render GTM noscript markup.");
|
|
246
|
+
}
|
|
247
|
+
const params = {
|
|
248
|
+
...defaultQueryParams,
|
|
249
|
+
...container.queryParams
|
|
250
|
+
};
|
|
251
|
+
const src = gtmKit.buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
|
|
252
|
+
const attributes = {
|
|
253
|
+
...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
|
|
254
|
+
...iframeAttributes
|
|
255
|
+
};
|
|
256
|
+
const iframeProps = {
|
|
257
|
+
src
|
|
258
|
+
};
|
|
259
|
+
for (const [attribute, value] of Object.entries(attributes)) {
|
|
260
|
+
if (attribute === "src") {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (value === void 0 || value === null) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (attribute === "style") {
|
|
267
|
+
if (typeof value === "string") {
|
|
268
|
+
iframeProps.style = parseStyle(value);
|
|
269
|
+
} else if (typeof value === "object") {
|
|
270
|
+
iframeProps.style = value;
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
iframeProps[attribute] = toStringValue(value);
|
|
275
|
+
}
|
|
276
|
+
return /* @__PURE__ */ jsxRuntime.jsx("noscript", { children: /* @__PURE__ */ jsxRuntime.jsx("iframe", { ...iframeProps }) }, container.id);
|
|
277
|
+
}) });
|
|
278
|
+
};
|
|
9
279
|
|
|
10
|
-
exports.GtmHeadScript =
|
|
11
|
-
exports.GtmNoScript =
|
|
12
|
-
exports.useTrackPageViews =
|
|
280
|
+
exports.GtmHeadScript = GtmHeadScript;
|
|
281
|
+
exports.GtmNoScript = GtmNoScript;
|
|
282
|
+
exports.useTrackPageViews = useTrackPageViews;
|
|
13
283
|
//# sourceMappingURL=out.js.map
|
|
14
284
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","value","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","acc","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["DEFAULT_DATA_LAYER_NAME","Fragment","jsx"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[gtm-kit/next] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,278 @@ import { usePathname, useSearchParams } from 'next/navigation';
|
|
|
3
3
|
import { pushEvent, normalizeContainers, buildGtmScriptUrl, DEFAULT_GTM_HOST, DEFAULT_DATA_LAYER_NAME, buildGtmNoscriptUrl, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';
|
|
4
4
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var DEFAULT_EVENT_NAME = "page_view";
|
|
7
|
+
var defaultBuildPayload = ({ pagePath, url, title }) => {
|
|
8
|
+
const payload = {
|
|
9
|
+
page_path: pagePath,
|
|
10
|
+
page_location: url
|
|
11
|
+
};
|
|
12
|
+
if (title) {
|
|
13
|
+
payload.page_title = title;
|
|
14
|
+
}
|
|
15
|
+
return payload;
|
|
16
|
+
};
|
|
17
|
+
var buildUrl = (pagePath, hash) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const origin = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.origin) ? window.location.origin : "";
|
|
20
|
+
if (!origin) {
|
|
21
|
+
return `${pagePath}${hash}`;
|
|
22
|
+
}
|
|
23
|
+
return `${origin}${pagePath}${hash}`;
|
|
24
|
+
};
|
|
25
|
+
var sanitizeHash = (hash) => hash && hash.startsWith("#") ? hash : hash ? `#${hash}` : "";
|
|
26
|
+
var useTrackPageViews = ({
|
|
27
|
+
client,
|
|
28
|
+
eventName = DEFAULT_EVENT_NAME,
|
|
29
|
+
buildPayload = defaultBuildPayload,
|
|
30
|
+
includeSearchParams = true,
|
|
31
|
+
trackHash = false,
|
|
32
|
+
trackOnMount = true,
|
|
33
|
+
skipSamePath = true,
|
|
34
|
+
pushEventFn = pushEvent,
|
|
35
|
+
waitForReady = false,
|
|
36
|
+
readyPromise
|
|
37
|
+
}) => {
|
|
38
|
+
if (!client) {
|
|
39
|
+
throw new Error("A GTM client is required to track page views.");
|
|
40
|
+
}
|
|
41
|
+
const pathname = usePathname();
|
|
42
|
+
const searchParams = useSearchParams();
|
|
43
|
+
const search = useMemo(() => {
|
|
44
|
+
if (!includeSearchParams || !searchParams) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
return searchParams.toString();
|
|
48
|
+
}, [includeSearchParams, searchParams]);
|
|
49
|
+
const previousRef = useRef(null);
|
|
50
|
+
const hasTrackedRef = useRef(false);
|
|
51
|
+
const pendingKeyRef = useRef(null);
|
|
52
|
+
const readinessRef = useRef(
|
|
53
|
+
waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
|
|
54
|
+
);
|
|
55
|
+
const isMountedRef = useRef(true);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
isMountedRef.current = true;
|
|
58
|
+
return () => {
|
|
59
|
+
isMountedRef.current = false;
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
|
|
64
|
+
}, [client, readyPromise, waitForReady]);
|
|
65
|
+
const logFailures = useCallback((states) => {
|
|
66
|
+
const failed = states.filter((state) => state.status === "failed");
|
|
67
|
+
if (!failed.length) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const details = failed.map((state) => state.containerId).join(", ");
|
|
71
|
+
console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);
|
|
72
|
+
}, []);
|
|
73
|
+
const handleRouteChange = useCallback(
|
|
74
|
+
(nextPathname, searchValue, rawHash) => {
|
|
75
|
+
if (!nextPathname) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const normalizedHash = trackHash ? sanitizeHash(rawHash) : "";
|
|
79
|
+
const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;
|
|
80
|
+
const key = `${pagePath}${normalizedHash}`;
|
|
81
|
+
const url = buildUrl(pagePath, normalizedHash);
|
|
82
|
+
if (!trackOnMount && !hasTrackedRef.current) {
|
|
83
|
+
previousRef.current = {
|
|
84
|
+
key,
|
|
85
|
+
pathname: nextPathname,
|
|
86
|
+
search: searchValue,
|
|
87
|
+
hash: normalizedHash,
|
|
88
|
+
pagePath,
|
|
89
|
+
url
|
|
90
|
+
};
|
|
91
|
+
hasTrackedRef.current = true;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (skipSamePath && previousRef.current && previousRef.current.key === key) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const title = typeof document !== "undefined" ? document.title : void 0;
|
|
98
|
+
const previous = previousRef.current ? {
|
|
99
|
+
pathname: previousRef.current.pathname,
|
|
100
|
+
search: previousRef.current.search,
|
|
101
|
+
hash: previousRef.current.hash,
|
|
102
|
+
pagePath: previousRef.current.pagePath,
|
|
103
|
+
url: previousRef.current.url
|
|
104
|
+
} : void 0;
|
|
105
|
+
const details = {
|
|
106
|
+
pathname: nextPathname,
|
|
107
|
+
search: searchValue,
|
|
108
|
+
hash: normalizedHash,
|
|
109
|
+
pagePath,
|
|
110
|
+
url,
|
|
111
|
+
title,
|
|
112
|
+
previous
|
|
113
|
+
};
|
|
114
|
+
const pushPayload = () => {
|
|
115
|
+
const payload = buildPayload(details);
|
|
116
|
+
pushEventFn(client, eventName, payload);
|
|
117
|
+
previousRef.current = {
|
|
118
|
+
key,
|
|
119
|
+
pathname: nextPathname,
|
|
120
|
+
search: searchValue,
|
|
121
|
+
hash: normalizedHash,
|
|
122
|
+
pagePath,
|
|
123
|
+
url
|
|
124
|
+
};
|
|
125
|
+
hasTrackedRef.current = true;
|
|
126
|
+
};
|
|
127
|
+
if (waitForReady && readinessRef.current) {
|
|
128
|
+
pendingKeyRef.current = key;
|
|
129
|
+
readinessRef.current.then((states) => {
|
|
130
|
+
if (!isMountedRef.current || pendingKeyRef.current !== key) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
logFailures(states);
|
|
134
|
+
pushPayload();
|
|
135
|
+
}).catch((error) => {
|
|
136
|
+
if (!isMountedRef.current || pendingKeyRef.current !== key) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.error("[gtm-kit/next] Error while waiting for GTM readiness.", error);
|
|
140
|
+
pushPayload();
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
pushPayload();
|
|
145
|
+
},
|
|
146
|
+
[buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
|
|
147
|
+
);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
var _a;
|
|
150
|
+
if (typeof window === "undefined") {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const hash = trackHash ? (_a = window.location.hash) != null ? _a : "" : "";
|
|
154
|
+
handleRouteChange(pathname, search, hash);
|
|
155
|
+
}, [handleRouteChange, pathname, search, trackHash]);
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!trackHash || typeof window === "undefined") {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const listener = () => {
|
|
161
|
+
var _a;
|
|
162
|
+
handleRouteChange(pathname, search, (_a = window.location.hash) != null ? _a : "");
|
|
163
|
+
};
|
|
164
|
+
window.addEventListener("hashchange", listener);
|
|
165
|
+
return () => {
|
|
166
|
+
window.removeEventListener("hashchange", listener);
|
|
167
|
+
};
|
|
168
|
+
}, [handleRouteChange, pathname, search, trackHash]);
|
|
169
|
+
};
|
|
170
|
+
var DEFAULT_ASYNC = true;
|
|
171
|
+
var GtmHeadScript = ({
|
|
172
|
+
containers,
|
|
173
|
+
host = DEFAULT_GTM_HOST,
|
|
174
|
+
defaultQueryParams,
|
|
175
|
+
scriptAttributes,
|
|
176
|
+
dataLayerName = DEFAULT_DATA_LAYER_NAME
|
|
177
|
+
}) => {
|
|
178
|
+
const normalized = normalizeContainers(containers);
|
|
179
|
+
if (!normalized.length) {
|
|
180
|
+
throw new Error("At least one GTM container is required to render script tags.");
|
|
181
|
+
}
|
|
182
|
+
return /* @__PURE__ */ jsx(Fragment, { children: normalized.map((container) => {
|
|
183
|
+
if (!container.id) {
|
|
184
|
+
throw new Error("Container id is required to render GTM script tags.");
|
|
185
|
+
}
|
|
186
|
+
const params = {
|
|
187
|
+
...defaultQueryParams,
|
|
188
|
+
...container.queryParams
|
|
189
|
+
};
|
|
190
|
+
const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);
|
|
191
|
+
const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
|
|
192
|
+
const scriptProps = {
|
|
193
|
+
src,
|
|
194
|
+
async: asyncAttr != null ? asyncAttr : DEFAULT_ASYNC
|
|
195
|
+
};
|
|
196
|
+
if (defer !== void 0) {
|
|
197
|
+
scriptProps.defer = defer;
|
|
198
|
+
}
|
|
199
|
+
if (nonce) {
|
|
200
|
+
scriptProps.nonce = nonce;
|
|
201
|
+
}
|
|
202
|
+
for (const [attribute, value] of Object.entries(restAttributes)) {
|
|
203
|
+
if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (value === void 0 || value === null) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
scriptProps[attribute] = value;
|
|
210
|
+
}
|
|
211
|
+
return /* @__PURE__ */ jsx("script", { "data-gtm-container-id": container.id, ...scriptProps }, container.id);
|
|
212
|
+
}) });
|
|
213
|
+
};
|
|
214
|
+
var toStringValue = (value) => String(value);
|
|
215
|
+
var parseStyle = (style) => {
|
|
216
|
+
return style.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, declaration) => {
|
|
217
|
+
const [property, rawValue] = declaration.split(":");
|
|
218
|
+
if (!property || rawValue === void 0) {
|
|
219
|
+
return acc;
|
|
220
|
+
}
|
|
221
|
+
const key = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
222
|
+
const value = rawValue.trim();
|
|
223
|
+
if (!key || !value) {
|
|
224
|
+
return acc;
|
|
225
|
+
}
|
|
226
|
+
acc[key] = value;
|
|
227
|
+
return acc;
|
|
228
|
+
}, {});
|
|
229
|
+
};
|
|
230
|
+
var GtmNoScript = ({
|
|
231
|
+
containers,
|
|
232
|
+
host = DEFAULT_GTM_HOST,
|
|
233
|
+
defaultQueryParams,
|
|
234
|
+
iframeAttributes,
|
|
235
|
+
dataLayerName = DEFAULT_DATA_LAYER_NAME
|
|
236
|
+
}) => {
|
|
237
|
+
const normalized = normalizeContainers(containers);
|
|
238
|
+
if (!normalized.length) {
|
|
239
|
+
throw new Error("At least one GTM container is required to render noscript markup.");
|
|
240
|
+
}
|
|
241
|
+
return /* @__PURE__ */ jsx(Fragment, { children: normalized.map((container) => {
|
|
242
|
+
if (!container.id) {
|
|
243
|
+
throw new Error("Container id is required to render GTM noscript markup.");
|
|
244
|
+
}
|
|
245
|
+
const params = {
|
|
246
|
+
...defaultQueryParams,
|
|
247
|
+
...container.queryParams
|
|
248
|
+
};
|
|
249
|
+
const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
|
|
250
|
+
const attributes = {
|
|
251
|
+
...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
|
|
252
|
+
...iframeAttributes
|
|
253
|
+
};
|
|
254
|
+
const iframeProps = {
|
|
255
|
+
src
|
|
256
|
+
};
|
|
257
|
+
for (const [attribute, value] of Object.entries(attributes)) {
|
|
258
|
+
if (attribute === "src") {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (value === void 0 || value === null) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (attribute === "style") {
|
|
265
|
+
if (typeof value === "string") {
|
|
266
|
+
iframeProps.style = parseStyle(value);
|
|
267
|
+
} else if (typeof value === "object") {
|
|
268
|
+
iframeProps.style = value;
|
|
269
|
+
}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
iframeProps[attribute] = toStringValue(value);
|
|
273
|
+
}
|
|
274
|
+
return /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("iframe", { ...iframeProps }) }, container.id);
|
|
275
|
+
}) });
|
|
276
|
+
};
|
|
7
277
|
|
|
8
|
-
export {
|
|
278
|
+
export { GtmHeadScript, GtmNoScript, useTrackPageViews };
|
|
9
279
|
//# sourceMappingURL=out.js.map
|
|
10
280
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","value","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","acc","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["DEFAULT_DATA_LAYER_NAME","Fragment","jsx"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[gtm-kit/next] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwiedeman/gtm-kit-next",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Next.js App Router helpers for GTM Kit - Google Tag Manager integration with Server Components support.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@jwiedeman/gtm-kit": "^1.
|
|
52
|
+
"@jwiedeman/gtm-kit": "^1.2.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"next": "^13.4.0 || ^14.0.0 || ^15.0.0",
|