@guardian/ophan-tracker-js 4.0.2 → 5.0.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 (210) 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/holidays.js +1 -0
  22. package/NPM-dist/holidays.js.map +1 -0
  23. package/NPM-dist/http-status.js +1 -0
  24. package/NPM-dist/http-status.js.map +1 -0
  25. package/NPM-dist/iframe-tracking.js +16 -3
  26. package/NPM-dist/iframe-tracking.js.map +1 -0
  27. package/NPM-dist/index.js +285 -0
  28. package/NPM-dist/index.js.map +1 -0
  29. package/NPM-dist/interactive.js +1 -0
  30. package/NPM-dist/interactive.js.map +1 -0
  31. package/NPM-dist/jobs-courses.js +1 -0
  32. package/NPM-dist/jobs-courses.js.map +1 -0
  33. package/NPM-dist/jobs.js +1 -0
  34. package/NPM-dist/jobs.js.map +1 -0
  35. package/NPM-dist/membership.js +1 -0
  36. package/NPM-dist/membership.js.map +1 -0
  37. package/NPM-dist/ng.js +10 -33
  38. package/NPM-dist/ng.js.map +1 -0
  39. package/NPM-dist/perf.js +3 -2
  40. package/NPM-dist/perf.js.map +1 -0
  41. package/NPM-dist/privatebrowsing.js +1 -0
  42. package/NPM-dist/privatebrowsing.js.map +1 -0
  43. package/NPM-dist/r2.js +8 -4
  44. package/NPM-dist/r2.js.map +1 -0
  45. package/NPM-dist/smart-news.js +10 -3
  46. package/NPM-dist/smart-news.js.map +1 -0
  47. package/NPM-dist/tests/attention.test.js +1 -0
  48. package/NPM-dist/tests/attention.test.js.map +1 -0
  49. package/NPM-dist/tests/click-path-capture.test.js +1 -0
  50. package/NPM-dist/tests/click-path-capture.test.js.map +1 -0
  51. package/NPM-dist/tests/components.test.js +1 -0
  52. package/NPM-dist/tests/components.test.js.map +1 -0
  53. package/NPM-dist/tests/core.test.js +1 -0
  54. package/NPM-dist/tests/core.test.js.map +1 -0
  55. package/NPM-dist/tests/heatmap.test.js +1 -0
  56. package/NPM-dist/tests/heatmap.test.js.map +1 -0
  57. package/NPM-dist/tests/index.test.js +18 -0
  58. package/NPM-dist/tests/index.test.js.map +1 -0
  59. package/NPM-dist/tests/transmit.test.js +1 -0
  60. package/NPM-dist/tests/transmit.test.js.map +1 -0
  61. package/NPM-dist/transmit.js +23 -5
  62. package/NPM-dist/transmit.js.map +1 -0
  63. package/NPM-dist/types/abtest.js +1 -0
  64. package/NPM-dist/types/abtest.js.map +1 -0
  65. package/NPM-dist/types/adblock-detection.d.ts +1 -0
  66. package/NPM-dist/types/adblock-detection.d.ts.map +1 -0
  67. package/NPM-dist/types/attention.d.ts +1 -0
  68. package/NPM-dist/types/attention.d.ts.map +1 -0
  69. package/NPM-dist/types/campaign.d.ts +1 -0
  70. package/NPM-dist/types/campaign.d.ts.map +1 -0
  71. package/NPM-dist/types/click-path-capture.d.ts +1 -0
  72. package/NPM-dist/types/click-path-capture.d.ts.map +1 -0
  73. package/NPM-dist/types/component-event.js +1 -0
  74. package/NPM-dist/types/component-event.js.map +1 -0
  75. package/NPM-dist/types/component-type.js +1 -0
  76. package/NPM-dist/types/component-type.js.map +1 -0
  77. package/NPM-dist/types/components.d.ts +1 -0
  78. package/NPM-dist/types/components.d.ts.map +1 -0
  79. package/NPM-dist/types/consent.js +1 -0
  80. package/NPM-dist/types/consent.js.map +1 -0
  81. package/NPM-dist/types/contribution.d.ts +2 -1
  82. package/NPM-dist/types/contribution.d.ts.map +1 -0
  83. package/NPM-dist/types/core.d.ts +1 -0
  84. package/NPM-dist/types/core.d.ts.map +1 -0
  85. package/NPM-dist/types/embed.d.ts +2 -1
  86. package/NPM-dist/types/embed.d.ts.map +1 -0
  87. package/NPM-dist/types/event.js +1 -0
  88. package/NPM-dist/types/event.js.map +1 -0
  89. package/NPM-dist/types/fb-instant.d.ts +1 -0
  90. package/NPM-dist/types/fb-instant.d.ts.map +1 -0
  91. package/NPM-dist/types/heatmap.d.ts +1 -0
  92. package/NPM-dist/types/heatmap.d.ts.map +1 -0
  93. package/NPM-dist/types/holidays.d.ts +2 -1
  94. package/NPM-dist/types/holidays.d.ts.map +1 -0
  95. package/NPM-dist/types/http-status.d.ts +1 -0
  96. package/NPM-dist/types/http-status.d.ts.map +1 -0
  97. package/NPM-dist/types/iframe-tracking.d.ts +1 -0
  98. package/NPM-dist/types/iframe-tracking.d.ts.map +1 -0
  99. package/NPM-dist/types/index.d.ts +199 -0
  100. package/NPM-dist/types/index.d.ts.map +1 -0
  101. package/NPM-dist/types/inpageclick.js +1 -0
  102. package/NPM-dist/types/inpageclick.js.map +1 -0
  103. package/NPM-dist/types/interaction.js +1 -0
  104. package/NPM-dist/types/interaction.js.map +1 -0
  105. package/NPM-dist/types/interactive.d.ts +1 -0
  106. package/NPM-dist/types/interactive.d.ts.map +1 -0
  107. package/NPM-dist/types/jobs-courses.d.ts +2 -1
  108. package/NPM-dist/types/jobs-courses.d.ts.map +1 -0
  109. package/NPM-dist/types/jobs.d.ts +2 -1
  110. package/NPM-dist/types/jobs.d.ts.map +1 -0
  111. package/NPM-dist/types/link.js +1 -0
  112. package/NPM-dist/types/link.js.map +1 -0
  113. package/NPM-dist/types/media.js +1 -0
  114. package/NPM-dist/types/media.js.map +1 -0
  115. package/NPM-dist/types/membership.d.ts +2 -1
  116. package/NPM-dist/types/membership.d.ts.map +1 -0
  117. package/NPM-dist/types/ng.d.ts +2 -13
  118. package/NPM-dist/types/ng.d.ts.map +1 -0
  119. package/NPM-dist/types/perf.d.ts +2 -1
  120. package/NPM-dist/types/perf.d.ts.map +1 -0
  121. package/NPM-dist/types/platform.js +1 -0
  122. package/NPM-dist/types/platform.js.map +1 -0
  123. package/NPM-dist/types/printproduct.js +1 -0
  124. package/NPM-dist/types/printproduct.js.map +1 -0
  125. package/NPM-dist/types/privatebrowsing.d.ts +1 -0
  126. package/NPM-dist/types/privatebrowsing.d.ts.map +1 -0
  127. package/NPM-dist/types/product.js +1 -0
  128. package/NPM-dist/types/product.js.map +1 -0
  129. package/NPM-dist/types/r2.d.ts +1 -0
  130. package/NPM-dist/types/r2.d.ts.map +1 -0
  131. package/NPM-dist/types/smart-news.d.ts +2 -1
  132. package/NPM-dist/types/smart-news.d.ts.map +1 -0
  133. package/NPM-dist/types/subscription.js +1 -0
  134. package/NPM-dist/types/subscription.js.map +1 -0
  135. package/NPM-dist/types/tests/attention.test.d.ts +1 -0
  136. package/NPM-dist/types/tests/attention.test.d.ts.map +1 -0
  137. package/NPM-dist/types/tests/click-path-capture.test.d.ts +1 -0
  138. package/NPM-dist/types/tests/click-path-capture.test.d.ts.map +1 -0
  139. package/NPM-dist/types/tests/components.test.d.ts +1 -0
  140. package/NPM-dist/types/tests/components.test.d.ts.map +1 -0
  141. package/NPM-dist/types/tests/core.test.d.ts +1 -0
  142. package/NPM-dist/types/tests/core.test.d.ts.map +1 -0
  143. package/NPM-dist/types/tests/heatmap.test.d.ts +1 -0
  144. package/NPM-dist/types/tests/heatmap.test.d.ts.map +1 -0
  145. package/NPM-dist/types/tests/index.test.d.ts +2 -0
  146. package/NPM-dist/types/tests/index.test.d.ts.map +1 -0
  147. package/NPM-dist/types/tests/transmit.test.d.ts +1 -0
  148. package/NPM-dist/types/tests/transmit.test.d.ts.map +1 -0
  149. package/NPM-dist/types/transmit.d.ts +1 -0
  150. package/NPM-dist/types/transmit.d.ts.map +1 -0
  151. package/NPM-dist/types/types/abtest.d.ts +1 -0
  152. package/NPM-dist/types/types/abtest.d.ts.map +1 -0
  153. package/NPM-dist/types/types/component-event.d.ts +15 -0
  154. package/NPM-dist/types/types/component-event.d.ts.map +1 -0
  155. package/NPM-dist/types/types/component-type.d.ts +7 -0
  156. package/NPM-dist/types/types/component-type.d.ts.map +1 -0
  157. package/NPM-dist/types/types/consent.d.ts +1 -0
  158. package/NPM-dist/types/types/consent.d.ts.map +1 -0
  159. package/NPM-dist/types/types/event.d.ts +11 -0
  160. package/NPM-dist/types/types/event.d.ts.map +1 -0
  161. package/NPM-dist/types/types/inpageclick.d.ts +1 -0
  162. package/NPM-dist/types/types/inpageclick.d.ts.map +1 -0
  163. package/NPM-dist/types/types/interaction.d.ts +1 -0
  164. package/NPM-dist/types/types/interaction.d.ts.map +1 -0
  165. package/NPM-dist/types/types/link.d.ts +1 -0
  166. package/NPM-dist/types/types/link.d.ts.map +1 -0
  167. package/NPM-dist/types/types/media.d.ts +1 -0
  168. package/NPM-dist/types/types/media.d.ts.map +1 -0
  169. package/NPM-dist/types/types/platform.d.ts +1 -0
  170. package/NPM-dist/types/types/platform.d.ts.map +1 -0
  171. package/NPM-dist/types/types/printproduct.d.ts +1 -0
  172. package/NPM-dist/types/types/printproduct.d.ts.map +1 -0
  173. package/NPM-dist/types/types/product.d.ts +7 -0
  174. package/NPM-dist/types/types/product.d.ts.map +1 -0
  175. package/NPM-dist/types/types/subscription.d.ts +1 -0
  176. package/NPM-dist/types/types/subscription.d.ts.map +1 -0
  177. package/NPM-dist/types/vendor/adBlockDetectionLib.d.ts +3 -1
  178. package/NPM-dist/types/vendor/adBlockDetectionLib.d.ts.map +1 -0
  179. package/NPM-dist/types/visibility.d.ts +2 -1
  180. package/NPM-dist/types/visibility.d.ts.map +1 -0
  181. package/NPM-dist/types/witness.d.ts +2 -1
  182. package/NPM-dist/types/witness.d.ts.map +1 -0
  183. package/NPM-dist/vendor/adBlockDetectionLib.js +19 -6
  184. package/NPM-dist/vendor/adBlockDetectionLib.js.map +1 -0
  185. package/NPM-dist/visibility.js +14 -5
  186. package/NPM-dist/visibility.js.map +1 -0
  187. package/NPM-dist/witness.js +1 -0
  188. package/NPM-dist/witness.js.map +1 -0
  189. package/package.json +12 -26
  190. package/readme.md +112 -53
  191. package/src/index.ts +360 -0
  192. package/src/ng.ts +33 -0
  193. package/src/transmit.ts +176 -0
  194. package/src/types/abtest.ts +45 -0
  195. package/src/types/component-event.ts +121 -0
  196. package/src/types/component-type.ts +70 -0
  197. package/src/types/consent.ts +49 -0
  198. package/src/types/event.ts +116 -0
  199. package/src/types/inpageclick.ts +13 -0
  200. package/src/types/interaction.ts +19 -0
  201. package/src/types/link.ts +14 -0
  202. package/src/types/media.ts +184 -0
  203. package/src/types/platform.ts +32 -0
  204. package/src/types/printproduct.ts +53 -0
  205. package/src/types/product.ts +72 -0
  206. package/src/types/subscription.ts +70 -0
  207. package/NPM-dist/manage-my-account.js +0 -46
  208. package/NPM-dist/support.js +0 -45
  209. package/NPM-dist/types/manage-my-account.d.ts +0 -28
  210. package/NPM-dist/types/support.d.ts +0 -28
