@guardian/ophan-tracker-js 4.0.2 → 5.1.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.
Files changed (215) hide show
  1. package/NPM-dist/adblock-detection.js +17 -11
  2. package/NPM-dist/adblock-detection.js.map +1 -0
  3. package/NPM-dist/attention.js +32 -19
  4. package/NPM-dist/attention.js.map +1 -0
  5. package/NPM-dist/campaign.js +6 -2
  6. package/NPM-dist/campaign.js.map +1 -0
  7. package/NPM-dist/click-path-capture.js +5 -2
  8. package/NPM-dist/click-path-capture.js.map +1 -0
  9. package/NPM-dist/components.js +1 -0
  10. package/NPM-dist/components.js.map +1 -0
  11. package/NPM-dist/contribution.js +1 -0
  12. package/NPM-dist/contribution.js.map +1 -0
  13. package/NPM-dist/core.js +40 -10
  14. package/NPM-dist/core.js.map +1 -0
  15. package/NPM-dist/embed.js +1 -0
  16. package/NPM-dist/embed.js.map +1 -0
  17. package/NPM-dist/fb-instant.js +1 -0
  18. package/NPM-dist/fb-instant.js.map +1 -0
  19. package/NPM-dist/heatmap.js +4 -0
  20. package/NPM-dist/heatmap.js.map +1 -0
  21. package/NPM-dist/help-centre.js +17 -0
  22. package/NPM-dist/help-centre.js.map +1 -0
  23. package/NPM-dist/holidays.js +1 -0
  24. package/NPM-dist/holidays.js.map +1 -0
  25. package/NPM-dist/http-status.js +1 -0
  26. package/NPM-dist/http-status.js.map +1 -0
  27. package/NPM-dist/iframe-tracking.js +16 -3
  28. package/NPM-dist/iframe-tracking.js.map +1 -0
  29. package/NPM-dist/index.js +291 -0
  30. package/NPM-dist/index.js.map +1 -0
  31. package/NPM-dist/interactive.js +1 -0
  32. package/NPM-dist/interactive.js.map +1 -0
  33. package/NPM-dist/jobs-courses.js +1 -0
  34. package/NPM-dist/jobs-courses.js.map +1 -0
  35. package/NPM-dist/jobs.js +1 -0
  36. package/NPM-dist/jobs.js.map +1 -0
  37. package/NPM-dist/membership.js +1 -0
  38. package/NPM-dist/membership.js.map +1 -0
  39. package/NPM-dist/ng.js +10 -33
  40. package/NPM-dist/ng.js.map +1 -0
  41. package/NPM-dist/perf.js +3 -2
  42. package/NPM-dist/perf.js.map +1 -0
  43. package/NPM-dist/privatebrowsing.js +1 -0
  44. package/NPM-dist/privatebrowsing.js.map +1 -0
  45. package/NPM-dist/r2.js +8 -4
  46. package/NPM-dist/r2.js.map +1 -0
  47. package/NPM-dist/smart-news.js +10 -3
  48. package/NPM-dist/smart-news.js.map +1 -0
  49. package/NPM-dist/tests/attention.test.js +1 -0
  50. package/NPM-dist/tests/attention.test.js.map +1 -0
  51. package/NPM-dist/tests/click-path-capture.test.js +1 -0
  52. package/NPM-dist/tests/click-path-capture.test.js.map +1 -0
  53. package/NPM-dist/tests/components.test.js +1 -0
  54. package/NPM-dist/tests/components.test.js.map +1 -0
  55. package/NPM-dist/tests/core.test.js +1 -0
  56. package/NPM-dist/tests/core.test.js.map +1 -0
  57. package/NPM-dist/tests/heatmap.test.js +1 -0
  58. package/NPM-dist/tests/heatmap.test.js.map +1 -0
  59. package/NPM-dist/tests/index.test.js +18 -0
  60. package/NPM-dist/tests/index.test.js.map +1 -0
  61. package/NPM-dist/tests/transmit.test.js +1 -0
  62. package/NPM-dist/tests/transmit.test.js.map +1 -0
  63. package/NPM-dist/transmit.js +23 -5
  64. package/NPM-dist/transmit.js.map +1 -0
  65. package/NPM-dist/types/abtest.js +1 -0
  66. package/NPM-dist/types/abtest.js.map +1 -0
  67. package/NPM-dist/types/adblock-detection.d.ts +1 -0
  68. package/NPM-dist/types/adblock-detection.d.ts.map +1 -0
  69. package/NPM-dist/types/attention.d.ts +1 -0
  70. package/NPM-dist/types/attention.d.ts.map +1 -0
  71. package/NPM-dist/types/campaign.d.ts +1 -0
  72. package/NPM-dist/types/campaign.d.ts.map +1 -0
  73. package/NPM-dist/types/click-path-capture.d.ts +1 -0
  74. package/NPM-dist/types/click-path-capture.d.ts.map +1 -0
  75. package/NPM-dist/types/component-event.js +1 -0
  76. package/NPM-dist/types/component-event.js.map +1 -0
  77. package/NPM-dist/types/component-type.js +1 -0
  78. package/NPM-dist/types/component-type.js.map +1 -0
  79. package/NPM-dist/types/components.d.ts +1 -0
  80. package/NPM-dist/types/components.d.ts.map +1 -0
  81. package/NPM-dist/types/consent.js +1 -0
  82. package/NPM-dist/types/consent.js.map +1 -0
  83. package/NPM-dist/types/contribution.d.ts +2 -1
  84. package/NPM-dist/types/contribution.d.ts.map +1 -0
  85. package/NPM-dist/types/core.d.ts +1 -0
  86. package/NPM-dist/types/core.d.ts.map +1 -0
  87. package/NPM-dist/types/embed.d.ts +2 -1
  88. package/NPM-dist/types/embed.d.ts.map +1 -0
  89. package/NPM-dist/types/event.js +1 -0
  90. package/NPM-dist/types/event.js.map +1 -0
  91. package/NPM-dist/types/fb-instant.d.ts +1 -0
  92. package/NPM-dist/types/fb-instant.d.ts.map +1 -0
  93. package/NPM-dist/types/heatmap.d.ts +1 -0
  94. package/NPM-dist/types/heatmap.d.ts.map +1 -0
  95. package/NPM-dist/types/help-centre.d.ts +2 -0
  96. package/NPM-dist/types/help-centre.d.ts.map +1 -0
  97. package/NPM-dist/types/holidays.d.ts +2 -1
  98. package/NPM-dist/types/holidays.d.ts.map +1 -0
  99. package/NPM-dist/types/http-status.d.ts +1 -0
  100. package/NPM-dist/types/http-status.d.ts.map +1 -0
  101. package/NPM-dist/types/iframe-tracking.d.ts +1 -0
  102. package/NPM-dist/types/iframe-tracking.d.ts.map +1 -0
  103. package/NPM-dist/types/index.d.ts +199 -0
  104. package/NPM-dist/types/index.d.ts.map +1 -0
  105. package/NPM-dist/types/inpageclick.js +1 -0
  106. package/NPM-dist/types/inpageclick.js.map +1 -0
  107. package/NPM-dist/types/interaction.js +1 -0
  108. package/NPM-dist/types/interaction.js.map +1 -0
  109. package/NPM-dist/types/interactive.d.ts +1 -0
  110. package/NPM-dist/types/interactive.d.ts.map +1 -0
  111. package/NPM-dist/types/jobs-courses.d.ts +2 -1
  112. package/NPM-dist/types/jobs-courses.d.ts.map +1 -0
  113. package/NPM-dist/types/jobs.d.ts +2 -1
  114. package/NPM-dist/types/jobs.d.ts.map +1 -0
  115. package/NPM-dist/types/link.js +1 -0
  116. package/NPM-dist/types/link.js.map +1 -0
  117. package/NPM-dist/types/media.js +1 -0
  118. package/NPM-dist/types/media.js.map +1 -0
  119. package/NPM-dist/types/membership.d.ts +2 -1
  120. package/NPM-dist/types/membership.d.ts.map +1 -0
  121. package/NPM-dist/types/ng.d.ts +2 -13
  122. package/NPM-dist/types/ng.d.ts.map +1 -0
  123. package/NPM-dist/types/perf.d.ts +2 -1
  124. package/NPM-dist/types/perf.d.ts.map +1 -0
  125. package/NPM-dist/types/platform.js +1 -0
  126. package/NPM-dist/types/platform.js.map +1 -0
  127. package/NPM-dist/types/printproduct.js +1 -0
  128. package/NPM-dist/types/printproduct.js.map +1 -0
  129. package/NPM-dist/types/privatebrowsing.d.ts +1 -0
  130. package/NPM-dist/types/privatebrowsing.d.ts.map +1 -0
  131. package/NPM-dist/types/product.js +1 -0
  132. package/NPM-dist/types/product.js.map +1 -0
  133. package/NPM-dist/types/r2.d.ts +1 -0
  134. package/NPM-dist/types/r2.d.ts.map +1 -0
  135. package/NPM-dist/types/smart-news.d.ts +2 -1
  136. package/NPM-dist/types/smart-news.d.ts.map +1 -0
  137. package/NPM-dist/types/subscription.js +1 -0
  138. package/NPM-dist/types/subscription.js.map +1 -0
  139. package/NPM-dist/types/tests/attention.test.d.ts +1 -0
  140. package/NPM-dist/types/tests/attention.test.d.ts.map +1 -0
  141. package/NPM-dist/types/tests/click-path-capture.test.d.ts +1 -0
  142. package/NPM-dist/types/tests/click-path-capture.test.d.ts.map +1 -0
  143. package/NPM-dist/types/tests/components.test.d.ts +1 -0
  144. package/NPM-dist/types/tests/components.test.d.ts.map +1 -0
  145. package/NPM-dist/types/tests/core.test.d.ts +1 -0
  146. package/NPM-dist/types/tests/core.test.d.ts.map +1 -0
  147. package/NPM-dist/types/tests/heatmap.test.d.ts +1 -0
  148. package/NPM-dist/types/tests/heatmap.test.d.ts.map +1 -0
  149. package/NPM-dist/types/tests/index.test.d.ts +2 -0
  150. package/NPM-dist/types/tests/index.test.d.ts.map +1 -0
  151. package/NPM-dist/types/tests/transmit.test.d.ts +1 -0
  152. package/NPM-dist/types/tests/transmit.test.d.ts.map +1 -0
  153. package/NPM-dist/types/transmit.d.ts +1 -0
  154. package/NPM-dist/types/transmit.d.ts.map +1 -0
  155. package/NPM-dist/types/types/abtest.d.ts +1 -0
  156. package/NPM-dist/types/types/abtest.d.ts.map +1 -0
  157. package/NPM-dist/types/types/component-event.d.ts +15 -0
  158. package/NPM-dist/types/types/component-event.d.ts.map +1 -0
  159. package/NPM-dist/types/types/component-type.d.ts +7 -0
  160. package/NPM-dist/types/types/component-type.d.ts.map +1 -0
  161. package/NPM-dist/types/types/consent.d.ts +1 -0
  162. package/NPM-dist/types/types/consent.d.ts.map +1 -0
  163. package/NPM-dist/types/types/event.d.ts +11 -0
  164. package/NPM-dist/types/types/event.d.ts.map +1 -0
  165. package/NPM-dist/types/types/inpageclick.d.ts +1 -0
  166. package/NPM-dist/types/types/inpageclick.d.ts.map +1 -0
  167. package/NPM-dist/types/types/interaction.d.ts +1 -0
  168. package/NPM-dist/types/types/interaction.d.ts.map +1 -0
  169. package/NPM-dist/types/types/link.d.ts +1 -0
  170. package/NPM-dist/types/types/link.d.ts.map +1 -0
  171. package/NPM-dist/types/types/media.d.ts +1 -0
  172. package/NPM-dist/types/types/media.d.ts.map +1 -0
  173. package/NPM-dist/types/types/platform.d.ts +1 -0
  174. package/NPM-dist/types/types/platform.d.ts.map +1 -0
  175. package/NPM-dist/types/types/printproduct.d.ts +1 -0
  176. package/NPM-dist/types/types/printproduct.d.ts.map +1 -0
  177. package/NPM-dist/types/types/product.d.ts +7 -0
  178. package/NPM-dist/types/types/product.d.ts.map +1 -0
  179. package/NPM-dist/types/types/subscription.d.ts +1 -0
  180. package/NPM-dist/types/types/subscription.d.ts.map +1 -0
  181. package/NPM-dist/types/vendor/adBlockDetectionLib.d.ts +3 -1
  182. package/NPM-dist/types/vendor/adBlockDetectionLib.d.ts.map +1 -0
  183. package/NPM-dist/types/visibility.d.ts +2 -1
  184. package/NPM-dist/types/visibility.d.ts.map +1 -0
  185. package/NPM-dist/types/witness.d.ts +2 -1
  186. package/NPM-dist/types/witness.d.ts.map +1 -0
  187. package/NPM-dist/vendor/adBlockDetectionLib.js +19 -6
  188. package/NPM-dist/vendor/adBlockDetectionLib.js.map +1 -0
  189. package/NPM-dist/visibility.js +14 -5
  190. package/NPM-dist/visibility.js.map +1 -0
  191. package/NPM-dist/witness.js +1 -0
  192. package/NPM-dist/witness.js.map +1 -0
  193. package/package.json +12 -26
  194. package/readme.md +132 -53
  195. package/src/help-centre.ts +23 -0
  196. package/src/index.ts +365 -0
  197. package/src/ng.ts +33 -0
  198. package/src/transmit.ts +176 -0
  199. package/src/types/abtest.ts +45 -0
  200. package/src/types/component-event.ts +121 -0
  201. package/src/types/component-type.ts +70 -0
  202. package/src/types/consent.ts +49 -0
  203. package/src/types/event.ts +116 -0
  204. package/src/types/inpageclick.ts +13 -0
  205. package/src/types/interaction.ts +19 -0
  206. package/src/types/link.ts +14 -0
  207. package/src/types/media.ts +184 -0
  208. package/src/types/platform.ts +32 -0
  209. package/src/types/printproduct.ts +53 -0
  210. package/src/types/product.ts +72 -0
  211. package/src/types/subscription.ts +70 -0
  212. package/NPM-dist/manage-my-account.js +0 -46
  213. package/NPM-dist/support.js +0 -45
  214. package/NPM-dist/types/manage-my-account.d.ts +0 -28
  215. package/NPM-dist/types/support.d.ts +0 -28
