@simplybusiness/services 0.1.1

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 (96) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/data/scripts-mock.js +59 -0
  3. package/dist/cjs/data/scripts-mock.js.map +1 -0
  4. package/dist/cjs/index.js +20 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/mocks/eventDefinitions.js +46 -0
  7. package/dist/cjs/mocks/eventDefinitions.js.map +1 -0
  8. package/dist/cjs/services/airbrake/index.js +23 -0
  9. package/dist/cjs/services/airbrake/index.js.map +1 -0
  10. package/dist/cjs/services/index.js +25 -0
  11. package/dist/cjs/services/index.js.map +1 -0
  12. package/dist/cjs/services/snowplow/SnowplowContext.js +61 -0
  13. package/dist/cjs/services/snowplow/SnowplowContext.js.map +1 -0
  14. package/dist/cjs/services/snowplow/contexts.js +18 -0
  15. package/dist/cjs/services/snowplow/contexts.js.map +1 -0
  16. package/dist/cjs/services/snowplow/event-definitions.js +235 -0
  17. package/dist/cjs/services/snowplow/event-definitions.js.map +1 -0
  18. package/dist/cjs/services/snowplow/getSnowplowConfig.js +16 -0
  19. package/dist/cjs/services/snowplow/getSnowplowConfig.js.map +1 -0
  20. package/dist/cjs/services/snowplow/index.js +138 -0
  21. package/dist/cjs/services/snowplow/index.js.map +1 -0
  22. package/dist/cjs/services/snowplow/types.js +6 -0
  23. package/dist/cjs/services/snowplow/types.js.map +1 -0
  24. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  25. package/dist/cjs/utils/index.js +20 -0
  26. package/dist/cjs/utils/index.js.map +1 -0
  27. package/dist/cjs/utils/testUtils.js +43 -0
  28. package/dist/cjs/utils/testUtils.js.map +1 -0
  29. package/dist/cjs/utils/text.js +13 -0
  30. package/dist/cjs/utils/text.js.map +1 -0
  31. package/dist/esm/data/scripts-mock.js +49 -0
  32. package/dist/esm/data/scripts-mock.js.map +1 -0
  33. package/dist/esm/index.js +3 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/mocks/eventDefinitions.js +36 -0
  36. package/dist/esm/mocks/eventDefinitions.js.map +1 -0
  37. package/dist/esm/services/airbrake/index.js +13 -0
  38. package/dist/esm/services/airbrake/index.js.map +1 -0
  39. package/dist/esm/services/index.js +8 -0
  40. package/dist/esm/services/index.js.map +1 -0
  41. package/dist/esm/services/snowplow/SnowplowContext.js +43 -0
  42. package/dist/esm/services/snowplow/SnowplowContext.js.map +1 -0
  43. package/dist/esm/services/snowplow/contexts.js +8 -0
  44. package/dist/esm/services/snowplow/contexts.js.map +1 -0
  45. package/dist/esm/services/snowplow/event-definitions.js +240 -0
  46. package/dist/esm/services/snowplow/event-definitions.js.map +1 -0
  47. package/dist/esm/services/snowplow/getSnowplowConfig.js +7 -0
  48. package/dist/esm/services/snowplow/getSnowplowConfig.js.map +1 -0
  49. package/dist/esm/services/snowplow/index.js +133 -0
  50. package/dist/esm/services/snowplow/index.js.map +1 -0
  51. package/dist/esm/services/snowplow/types.js +3 -0
  52. package/dist/esm/services/snowplow/types.js.map +1 -0
  53. package/dist/esm/utils/index.js +3 -0
  54. package/dist/esm/utils/index.js.map +1 -0
  55. package/dist/esm/utils/testUtils.js +26 -0
  56. package/dist/esm/utils/testUtils.js.map +1 -0
  57. package/dist/esm/utils/text.js +3 -0
  58. package/dist/esm/utils/text.js.map +1 -0
  59. package/dist/types/data/scripts-mock.d.ts +44 -0
  60. package/dist/types/index.d.ts +1 -0
  61. package/dist/types/mocks/eventDefinitions.d.ts +32 -0
  62. package/dist/types/services/airbrake/index.d.ts +2 -0
  63. package/dist/types/services/index.d.ts +5 -0
  64. package/dist/types/services/snowplow/SnowplowContext.d.ts +14 -0
  65. package/dist/types/services/snowplow/SnowplowContext.test.d.ts +1 -0
  66. package/dist/types/services/snowplow/contexts.d.ts +3 -0
  67. package/dist/types/services/snowplow/contexts.test.d.ts +1 -0
  68. package/dist/types/services/snowplow/event-definitions.d.ts +18 -0
  69. package/dist/types/services/snowplow/getSnowplowConfig.d.ts +2 -0
  70. package/dist/types/services/snowplow/index.d.ts +29 -0
  71. package/dist/types/services/snowplow/index.test.d.ts +1 -0
  72. package/dist/types/services/snowplow/types.d.ts +37 -0
  73. package/dist/types/utils/index.d.ts +1 -0
  74. package/dist/types/utils/testUtils.d.ts +7 -0
  75. package/dist/types/utils/text.d.ts +1 -0
  76. package/dist/types/utils/text.test.d.ts +1 -0
  77. package/package.json +86 -0
  78. package/src/__mocks__/snowplowBrowserTrackerMock.js +6 -0
  79. package/src/data/scripts-mock.ts +44 -0
  80. package/src/index.tsx +1 -0
  81. package/src/mocks/eventDefinitions.ts +39 -0
  82. package/src/services/airbrake/index.ts +16 -0
  83. package/src/services/index.tsx +6 -0
  84. package/src/services/snowplow/SnowplowContext.test.tsx +32 -0
  85. package/src/services/snowplow/SnowplowContext.tsx +68 -0
  86. package/src/services/snowplow/contexts.test.ts +42 -0
  87. package/src/services/snowplow/contexts.ts +14 -0
  88. package/src/services/snowplow/event-definitions.ts +256 -0
  89. package/src/services/snowplow/getSnowplowConfig.ts +8 -0
  90. package/src/services/snowplow/index.test.ts +194 -0
  91. package/src/services/snowplow/index.ts +163 -0
  92. package/src/services/snowplow/types.ts +65 -0
  93. package/src/utils/index.ts +1 -0
  94. package/src/utils/testUtils.tsx +36 -0
  95. package/src/utils/text.test.ts +9 -0
  96. package/src/utils/text.ts +2 -0