package/readme.md CHANGED
@@ -10,76 +10,128 @@ $ 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
24
48
 
25
49
  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
50
 
27
- 1. **Import and start the library:**
51
+ 1. **Import and initialise the library:**
28
52
 
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.
53
+ 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
54
 
31
55
  ```javascript
32
- import ophan from '@guardian/ophan-tracker-js';
56
+ import { init, record } from '@guardian/ophan-tracker-js';
57
+
58
+ // Initialise for your platform. This sends the initial page-view event.
59
+ init('ng'); // or 'manage-my-account' or 'support'
33
60
  ```
34
61
 
35
- 2. **Send Tracking Events with `ophan.record()`:**
62
+ 2. **Send Tracking Events with `record()`:**
36
63
 
37
64
  ```javascript
38
- ophan.record({
39
- // Your tracking data in JSON format
65
+ record({
66
+ edition: 'UK',
40
67
  });
41
68
  ```
42
69
 
43
- ### Usage with Typescript
70
+ ### Usage with TypeScript
71
+
72
+ 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
73
 
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).
74
+ 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
75
 
47
76
  #### Example Usage
48
77
 
49
78
  ```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";
79
+ import {
80
+ getViewId,
81
+ init,
82
+ record,
83
+ sendInitialEvent,
84
+ } from '@guardian/ophan-tracker-js';
85
+ import type {
86
+ EventPayload,
87
+ Product,
88
+ ComponentEvent,
89
+ AbTest,
90
+ } from '@guardian/ophan-tracker-js';
91
+
92
+ // Initialise the library for your platform. This sends the initial page-view event.
93
+ init('ng'); // or 'manage-my-account' or 'support'
94
+
95
+ const exampleProduct: Product = 'APP_PREMIUM_TIER';
54
96
  const exampleAbTest: AbTest = {
55
- name: "myAbTest",
56
- variant: "variant",
57
- campaignCodes: ["code1", "code2"]
97
+ name: 'myAbTest',
98
+ variant: 'variant',
99
+ campaignCodes: ['code1', 'code2'],
58
100
  };
59
101
 
60
102
  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"]),
103
+ componentType: 'ACQUISITIONS_BUTTON',
104
+ id: 'component-id',
105
+ products: [exampleProduct],
106
+ campaignCode: 'campaign-code',
107
+ labels: ['label1', 'label2'],
66
108
  };
67
109
 
68
110
  const componentEvent: ComponentEvent = {
69
- component: componentV2,
70
- action: "CLICK",
71
- value: "some-value",
72
- id: "event-id",
73
- abTest: exampleAbTest,
74
- targetingAbTest: exampleAbTest,
111
+ component: componentV2,
112
+ action: 'CLICK',
113
+ value: 'some-value',
114
+ id: 'event-id',
115
+ abTest: exampleAbTest,
116
+ targetingAbTest: exampleAbTest,
75
117
  };
76
118
 
77
- ophan.record({
78
- componentEvent: componentEvent
79
- });
119
+ const payload: EventPayload = {
120
+ componentEvent: componentEvent,
121
+ };
122
+
123
+ record(payload);
124
+
125
+ const currentViewId = getViewId();
126
+
127
+ // Use sendInitialEvent() only when intentionally creating a new page view,
128
+ // for example after a single-page app route change.
129
+ sendInitialEvent();
80
130
  ```
