@reykjavik/webtools 0.1.6 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.1.7 – 0.1.8
8
+
9
+ _2023-04-19_
10
+
11
+ - `@reykjavik/webtools/next/SiteImprove`:
12
+ - feat: Always push events to `window._sz`, even in development mode
13
+ - feat: Auto-track outbound link clicks, also for late-injected elements.
14
+ - feat: Add `pingSiteImproveOutbound` helper
15
+ - fix: Strip pageview ref URLs on history back/forward traversal
16
+ - `@reykjavik/webtools/next/http`
17
+ - fix: Make `ErrorProps` accept `HTTP_ERROR_ALL`
18
+
7
19
  ## 0.1.0 – 0.1.6
8
20
 
9
21
  _2023-03-24_
package/README.md CHANGED
@@ -29,7 +29,7 @@ yarn add @reykjavik/webtools
29
29
  - [`@reykjavik/webtools/next/SiteImprove`](#reykjavikwebtoolsnextsiteimprove)
30
30
  - [`SiteImprove` component](#siteimprove-component)
31
31
  - [`pingSiteImprove` helper](#pingsiteimprove-helper)
32
- - [Changelog](#changelog)
32
+ - [`pingSiteImproveOutbound` helper](#pingsiteimproveoutbound-helper)
33
33
 
34
34
  <!-- prettier-ignore-start -->
35
35
 
@@ -399,6 +399,8 @@ applications and perform custom event tracking.
399
399
  A component for loading a SiteImprove analytics script and set up page-view
400
400
  tracking across Next.js routes.
401
401
 
402
+ It also automatically logs all out-öbound link clicks.
403
+
402
404
  ```js
403
405
  import { SiteImprove } from '@reykjavik/webtools/next/SiteImprove';
404
406
 
@@ -446,7 +448,26 @@ const handleSubmit = () => {
446
448
  };
447
449
  ```
448
450
 
449
- In dev mode it only logs tracking events to the console.
451
+ ### `pingSiteImproveOutbound` helper
452
+
453
+ **Syntax:** `pingSiteImproveOutbound(ourl: string): void`
454
+
455
+ A small helper for reporting to SiteImrove when the user is programmatically
456
+ being sent to a different URL/resource.
457
+
458
+ ```js
459
+ import { pingSiteImproveOutbound } from '@reykjavik/webtools/next/SiteImprove';
460
+
461
+ const handleSubmit = () => {
462
+ // perform submit action...
463
+ if (success) {
464
+ const fileUrl ='/download/report.pdf'
465
+ pingSiteImproveOutbound(fileUrl);
466
+ document.location.href = fileUrl
467
+ }
468
+ };
469
+
470
+
450
471
 
451
472
  ---
452
473
 
@@ -454,3 +475,4 @@ In dev mode it only logs tracking events to the console.
454
475
 
455
476
  See
456
477
  [CHANGELOG.md](https://github.com/reykjavikcity/webtools/blob/main/CHANGELOG.md)
478
+ ```
@@ -9,21 +9,34 @@ declare global {
9
9
  * just-in-time.
10
10
  */
11
11
  _jit_defined_?: true;
12
+ core?: {
13
+ data: Array<SiteImproveEvent>;
14
+ };
12
15
  };
13
16
  }
14
17
  }
15
- type SiteImproveEvent = SiteImprovePageView | SiteImproveCustomEvent;
18
+ type SiteImproveEvent = SiteImprovePageView | SiteImproveRequest | SiteImproveCustomEvent;
16
19
  type SiteImprovePageView = [
17
20
  type: 'trackdynamic',
18
21
  data: {
19
22
  /** New page URL */
20
23
  url: string;
21
24
  /** The previous (referer) URL */
22
- ref: string;
25
+ ref?: string;
23
26
  /** New page title */
24
27
  title?: string;
25
28
  }
26
29
  ];
30
+ type SiteImproveRequest = [
31
+ type: 'request',
32
+ data: {
33
+ /** Outbound URL */
34
+ ourl: string;
35
+ /** The current page URL */
36
+ ref: string;
37
+ autoonclick?: 1;
38
+ }
39
+ ];
27
40
  type SiteImproveCustomEvent = [
28
41
  type: 'event',
29
42
  category: string,
@@ -78,4 +91,11 @@ export declare const SiteImprove: (props: SiteImproveProps) => JSX.Element | nul
78
91
  * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimprove-helper
79
92
  */
80
93
  export declare const pingSiteImprove: (category: string, action: string, label?: string) => void;
94
+ /**
95
+ * A small helper for reporting to SiteImrove when the user is programmatically
96
+ * being sent to a different URL/resource.
97
+ *
98
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimproveoutbound-helper
99
+ */
100
+ export declare const pingSiteImproveOutbound: (ourl: string) => void;
81
101
  export {};
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
- import { Router } from 'next/router';
3
- import Script from 'next/script';
2
+ import { Router } from 'next/router.js';
3
+ import Script from 'next/script.js';
4
4
  import { useCookieHubConsent } from '../CookieHubConsent.js';
5
5
  // END: Mock typing of SiteImprove's event tracking API
6
6
  // --------------------------------------------------------------------------
@@ -14,12 +14,10 @@ const _emitEvent = typeof window === 'undefined'
14
14
  _sz = window._sz = [];
15
15
  _sz._jit_defined_ = true;
16
16
  }
17
+ _sz.push(event);
17
18
  if (process.env.NODE_ENV === 'development') {
18
19
  console.info('SiteImprove:', event);
19
20
  }
20
- else {
21
- _sz.push(event);
22
- }
23
21
  };
24
22
  /*
25
23
  SiteImprove's "trackdynamic" (page view) event requires both the new URL
@@ -40,11 +38,44 @@ const sendRoutingEvent = (url) => _emitEvent([
40
38
  'trackdynamic',
41
39
  {
42
40
  url,
43
- ref: refUrl,
41
+ // On `history.back()`/`history.forward()` the URL change happens before
42
+ // `routeChangeStart`, so `refUrl` and `url` become the same.
43
+ // in that case we suppress the `ref`
44
+ ref: refUrl !== url ? refUrl : undefined,
44
45
  title: document.title,
45
46
  },
46
47
  ]);
47
48
  // ---------------------------------------------------------------------------
49
+ const logOutboundLinks = () => {
50
+ const captureLinkClicks = (e) => {
51
+ const link = e.target.closest('a[href]');
52
+ if (!link || link.$$bound) {
53
+ return;
54
+ }
55
+ link.$$bound = true;
56
+ // Waiting for the bubble phase allows other click handlers to preventDefault()
57
+ link.addEventListener('click', (e) => {
58
+ var _a, _b;
59
+ if (e.defaultPrevented) {
60
+ return;
61
+ }
62
+ // Skip logging outbound request if SiteImprove has already done so.
63
+ // BTW, SiteImprove binds its autoonclick handlers on "mousedown"
64
+ // so they're guaranteed to have run before our "click" listener.
65
+ const events = (_b = (_a = window._sz) === null || _a === void 0 ? void 0 : _a.core) === null || _b === void 0 ? void 0 : _b.data;
66
+ const [type, data] = (events && events[events.length - 1]) || [];
67
+ if (type === 'request' && data.autoonclick && data.ourl === link.href) {
68
+ return;
69
+ }
70
+ pingSiteImproveOutbound(link.href);
71
+ });
72
+ };
73
+ const { body } = document;
74
+ // bind 'click' listener to the capture phase
75
+ body.addEventListener('click', captureLinkClicks, true);
76
+ return () => body.removeEventListener('click', captureLinkClicks, true);
77
+ };
78
+ // ---------------------------------------------------------------------------
48
79
  const idToken = '[ACCOUNT_ID]';
49
80
  const scriptUrlTemplate = `https://siteimproveanalytics.com/js/siteanalyze_${idToken}.js`;
50
81
  /**
@@ -70,9 +101,11 @@ export const SiteImprove = (props) => {
70
101
  const routerEvents = Router.events;
71
102
  routerEvents.on('routeChangeStart', captureRefUrl);
72
103
  routerEvents.on('routeChangeComplete', sendRoutingEvent);
104
+ const stopLoggingOutboundLinks = logOutboundLinks();
73
105
  return () => {
74
106
  routerEvents.off('routeChangeStart', captureRefUrl);
75
107
  routerEvents.off('routeChangeComplete', sendRoutingEvent);
108
+ stopLoggingOutboundLinks();
76
109
  };
77
110
  }
78
111
  },
@@ -95,7 +128,21 @@ export const SiteImprove = (props) => {
95
128
  export const pingSiteImprove = (category, action, label) => {
96
129
  if (process.env.NODE_ENV === 'development' &&
97
130
  (!window._sz || window._sz._jit_defined_)) {
98
- console.warn('`pingSiteImprove` Was called before SiteImprove script was loaded.');
131
+ console.warn('`pingSiteImprove` was called before SiteImprove script was loaded.');
99
132
  }
100
133
  _emitEvent(['event', category, action, label]);
101
134
  };
135
+ // ---------------------------------------------------------------------------
136
+ /**
137
+ * A small helper for reporting to SiteImrove when the user is programmatically
138
+ * being sent to a different URL/resource.
139
+ *
140
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimproveoutbound-helper
141
+ */
142
+ export const pingSiteImproveOutbound = (ourl) => {
143
+ if (process.env.NODE_ENV === 'development' &&
144
+ (!window._sz || window._sz._jit_defined_)) {
145
+ console.warn('`pingSiteImproveOutbound` was called before SiteImprove script was loaded.');
146
+ }
147
+ _emitEvent(['request', { ourl, ref: document.location.href }]);
148
+ };
@@ -2,21 +2,20 @@
2
2
  import React, { FunctionComponent } from 'react';
3
3
  import { Cleanup } from '@reykjavik/hanna-utils';
4
4
  import { ServerResponse } from 'http';
5
- import type { AppType } from 'next/app';
6
- import type { HTTP_418_ImATeapot, HTTP_ERROR, TTLConfig } from '../http.js';
7
- type HTTP_ERROR_all = HTTP_ERROR | typeof HTTP_418_ImATeapot;
5
+ import type { AppType } from 'next/app.js';
6
+ import type { HTTP_ERROR_ALL, TTLConfig } from '../http.js';
8
7
  export * from '../http.js';
9
8
  type NextContextLike = {
10
9
  res: ServerResponse;
11
10
  };
12
11
  export type ErrorProps = {
13
- statusCode: HTTP_ERROR_all;
12
+ statusCode: HTTP_ERROR_ALL;
14
13
  message?: string;
15
14
  };
16
15
  type ErrorizedPageProps<EP extends ErrorProps = ErrorProps> = {
17
16
  __error: ErrorProps;
18
17
  } & Omit<EP, keyof ErrorProps>;
19
- type ShowErrorPageFn<EP extends ErrorProps = ErrorProps> = (response: ServerResponse | NextContextLike, error: (ErrorProps extends EP ? HTTP_ERROR_all : never) | EP,
18
+ type ShowErrorPageFn<EP extends ErrorProps = ErrorProps> = (response: ServerResponse | NextContextLike, error: (ErrorProps extends EP ? HTTP_ERROR_ALL : never) | EP,
20
19
  /** Defaults to `"2s"`. Gets forwarded on to the `cacheControl` helper from `@reykjavik/webtools/http` */
21
20
  ttl?: TTLConfig) => {
22
21
  props: ErrorizedPageProps<EP>;
@@ -9,21 +9,34 @@ declare global {
9
9
  * just-in-time.
10
10
  */
11
11
  _jit_defined_?: true;
12
+ core?: {
13
+ data: Array<SiteImproveEvent>;
14
+ };
12
15
  };
13
16
  }
14
17
  }
15
- type SiteImproveEvent = SiteImprovePageView | SiteImproveCustomEvent;
18
+ type SiteImproveEvent = SiteImprovePageView | SiteImproveRequest | SiteImproveCustomEvent;
16
19
  type SiteImprovePageView = [
17
20
  type: 'trackdynamic',
18
21
  data: {
19
22
  /** New page URL */
20
23
  url: string;
21
24
  /** The previous (referer) URL */
22
- ref: string;
25
+ ref?: string;
23
26
  /** New page title */
24
27
  title?: string;
25
28
  }
26
29
  ];
30
+ type SiteImproveRequest = [
31
+ type: 'request',
32
+ data: {
33
+ /** Outbound URL */
34
+ ourl: string;
35
+ /** The current page URL */
36
+ ref: string;
37
+ autoonclick?: 1;
38
+ }
39
+ ];
27
40
  type SiteImproveCustomEvent = [
28
41
  type: 'event',
29
42
  category: string,
@@ -78,4 +91,11 @@ export declare const SiteImprove: (props: SiteImproveProps) => JSX.Element | nul
78
91
  * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimprove-helper
79
92
  */
80
93
  export declare const pingSiteImprove: (category: string, action: string, label?: string) => void;
94
+ /**
95
+ * A small helper for reporting to SiteImrove when the user is programmatically
96
+ * being sent to a different URL/resource.
97
+ *
98
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimproveoutbound-helper
99
+ */
100
+ export declare const pingSiteImproveOutbound: (ourl: string) => void;
81
101
  export {};
@@ -26,10 +26,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.pingSiteImprove = exports.SiteImprove = void 0;
29
+ exports.pingSiteImproveOutbound = exports.pingSiteImprove = exports.SiteImprove = void 0;
30
30
  const react_1 = __importStar(require("react"));
31
- const router_1 = require("next/router");
32
- const script_1 = __importDefault(require("next/script"));
31
+ const router_js_1 = require("next/router.js");
32
+ const script_js_1 = __importDefault(require("next/script.js"));
33
33
  const CookieHubConsent_js_1 = require("../CookieHubConsent.js");
34
34
  // END: Mock typing of SiteImprove's event tracking API
35
35
  // --------------------------------------------------------------------------
@@ -43,12 +43,10 @@ const _emitEvent = typeof window === 'undefined'
43
43
  _sz = window._sz = [];
44
44
  _sz._jit_defined_ = true;
45
45
  }
46
+ _sz.push(event);
46
47
  if (process.env.NODE_ENV === 'development') {
47
48
  console.info('SiteImprove:', event);
48
49
  }
49
- else {
50
- _sz.push(event);
51
- }
52
50
  };
53
51
  /*
54
52
  SiteImprove's "trackdynamic" (page view) event requires both the new URL
@@ -69,11 +67,44 @@ const sendRoutingEvent = (url) => _emitEvent([
69
67
  'trackdynamic',
70
68
  {
71
69
  url,
72
- ref: refUrl,
70
+ // On `history.back()`/`history.forward()` the URL change happens before
71
+ // `routeChangeStart`, so `refUrl` and `url` become the same.
72
+ // in that case we suppress the `ref`
73
+ ref: refUrl !== url ? refUrl : undefined,
73
74
  title: document.title,
74
75
  },
75
76
  ]);
76
77
  // ---------------------------------------------------------------------------
78
+ const logOutboundLinks = () => {
79
+ const captureLinkClicks = (e) => {
80
+ const link = e.target.closest('a[href]');
81
+ if (!link || link.$$bound) {
82
+ return;
83
+ }
84
+ link.$$bound = true;
85
+ // Waiting for the bubble phase allows other click handlers to preventDefault()
86
+ link.addEventListener('click', (e) => {
87
+ var _a, _b;
88
+ if (e.defaultPrevented) {
89
+ return;
90
+ }
91
+ // Skip logging outbound request if SiteImprove has already done so.
92
+ // BTW, SiteImprove binds its autoonclick handlers on "mousedown"
93
+ // so they're guaranteed to have run before our "click" listener.
94
+ const events = (_b = (_a = window._sz) === null || _a === void 0 ? void 0 : _a.core) === null || _b === void 0 ? void 0 : _b.data;
95
+ const [type, data] = (events && events[events.length - 1]) || [];
96
+ if (type === 'request' && data.autoonclick && data.ourl === link.href) {
97
+ return;
98
+ }
99
+ (0, exports.pingSiteImproveOutbound)(link.href);
100
+ });
101
+ };
102
+ const { body } = document;
103
+ // bind 'click' listener to the capture phase
104
+ body.addEventListener('click', captureLinkClicks, true);
105
+ return () => body.removeEventListener('click', captureLinkClicks, true);
106
+ };
107
+ // ---------------------------------------------------------------------------
77
108
  const idToken = '[ACCOUNT_ID]';
78
109
  const scriptUrlTemplate = `https://siteimproveanalytics.com/js/siteanalyze_${idToken}.js`;
79
110
  /**
@@ -96,12 +127,14 @@ const SiteImprove = (props) => {
96
127
  }, 300);
97
128
  }
98
129
  }
99
- const routerEvents = router_1.Router.events;
130
+ const routerEvents = router_js_1.Router.events;
100
131
  routerEvents.on('routeChangeStart', captureRefUrl);
101
132
  routerEvents.on('routeChangeComplete', sendRoutingEvent);
133
+ const stopLoggingOutboundLinks = logOutboundLinks();
102
134
  return () => {
103
135
  routerEvents.off('routeChangeStart', captureRefUrl);
104
136
  routerEvents.off('routeChangeComplete', sendRoutingEvent);
137
+ stopLoggingOutboundLinks();
105
138
  };
106
139
  }
107
140
  },
@@ -113,7 +146,7 @@ const SiteImprove = (props) => {
113
146
  const scriptUrl = props.scriptUrl != null
114
147
  ? props.scriptUrl
115
148
  : scriptUrlTemplate.replace(idToken, props.accountId);
116
- return (react_1.default.createElement(script_1.default, { type: "text/javascript", strategy: "afterInteractive", src: scriptUrl, onLoad: props.onLoad, onError: props.onError }));
149
+ return (react_1.default.createElement(script_js_1.default, { type: "text/javascript", strategy: "afterInteractive", src: scriptUrl, onLoad: props.onLoad, onError: props.onError }));
117
150
  };
118
151
  exports.SiteImprove = SiteImprove;
119
152
  // ---------------------------------------------------------------------------
@@ -125,8 +158,23 @@ exports.SiteImprove = SiteImprove;
125
158
  const pingSiteImprove = (category, action, label) => {
126
159
  if (process.env.NODE_ENV === 'development' &&
127
160
  (!window._sz || window._sz._jit_defined_)) {
128
- console.warn('`pingSiteImprove` Was called before SiteImprove script was loaded.');
161
+ console.warn('`pingSiteImprove` was called before SiteImprove script was loaded.');
129
162
  }
130
163
  _emitEvent(['event', category, action, label]);
131
164
  };
132
165
  exports.pingSiteImprove = pingSiteImprove;
166
+ // ---------------------------------------------------------------------------
167
+ /**
168
+ * A small helper for reporting to SiteImrove when the user is programmatically
169
+ * being sent to a different URL/resource.
170
+ *
171
+ * @see https://github.com/reykjavikcity/webtools/tree/v0.1##pingsiteimproveoutbound-helper
172
+ */
173
+ const pingSiteImproveOutbound = (ourl) => {
174
+ if (process.env.NODE_ENV === 'development' &&
175
+ (!window._sz || window._sz._jit_defined_)) {
176
+ console.warn('`pingSiteImproveOutbound` was called before SiteImprove script was loaded.');
177
+ }
178
+ _emitEvent(['request', { ourl, ref: document.location.href }]);
179
+ };
180
+ exports.pingSiteImproveOutbound = pingSiteImproveOutbound;
package/next/http.d.ts CHANGED
@@ -2,21 +2,20 @@
2
2
  import React, { FunctionComponent } from 'react';
3
3
  import { Cleanup } from '@reykjavik/hanna-utils';
4
4
  import { ServerResponse } from 'http';
5
- import type { AppType } from 'next/app';
6
- import type { HTTP_418_ImATeapot, HTTP_ERROR, TTLConfig } from '../http.js';
7
- type HTTP_ERROR_all = HTTP_ERROR | typeof HTTP_418_ImATeapot;
5
+ import type { AppType } from 'next/app.js';
6
+ import type { HTTP_ERROR_ALL, TTLConfig } from '../http.js';
8
7
  export * from '../http.js';
9
8
  type NextContextLike = {
10
9
  res: ServerResponse;
11
10
  };
12
11
  export type ErrorProps = {
13
- statusCode: HTTP_ERROR_all;
12
+ statusCode: HTTP_ERROR_ALL;
14
13
  message?: string;
15
14
  };
16
15
  type ErrorizedPageProps<EP extends ErrorProps = ErrorProps> = {
17
16
  __error: ErrorProps;
18
17
  } & Omit<EP, keyof ErrorProps>;
19
- type ShowErrorPageFn<EP extends ErrorProps = ErrorProps> = (response: ServerResponse | NextContextLike, error: (ErrorProps extends EP ? HTTP_ERROR_all : never) | EP,
18
+ type ShowErrorPageFn<EP extends ErrorProps = ErrorProps> = (response: ServerResponse | NextContextLike, error: (ErrorProps extends EP ? HTTP_ERROR_ALL : never) | EP,
20
19
  /** Defaults to `"2s"`. Gets forwarded on to the `cacheControl` helper from `@reykjavik/webtools/http` */
21
20
  ttl?: TTLConfig) => {
22
21
  props: ErrorizedPageProps<EP>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
5
5
  "main": "index.js",
6
6
  "repository": "ssh://git@github.com:reykjavikcity/webtools.git",