@@ -0,0 +1,256 @@
1
+ import { EventDefinition, ParamsType } from "./types";
2
+ import { snakeCase } from "../../utils";
3
+
4
+ /**
5
+ * Event definitions for Snowplow
6
+ * @type {EventDefinition[]}
7
+ * @property {string} name - The name of the event, to use when triggering
8
+ * @property {string} type - The type of the event (structured | unstructured)
9
+ * @property {makePayload} makePayload
10
+ * - Function that creates the payload for the event;
11
+ * - Allows optional params object to be passed in
12
+ *
13
+ * @example
14
+ * import { Snowplow } from "./Snowplow";
15
+ * import { eventDefinitions } from "./event-definitions";
16
+ *
17
+ * const snowplow = new Snowplow();
18
+ * snowplow.addEventHandlers(eventDefinitions);
19
+ */
20
+ export const eventDefinitions: EventDefinition[] = [
21
+ {
22
+ name: "mobileLinkClick",
23
+ type: "structured",
24
+ makePayload: () => ({
25
+ category: "marketing",
26
+ action: "link-click",
27
+ label: "mobile_call_button",
28
+ property: window.location.href,
29
+ }),
30
+ },
31
+ {
32
+ name: "operatingHoursClick",
33
+ type: "unstructured",
34
+ makePayload: () => ({
35
+ schema:
36
+ "iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0",
37
+ data: {
38
+ schema:
39
+ "iglu:com.simplybusiness/operating_hours_clicked/jsonschema/1-0-2",
40
+ data: {},
41
+ },
42
+ }),
43
+ },
44
+ {
45
+ // QDP details button
46
+ name: "detailsClicked",
47
+ type: "structured",
48
+ makePayload: () => ({
49
+ category: "comparison_cta",
50
+ action: "link_click",
51
+ label: "Details",
52
+ }),
53
+ },
54
+ {
55
+ // Buy button
56
+ name: "selectClicked",
57
+ type: "structured",
58
+ makePayload: () => ({
59
+ category: "comparison_cta",
60
+ action: "link_click",
61
+ label: "Select",
62
+ }),
63
+ },
64
+ {
65
+ // Quote Details Slider Next steps button
66
+ name: "nextStepsClicked",
67
+ type: "structured",
68
+ makePayload: () => ({
69
+ category: "quote_details_slider_next_step_cta",
70
+ action: "link_click",
71
+ label: "Next steps",
72
+ }),
73
+ },
74
+ {
75
+ // Toggle deductibles accordion
76
+ name: "deductiblesClicked",
77
+ type: "structured",
78
+ makePayload: () => ({
79
+ category: "us-qcp-react",
80
+ action: "view_deductables_clicked",
81
+ label: "view_deductables_clicked",
82
+ property: window.location.href,
83
+ }),
84
+ },
85
+ {
86
+ name: "deductiblesClickedUk",
87
+ type: "structured",
88
+ makePayload: params => {
89
+ const { label, deviceType } = params as ParamsType;
90
+ const urlFriendlyLabel = label.replace(/ /g, "-").toLowerCase();
91
+
92
+ return {
93
+ category: `uk-qcp-react-${deviceType}-${urlFriendlyLabel}-view-excess-toggle`,
94
+ action: "view_excess_clicked",
95
+ label,
96
+ property: window.location.href,
97
+ };
98
+ },
99
+ },
100
+ {
101
+ // Quote Details Slider opened
102
+ name: "sliderOpened",
103
+ type: "structured",
104
+ makePayload: params => {
105
+ const { label } = params as ParamsType;
106
+
107
+ return {
108
+ category: "comparison_cta",
109
+ action: "quote_details_slider_opened",
110
+ label,
111
+ };
112
+ },
113
+ },
114
+ {
115
+ // Coverage modal opened
116
+ name: "coverageModalOpened",
117
+ type: "structured",
118
+ makePayload: params => {
119
+ const {
120
+ category,
121
+ product = "extra_coverage",
122
+ title,
123
+ } = params as ParamsType;
124
+ const productLabel = snakeCase(product);
125
+
126
+ return {
127
+ category,
128
+ action: `${productLabel}_${snakeCase(title)}_popup_opened`,
129
+ label: productLabel,
130
+ property: window.location.href,
131
+ };
132
+ },
133
+ },
134
+ {
135
+ // Toggle cover select
136
+ name: "coverChanged",
137
+ type: "unstructured",
138
+ makePayload: params => {
139
+ const { name = "", fromValue = "", toValue = "" } = params as ParamsType;
140
+ // Derive data
141
+ let action = "change";
142
+
143
+ // This logic is taken directly from Chopin without documentation:
144
+ // If a cover has zero value, then change the action to add or remove
145
+ if (fromValue === "0") {
146
+ action = "add";
147
+ }
148
+ if (toValue === "0") {
149
+ action = "remove";
150
+ }
151
+
152
+ return {
153
+ schema:
154
+ "iglu:com.simplybusiness/comparison_page_cover_changed/jsonschema/1-0-0",
155
+ data: {
156
+ name,
157
+ action,
158
+ from_value: fromValue,
159
+ to_value: toValue,
160
+ },
161
+ };
162
+ },
163
+ },
164
+ {
165
+ name: "ratingsModalOpened",
166
+ type: "structured",
167
+ makePayload: params => {
168
+ const { category, label } = params as ParamsType;
169
+
170
+ return {
171
+ category,
172
+ action: "insurer_rating_help_popup_triggered",
173
+ label,
174
+ property: window.location.href,
175
+ };
176
+ },
177
+ },
178
+ {
179
+ name: "coverToggleOpened",
180
+ type: "structured",
181
+ makePayload: () => ({
182
+ category: "qcp_limit_interaction",
183
+ label: "limit_interaction",
184
+ action: "limit_interaction_clicked",
185
+ property: window.location.href,
186
+ }),
187
+ },
188
+ {
189
+ name: "paymentToggleClicked",
190
+ type: "structured",
191
+ makePayload: params => {
192
+ const { category, label } = params as ParamsType;
193
+
194
+ return {
195
+ category,
196
+ action: "button_click",
197
+ label,
198
+ property: window.location.href,
199
+ };
200
+ },
201
+ },
202
+ {
203
+ name: "insurerDetailsAccordionClicked",
204
+ type: "structured",
205
+ makePayload: () => ({
206
+ category: "quote_details_slider_insurer_details_description",
207
+ action: "accordion_clicked",
208
+ label: "accordion_clicked",
209
+ property: window.location.href,
210
+ }),
211
+ },
212
+ {
213
+ name: "priceDetailsPopUpOpened",
214
+ type: "structured",
215
+ makePayload: () => ({
216
+ category: "price_details",
217
+ action: "price_details_popup_opened",
218
+ label: "Price details",
219
+ property: window.location.href,
220
+ }),
221
+ },
222
+ {
223
+ name: "editLimitButtonClicked",
224
+ type: "structured",
225
+ makePayload: () => ({
226
+ category: "cover_limit_changes",
227
+ action: "edit_limit_button_clicked",
228
+ label: "Edit Limit",
229
+ property: window.location.href,
230
+ }),
231
+ },
232
+ {
233
+ name: "applyButtonClicked",
234
+ type: "structured",
235
+ makePayload: () => ({
236
+ category: "cover_limit_changes",
237
+ action: "apply_button_clicked",
238
+ label: "Apply Button Clicked",
239
+ property: window.location.href,
240
+ }),
241
+ },
242
+ {
243
+ name: "coverageInfoClicked",
244
+ type: "structured",
245
+ makePayload: params => {
246
+ const { deviceType } = params as ParamsType;
247
+
248
+ return {
249
+ action: "show_coverage_info_clicked",
250
+ category: `uk-qcp-react-${deviceType}-show-coverage-info-toggle`,
251
+ label: "show_coverage_info_clicked",
252
+ property: window.location.href,
253
+ };
254
+ },
255
+ },
256
+ ];
@@ -0,0 +1,8 @@
1
+ import type { TrackingProps, PageDataProps } from "./types";
2
+
3
+ // Get Snowplow config from page data props
4
+ export const getSnowplowConfig = (
5
+ pageData: PageDataProps,
6
+ ): TrackingProps | undefined =>
7
+ pageData.scripts?.find(({ metadata }) => metadata.name === "snowplow")
8
+ ?.props as TrackingProps;
@@ -0,0 +1,194 @@
1
+ import { waitFor } from "@testing-library/dom";
2
+ import * as SnowplowLib from "@snowplow/browser-tracker";
3
+ import { pageData } from "../../data/scripts-mock";
4
+ import eventDefinitions from "../../mocks/eventDefinitions";
5
+ import { getContexts } from "./contexts";
6
+ import { getSnowplowConfig } from "./getSnowplowConfig";
7
+ import { Snowplow } from "./index";
8
+ import { EventDefinition, PageDataProps, TrackingProps } from "./types";
9
+
10
+ const snowplowProps = getSnowplowConfig(pageData as PageDataProps);
11
+ const contexts = getContexts(snowplowProps as TrackingProps);
12
+
13
+ let testSnowplow = new Snowplow(snowplowProps as TrackingProps);
14
+ testSnowplow.setContexts(contexts);
15
+
16
+ describe("Snowplow Analytics Class", () => {
17
+ describe("given no snowplowProps", () => {
18
+ it("should render children without Snowplow instance errors", () => {
19
+ const snowplowInstance = new Snowplow();
20
+ expect(snowplowInstance).toBeDefined();
21
+ });
22
+ });
23
+
24
+ describe("given snowplowProps exist", () => {
25
+ beforeEach(() => {
26
+ testSnowplow = new Snowplow(snowplowProps as TrackingProps);
27
+ testSnowplow.setContexts(contexts);
28
+ jest.clearAllMocks();
29
+ jest.resetModules();
30
+ });
31
+
32
+ it("exports a Singleton instance with static properties", () => {
33
+ const secondInstance = new Snowplow(snowplowProps as TrackingProps);
34
+ secondInstance.setContexts(contexts);
35
+
36
+ expect(testSnowplow).toEqual(secondInstance);
37
+ expect(testSnowplow.bronzeAvalancheTrackerName).toEqual("sb-ava-br");
38
+ });
39
+
40
+ it("sets contexts on instantiation", () => {
41
+ expect(testSnowplow.contexts).toEqual(contexts);
42
+ });
43
+
44
+ it("tracks page view event and adds contexts", async () => {
45
+ const trackViewSpy = jest.spyOn(testSnowplow, "trackView");
46
+ const innerSpy = jest
47
+ .spyOn(SnowplowLib, "trackPageView")
48
+ .mockImplementation(() => {});
49
+
50
+ await testSnowplow.trackView();
51
+
52
+ waitFor(() => {
53
+ expect(trackViewSpy).toHaveBeenCalledTimes(1);
54
+ expect(trackViewSpy).toHaveBeenCalledWith();
55
+ expect(innerSpy).toHaveBeenCalledTimes(1);
56
+ expect(innerSpy).toHaveBeenCalledWith({ context: contexts });
57
+ });
58
+ });
59
+
60
+ it("tracks structured event and adds contexts", async () => {
61
+ const trackStructEventSpy = jest.spyOn(testSnowplow, "trackEvent");
62
+ const innerSpy = jest
63
+ .spyOn(SnowplowLib, "trackStructEvent")
64
+ .mockImplementation(() => {});
65
+
66
+ const payload = {
67
+ category: "mobile-link",
68
+ action: "clicked",
69
+ label: "test-label",
70
+ };
71
+
72
+ await testSnowplow.trackEvent(payload);
73
+
74
+ expect(trackStructEventSpy).toHaveBeenCalledTimes(1);
75
+ expect(trackStructEventSpy).toHaveBeenCalledWith(payload);
76
+ expect(innerSpy).toHaveBeenCalledTimes(1);
77
+ expect(innerSpy).toHaveBeenCalledWith({ ...payload, context: contexts }, [
78
+ "sb-ava-br",
79
+ ]);
80
+ });
81
+
82
+ it("tracks self-describing event and adds contexts", async () => {
83
+ const trackUnstructEventSpy = jest.spyOn(
84
+ testSnowplow,
85
+ "trackUnstructEvent",
86
+ );
87
+ const innerSpy = jest
88
+ .spyOn(SnowplowLib, "trackSelfDescribingEvent")
89
+ .mockImplementation(() => {});
90
+
91
+ const testEvent = {
92
+ schema:
93
+ "iglu:com.simplybusiness/comparison_page_cover_changed/jsonschema/1-0-0",
94
+ data: {
95
+ name: "Public liability",
96
+ action: "change",
97
+ from_value: "1000000",
98
+ to_value: "2000000",
99
+ },
100
+ };
101
+
102
+ await testSnowplow.trackUnstructEvent(testEvent);
103
+ expect(trackUnstructEventSpy).toHaveBeenCalledTimes(1);
104
+ expect(trackUnstructEventSpy).toHaveBeenCalledWith(testEvent);
105
+ expect(innerSpy).toHaveBeenCalledTimes(1);
106
+ expect(innerSpy).toHaveBeenCalledWith(
107
+ {
108
+ event: testEvent,
109
+ context: contexts,
110
+ },
111
+ ["sb-ava"],
112
+ );
113
+ });
114
+
115
+ describe("creates event handlers from definitions", () => {
116
+ it("adds a list of event handlers", () => {
117
+ testSnowplow.addEventHandlers(eventDefinitions as EventDefinition[]);
118
+ expect(Object.keys(testSnowplow.eventHandlers)).toEqual([
119
+ "navButtonClicked",
120
+ "questionAnswered",
121
+ ]);
122
+ });
123
+
124
+ it("triggers an added structured event", async () => {
125
+ testSnowplow.addEventHandlers(eventDefinitions as EventDefinition[]);
126
+
127
+ const trackStructEventSpy = jest.spyOn(testSnowplow, "trackEvent");
128
+ const innerSpy = jest
129
+ .spyOn(SnowplowLib, "trackStructEvent")
130
+ .mockImplementation(() => {});
131
+
132
+ await testSnowplow.trigger("navButtonClicked", { label: "test-label" });
133
+
134
+ const expectedPayload = {
135
+ category: "navigation",
136
+ action: "thankyou_navigation_button_click",
137
+ label: "test-label",
138
+ property: "test-property",
139
+ };
140
+
141
+ expect(trackStructEventSpy).toHaveBeenCalledTimes(1);
142
+ expect(trackStructEventSpy).toHaveBeenCalledWith(expectedPayload);
143
+ expect(innerSpy).toHaveBeenCalledTimes(1);
144
+ expect(innerSpy).toHaveBeenCalledWith(
145
+ { ...expectedPayload, context: contexts },
146
+ ["sb-ava-br"],
147
+ );
148
+ });
149
+
150
+ it("triggers an added unstructured event", async () => {
151
+ testSnowplow.addEventHandlers(eventDefinitions as EventDefinition[]);
152
+
153
+ const trackUnstructEventSpy = jest.spyOn(
154
+ testSnowplow,
155
+ "trackUnstructEvent",
156
+ );
157
+ const innerSpy = jest
158
+ .spyOn(SnowplowLib, "trackSelfDescribingEvent")
159
+ .mockImplementation(() => {});
160
+
161
+ await testSnowplow.trigger("questionAnswered", {
162
+ vertical: "shop",
163
+ question: "test-question",
164
+ answer: "test-answer",
165
+ });
166
+
167
+ const expectedPayload = {
168
+ schema:
169
+ "iglu:com.simplybusiness/form_question_answered/jsonschema/1-0-1",
170
+ data: {
171
+ site: "",
172
+ vertical: "shop",
173
+ page_index: 1,
174
+ page_name: "Coverage diagnosis questionnaire",
175
+ section_name: "Coverage diagnosis questionnaire",
176
+ question: "test-question",
177
+ answer: "test-answer",
178
+ },
179
+ };
180
+
181
+ expect(trackUnstructEventSpy).toHaveBeenCalledTimes(1);
182
+ expect(trackUnstructEventSpy).toHaveBeenCalledWith(expectedPayload);
183
+ expect(innerSpy).toHaveBeenCalledTimes(1);
184
+ expect(innerSpy).toHaveBeenCalledWith(
185
+ {
186
+ event: expectedPayload,
187
+ context: contexts,
188
+ },
189
+ ["sb-ava"],
190
+ );
191
+ });
192
+ });
193
+ });
194
+ });
@@ -0,0 +1,163 @@
1
+ import {
2
+ SelfDescribingJson,
3
+ StructuredEvent,
4
+ TrackerConfiguration,
5
+ newTracker,
6
+ setCookiePath,
7
+ setUserId,
8
+ trackPageView,
9
+ trackSelfDescribingEvent,
10
+ trackStructEvent,
11
+ } from "@snowplow/browser-tracker";
12
+ import { EventDefinition, TrackingProps } from "./types";
13
+
14
+ export type FrontOfficeStructuredEvent = StructuredEvent & {
15
+ serviceChannelIdentifier: string;
16
+ };
17
+
18
+ /**
19
+ * This class is an abstraction which wraps Snowplow
20
+ * and exposes common methods with other services:
21
+ * - trackEvent : sends a standard payload
22
+ * - trackUnstructEvent : sends a payload for custom schema
23
+ */
24
+ export class Snowplow {
25
+ avalancheTrackerName = "sb-ava";
26
+
27
+ bronzeAvalancheTrackerName = "sb-ava-br";
28
+
29
+ pvAvalancheTrackerName = "sb-ava-pv";
30
+
31
+ uid: unknown = "";
32
+
33
+ trackPageView: boolean = false;
34
+
35
+ contexts: SelfDescribingJson<Record<string, unknown>>[] = [];
36
+
37
+ eventHandlers: Record<string, (params?: Record<string, unknown>) => void> =
38
+ {};
39
+
40
+ constructor(props?: TrackingProps) {
41
+ if (!props) return;
42
+
43
+ const {
44
+ appId,
45
+ cookieDomain,
46
+ avalancheCollector,
47
+ eventMethod,
48
+ uid,
49
+ postPath,
50
+ // includeGAContext,
51
+ // trackActivity,
52
+ trackPageView: tpv,
53
+ } = props;
54
+ this.uid = uid;
55
+ this.trackPageView = tpv;
56
+
57
+ // Set options
58
+ const stateStorageStrategy = "cookieAndLocalStorage";
59
+ const baseOptions: TrackerConfiguration = {
60
+ appId,
61
+ cookieDomain,
62
+ eventMethod,
63
+ stateStorageStrategy,
64
+ postPath,
65
+ };
66
+ // Initialize trackers
67
+ newTracker(this.avalancheTrackerName, avalancheCollector, baseOptions);
68
+
69
+ newTracker(
70
+ this.bronzeAvalancheTrackerName,
71
+ avalancheCollector,
72
+ baseOptions,
73
+ );
74
+
75
+ // Page view tracker
76
+ newTracker(this.pvAvalancheTrackerName, avalancheCollector, {
77
+ ...baseOptions,
78
+ eventMethod: eventMethod === "post" ? "beacon" : eventMethod,
79
+ });
80
+
81
+ setCookiePath("/");
82
+ if (uid) {
83
+ setUserId(uid);
84
+ }
85
+ }
86
+
87
+ setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]) {
88
+ this.contexts = contexts;
89
+ // Update identity context
90
+ const index = this.contexts?.findIndex(ctx =>
91
+ ctx.schema?.includes("identity_context"),
92
+ );
93
+ if (index > -1) {
94
+ this.contexts[index].data.domain_userid = this.uid;
95
+ }
96
+ return this;
97
+ }
98
+
99
+ // Send a page view event
100
+ trackView() {
101
+ if (this.trackPageView) {
102
+ trackPageView({ context: this.contexts });
103
+ }
104
+ return this;
105
+ }
106
+
107
+ // Send a structured event with contexts
108
+ async trackEvent(event: StructuredEvent) {
109
+ await trackStructEvent({ ...event, context: this.contexts }, [
110
+ this.bronzeAvalancheTrackerName,
111
+ ]);
112
+ return this;
113
+ }
114
+
115
+ // Send a custom event with defined schema and optional contexts
116
+ async trackUnstructEvent(event: SelfDescribingJson<Record<string, unknown>>) {
117
+ if (!event) {
118
+ return this;
119
+ }
120
+ await trackSelfDescribingEvent({ event, context: this.contexts }, [
121
+ this.avalancheTrackerName,
122
+ ]);
123
+ return this;
124
+ }
125
+
126
+ addEventHandlers(eventDefinitions: EventDefinition[]) {
127
+ eventDefinitions.forEach(({ name, type, makePayload }) => {
128
+ // Convert type into relevant function
129
+ if (type === "structured") {
130
+ this.addEventHandler(name, (params?: Record<string, unknown>) => {
131
+ this.trackEvent(makePayload(params) as StructuredEvent);
132
+ });
133
+ } else {
134
+ this.addEventHandler(name, (params?: Record<string, unknown>) => {
135
+ this.trackUnstructEvent(
136
+ makePayload(params) as SelfDescribingJson<Record<string, unknown>>,
137
+ );
138
+ });
139
+ }
140
+ });
141
+ return this;
142
+ }
143
+
144
+ private addEventHandler(
145
+ name: string,
146
+ handler: (params?: Record<string, unknown>) => void,
147
+ ) {
148
+ this.eventHandlers[name] = handler;
149
+ return this;
150
+ }
151
+
152
+ private removeEventHandler(name: string) {
153
+ delete this.eventHandlers[name];
154
+ return this;
155
+ }
156
+
157
+ trigger(name: string, params?: Record<string, unknown>) {
158
+ if (this.eventHandlers[name]) {
159
+ this.eventHandlers[name](params);
160
+ }
161
+ return this;
162
+ }
163
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ EventMethod,
3
+ SelfDescribingJson,
4
+ StructuredEvent,
5
+ } from "@snowplow/browser-tracker";
6
+
7
+ type BaseConfig = {
8
+ appId: string;
9
+ avalancheCollector: string;
10
+ eventMethod: EventMethod;
11
+ trackPageView: boolean;
12
+ includeGAContext: boolean;
13
+ uid?: string;
14
+ postPath?: string;
15
+ };
16
+
17
+ export type EnvConfig = BaseConfig & {
18
+ cookieDomain: Record<string, string>;
19
+ };
20
+
21
+ export type TrackingProps = BaseConfig & {
22
+ eventMethod: EventMethod;
23
+ cookieDomain?: string;
24
+ };
25
+
26
+ export type ChannelContext = {
27
+ schema: string;
28
+ data: Record<string, string | number>;
29
+ };
30
+
31
+ export type ChannelContexts = Record<string, ChannelContext>;
32
+
33
+ export type ArrayOneOrMore<T> = [T, ...T[]];
34
+
35
+ export type ParamsType = Record<
36
+ | "label"
37
+ | "deviceType"
38
+ | "category"
39
+ | "product"
40
+ | "title"
41
+ | "label"
42
+ | "name"
43
+ | "fromValue"
44
+ | "toValue",
45
+ string
46
+ >;
47
+
48
+ export type EventDefinition = {
49
+ name: string;
50
+ type: "structured" | "unstructured";
51
+ makePayload: (
52
+ params?: Record<string, unknown>,
53
+ ) => StructuredEvent | SelfDescribingJson<Record<string, unknown>>;
54
+ };
55
+
56
+ export interface PageDataProps
57
+ extends Partial<
58
+ Record<
59
+ "scripts",
60
+ Array<{
61
+ metadata: { name: string };
62
+ props?: Record<string, unknown>;
63
+ }>
64
+ >
65
+ > {}
@@ -0,0 +1 @@
1
+ export * from "./text";