81
131
 
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).
132
+ `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.
133
+
134
+ 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
135
 
84
136
  ### Sending events straight to the backend
85
137
 
@@ -91,9 +143,9 @@ The backend of tracker exposes two endpoints, `img/1` which takes in page view e
91
143
 
92
144
  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
145
 
94
- Typescript interfaces and types are developed in the `types` folder.
146
+ Typescript interfaces and types are developed in the `src/types` folder.
95
147
 
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.
148
+ 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
149
 
98
150
  ## Compile
99
151
 
@@ -101,20 +153,26 @@ The TypeScript compiler is configured to generate compiled JavaScript files and
101
153
  .../ophan/tracker-js $ npm run build-for-npm
102
154
  ```
103
155
 
156
+ ## Type checks
157
+
158
+ ```
159
+ .../ophan/tracker-js $ npm run test:types
160
+ ```
161
+
104
162
  #### Proxy tracker-js:
105
163
 
106
164
  1. Install dependencies
107
165
 
108
- ```
109
- $ brew install lighttpd
110
- .../ophan/tracker-js $ npm install
111
- ```
166
+ ```
167
+ $ brew install lighttpd
168
+ .../ophan/tracker-js $ npm install
169
+ ```
112
170
 
113
171
  2. In another terminal window:
114
172
 
