@jwiedeman/gtm-kit-next 1.2.0 → 1.2.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.cjs CHANGED
@@ -51,19 +51,14 @@ var useTrackPageViews = ({
51
51
  const previousRef = react.useRef(null);
52
52
  const hasTrackedRef = react.useRef(false);
53
53
  const pendingKeyRef = react.useRef(null);
54
- const readinessRef = react.useRef(
55
- waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
56
- );
57
54
  const isMountedRef = react.useRef(true);
55
+ const readinessPromise = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
58
56
  react.useEffect(() => {
59
57
  isMountedRef.current = true;
60
58
  return () => {
61
59
  isMountedRef.current = false;
62
60
  };
63
61
  }, []);
64
- react.useEffect(() => {
65
- readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
66
- }, [client, readyPromise, waitForReady]);
67
62
  const logFailures = react.useCallback((states) => {
68
63
  const failed = states.filter((state) => state.status === "failed");
69
64
  if (!failed.length) {
@@ -126,9 +121,9 @@ var useTrackPageViews = ({
126
121
  };
127
122
  hasTrackedRef.current = true;
128
123
  };
129
- if (waitForReady && readinessRef.current) {
124
+ if (waitForReady && readinessPromise) {
130
125
  pendingKeyRef.current = key;
131
- readinessRef.current.then((states) => {
126
+ readinessPromise.then((states) => {
132
127
  if (!isMountedRef.current || pendingKeyRef.current !== key) {
133
128
  return;
134
129
  }
@@ -145,7 +140,7 @@ var useTrackPageViews = ({
145
140
  }
146
141
  pushPayload();
147
142
  },
148
- [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
143
+ [buildPayload, client, eventName, logFailures, pushEventFn, readinessPromise, skipSamePath, trackHash, trackOnMount, waitForReady]
149
144
  );
150
145
  react.useEffect(() => {
151
146
  var _a;
@@ -202,10 +197,11 @@ var GtmHeadScript = ({
202
197
  scriptProps.nonce = nonce;
203
198
  }
204
199
  for (const [attribute, value] of Object.entries(restAttributes)) {
205
- if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
200
+ if (value === void 0 || value === null) {
206
201
  continue;
207
202
  }
208
- if (value === void 0 || value === null) {
203
+ const lowerAttr = attribute.toLowerCase();
204
+ if (lowerAttr === "src" || lowerAttr.startsWith("on")) {
209
205
  continue;
210
206
  }
211
207
  scriptProps[attribute] = value;
@@ -263,6 +259,9 @@ var GtmNoScript = ({
263
259
  if (value === void 0 || value === null) {
264
260
  continue;
265
261
  }
262
+ if (attribute.toLowerCase().startsWith("on")) {
263
+ continue;
264
+ }
266
265
  if (attribute === "style") {
267
266
  if (typeof value === "string") {
268
267
  iframeProps.style = parseStyle(value);
@@ -276,7 +275,47 @@ var GtmNoScript = ({
276
275
  return /* @__PURE__ */ jsxRuntime.jsx("noscript", { children: /* @__PURE__ */ jsxRuntime.jsx("iframe", { ...iframeProps }) }, container.id);
277
276
  }) });
278
277
  };
278
+ var GtmErrorBoundary = class extends react.Component {
279
+ constructor(props) {
280
+ super(props);
281
+ this.reset = () => {
282
+ this.setState({ hasError: false, error: null });
283
+ };
284
+ this.state = { hasError: false, error: null };
285
+ }
286
+ static getDerivedStateFromError(error) {
287
+ return { hasError: true, error };
288
+ }
289
+ componentDidCatch(error, errorInfo) {
290
+ const { onError, logErrors = process.env.NODE_ENV !== "production" } = this.props;
291
+ if (logErrors) {
292
+ console.error("[gtm-kit/next] Error caught by GtmErrorBoundary:", error);
293
+ console.error("[gtm-kit/next] Component stack:", errorInfo.componentStack);
294
+ }
295
+ if (onError) {
296
+ try {
297
+ onError(error, errorInfo);
298
+ } catch (e) {
299
+ }
300
+ }
301
+ }
302
+ render() {
303
+ const { hasError, error } = this.state;
304
+ const { children, fallback } = this.props;
305
+ if (hasError && error) {
306
+ if (fallback === void 0) {
307
+ return children;
308
+ }
309
+ if (typeof fallback === "function") {
310
+ return fallback(error, this.reset);
311
+ }
312
+ return fallback;
313
+ }
314
+ return children;
315
+ }
316
+ };
279
317
 
318
+ exports.GtmErrorBoundary = GtmErrorBoundary;
280
319
  exports.GtmHeadScript = GtmHeadScript;
281
320
  exports.GtmNoScript = GtmNoScript;
282
321
  exports.useTrackPageViews = useTrackPageViews;
@@ -1 +1 @@
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":";;;AAEA,SAAS,aAAa,WAAW,SAAS,cAAc;AACxD,SAAS,aAAa,uBAAuB;AAE7C,SAAS,iBAAiB;AAE1B,IAAM,qBAAqB;AAkC3B,IAAM,sBAA8C,CAAC,EAAE,UAAU,KAAK,MAAM,MAAM;AAChF,QAAM,UAA2B;AAAA,IAC/B,WAAW;AAAA,IACX,eAAe;AAAA,EACjB;AAEA,MAAI,OAAO;AACT,YAAQ,aAAa;AAAA,EACvB;AAEA,SAAO;AACT;AAEA,IAAM,WAAW,CAAC,UAAkB,SAAyB;AAtD7D;AAuDE,QAAM,SAAS,OAAO,WAAW,iBAAe,YAAO,aAAP,mBAAiB,UAAS,OAAO,SAAS,SAAS;AACnG,MAAI,CAAC,QAAQ;AACX,WAAO,GAAG,QAAQ,GAAG,IAAI;AAAA,EAC3B;AAEA,SAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI;AACpC;AAEA,IAAM,eAAe,CAAC,SAA0B,QAAQ,KAAK,WAAW,GAAG,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK;AAEnG,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,eAAe;AAAA,EACf,cAAc;AAAA,EACd,eAAe;AAAA,EACf;AACF,MAAsC;AACpC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,gBAAgB;AAErC,QAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,CAAC,uBAAuB,CAAC,cAAc;AACzC,aAAO;AAAA,IACT;AAEA,WAAO,aAAa,SAAS;AAAA,EAC/B,GAAG,CAAC,qBAAqB,YAAY,CAAC;AAEtC,QAAM,cAAc,OAA6B,IAAI;AACrD,QAAM,gBAAgB,OAAO,KAAK;AAClC,QAAM,gBAAgB,OAAsB,IAAI;AAChD,QAAM,eAAe;AAAA,IACnB,eAAgB,sCAAgB,OAAO,UAAU,IAAK;AAAA,EACxD;AACA,QAAM,eAAe,OAAO,IAAI;AAEhC,YAAU,MAAM;AACd,iBAAa,UAAU;AACvB,WAAO,MAAM;AACX,mBAAa,UAAU;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,iBAAa,UAAU,eAAgB,sCAAgB,OAAO,UAAU,IAAK;AAAA,EAC/E,GAAG,CAAC,QAAQ,cAAc,YAAY,CAAC;AAEvC,QAAM,cAAc,YAAY,CAAC,WAA8B;AAC7D,UAAM,SAAS,OAAO,OAAO,CAAC,UAAU,MAAM,WAAW,QAAQ;AACjE,QAAI,CAAC,OAAO,QAAQ;AAClB;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,IAAI,CAAC,UAAU,MAAM,WAAW,EAAE,KAAK,IAAI;AAElE,YAAQ,MAAM,0DAA0D,OAAO,IAAI,MAAM;AAAA,EAC3F,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB;AAAA,IACxB,CAAC,cAA6B,aAAqB,YAAoB;AACrE,UAAI,CAAC,cAAc;AACjB;AAAA,MACF;AAEA,YAAM,iBAAiB,YAAY,aAAa,OAAO,IAAI;AAC3D,YAAM,WAAW,cAAc,GAAG,YAAY,IAAI,WAAW,KAAK;AAClE,YAAM,MAAM,GAAG,QAAQ,GAAG,cAAc;AACxC,YAAM,MAAM,SAAS,UAAU,cAAc;AAE7C,UAAI,CAAC,gBAAgB,CAAC,cAAc,SAAS;AAC3C,oBAAY,UAAU;AAAA,UACpB;AAAA,UACA,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACF;AACA,sBAAc,UAAU;AACxB;AAAA,MACF;AAEA,UAAI,gBAAgB,YAAY,WAAW,YAAY,QAAQ,QAAQ,KAAK;AAC1E;AAAA,MACF;AAEA,YAAM,QAAQ,OAAO,aAAa,cAAc,SAAS,QAAQ;AACjE,YAAM,WAAW,YAAY,UACzB;AAAA,QACE,UAAU,YAAY,QAAQ;AAAA,QAC9B,QAAQ,YAAY,QAAQ;AAAA,QAC5B,MAAM,YAAY,QAAQ;AAAA,QAC1B,UAAU,YAAY,QAAQ;AAAA,QAC9B,KAAK,YAAY,QAAQ;AAAA,MAC3B,IACA;AAEJ,YAAM,UAAmC;AAAA,QACvC,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,YAAM,cAAc,MAAY;AAC9B,cAAM,UAAU,aAAa,OAAO;AACpC,oBAAY,QAAQ,WAAW,OAAO;AAEtC,oBAAY,UAAU;AAAA,UACpB;AAAA,UACA,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,MAAM;AAAA,UACN;AAAA,UACA;AAAA,QACF;AACA,sBAAc,UAAU;AAAA,MAC1B;AAEA,UAAI,gBAAgB,aAAa,SAAS;AACxC,sBAAc,UAAU;AACxB,qBAAa,QACV,KAAK,CAAC,WAAW;AAChB,cAAI,CAAC,aAAa,WAAW,cAAc,YAAY,KAAK;AAC1D;AAAA,UACF;AAEA,sBAAY,MAAM;AAClB,sBAAY;AAAA,QACd,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,cAAI,CAAC,aAAa,WAAW,cAAc,YAAY,KAAK;AAC1D;AAAA,UACF;AAEA,kBAAQ,MAAM,yDAAyD,KAAK;AAC5E,sBAAY;AAAA,QACd,CAAC;AAEH;AAAA,MACF;AAEA,kBAAY;AAAA,IACd;AAAA,IACA,CAAC,cAAc,QAAQ,WAAW,aAAa,aAAa,cAAc,WAAW,cAAc,YAAY;AAAA,EACjH;AAEA,YAAU,MAAM;AAtNlB;AAuNI,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,UAAM,OAAO,aAAa,YAAO,SAAS,SAAhB,YAAwB,KAAM;AACxD,sBAAkB,UAAU,QAAQ,IAAI;AAAA,EAC1C,GAAG,CAAC,mBAAmB,UAAU,QAAQ,SAAS,CAAC;AAEnD,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,OAAO,WAAW,aAAa;AAC/C;AAAA,IACF;AAEA,UAAM,WAAW,MAAY;AApOjC;AAqOM,wBAAkB,UAAU,SAAQ,YAAO,SAAS,SAAhB,YAAwB,EAAE;AAAA,IAChE;AAEA,WAAO,iBAAiB,cAAc,QAAQ;AAC9C,WAAO,MAAM;AACX,aAAO,oBAAoB,cAAc,QAAQ;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,mBAAmB,UAAU,QAAQ,SAAS,CAAC;AACrD;;;AC5OA,SAAS,+BAA+B;;;ACAxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACqB;AAAA,EACE;AAAA,OAClB;;;ADsBH,mBAuCW,WAvCX;AAhBJ,IAAM,gBAAgB;AAEf,IAAM,gBAAgB,CAAC;AAAA,EAC5B;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,MAA8C;AAC5C,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AAEA,SACE,gCACG,qBAAW,IAAI,CAAC,cAAc;AAC7B,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,kBAAe,MAAM,UAAU,IAAI,QAAQ,aAAa;AACpE,UAAM,EAAE,OAAO,WAAW,OAAO,OAAO,GAAG,eAAe,IAAI,8CAAoB,CAAC;AAEnF,UAAM,cAA6D;AAAA,MACjE;AAAA,MACA,OAAO,gCAAa;AAAA,IACtB;AAEA,QAAI,UAAU,QAAW;AACvB,kBAAY,QAAQ;AAAA,IACtB;AAEA,QAAI,OAAO;AACT,kBAAY,QAAQ;AAAA,IACtB;AAEA,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,cAAc,GAAG;AAC/D,UAAI,cAAc,WAAW,cAAc,WAAW,cAAc,WAAW,cAAc,OAAO;AAClG;AAAA,MACF;AAEA,UAAI,UAAU,UAAa,UAAU,MAAM;AACzC;AAAA,MACF;AAEA,MAAC,YAAwC,SAAS,IAAI;AAAA,IACxD;AAEA,WAAO,oBAAC,YAA0B,yBAAuB,UAAU,IAAK,GAAG,eAAvD,UAAU,EAA0D;AAAA,EAC1F,CAAC,GACH;AAEJ;;;AEvEA,SAAS,2BAAAA,gCAA+B;AAExC,SAAS,0CAA0C;AAiD/C,qBAAAC,WA4CQ,OAAAC,YA5CR;AAtCJ,IAAM,gBAAgB,CAAC,UAA6C,OAAO,KAAK;AAEhF,IAAM,aAAa,CAAC,UAAuC;AACzD,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,OAAO,EACd,OAA4B,CAAC,KAAK,gBAAgB;AACjD,UAAM,CAAC,UAAU,QAAQ,IAAI,YAAY,MAAM,GAAG;AAClD,QAAI,CAAC,YAAY,aAAa,QAAW;AACvC,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,SAAS,KAAK,EAAE,QAAQ,aAAa,CAAC,GAAG,SAAiB,KAAK,YAAY,CAAC;AACxF,UAAM,QAAQ,SAAS,KAAK;AAC5B,QAAI,CAAC,OAAO,CAAC,OAAO;AAClB,aAAO;AAAA,IACT;AAEA,IAAC,IAA+B,GAAG,IAAI;AACvC,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AACT;AAEO,IAAM,cAAc,CAAC;AAAA,EAC1B;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,gBAAgBF;AAClB,MAA4C;AAC1C,QAAM,aAAa,oBAAoB,UAAU;AAEjD,MAAI,CAAC,WAAW,QAAQ;AACtB,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AAEA,SACE,gBAAAE,KAAAD,WAAA,EACG,qBAAW,IAAI,CAAC,cAAc;AAC7B,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,yDAAyD;AAAA,IAC3E;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAG,UAAU;AAAA,IACf;AAEA,UAAM,MAAM,oBAAiB,MAAM,UAAU,IAAI,QAAQ,aAAa;AACtE,UAAM,aAAa;AAAA,MACjB,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAEA,UAAM,cAA6D;AAAA,MACjE;AAAA,IACF;AAEA,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC3D,UAAI,cAAc,OAAO;AACvB;AAAA,MACF;AAEA,UAAI,UAAU,UAAa,UAAU,MAAM;AACzC;AAAA,MACF;AAEA,UAAI,cAAc,SAAS;AACzB,YAAI,OAAO,UAAU,UAAU;AAC7B,sBAAY,QAAQ,WAAW,KAAK;AAAA,QACtC,WAAW,OAAO,UAAU,UAAU;AACpC,sBAAY,QAAQ;AAAA,QACtB;AACA;AAAA,MACF;AAEA,MAAC,YAAwC,SAAS,IAAI,cAAc,KAAkC;AAAA,IACxG;AAEA,WACE,gBAAAC,KAAC,cACC,0BAAAA,KAAC,YAAQ,GAAG,aAAa,KADZ,UAAU,EAEzB;AAAA,EAEJ,CAAC,GACH;AAEJ","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"]}
1
+ {"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx","../src/error-boundary.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 isMountedRef = useRef(true);\n\n // Compute readiness promise inline to avoid stale ref from separate useEffect\n const readinessPromise = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\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 && readinessPromise) {\n pendingKeyRef.current = key;\n readinessPromise\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, readinessPromise, 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 (value === undefined || value === null) {\n continue;\n }\n\n // Block src (already set) and event handler attributes to prevent XSS\n const lowerAttr = attribute.toLowerCase();\n if (lowerAttr === 'src' || lowerAttr.startsWith('on')) {\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 // Block event handler attributes to prevent XSS\n if (attribute.toLowerCase().startsWith('on')) {\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","'use client';\n\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\n\n/**\n * Props for GtmErrorBoundary component.\n */\nexport interface GtmErrorBoundaryProps {\n children: ReactNode;\n /** Fallback UI to render when an error occurs */\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n /** Callback invoked when an error is caught */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Whether to log errors to console (default: true in development) */\n logErrors?: boolean;\n}\n\ninterface GtmErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error boundary component for GTM provider in Next.js apps.\n * Catches errors during GTM initialization and renders a fallback UI.\n * Analytics and tracking will be disabled when an error occurs.\n *\n * @example\n * ```tsx\n * import { GtmErrorBoundary } from '@jwiedeman/gtm-kit-next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <GtmErrorBoundary fallback={<div>GTM failed to load</div>}>\n * {children}\n * </GtmErrorBoundary>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport class GtmErrorBoundary extends Component<GtmErrorBoundaryProps, GtmErrorBoundaryState> {\n constructor(props: GtmErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): GtmErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n const { onError, logErrors = process.env.NODE_ENV !== 'production' } = this.props;\n\n if (logErrors) {\n console.error('[gtm-kit/next] Error caught by GtmErrorBoundary:', error);\n console.error('[gtm-kit/next] Component stack:', errorInfo.componentStack);\n }\n\n if (onError) {\n try {\n onError(error, errorInfo);\n } catch {\n // Ignore callback errors\n }\n }\n }\n\n reset = (): void => {\n this.setState({ hasError: false, error: null });\n };\n\n render(): ReactNode {\n const { hasError, error } = this.state;\n const { children, fallback } = this.props;\n\n if (hasError && error) {\n if (fallback === undefined) {\n // Default: render children without GTM (silent fallback)\n return children;\n }\n\n if (typeof fallback === 'function') {\n return fallback(error, this.reset);\n }\n\n return fallback;\n }\n\n return children;\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PageViewPayload, GtmClient, pushEvent, ScriptLoadState, ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';
2
- import React from 'react';
2
+ import React, { ReactNode, ErrorInfo, Component } from 'react';
3
3
 
4
4
  interface RouteLocationSnapshot {
5
5
  pathname: string;
@@ -45,4 +45,50 @@ interface GtmNoScriptProps {
45
45
  }
46
46
  declare const GtmNoScript: ({ containers, host, defaultQueryParams, iframeAttributes, dataLayerName }: GtmNoScriptProps) => React.ReactElement;
47
47
 
48
- export { GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
48
+ /**
49
+ * Props for GtmErrorBoundary component.
50
+ */
51
+ interface GtmErrorBoundaryProps {
52
+ children: ReactNode;
53
+ /** Fallback UI to render when an error occurs */
54
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
55
+ /** Callback invoked when an error is caught */
56
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
57
+ /** Whether to log errors to console (default: true in development) */
58
+ logErrors?: boolean;
59
+ }
60
+ interface GtmErrorBoundaryState {
61
+ hasError: boolean;
62
+ error: Error | null;
63
+ }
64
+ /**
65
+ * Error boundary component for GTM provider in Next.js apps.
66
+ * Catches errors during GTM initialization and renders a fallback UI.
67
+ * Analytics and tracking will be disabled when an error occurs.
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * import { GtmErrorBoundary } from '@jwiedeman/gtm-kit-next';
72
+ *
73
+ * export default function RootLayout({ children }) {
74
+ * return (
75
+ * <html>
76
+ * <body>
77
+ * <GtmErrorBoundary fallback={<div>GTM failed to load</div>}>
78
+ * {children}
79
+ * </GtmErrorBoundary>
80
+ * </body>
81
+ * </html>
82
+ * );
83
+ * }
84
+ * ```
85
+ */
86
+ declare class GtmErrorBoundary extends Component<GtmErrorBoundaryProps, GtmErrorBoundaryState> {
87
+ constructor(props: GtmErrorBoundaryProps);
88
+ static getDerivedStateFromError(error: Error): GtmErrorBoundaryState;
89
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
90
+ reset: () => void;
91
+ render(): ReactNode;
92
+ }
93
+
94
+ export { GtmErrorBoundary, GtmErrorBoundaryProps, GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PageViewPayload, GtmClient, pushEvent, ScriptLoadState, ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';
2
- import React from 'react';
2
+ import React, { ReactNode, ErrorInfo, Component } from 'react';
3
3
 
4
4
  interface RouteLocationSnapshot {
5
5
  pathname: string;
@@ -45,4 +45,50 @@ interface GtmNoScriptProps {
45
45
  }
46
46
  declare const GtmNoScript: ({ containers, host, defaultQueryParams, iframeAttributes, dataLayerName }: GtmNoScriptProps) => React.ReactElement;
47
47
 
48
- export { GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
48
+ /**
49
+ * Props for GtmErrorBoundary component.
50
+ */
51
+ interface GtmErrorBoundaryProps {
52
+ children: ReactNode;
53
+ /** Fallback UI to render when an error occurs */
54
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
55
+ /** Callback invoked when an error is caught */
56
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
57
+ /** Whether to log errors to console (default: true in development) */
58
+ logErrors?: boolean;
59
+ }
60
+ interface GtmErrorBoundaryState {
61
+ hasError: boolean;
62
+ error: Error | null;
63
+ }
64
+ /**
65
+ * Error boundary component for GTM provider in Next.js apps.
66
+ * Catches errors during GTM initialization and renders a fallback UI.
67
+ * Analytics and tracking will be disabled when an error occurs.
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * import { GtmErrorBoundary } from '@jwiedeman/gtm-kit-next';
72
+ *
73
+ * export default function RootLayout({ children }) {
74
+ * return (
75
+ * <html>
76
+ * <body>
77
+ * <GtmErrorBoundary fallback={<div>GTM failed to load</div>}>
78
+ * {children}
79
+ * </GtmErrorBoundary>
80
+ * </body>
81
+ * </html>
82
+ * );
83
+ * }
84
+ * ```
85
+ */
86
+ declare class GtmErrorBoundary extends Component<GtmErrorBoundaryProps, GtmErrorBoundaryState> {
87
+ constructor(props: GtmErrorBoundaryProps);
88
+ static getDerivedStateFromError(error: Error): GtmErrorBoundaryState;
89
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
90
+ reset: () => void;
91
+ render(): ReactNode;
92
+ }
93
+
94
+ export { GtmErrorBoundary, GtmErrorBoundaryProps, GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useMemo, useRef, useEffect, useCallback } from 'react';
1
+ import { useMemo, useRef, useEffect, useCallback, Component } from 'react';
2
2
  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';
@@ -49,19 +49,14 @@ var useTrackPageViews = ({
49
49
  const previousRef = useRef(null);
50
50
  const hasTrackedRef = useRef(false);
51
51
  const pendingKeyRef = useRef(null);
52
- const readinessRef = useRef(
53
- waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
54
- );
55
52
  const isMountedRef = useRef(true);
53
+ const readinessPromise = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
56
54
  useEffect(() => {
57
55
  isMountedRef.current = true;
58
56
  return () => {
59
57
  isMountedRef.current = false;
60
58
  };
61
59
  }, []);
62
- useEffect(() => {
63
- readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
64
- }, [client, readyPromise, waitForReady]);
65
60
  const logFailures = useCallback((states) => {
66
61
  const failed = states.filter((state) => state.status === "failed");
67
62
  if (!failed.length) {
@@ -124,9 +119,9 @@ var useTrackPageViews = ({
124
119
  };
125
120
  hasTrackedRef.current = true;
126
121
  };
127
- if (waitForReady && readinessRef.current) {
122
+ if (waitForReady && readinessPromise) {
128
123
  pendingKeyRef.current = key;
129
- readinessRef.current.then((states) => {
124
+ readinessPromise.then((states) => {
130
125
  if (!isMountedRef.current || pendingKeyRef.current !== key) {
131
126
  return;
132
127
  }
@@ -143,7 +138,7 @@ var useTrackPageViews = ({
143
138
  }
144
139
  pushPayload();
145
140
  },
146
- [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
141
+ [buildPayload, client, eventName, logFailures, pushEventFn, readinessPromise, skipSamePath, trackHash, trackOnMount, waitForReady]
147
142
  );
148
143
  useEffect(() => {
149
144
  var _a;
@@ -200,10 +195,11 @@ var GtmHeadScript = ({
200
195
  scriptProps.nonce = nonce;
201
196
  }
202
197
  for (const [attribute, value] of Object.entries(restAttributes)) {
203
- if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
198
+ if (value === void 0 || value === null) {
204
199
  continue;
205
200
  }
206
- if (value === void 0 || value === null) {
201
+ const lowerAttr = attribute.toLowerCase();
202
+ if (lowerAttr === "src" || lowerAttr.startsWith("on")) {
207
203
  continue;
208
204
  }
209
205
  scriptProps[attribute] = value;
@@ -261,6 +257,9 @@ var GtmNoScript = ({
261
257
  if (value === void 0 || value === null) {
262
258
  continue;
263
259
  }
260
+ if (attribute.toLowerCase().startsWith("on")) {
261
+ continue;
262
+ }
264
263
  if (attribute === "style") {
265
264
  if (typeof value === "string") {
266
265
  iframeProps.style = parseStyle(value);
@@ -274,7 +273,46 @@ var GtmNoScript = ({
274
273
  return /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("iframe", { ...iframeProps }) }, container.id);
275
274
  }) });
276
275
  };
276
+ var GtmErrorBoundary = class extends Component {
277
+ constructor(props) {
278
+ super(props);
279
+ this.reset = () => {
280
+ this.setState({ hasError: false, error: null });
281
+ };
282
+ this.state = { hasError: false, error: null };
283
+ }
284
+ static getDerivedStateFromError(error) {
285
+ return { hasError: true, error };
286
+ }
287
+ componentDidCatch(error, errorInfo) {
288
+ const { onError, logErrors = process.env.NODE_ENV !== "production" } = this.props;
289
+ if (logErrors) {
290
+ console.error("[gtm-kit/next] Error caught by GtmErrorBoundary:", error);
291
+ console.error("[gtm-kit/next] Component stack:", errorInfo.componentStack);
292
+ }
293
+ if (onError) {
294
+ try {
295
+ onError(error, errorInfo);
296
+ } catch (e) {
297
+ }
298
+ }
299
+ }
300
+ render() {
301
+ const { hasError, error } = this.state;
302
+ const { children, fallback } = this.props;
303
+ if (hasError && error) {
304
+ if (fallback === void 0) {
305
+ return children;
306
+ }
307
+ if (typeof fallback === "function") {
308
+ return fallback(error, this.reset);
309
+ }
310
+ return fallback;
311
+ }
312
+ return children;
313
+ }
314
+ };
277
315
 
278
- export { GtmHeadScript, GtmNoScript, useTrackPageViews };
316
+ export { GtmErrorBoundary, GtmHeadScript, GtmNoScript, useTrackPageViews };
279
317
  //# sourceMappingURL=out.js.map
280
318
  //# 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":["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"]}
1
+ {"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx","../src/error-boundary.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 isMountedRef = useRef(true);\n\n // Compute readiness promise inline to avoid stale ref from separate useEffect\n const readinessPromise = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\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 && readinessPromise) {\n pendingKeyRef.current = key;\n readinessPromise\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, readinessPromise, 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 (value === undefined || value === null) {\n continue;\n }\n\n // Block src (already set) and event handler attributes to prevent XSS\n const lowerAttr = attribute.toLowerCase();\n if (lowerAttr === 'src' || lowerAttr.startsWith('on')) {\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 // Block event handler attributes to prevent XSS\n if (attribute.toLowerCase().startsWith('on')) {\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","'use client';\n\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\n\n/**\n * Props for GtmErrorBoundary component.\n */\nexport interface GtmErrorBoundaryProps {\n children: ReactNode;\n /** Fallback UI to render when an error occurs */\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n /** Callback invoked when an error is caught */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Whether to log errors to console (default: true in development) */\n logErrors?: boolean;\n}\n\ninterface GtmErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error boundary component for GTM provider in Next.js apps.\n * Catches errors during GTM initialization and renders a fallback UI.\n * Analytics and tracking will be disabled when an error occurs.\n *\n * @example\n * ```tsx\n * import { GtmErrorBoundary } from '@jwiedeman/gtm-kit-next';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <GtmErrorBoundary fallback={<div>GTM failed to load</div>}>\n * {children}\n * </GtmErrorBoundary>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport class GtmErrorBoundary extends Component<GtmErrorBoundaryProps, GtmErrorBoundaryState> {\n constructor(props: GtmErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): GtmErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n const { onError, logErrors = process.env.NODE_ENV !== 'production' } = this.props;\n\n if (logErrors) {\n console.error('[gtm-kit/next] Error caught by GtmErrorBoundary:', error);\n console.error('[gtm-kit/next] Component stack:', errorInfo.componentStack);\n }\n\n if (onError) {\n try {\n onError(error, errorInfo);\n } catch {\n // Ignore callback errors\n }\n }\n }\n\n reset = (): void => {\n this.setState({ hasError: false, error: null });\n };\n\n render(): ReactNode {\n const { hasError, error } = this.state;\n const { children, fallback } = this.props;\n\n if (hasError && error) {\n if (fallback === undefined) {\n // Default: render children without GTM (silent fallback)\n return children;\n }\n\n if (typeof fallback === 'function') {\n return fallback(error, this.reset);\n }\n\n return fallback;\n }\n\n return children;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwiedeman/gtm-kit-next",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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.2.0"
52
+ "@jwiedeman/gtm-kit": "^1.2.2"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "next": "^13.4.0 || ^14.0.0 || ^15.0.0",