package/readme.md CHANGED
@@ -10,76 +10,148 @@ $ npm install @guardian/ophan-tracker-js
10
10
 
11
11
  ## Usage
12
12
 
13
- Tracker JS contains multiple entry point files tailored to specific platforms. These include:
13
+ Tracker JS is available as both an NPM package and a CDN distribution.
14
14
 
15
- - `ng.js` (short for next-gen) for the theguardian.com
16
- - `manage-my-account.js` for manage.theguardian.com
17
- - `support.js` for support.theguardian.com
18
- - `membership.js` for membership-related sections
15
+ ### NPM Package
19
16
 
20
- **Default Behaviour**
17
+ The NPM package uses a unified entry point that supports multiple platforms. Import it eagerly and call `init()` once for your platform before calling any other tracker method. The platform defaults to `ng` when omitted or unrecognised at runtime.
21
18
 
22
- The [default entry point](https://github.com/guardian/ophan/blob/main/tracker-js/package.json#L12) for the library is `ng.js`. With this entry point, the library automatically initializes itself on the global window object and provides a suite of functionalities to monitor user interactions, visibility, and more.
23
- The library will send an initial [page view event](/tracker-js/src/types/event.ts#L15) on page load. You are then in control of sending other events to Ophan.
19
+ **NPM Supported Platforms**
20
+
21
+ - `ng` (short for next-gen) for theguardian.com
22
+ - `manage-my-account` for manage.theguardian.com
23
+ - `support` for support.theguardian.com
24
+
25
+ **Default behaviour**
26
+
27
+ The default entry point for the library is `@guardian/ophan-tracker-js`, which resolves to `NPM-dist/index.js`. Calling `init()` starts the tracker for the chosen platform and sends the initial page-view event. Use `record()` for additional events. Use `sendInitialEvent()` only when you intentionally need a new page-view event, for example after a single-page app route change.
28
+
29
+ If a tracker method is called before `init()` in a browser context, the method warns and does not send the event. In server-side rendering or other non-browser contexts, tracker methods return without touching browser globals.
30
+
31
+ ### CDN Distribution
32
+
33
+ The CDN version provides individual entry points for specific use cases, available at `https://j.ophan.co.uk/`. These entry points initialise themselves for their historical platform and are kept for existing CDN consumers:
34
+
35
+ - `contribution.js` - Contribution tracking
36
+ - `embed.js` - For embedded content
37
+ - `fb-instant.js` - For Facebook Instant Articles
38
+ - `holidays.js` - Guardian Holidays
39
+ - `http-status.js` - HTTP status page tracking
40
+ - `interactive.js` - For Guardian interactives
41
+ - `jobs.js` - For Guardian Jobs site
42
+ - `jobs-courses.js` - For Guardian Jobs courses site
43
+ - `membership.js` - For Guardian membership (old site)
44
+ - `ng.js` - For theguardian.com
45
+ - `r2.js` - Legacy R2 platform tracking
46
+ - `smart-news.js` - SmartNews platform tracking
47
+ - `witness.js` - GuardianWitness tracking
48
+
49
+ #### Plain `<script src>` (IIFE) build
50
+
51
+ The AMD (`ophan/*.js`) and SystemJS (`*.js`) CDN builds both require a module
52
+ loader on the page. For consumers that have **no module loader and no bundler**
53
+ — e.g. the Salesforce-hosted Help Centre — there is a self-executing IIFE build:
54
+
55
+ - `help-centre.iife.js` - Self-initialises platform) and exposes the full
56
+ tracker API at `window.guardian.ophan`.
57
+
58
+ ```html
59
+ <!-- Sends the first page view on load; no page-side boot code needed. -->
60
+ <script src="https://j.ophan.co.uk/help-centre.iife.js"></script>
61
+ <script>
62
+ // Single-page-app route change: sends a fresh page view.
63
+ window.guardian.ophan.sendInitialEvent();
64
+ // Component/interaction events:
65
+ window.guardian.ophan.record({ componentEvent: { /* … */ } });
66
+ </script>
67
+ ```
24
68
 
25
69
  You can see example usage of the library on theguardian.com [here](https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/src/client/ophan/ophan.ts)
26
70
 
27
- 1. **Import and start the library:**
71
+ 1. **Import and initialise the library:**
28
72
 
29
- The library should be imported and executed eagerly (non-lazily) to ensure proper initialization. The exact syntax may vary depending on your build tool or environment.
73
+ The library should be imported and initialised eagerly (non-lazily) to ensure proper initialisation. The exact syntax may vary depending on your build tool or environment.
30
74
 
31
75
  ```javascript
32
- import ophan from '@guardian/ophan-tracker-js';
76
+ import { init, record } from '@guardian/ophan-tracker-js';
77
+
78
+ // Initialise for your platform. This sends the initial page-view event.
79
+ init('ng'); // or 'manage-my-account' or 'support'
33
80
  ```
34
81
 
35
- 2. **Send Tracking Events with `ophan.record()`:**
82
+ 2. **Send Tracking Events with `record()`:**
36
83
 
37
84
  ```javascript
38
- ophan.record({
39
- // Your tracking data in JSON format
85
+ record({
86
+ edition: 'UK',
40
87
  });
41
88
  ```
42
89
 
43
- ### Usage with Typescript
90
+ ### Usage with TypeScript
91
+
92
+ The package exports TypeScript declarations, declaration maps, and source files. Editors should let you click through from `record()` to `EventPayload`, `ComponentEvent`, `Product`, and the related union types in [`src/types/event.ts`](/tracker-js/src/types/event.ts) and the neighbouring type files.
44
93
 
45
- Refer to the type definitions within the library for the structure of valid tracking events. The `ophan.record()` function accepts a type of `EventPayload` found [here](/tracker-js/src/types/event.ts#L90).
94
+ When you pass an object literal to `record()`, TypeScript will reject unknown top-level fields and invalid nested values such as unsupported component actions, component types, products, or editions. If you need to add a new event type or property, update the tracker-js types and the downstream Ophan model deliberately.
46
95
 
47
96
  #### Example Usage
48
97
 
49
98
  ```typescript
50
- import ophan, { Product, ComponentEvent } from '@guardian/ophan-tracker-js';
51
- import type { AbTest } from '@guardian/ophan-tracker-js';
52
-
53
- const exampleProduct: TProduct = "APP_PREMIUM_TIER";
99
+ import {
100
+ getViewId,
101
+ init,
102
+ record,
103
+ sendInitialEvent,
104
+ } from '@guardian/ophan-tracker-js';
105
+ import type {
106
+ EventPayload,
107
+ Product,
108
+ ComponentEvent,
109
+ AbTest,
110
+ } from '@guardian/ophan-tracker-js';
111
+
112
+ // Initialise the library for your platform. This sends the initial page-view event.
113
+ init('ng'); // or 'manage-my-account' or 'support'
114
+
115
+ const exampleProduct: Product = 'APP_PREMIUM_TIER';
54
116
  const exampleAbTest: AbTest = {
55
- name: "myAbTest",
56
- variant: "variant",
57
- campaignCodes: ["code1", "code2"]
117
+ name: 'myAbTest',
118
+ variant: 'variant',
119
+ campaignCodes: ['code1', 'code2'],
58
120
  };
59
121
 
60
122
  const componentV2 = {
61
- componentType: "ACQUISITIONS_BUTTON",
62
- id: "component-id",
63
- products: new Set([exampleProduct]),
64
- campaignCode: "campaign-code",
65
- labels: new Set(["label1", "label2"]),
123
+ componentType: 'ACQUISITIONS_BUTTON',
124
+ id: 'component-id',
125
+ products: [exampleProduct],
126
+ campaignCode: 'campaign-code',
127
+ labels: ['label1', 'label2'],
66
128
  };
67
129
 
68
130
  const componentEvent: ComponentEvent = {
69
- component: componentV2,
70
- action: "CLICK",
71
- value: "some-value",
72
- id: "event-id",
73
- abTest: exampleAbTest,
74
- targetingAbTest: exampleAbTest,
131
+ component: componentV2,
132
+ action: 'CLICK',
133
+ value: 'some-value',
134
+ id: 'event-id',
135
+ abTest: exampleAbTest,
136
+ targetingAbTest: exampleAbTest,
75
137
  };
76
138
 
77
- ophan.record({
78
- componentEvent: componentEvent
79
- });
139
+ const payload: EventPayload = {
140
+ componentEvent: componentEvent,
141
+ };
142
+
143
+ record(payload);
144
+
145
+ const currentViewId = getViewId();
146
+
147
+ // Use sendInitialEvent() only when intentionally creating a new page view,
148
+ // for example after a single-page app route change.
149
+ sendInitialEvent();
80
150
  ```
81
151
 
82
- If you would like to add a new event type or event property which does not exist, please [contact the Ophan team](Ophan.Dev@guardian.co.uk).
152
+ `getViewId()` and `getPageViewId()` return the current live page view ID. The default export also exposes `viewId` and `pageViewId` as getters for compatibility with object-style consumers.
153
+
154
+ If you would like to add a new event type or event property which does not exist, please [contact the Ophan team](mailto:Ophan.Dev@guardian.co.uk).
83
155
 
84
156
  ### Sending events straight to the backend
85
157
 
@@ -91,9 +163,9 @@ The backend of tracker exposes two endpoints, `img/1` which takes in page view e
91
163
 
92
164
  The library is implemented using both Javascript and TypeScript, with the source code located in the `src` directory and its subdirectories. The project is set up to include both TypeScript (.ts) and JavaScript (.js) files, allowing for a mix of both languages in the library's implementation.
93
165
 
94
- Typescript interfaces and types are developed in the `types` folder.
166
+ Typescript interfaces and types are developed in the `src/types` folder.
95
167
 
96
- The TypeScript compiler is configured to generate compiled JavaScript files and corresponding declaration files (.d.ts) in the ./dist directory. The declaration files provide type information for the library's public API. The compiler also generates source map files for the declaration files.
168
+ The TypeScript compiler is configured to generate compiled JavaScript files and corresponding declaration files (`.d.ts`) in the `NPM-dist` directory. The declaration files provide type information for the library's public API, and declaration maps point editors back to the source files.
97
169
 
98
170
  ## Compile
99
171
 
@@ -101,20 +173,26 @@ The TypeScript compiler is configured to generate compiled JavaScript files and
101
173
  .../ophan/tracker-js $ npm run build-for-npm
102
174
  ```
103
175
 
176
+ ## Type checks
177
+
178
+ ```
179
+ .../ophan/tracker-js $ npm run test:types
180
+ ```
181
+
104
182
  #### Proxy tracker-js:
105
183
 
106
184
  1. Install dependencies
107
185
 
108
- ```
109
- $ brew install lighttpd
110
- .../ophan/tracker-js $ npm install
111
- ```
186
+ ```
187
+ $ brew install lighttpd
188
+ .../ophan/tracker-js $ npm install
189
+ ```
112
190
 
113
191
  2. In another terminal window:
114
192
 
115
- ```
116
- .../ophan/tracker-js $ lighttpd -f lighttpd.conf -D
117
- ```
193
+ ```
194
+ .../ophan/tracker-js $ lighttpd -f lighttpd.conf -D
195
+ ```
118
196
 
119
197
  3. Install FoxyProxy (https://getfoxyproxy.org/downloads/) in your browser and create a rule that forces j.ophan.co.uk
120
198
  to localhost 8000. (For me FoxyProxy needed a few switches on and off before it decided to start talking to lighttpd.)
@@ -131,7 +209,7 @@ The TypeScript compiler is configured to generate compiled JavaScript files and
131
209
 
132
210
  ### Two formats for release
133
211
 
134
- When you make a change to Tracker JS, you must release on *both* these systems:
212
+ When you make a change to Tracker JS, you must release on _both_ these systems:
135
213
 
136
214
  #### NPM
137
215
 
@@ -139,9 +217,9 @@ We use `changesets` for automated publishing of the NPM package:
139
217
 
140
218
  1. Add a changeset, run the following command and follow the instructions
141
219
 
142
- ```
143
- .../ophan/tracker-js $ npx changeset
144
- ```
220
+ ```
221
+ .../ophan/tracker-js $ npx changeset
222
+ ```
145
223
 
146
224
  2. When you raise a PR, the changeset bot won’t correctly see that you have included a changeset – it only checks for the .changesets folder in the root of the repository, whereas we have it in a subdirectory. Don’t worry.
147
225
 
@@ -149,15 +227,16 @@ We use `changesets` for automated publishing of the NPM package:
149
227
 
150
228
  #### S3 Bucket
151
229
 
152
- * [https://j.ophan.co.uk/](https://j.ophan.co.uk/jobs.js) - CDN/S3, [deployed through RiffRaff](https://riffraff.gutools.co.uk/deployment/history?projectName=ophan%3A%3Aophan-tracker-js&page=1) and used by some Guardian sites like https://jobs.theguardian.com/. An advantage of this approach is that sites using it immediately get Tracker JS updates, without developer intervention.
230
+ - [https://j.ophan.co.uk/](https://j.ophan.co.uk/jobs.js) - CDN/S3, [deployed through RiffRaff](https://riffraff.gutools.co.uk/deployment/history?projectName=ophan%3A%3Aophan-tracker-js&page=1) and used by some Guardian sites like https://jobs.theguardian.com/. An advantage of this approach is that sites using it immediately get Tracker JS updates, without developer intervention.
153
231
 
154
232
  #### Rollup
155
233
 
156
234
  The generated files from Rollup (using the `build-for-cdn` script in `package.json`) are only used for publishing to the CDN to use SystemJS / AMD
157
235
 
158
236
  ## Post publish
237
+
159
238
  Post publish it would be good practice to inform the teams using Tracker-JS about the update.
160
239
 
161
240
  See the [google doc ](https://docs.google.com/document/d/1MndFG-DLDQAMHIi55T4kZkGq4ONcded1-MDd9QS_NZE/edit?tab=t.0) for a list of teams depending on tracker-js.
162
241
 
163
- If the change to tracker-js is small, we could raise a quick PR in the relevant repo and ask the team to review it.
242
+ If the change to tracker-js is small, we could raise a quick PR in the relevant repo and ask the team to review it.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Self-initialising IIFE entry for `<script src>` consumers with no module
3
+ * loader or bundler (e.g. the Salesforce-hosted Help Centre). On load it exposes
4
+ * the tracker at `window.guardian.ophan` and sends the first page view.
5
+ */
6
+ import ophan, { init, type Platform } from './index.js';
7
+
8
+ const HELP_CENTRE_PLATFORM: Platform = 'help-centre';
9
+
10
+ if (typeof window !== 'undefined') {
11
+ const windowWithGuardian = window as typeof window & {
12
+ guardian?: {
13
+ ophan?: typeof ophan;
14
+ };
15
+ };
16
+
17
+ // Don't clobber an existing tracker (e.g. script included twice).
18
+ if (!windowWithGuardian.guardian?.ophan) {
19
+ windowWithGuardian.guardian = windowWithGuardian.guardian ?? {};
20
+ windowWithGuardian.guardian.ophan = ophan;
21
+ init(HELP_CENTRE_PLATFORM);
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,365 @@
1
+ import core from './core.js';
2
+ import transmit from './transmit.js';
3
+ import attention from './attention.js';
4
+ import visibility from './visibility.js';
5
+ import privatebrowsing from './privatebrowsing.js';
6
+ import heatmap from './heatmap.js';
7
+
8
+ import './click-path-capture.js';
9
+ import './perf.js';
10
+ import './iframe-tracking.js';
11
+ import './campaign.js';
12
+
13
+ import { EventPayload } from './types/event.js';
14
+ import clickPathCapture from './click-path-capture.js';
15
+
16
+ /**
17
+ * Product surface that is initialising the tracker.
18
+ *
19
+ * The platform controls the historical Ophan serving-platform value and any
20
+ * platform-specific browser integrations started by `init()`.
21
+ */
22
+ export type Platform = 'ng' | 'manage-my-account' | 'support' | 'help-centre';
23
+
24
+ const isBrowser = (): boolean =>
25
+ typeof window !== 'undefined' && typeof document !== 'undefined';
26
+
27
+ let initialized = false;
28
+ let currentPlatform: Platform | null = null;
29
+
30
+ const warnIfUninitialized = (methodName: string): void => {
31
+ console.warn(
32
+ `Ophan tracker ${methodName}() called before init(); ignoring event.`,
33
+ );
34
+ };
35
+
36
+ const canUseTracker = (methodName: string): boolean => {
37
+ if (!isBrowser()) {
38
+ return false;
39
+ }
40
+
41
+ if (!initialized) {
42
+ warnIfUninitialized(methodName);
43
+ return false;
44
+ }
45
+
46
+ return true;
47
+ };
48
+
49
+ /**
50
+ * Initialise the Ophan tracker for a specific platform.
51
+ *
52
+ * Call this once, eagerly, in a browser context before using the other tracker
53
+ * methods. Initialisation sends the first page-view event.
54
+ *
55
+ * @param platform - The platform identifier ('ng' for next-gen Guardian sites, 'manage-my-account', or 'support'). Defaults to 'ng'.
56
+ *
57
+ * @example
58
+ * ```javascript
59
+ * import { init } from '@guardian/ophan-tracker-js';
60
+ * init('ng');
61
+ * ```
62
+ */
63
+ export const init = (platform: Platform = 'ng'): void => {
64
+ if (!isBrowser()) {
65
+ return;
66
+ }
67
+
68
+ const resolvedPlatform: Platform =
69
+ platform === 'manage-my-account' || platform === 'support' || platform === 'help-centre' ? platform : 'ng';
70
+
71
+ if (initialized) {
72
+ console.warn(
73
+ `Ophan tracker already initialized with platform: ${currentPlatform}`,
74
+ );
75
+ return;
76
+ }
77
+
78
+ currentPlatform = resolvedPlatform;
79
+ initialized = true;
80
+
81
+ if (resolvedPlatform === 'ng') {
82
+ core.init('next-gen');
83
+ privatebrowsing.init();
84
+ heatmap.init();
85
+
86
+ if ((window as any).addEventListener) {
87
+ attention.init(visibility);
88
+ }
89
+ } else if (resolvedPlatform === 'manage-my-account') {
90
+ core.init('manage-my-account');
91
+ if ((window as any).addEventListener) {
92
+ attention.init(visibility);
93
+ }
94
+ } else if (resolvedPlatform === 'support') {
95
+ core.init('support');
96
+ if ((window as any).addEventListener) {
97
+ attention.init(visibility);
98
+ }
99
+ } else if (resolvedPlatform === 'help-centre') {
100
+ core.init('help-centre');
101
+ if ((window as any).addEventListener) {
102
+ attention.init(visibility);
103
+ }
104
+ }
105
+ };
106
+
107
+ /**
108
+ * Send a new page-view event to Ophan.
109
+ *
110
+ * This generates a new view ID and tracks the current page.
111
+ * `init()` already sends the first page-view event, so call this only when
112
+ * intentionally creating another page view, such as after a single-page app
113
+ * route change.
114
+ *
115
+ * @param url - The page URL to track (defaults to current location)
116
+ * @param referrer - The referrer URL (defaults to document.referrer)
117
+ *
118
+ * @example
119
+ * ```javascript
120
+ * import { sendInitialEvent } from '@guardian/ophan-tracker-js';
121
+ * sendInitialEvent('https://www.theguardian.com/article', 'https://google.com');
122
+ * ```
123
+ */
124
+ export const sendInitialEvent = (
125
+ url: string = typeof window !== 'undefined' ? location.href : '',
126
+ referrer: string = typeof window !== 'undefined' &&
127
+ typeof document !== 'undefined'
128
+ ? document.referrer
129
+ : '',
130
+ ): void => {
131
+ if (!canUseTracker('sendInitialEvent')) {
132
+ return;
133
+ }
134
+
135
+ try {
136
+ transmit.bumpViewId();
137
+ core.sendInitialEvent(null, url, referrer);
138
+ } catch (error) {
139
+ console.log(error);
140
+ }
141
+ };
142
+
143
+ /**
144
+ * Record an event against the current page view.
145
+ *
146
+ * Use this for component interactions, purchases, subscriptions, media events,
147
+ * consent events, and other non-page-view payloads. `init()` must have run
148
+ * first so the event is associated with a valid page view and platform.
149
+ *
150
+ * @param event - The event data payload containing metrics and metadata
151
+ * @param callback - Optional callback function executed after the event is sent
152
+ *
153
+ * @example
154
+ * ```javascript
155
+ * import { record } from '@guardian/ophan-tracker-js';
156
+ *
157
+ * // Track a component interaction
158
+ * record({
159
+ * componentEvent: {
160
+ * component: {
161
+ * componentType: 'ACQUISITIONS_ENGAGEMENT_BANNER',
162
+ * id: 'end-of-article-banner',
163
+ * campaignCode: '2024-annual-campaign',
164
+ * labels: ['end-of-article']
165
+ * },
166
+ * action: 'CLICK',
167
+ * abTest: {
168
+ * name: 'BannerTest',
169
+ * variant: 'control'
170
+ * }
171
+ * }
172
+ * });
173
+ *
174
+ * // Track a rendered component list
175
+ * record({
176
+ * renderedComponents: ['nav', 'footer']
177
+ * });
178
+ * ```
179
+ */
180
+ export const record = (event: EventPayload, callback?: () => void): void => {
181
+ if (!isBrowser()) {
182
+ callback?.();
183
+ return;
184
+ }
185
+
186
+ if (!initialized) {
187
+ warnIfUninitialized('record');
188
+ callback?.();
189
+ return;
190
+ }
191
+
192
+ transmit.sendMore(event, callback);
193
+ };
194
+
195
+ /**
196
+ * Track component attention and visibility analytics.
197
+ *
198
+ * Monitors when components enter and leave the viewport.
199
+ *
200
+ * @param name - Unique identifier for the component
201
+ * @param el - The DOM element to track
202
+ * @param visibilityThreshold - Percentage of element that must be visible (0-1)
203
+ * @param isTrackingVideo - Whether this component contains video content
204
+ *
205
+ * @example
206
+ * ```javascript
207
+ * import { trackComponentAttention } from '@guardian/ophan-tracker-js';
208
+ *
209
+ * const banner = document.getElementById('promo-banner');
210
+ * trackComponentAttention('promo-banner', banner, 0.5);
211
+ * ```
212
+ */
213
+ export const trackComponentAttention = (
214
+ name: string,
215
+ el: HTMLElement,
216
+ visibilityThreshold: number,
217
+ isTrackingVideo?: boolean,
218
+ ): void => {
219
+ if (!canUseTracker('trackComponentAttention')) {
220
+ return;
221
+ }
222
+
223
+ attention.initComponent(name, el, visibilityThreshold, isTrackingVideo);
224
+ };
225
+
226
+ /**
227
+ * Set a custom event emitter for attention tracking.
228
+ *
229
+ * This is primarily for integrations that need to bridge the tracker's
230
+ * attention events into an existing event system.
231
+ *
232
+ * @param event - Event emitter object with emit/on methods
233
+ *
234
+ * @example
235
+ * ```javascript
236
+ * import { setEventEmitter } from '@guardian/ophan-tracker-js';
237
+ *
238
+ * const customEmitter = new EventEmitter();
239
+ * setEventEmitter(customEmitter);
240
+ * ```
241
+ */
242
+ export const setEventEmitter = (event: Object): void => {
243
+ attention.setEventEmitter(event);
244
+ };
245
+
246
+ /**
247
+ * Manually track a click event on an element with data-link-name or data-component attributes.
248
+ * This captures click path information and component context for analytics.
249
+ * Useful for elements that stop event propagation or need explicit tracking.
250
+ *
251
+ * @param element - The clicked DOM element (should have data-link-name or data-component attributes)
252
+ *
253
+ * @example
254
+ * ```javascript
255
+ * import { trackClickComponentEvent } from '@guardian/ophan-tracker-js';
256
+ *
257
+ * // Track a click on an element with tracking attributes
258
+ * const link = document.querySelector('a[data-link-name="subscribe-button"]');
259
+ * trackClickComponentEvent(link);
260
+ *
261
+ * // Or track a button inside a component
262
+ * const button = document.querySelector('[data-component="newsletter-signup"] button');
263
+ * trackClickComponentEvent(button);
264
+ * ```
265
+ */
266
+ export const trackClickComponentEvent = (element: Element): void => {
267
+ if (!canUseTracker('trackClickComponentEvent')) {
268
+ return;
269
+ }
270
+
271
+ clickPathCapture.trackClickComponentEvent(element);
272
+ };
273
+
274
+ /**
275
+ * Generate a new page view ID for the current browser session.
276
+ *
277
+ * Useful for single-page applications when navigating between pages.
278
+ *
279
+ * @example
280
+ * ```javascript
281
+ * import { bumpViewId } from '@guardian/ophan-tracker-js';
282
+ *
283
+ * // Call when navigating to a new page in SPA
284
+ * bumpViewId();
285
+ * ```
286
+ */
287
+ export const bumpViewId = (): void => {
288
+ if (!canUseTracker('bumpViewId')) {
289
+ return;
290
+ }
291
+
292
+ transmit.bumpViewId();
293
+ };
294
+
295
+ /**
296
+ * Return the current live Ophan page view ID.
297
+ *
298
+ * Prefer this function over destructuring `viewId` from the default export,
299
+ * because the value changes after `bumpViewId()` or `sendInitialEvent()`.
300
+ */
301
+ export const getViewId = (): string => transmit.viewId;
302
+
303
+ /**
304
+ * Alias for `getViewId()` kept for consumers that use page-view terminology.
305
+ */
306
+ export const getPageViewId = (): string => transmit.viewId;
307
+
308
+ /**
309
+ * Object-shaped tracker API exposed by the package default export.
310
+ *
311
+ * Named exports are preferred for new code, but the default export keeps the
312
+ * historical object-style API and exposes `viewId`/`pageViewId` as live getters.
313
+ */
314
+ export interface OphanTracker {
315
+ init: (platform?: Platform) => void;
316
+ sendInitialEvent: (url?: string, referrer?: string) => void;
317
+ record: (event: EventPayload, callback?: () => void) => void;
318
+ trackComponentAttention: (
319
+ name: string,
320
+ el: HTMLElement,
321
+ visibilityThreshold: number,
322
+ isTrackingVideo?: boolean,
323
+ ) => void;
324
+ setEventEmitter: (event: Object) => void;
325
+ trackClickComponentEvent: (element: Element) => void;
326
+ bumpViewId: () => void;
327
+ getViewId: () => string;
328
+ getPageViewId: () => string;
329
+ viewId: string;
330
+ pageViewId: string;
331
+ }
332
+
333
+ const ophanTracker: OphanTracker = {
334
+ init,
335
+ sendInitialEvent,
336
+ record,
337
+ trackComponentAttention,
338
+ setEventEmitter,
339
+ trackClickComponentEvent,
340
+ bumpViewId,
341
+ getViewId,
342
+ getPageViewId,
343
+ get viewId() {
344
+ return getViewId();
345
+ },
346
+ get pageViewId() {
347
+ return getPageViewId();
348
+ },
349
+ };
350
+
351
+ export default ophanTracker;
352
+
353
+ // Type exports
354
+ export * from './types/abtest.js';
355
+ export * from './types/subscription.js';
356
+ export * from './types/interaction.js';
357
+ export * from './types/inpageclick.js';
358
+ export * from './types/printproduct.js';
359
+ export * from './types/product.js';
360
+ export * from './types/platform.js';
361
+ export * from './types/media.js';
362
+ export * from './types/consent.js';
363
+ export * from './types/component-type.js';
364
+ export * from './types/component-event.js';
365
+ export * from './types/event.js';