115
- ```
116
- .../ophan/tracker-js $ lighttpd -f lighttpd.conf -D
117
- ```
173
+ ```
174
+ .../ophan/tracker-js $ lighttpd -f lighttpd.conf -D
175
+ ```
118
176
 
119
177
  3. Install FoxyProxy (https://getfoxyproxy.org/downloads/) in your browser and create a rule that forces j.ophan.co.uk
120
178
  to localhost 8000. (For me FoxyProxy needed a few switches on and off before it decided to start talking to lighttpd.)
@@ -131,7 +189,7 @@ The TypeScript compiler is configured to generate compiled JavaScript files and
131
189
 
132
190
  ### Two formats for release
133
191
 
134
- When you make a change to Tracker JS, you must release on *both* these systems:
192
+ When you make a change to Tracker JS, you must release on _both_ these systems:
135
193
 
136
194
  #### NPM
137
195
 
@@ -139,9 +197,9 @@ We use `changesets` for automated publishing of the NPM package:
139
197
 
140
198
  1. Add a changeset, run the following command and follow the instructions
141
199
 
142
- ```
143
- .../ophan/tracker-js $ npx changeset
144
- ```
200
+ ```
201
+ .../ophan/tracker-js $ npx changeset
202
+ ```
145
203
 
146
204
  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
205
 
@@ -149,15 +207,16 @@ We use `changesets` for automated publishing of the NPM package:
149
207
 
150
208
  #### S3 Bucket
151
209
 
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.
210
+ - [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
211
 
154
212
  #### Rollup
155
213
 
156
214
  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
215
 
158
216
  ## Post publish
217
+
159
218
  Post publish it would be good practice to inform the teams using Tracker-JS about the update.
160
219
 
161
220
  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
221
 
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.
222
+ 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.
package/src/index.ts ADDED
@@ -0,0 +1,360 @@
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';
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 : '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
+ }
100
+ };
101
+
102
+ /**
103
+ * Send a new page-view event to Ophan.
104
+ *
105
+ * This generates a new view ID and tracks the current page.
106
+ * `init()` already sends the first page-view event, so call this only when
107
+ * intentionally creating another page view, such as after a single-page app
108
+ * route change.
109
+ *
110
+ * @param url - The page URL to track (defaults to current location)
111
+ * @param referrer - The referrer URL (defaults to document.referrer)
112
+ *
113
+ * @example
114
+ * ```javascript
115
+ * import { sendInitialEvent } from '@guardian/ophan-tracker-js';
116
+ * sendInitialEvent('https://www.theguardian.com/article', 'https://google.com');
117
+ * ```
118
+ */
119
+ export const sendInitialEvent = (
120
+ url: string = typeof window !== 'undefined' ? location.href : '',
121
+ referrer: string = typeof window !== 'undefined' &&
122
+ typeof document !== 'undefined'
123
+ ? document.referrer
124
+ : '',
125
+ ): void => {
126
+ if (!canUseTracker('sendInitialEvent')) {
127
+ return;
128
+ }
129
+
130
+ try {
131
+ transmit.bumpViewId();
132
+ core.sendInitialEvent(null, url, referrer);
133
+ } catch (error) {
134
+ console.log(error);
135
+ }
136
+ };
137
+
138
+ /**
139
+ * Record an event against the current page view.
140
+ *
141
+ * Use this for component interactions, purchases, subscriptions, media events,
142
+ * consent events, and other non-page-view payloads. `init()` must have run
143
+ * first so the event is associated with a valid page view and platform.
144
+ *
145
+ * @param event - The event data payload containing metrics and metadata
146
+ * @param callback - Optional callback function executed after the event is sent
147
+ *
148
+ * @example
149
+ * ```javascript
150
+ * import { record } from '@guardian/ophan-tracker-js';
151
+ *
152
+ * // Track a component interaction
153
+ * record({
154
+ * componentEvent: {
155
+ * component: {
156
+ * componentType: 'ACQUISITIONS_ENGAGEMENT_BANNER',
157
+ * id: 'end-of-article-banner',
158
+ * campaignCode: '2024-annual-campaign',
159
+ * labels: ['end-of-article']
160
+ * },
161
+ * action: 'CLICK',
162
+ * abTest: {
163
+ * name: 'BannerTest',
164
+ * variant: 'control'
165
+ * }
166
+ * }
167
+ * });
168
+ *
169
+ * // Track a rendered component list
170
+ * record({
171
+ * renderedComponents: ['nav', 'footer']
172
+ * });
173
+ * ```
174
+ */
175
+ export const record = (event: EventPayload, callback?: () => void): void => {
176
+ if (!isBrowser()) {
177
+ callback?.();
178
+ return;
179
+ }
180
+
181
+ if (!initialized) {
182
+ warnIfUninitialized('record');
183
+ callback?.();
184
+ return;
185
+ }
186
+
187
+ transmit.sendMore(event, callback);
188
+ };
189
+
190
+ /**
191
+ * Track component attention and visibility analytics.
192
+ *
193
+ * Monitors when components enter and leave the viewport.
194
+ *
195
+ * @param name - Unique identifier for the component
196
+ * @param el - The DOM element to track
197
+ * @param visibilityThreshold - Percentage of element that must be visible (0-1)
198
+ * @param isTrackingVideo - Whether this component contains video content
199
+ *
200
+ * @example
201
+ * ```javascript
202
+ * import { trackComponentAttention } from '@guardian/ophan-tracker-js';
203
+ *
204
+ * const banner = document.getElementById('promo-banner');
205
+ * trackComponentAttention('promo-banner', banner, 0.5);
206
+ * ```
207
+ */
208
+ export const trackComponentAttention = (
209
+ name: string,
210
+ el: HTMLElement,
211
+ visibilityThreshold: number,
212
+ isTrackingVideo?: boolean,
213
+ ): void => {
214
+ if (!canUseTracker('trackComponentAttention')) {
215
+ return;
216
+ }
217
+
218
+ attention.initComponent(name, el, visibilityThreshold, isTrackingVideo);
219
+ };
220
+
221
+ /**
222
+ * Set a custom event emitter for attention tracking.
223
+ *
224
+ * This is primarily for integrations that need to bridge the tracker's
225
+ * attention events into an existing event system.
226
+ *
227
+ * @param event - Event emitter object with emit/on methods
228
+ *
229
+ * @example
230
+ * ```javascript
231
+ * import { setEventEmitter } from '@guardian/ophan-tracker-js';
232
+ *
233
+ * const customEmitter = new EventEmitter();
234
+ * setEventEmitter(customEmitter);
235
+ * ```
236
+ */
237
+ export const setEventEmitter = (event: Object): void => {
238
+ attention.setEventEmitter(event);
239
+ };
240
+
241
+ /**
242
+ * Manually track a click event on an element with data-link-name or data-component attributes.
243
+ * This captures click path information and component context for analytics.
244
+ * Useful for elements that stop event propagation or need explicit tracking.
245
+ *
246
+ * @param element - The clicked DOM element (should have data-link-name or data-component attributes)
247
+ *
248
+ * @example
249
+ * ```javascript
250
+ * import { trackClickComponentEvent } from '@guardian/ophan-tracker-js';
251
+ *
252
+ * // Track a click on an element with tracking attributes
253
+ * const link = document.querySelector('a[data-link-name="subscribe-button"]');
254
+ * trackClickComponentEvent(link);
255
+ *
256
+ * // Or track a button inside a component
257
+ * const button = document.querySelector('[data-component="newsletter-signup"] button');
258
+ * trackClickComponentEvent(button);
259
+ * ```
260
+ */
261
+ export const trackClickComponentEvent = (element: Element): void => {
262
+ if (!canUseTracker('trackClickComponentEvent')) {
263
+ return;
264
+ }
265
+
266
+ clickPathCapture.trackClickComponentEvent(element);
267
+ };
268
+
269
+ /**
270
+ * Generate a new page view ID for the current browser session.
271
+ *
272
+ * Useful for single-page applications when navigating between pages.
273
+ *
274
+ * @example
275
+ * ```javascript
276
+ * import { bumpViewId } from '@guardian/ophan-tracker-js';
277
+ *
278
+ * // Call when navigating to a new page in SPA
279
+ * bumpViewId();
280
+ * ```
281
+ */
282
+ export const bumpViewId = (): void => {
283
+ if (!canUseTracker('bumpViewId')) {
284
+ return;
285
+ }
286
+
287
+ transmit.bumpViewId();
288
+ };
289
+
290
+ /**
291
+ * Return the current live Ophan page view ID.
292
+ *
293
+ * Prefer this function over destructuring `viewId` from the default export,
294
+ * because the value changes after `bumpViewId()` or `sendInitialEvent()`.
295
+ */
296
+ export const getViewId = (): string => transmit.viewId;
297
+
298
+ /**
299
+ * Alias for `getViewId()` kept for consumers that use page-view terminology.
300
+ */
301
+ export const getPageViewId = (): string => transmit.viewId;
302
+
303
+ /**
304
+ * Object-shaped tracker API exposed by the package default export.
305
+ *
306
+ * Named exports are preferred for new code, but the default export keeps the
307
+ * historical object-style API and exposes `viewId`/`pageViewId` as live getters.
308
+ */
309
+ export interface OphanTracker {
310
+ init: (platform?: Platform) => void;
311
+ sendInitialEvent: (url?: string, referrer?: string) => void;
312
+ record: (event: EventPayload, callback?: () => void) => void;
313
+ trackComponentAttention: (
314
+ name: string,
315
+ el: HTMLElement,
316
+ visibilityThreshold: number,
317
+ isTrackingVideo?: boolean,
318
+ ) => void;
319
+ setEventEmitter: (event: Object) => void;
320
+ trackClickComponentEvent: (element: Element) => void;
321
+ bumpViewId: () => void;
322
+ getViewId: () => string;
323
+ getPageViewId: () => string;
324
+ viewId: string;
325
+ pageViewId: string;
326
+ }
327
+
328
+ const ophanTracker: OphanTracker = {
329
+ init,
330
+ sendInitialEvent,
331
+ record,
332
+ trackComponentAttention,
333
+ setEventEmitter,
334
+ trackClickComponentEvent,
335
+ bumpViewId,
336
+ getViewId,
337
+ getPageViewId,
338
+ get viewId() {
339
+ return getViewId();
340
+ },
341
+ get pageViewId() {
342
+ return getPageViewId();
343
+ },
344
+ };
345
+
346
+ export default ophanTracker;
347
+
348
+ // Type exports
349
+ export * from './types/abtest.js';
350
+ export * from './types/subscription.js';
351
+ export * from './types/interaction.js';
352
+ export * from './types/inpageclick.js';
353
+ export * from './types/printproduct.js';
354
+ export * from './types/product.js';
355
+ export * from './types/platform.js';
356
+ export * from './types/media.js';
357
+ export * from './types/consent.js';
358
+ export * from './types/component-type.js';
359
+ export * from './types/component-event.js';
360
+ export * from './types/event.js';
package/src/ng.ts ADDED
@@ -0,0 +1,33 @@
1
+ import ophan, { init } from './index.js';
2
+
3
+ export * from './types/abtest.js';
4
+ export * from './types/subscription.js';
5
+ export * from './types/interaction.js';
6
+ export * from './types/inpageclick.js';
7
+ export * from './types/printproduct.js';
8
+ export * from './types/product.js';
9
+ export * from './types/platform.js';
10
+ export * from './types/media.js';
11
+ export * from './types/consent.js';
12
+ export * from './types/component-type.js';
13
+ export * from './types/component-event.js';
14
+ export * from './types/event.js';
15
+
16
+ const windowWithGuardian =
17
+ typeof window !== 'undefined'
18
+ ? (window as typeof window & {
19
+ guardian?: {
20
+ ophan?: typeof ophan;
21
+ };
22
+ })
23
+ : undefined;
24
+
25
+ const ng = windowWithGuardian?.guardian?.ophan ?? ophan;
26
+
27
+ if (windowWithGuardian && !windowWithGuardian.guardian?.ophan) {
28
+ init('ng');
29
+ windowWithGuardian.guardian = windowWithGuardian.guardian ?? {};
30
+ windowWithGuardian.guardian.ophan = ng;
31
+ }
32
+
33
+ export default ng;