@simplybusiness/services 0.20.1 → 0.21.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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/mocks/eventDefinitions.js +5 -21
  3. package/dist/cjs/mocks/eventDefinitions.js.map +1 -1
  4. package/dist/cjs/snowplow/Snowplow.js +41 -28
  5. package/dist/cjs/snowplow/Snowplow.js.map +1 -1
  6. package/dist/cjs/snowplow/contexts.js +52 -12
  7. package/dist/cjs/snowplow/contexts.js.map +1 -1
  8. package/dist/cjs/snowplow/event-definitions/index.js +10 -1
  9. package/dist/cjs/snowplow/event-definitions/index.js.map +1 -1
  10. package/dist/cjs/snowplow/event-definitions/personalised_cover.js +122 -0
  11. package/dist/cjs/snowplow/event-definitions/personalised_cover.js.map +1 -0
  12. package/dist/cjs/snowplow/event-definitions/qcp.js +8 -2
  13. package/dist/cjs/snowplow/event-definitions/qcp.js.map +1 -1
  14. package/dist/cjs/snowplow/event-definitions/questionnaire.js +4 -1
  15. package/dist/cjs/snowplow/event-definitions/questionnaire.js.map +1 -1
  16. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  17. package/dist/esm/mocks/eventDefinitions.js +5 -21
  18. package/dist/esm/mocks/eventDefinitions.js.map +1 -1
  19. package/dist/esm/snowplow/Snowplow.js +41 -28
  20. package/dist/esm/snowplow/Snowplow.js.map +1 -1
  21. package/dist/esm/snowplow/contexts.js +38 -9
  22. package/dist/esm/snowplow/contexts.js.map +1 -1
  23. package/dist/esm/snowplow/event-definitions/index.js +7 -1
  24. package/dist/esm/snowplow/event-definitions/index.js.map +1 -1
  25. package/dist/esm/snowplow/event-definitions/personalised_cover.js +131 -0
  26. package/dist/esm/snowplow/event-definitions/personalised_cover.js.map +1 -0
  27. package/dist/esm/snowplow/event-definitions/qcp.js +9 -22
  28. package/dist/esm/snowplow/event-definitions/qcp.js.map +1 -1
  29. package/dist/esm/snowplow/event-definitions/questionnaire.js +4 -1
  30. package/dist/esm/snowplow/event-definitions/questionnaire.js.map +1 -1
  31. package/dist/esm/snowplow/types.js.map +1 -1
  32. package/dist/types/mocks/eventDefinitions.d.ts +4 -19
  33. package/dist/types/snowplow/Snowplow.d.ts +7 -5
  34. package/dist/types/snowplow/contexts.d.ts +4 -3
  35. package/dist/types/snowplow/event-definitions/index.d.ts +1 -0
  36. package/dist/types/snowplow/event-definitions/personalised_cover.d.ts +22 -0
  37. package/dist/types/snowplow/event-definitions/qcp.d.ts +0 -20
  38. package/dist/types/snowplow/types.d.ts +3 -1
  39. package/package.json +16 -16
  40. package/src/mocks/eventDefinitions.ts +2 -25
  41. package/src/snowplow/Snowplow.ts +46 -29
  42. package/src/snowplow/contexts.test.ts +134 -6
  43. package/src/snowplow/contexts.ts +44 -14
  44. package/src/snowplow/event-definitions/index.ts +7 -0
  45. package/src/snowplow/event-definitions/personalised_cover.ts +142 -0
  46. package/src/snowplow/event-definitions/qcp.ts +2 -21
  47. package/src/snowplow/event-definitions/questionnaire.ts +1 -0
  48. package/src/snowplow/index.test.ts +40 -30
  49. package/src/snowplow/types.ts +4 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/snowplow/types.ts"],"sourcesContent":["import {\n EventMethod,\n SelfDescribingJson,\n StructuredEvent,\n} from \"@snowplow/browser-tracker\";\n\ntype BaseConfig = {\n appId: string;\n avalancheCollector: string;\n eventMethod: EventMethod;\n trackPageView: boolean;\n includeGAContext: boolean;\n uid?: string;\n postPath?: string;\n};\n\nexport type EnvConfig = BaseConfig & {\n cookieDomain: Record<string, string>;\n};\n\nexport type TrackingProps = BaseConfig & {\n eventMethod: EventMethod;\n cookieDomain?: string;\n pageViewContext?: ChannelContext;\n};\n\nexport type ChannelContext = {\n schema: string;\n data: Record<string, string | number>;\n};\n\nexport type ChannelContexts = Record<string, ChannelContext>;\n\nexport type ArrayOneOrMore<T> = [T, ...T[]];\n\nexport type ParamsType = Record<string, string> & {\n context: ServerContext;\n};\n\ntype ServerContext = {\n site: string;\n vertical: string;\n primary_detail: string;\n journey_name: string;\n journey_id: string;\n};\n\nexport type LinkType = Record<string, string> & {\n targetUrl: string;\n elementContent: string;\n};\n\nexport type EventDefinition = {\n name: string;\n type: \"structured\" | \"unstructured\";\n makePayload: (\n params?: Record<string, unknown>,\n ) => StructuredEvent | SelfDescribingJson<Record<string, unknown>>;\n};\n\nexport interface PageDataProps\n extends Partial<\n Record<\n \"scripts\",\n Array<{\n metadata: { name: string };\n props?: Record<string, unknown>;\n }>\n >\n > {}\n"],"names":[],"mappings":"AA4DA,WASM"}
1
+ {"version":3,"sources":["../../../src/snowplow/types.ts"],"sourcesContent":["import {\n EventMethod,\n SelfDescribingJson,\n StructuredEvent,\n} from \"@snowplow/browser-tracker\";\n\ntype BaseConfig = {\n appId: string;\n avalancheCollector: string;\n eventMethod: EventMethod;\n trackPageView: boolean;\n includeGAContext: boolean;\n uid?: string;\n postPath?: string;\n};\n\nexport type EnvConfig = BaseConfig & {\n cookieDomain: Record<string, string>;\n};\n\nexport type TrackingProps = BaseConfig & {\n eventMethod: EventMethod;\n cookieDomain?: string;\n pageViewContext?: ChannelContext;\n};\n\nexport type ChannelContext = {\n schema: string;\n data: Record<string, string | number>;\n};\n\nexport type ChannelContexts = Record<string, ChannelContext>;\n\nexport type ArrayOneOrMore<T> = [T, ...T[]];\n\nexport type ParamsType = Record<string, string> & {\n context: ServerContext;\n};\n\ntype ServerContext = {\n site: string;\n vertical: string;\n primary_detail: string;\n journey_name: string;\n journey_id: string;\n};\n\nexport type LinkType = Record<string, string> & {\n targetUrl: string;\n elementContent: string;\n};\n\nexport type SerialisedEvent = SelfDescribingJson<Record<string, unknown>>;\n\nexport type EventDefinition = {\n name: string;\n type: \"structured\" | \"unstructured\";\n makePayload: (\n params?: Record<string, unknown>,\n ) => StructuredEvent | SerialisedEvent;\n contexts?: string[];\n};\n\nexport interface PageDataProps\n extends Partial<\n Record<\n \"scripts\",\n Array<{\n metadata: { name: string };\n props?: Record<string, unknown>;\n }>\n >\n > {}\n"],"names":[],"mappings":"AA+DA,WASM"}
@@ -10,25 +10,7 @@ declare const _default: ({
10
10
  label: "next" | "back" | "redirect";
11
11
  property: string;
12
12
  };
13
- } | {
14
- name: string;
15
- type: string;
16
- makePayload: (params: {
17
- vertical?: string;
18
- question: string;
19
- answer?: string;
20
- }) => {
21
- schema: string;
22
- data: {
23
- site: string;
24
- vertical: string;
25
- page_index: number;
26
- page_name: string;
27
- section_name: string;
28
- question: string;
29
- answer: string | undefined;
30
- };
31
- };
13
+ contexts?: undefined;
32
14
  } | {
33
15
  name: string;
34
16
  type: string;
@@ -45,6 +27,7 @@ declare const _default: ({
45
27
  vertical: string;
46
28
  };
47
29
  };
30
+ contexts: string[];
48
31
  } | {
49
32
  name: string;
50
33
  type: string;
@@ -56,6 +39,7 @@ declare const _default: ({
56
39
  primary_detail: string;
57
40
  };
58
41
  };
42
+ contexts?: undefined;
59
43
  } | {
60
44
  name: string;
61
45
  type: string;
@@ -70,5 +54,6 @@ declare const _default: ({
70
54
  page_name: string;
71
55
  };
72
56
  };
57
+ contexts?: undefined;
73
58
  })[];
74
59
  export default _default;
@@ -1,5 +1,5 @@
1
- import { SelfDescribingJson, StructuredEvent, PageViewEvent } from "@snowplow/browser-tracker";
2
- import { EventDefinition, TrackingProps } from "./types";
1
+ import { PageViewEvent, StructuredEvent } from "@snowplow/browser-tracker";
2
+ import { EventDefinition, SerialisedEvent, TrackingProps } from "./types";
3
3
  export type FrontOfficeStructuredEvent = StructuredEvent & {
4
4
  serviceChannelIdentifier: string;
5
5
  };
@@ -15,15 +15,17 @@ export declare class Snowplow {
15
15
  pvAvalancheTrackerName: string;
16
16
  uid: unknown;
17
17
  trackPageView: boolean;
18
- contexts: SelfDescribingJson<Record<string, unknown>>[];
18
+ contexts: Record<string, SerialisedEvent>;
19
+ pvContext: SerialisedEvent[];
20
+ structContext: SerialisedEvent[];
19
21
  serverData: Record<string, unknown>;
20
22
  eventHandlers: Record<string, (params?: Record<string, unknown>) => void>;
21
23
  static instance: Snowplow | undefined;
22
24
  constructor(props?: TrackingProps);
23
- setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]): this;
25
+ setContexts(contexts: Record<string, SerialisedEvent>): this;
24
26
  trackView(event?: PageViewEvent): Promise<this>;
25
27
  trackEvent(event: StructuredEvent): Promise<this>;
26
- trackUnstructEvent(event: SelfDescribingJson<Record<string, unknown>>): Promise<this>;
28
+ trackUnstructEvent(event: SerialisedEvent, contexts?: string[]): Promise<this>;
27
29
  addEventHandlers(eventDefinitions: EventDefinition[]): this;
28
30
  private addEventHandler;
29
31
  private removeEventHandler;
@@ -1,3 +1,4 @@
1
- import { SelfDescribingJson } from "@snowplow/browser-tracker";
2
- import { TrackingProps } from "./types";
3
- export declare const getContexts: (config: TrackingProps) => SelfDescribingJson<Record<string, unknown>>[];
1
+ import { SerialisedEvent, TrackingProps } from "./types";
2
+ export declare const getContexts: (config: TrackingProps) => Record<string, SerialisedEvent>;
3
+ export declare const updateIdentityContext: (contexts: Record<string, SerialisedEvent>, uid: unknown) => Record<string, SerialisedEvent>;
4
+ export declare const makeContexts: (keys: string[], config: Record<string, SerialisedEvent>) => SerialisedEvent[];
@@ -1,6 +1,7 @@
1
1
  export declare const eventDefinitions: import("..").EventDefinition[];
2
2
  export declare const qcpPageEvents: import("..").EventDefinition[];
3
3
  export declare const referralPageEvents: import("..").EventDefinition[];
4
+ export declare const personalisedCoverPageEvents: import("..").EventDefinition[];
4
5
  export declare const interventionPageEvents: import("..").EventDefinition[];
5
6
  export type { InterventionPayload } from "./intervention";
6
7
  export declare const questionnairePageEvents: import("..").EventDefinition[];
@@ -0,0 +1,22 @@
1
+ import { EventDefinition } from "../types";
2
+ /**
3
+ * Event definitions for Snowplow
4
+ * @type {EventDefinition[]}
5
+ * @property {string} name - The name of the event, to use when triggering
6
+ * @property {string} type - The type of the event (structured | unstructured)
7
+ * @property {makePayload} makePayload
8
+ * - Function that creates the payload for the event;
9
+ * - Allows optional params object to be passed in
10
+ *
11
+ * @example
12
+ * Parent
13
+ * import { getSnowplowConfig, SnowplowProvider } from "@simplybusiness/services";
14
+ * const snowplowProps = getSnowplowConfig(pageData);
15
+ * <SnowplowProvider scripts={snowplowProps!}>{children}</SnowplowProvider>
16
+ *
17
+ * Child
18
+ * import { useSnowplowContext } from "@simplybusiness/services";
19
+ * const { snowplow } = useSnowplowContext();
20
+ * const handlerFunction = () => snowplow?.trigger("eventNameHere");
21
+ */
22
+ export declare const personalisedCoverEventDefinitions: EventDefinition[];
@@ -1,22 +1,2 @@
1
1
  import { EventDefinition } from "../types";
2
- /**
3
- * Event definitions for Snowplow
4
- * @type {EventDefinition[]}
5
- * @property {string} name - The name of the event, to use when triggering
6
- * @property {string} type - The type of the event (structured | unstructured)
7
- * @property {makePayload} makePayload
8
- * - Function that creates the payload for the event;
9
- * - Allows optional params object to be passed in
10
- *
11
- * @example
12
- * Parent
13
- * import { getSnowplowConfig, SnowplowProvider } from "@simplybusiness/services";
14
- * const snowplowProps = getSnowplowConfig(pageData);
15
- * <SnowplowProvider scripts={snowplowProps!}>{children}</SnowplowProvider>
16
- *
17
- * Child
18
- * import { useSnowplowContext } from "@simplybusiness/services";
19
- * const { snowplow } = useSnowplowContext();
20
- * const handlerFunction = () => snowplow?.trigger("eventNameHere");
21
- */
22
2
  export declare const qcpEventDefinitions: EventDefinition[];
@@ -36,10 +36,12 @@ export type LinkType = Record<string, string> & {
36
36
  targetUrl: string;
37
37
  elementContent: string;
38
38
  };
39
+ export type SerialisedEvent = SelfDescribingJson<Record<string, unknown>>;
39
40
  export type EventDefinition = {
40
41
  name: string;
41
42
  type: "structured" | "unstructured";
42
- makePayload: (params?: Record<string, unknown>) => StructuredEvent | SelfDescribingJson<Record<string, unknown>>;
43
+ makePayload: (params?: Record<string, unknown>) => StructuredEvent | SerialisedEvent;
44
+ contexts?: string[];
43
45
  };
44
46
  export interface PageDataProps extends Partial<Record<"scripts", Array<{
45
47
  metadata: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/services",
3
3
  "license": "UNLICENSED",
4
- "version": "0.20.1",
4
+ "version": "0.21.1",
5
5
  "description": "Internal library for services",
6
6
  "repository": {
7
7
  "type": "git",
@@ -42,40 +42,40 @@
42
42
  "react-dom": "^18.2.0 || ^19.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@swc/cli": "^0.5.2",
46
- "@swc/core": "^1.10.1",
45
+ "@swc/cli": "^0.6.0",
46
+ "@swc/core": "^1.11.9",
47
47
  "@swc/jest": "^0.2.37",
48
48
  "@testing-library/dom": "^10.4.0",
49
49
  "@testing-library/jest-dom": "6.6.3",
50
- "@testing-library/react": "^16.1.0",
50
+ "@testing-library/react": "^16.2.0",
51
51
  "@types/jest": "^29.5.14",
52
- "@types/react": "^18.3.16",
52
+ "@types/react": "^18.3.18",
53
53
  "@types/react-dom": "^18.3.5",
54
- "@typescript-eslint/eslint-plugin": "^8.18.0",
55
- "@typescript-eslint/parser": "^8.18.0",
54
+ "@typescript-eslint/eslint-plugin": "^8.26.1",
55
+ "@typescript-eslint/parser": "^8.26.1",
56
56
  "eslint": "^8.57.1",
57
57
  "eslint-config-airbnb": "^19.0.4",
58
58
  "eslint-config-prettier": "^9.1.0",
59
- "eslint-import-resolver-typescript": "^3.7.0",
59
+ "eslint-import-resolver-typescript": "^3.8.6",
60
60
  "eslint-plugin-import": "^2.31.0",
61
61
  "eslint-plugin-jsx-a11y": "^6.10.2",
62
62
  "eslint-plugin-no-only-tests": "^3.3.0",
63
- "eslint-plugin-prettier": "^5.2.1",
64
- "eslint-plugin-react": "^7.37.2",
65
- "eslint-plugin-react-hooks": "^5.1.0",
63
+ "eslint-plugin-prettier": "^5.2.3",
64
+ "eslint-plugin-react": "^7.37.4",
65
+ "eslint-plugin-react-hooks": "^5.2.0",
66
66
  "identity-obj-proxy": "^3.0.0",
67
67
  "jest": "^29.7.0",
68
68
  "jest-environment-jsdom": "^29.7.0",
69
- "prettier": "^3.4.2",
69
+ "prettier": "^3.5.3",
70
70
  "react": "^18.3.1",
71
71
  "react-dom": "^18.3.1",
72
- "ts-jest": "^29.2.5",
72
+ "ts-jest": "^29.2.6",
73
73
  "tslib": "^2.8.1",
74
- "typescript": "^5.7.2"
74
+ "typescript": "^5.8.2"
75
75
  },
76
76
  "dependencies": {
77
- "@airbrake/browser": "^2.1.8",
78
- "@simplybusiness/mobius": "^5.24.2",
77
+ "@airbrake/browser": "^2.1.9",
78
+ "@simplybusiness/mobius": "^5.24.3",
79
79
  "@snowplow/browser-tracker": "^3.24.6",
80
80
  "classnames": "^2.5.1"
81
81
  },
@@ -15,30 +15,6 @@ export default [
15
15
  };
16
16
  },
17
17
  },
18
- {
19
- name: "questionAnswered",
20
- type: "unstructured",
21
- makePayload: (params: {
22
- vertical?: string;
23
- question: string;
24
- answer?: string;
25
- }) => {
26
- const { vertical, question, answer } = params;
27
- return {
28
- schema:
29
- "iglu:com.simplybusiness/form_question_answered/jsonschema/1-0-1",
30
- data: {
31
- site: "",
32
- vertical: vertical || "business",
33
- page_index: 1,
34
- page_name: "Coverage diagnosis questionnaire",
35
- section_name: "Coverage diagnosis questionnaire",
36
- question,
37
- answer,
38
- },
39
- };
40
- },
41
- },
42
18
  {
43
19
  name: "questionAnswered",
44
20
  type: "unstructured",
@@ -61,6 +37,7 @@ export default [
61
37
  },
62
38
  };
63
39
  },
40
+ contexts: ["distributionChannelContext"],
64
41
  },
65
42
  {
66
43
  name: "primaryDetailSelected",
@@ -68,7 +45,7 @@ export default [
68
45
  makePayload: (params: ParamsType) => {
69
46
  const { context, answer, vertical } = params as ParamsType;
70
47
  const { site } = context;
71
- let verticalName = vertical ? vertical : context.vertical;
48
+ let verticalName = vertical || context.vertical;
72
49
 
73
50
  if (verticalName.toLowerCase().indexOf("landlord") > -1) {
74
51
  verticalName =
@@ -1,5 +1,5 @@
1
1
  import {
2
- SelfDescribingJson,
2
+ PageViewEvent,
3
3
  StructuredEvent,
4
4
  TrackerConfiguration,
5
5
  newTracker,
@@ -8,9 +8,9 @@ import {
8
8
  trackPageView,
9
9
  trackSelfDescribingEvent,
10
10
  trackStructEvent,
11
- PageViewEvent,
12
11
  } from "@snowplow/browser-tracker";
13
- import { EventDefinition, TrackingProps } from "./types";
12
+ import { makeContexts, updateIdentityContext } from "./contexts";
13
+ import { EventDefinition, SerialisedEvent, TrackingProps } from "./types";
14
14
 
15
15
  export type FrontOfficeStructuredEvent = StructuredEvent & {
16
16
  serviceChannelIdentifier: string;
@@ -33,7 +33,11 @@ export class Snowplow {
33
33
 
34
34
  trackPageView: boolean = false;
35
35
 
36
- contexts: SelfDescribingJson<Record<string, unknown>>[] = [];
36
+ contexts: Record<string, SerialisedEvent> = {};
37
+
38
+ pvContext: SerialisedEvent[] = [];
39
+
40
+ structContext: SerialisedEvent[] = [];
37
41
 
38
42
  serverData: Record<string, unknown> = {};
39
43
 
@@ -52,8 +56,6 @@ export class Snowplow {
52
56
  eventMethod,
53
57
  uid,
54
58
  postPath,
55
- // includeGAContext,
56
- // trackActivity,
57
59
  trackPageView: tpv,
58
60
  } = props;
59
61
  this.uid = uid;
@@ -92,22 +94,20 @@ export class Snowplow {
92
94
  Snowplow.instance = this;
93
95
  }
94
96
 
95
- setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]) {
96
- this.contexts = contexts;
97
- // Update identity context
98
- const index = this.contexts?.findIndex(ctx =>
99
- ctx.schema?.includes("identity_context"),
97
+ setContexts(contexts: Record<string, SerialisedEvent>) {
98
+ this.contexts = updateIdentityContext(contexts, this.uid);
99
+ this.pvContext = Object.values(this.contexts);
100
+ this.structContext = makeContexts(
101
+ ["distributionChannelContext", "serviceChannelContext"],
102
+ this.contexts,
100
103
  );
101
- if (index > -1) {
102
- this.contexts[index].data.domain_userid = this.uid;
103
- }
104
104
  return this;
105
105
  }
106
106
 
107
107
  // Send a page view event
108
108
  async trackView(event?: PageViewEvent) {
109
109
  if (this.trackPageView) {
110
- await trackPageView({ ...event, context: this.contexts }, [
110
+ await trackPageView({ ...event, context: this.pvContext }, [
111
111
  this.avalancheTrackerName,
112
112
  ]);
113
113
  }
@@ -116,20 +116,28 @@ export class Snowplow {
116
116
 
117
117
  // Send a structured event with contexts
118
118
  async trackEvent(event: StructuredEvent) {
119
- await trackStructEvent({ ...event, context: this.contexts }, [
119
+ await trackStructEvent({ ...event, context: this.structContext }, [
120
120
  this.bronzeAvalancheTrackerName,
121
121
  ]);
122
122
  return this;
123
123
  }
124
124
 
125
125
  // Send a custom event with defined schema and optional contexts
126
- async trackUnstructEvent(event: SelfDescribingJson<Record<string, unknown>>) {
126
+ async trackUnstructEvent(event: SerialisedEvent, contexts?: string[]) {
127
127
  if (!event) {
128
128
  return this;
129
129
  }
130
- await trackSelfDescribingEvent({ event, context: this.contexts }, [
131
- this.avalancheTrackerName,
132
- ]);
130
+
131
+ if (contexts && Array.isArray(contexts) && contexts.length > 0) {
132
+ // Add context to the event
133
+ const context = makeContexts(contexts!, this.contexts);
134
+
135
+ await trackSelfDescribingEvent({ event, context }, [
136
+ this.avalancheTrackerName,
137
+ ]);
138
+ } else {
139
+ await trackSelfDescribingEvent({ event }, [this.avalancheTrackerName]);
140
+ }
133
141
  return this;
134
142
  }
135
143
 
@@ -137,7 +145,7 @@ export class Snowplow {
137
145
  // Add server context to makePayload functions
138
146
  const context = this.serverData;
139
147
 
140
- eventDefinitions.forEach(({ name, type, makePayload }) => {
148
+ eventDefinitions.forEach(({ name, type, makePayload, contexts }) => {
141
149
  // Convert type into relevant function
142
150
  if (type === "structured") {
143
151
  this.addEventHandler(name, (params?: Record<string, unknown>) => {
@@ -150,12 +158,16 @@ export class Snowplow {
150
158
  });
151
159
  } else {
152
160
  this.addEventHandler(name, (params?: Record<string, unknown>) => {
153
- this.trackUnstructEvent(
154
- makePayload({
155
- ...params,
156
- context,
157
- }) as SelfDescribingJson<Record<string, unknown>>,
158
- );
161
+ const payload = makePayload({
162
+ ...params,
163
+ context,
164
+ }) as SerialisedEvent;
165
+
166
+ if (contexts && Array.isArray(contexts) && contexts.length > 0) {
167
+ this.trackUnstructEvent(payload, contexts);
168
+ } else {
169
+ this.trackUnstructEvent(payload);
170
+ }
159
171
  });
160
172
  }
161
173
  });
@@ -176,8 +188,13 @@ export class Snowplow {
176
188
  }
177
189
 
178
190
  trigger(name: string, params?: Record<string, unknown>) {
179
- if (this.eventHandlers[name]) {
180
- this.eventHandlers[name](params);
191
+ const handler = this.eventHandlers[name];
192
+
193
+ if (
194
+ Object.prototype.hasOwnProperty.call(this.eventHandlers, name) &&
195
+ typeof handler === "function"
196
+ ) {
197
+ handler(params);
181
198
  }
182
199
  return this;
183
200
  }
@@ -1,5 +1,5 @@
1
1
  import { pageData } from "../mocks/scripts-mock";
2
- import { getContexts } from "./contexts";
2
+ import { getContexts, makeContexts, updateIdentityContext } from "./contexts";
3
3
  import { getSnowplowConfig } from "./getSnowplowConfig";
4
4
  import { PageDataProps, TrackingProps } from "./types";
5
5
 
@@ -7,9 +7,15 @@ describe("Snowplow Contexts", () => {
7
7
  it("should extract all context records from snowplow props", () => {
8
8
  const snowplowProps = getSnowplowConfig(pageData as PageDataProps);
9
9
  const contexts = getContexts(snowplowProps as TrackingProps);
10
+ const keys = Object.keys(contexts);
10
11
 
11
- expect(contexts).toHaveLength(3);
12
- expect(contexts[0]).toEqual({
12
+ expect(keys).toEqual([
13
+ "pageViewContext",
14
+ "distributionChannelContext",
15
+ "serviceChannelContext",
16
+ ]);
17
+
18
+ expect(contexts["pageViewContext"]).toEqual({
13
19
  schema: "iglu:uk.co.simplybusiness/journey_context/jsonschema/1-0-0",
14
20
  data: {
15
21
  site: "simplybusiness_us",
@@ -22,14 +28,136 @@ describe("Snowplow Contexts", () => {
22
28
  page_step_depth: -1,
23
29
  },
24
30
  });
25
- expect(contexts[1]).toEqual({
31
+
32
+ expect(contexts["distributionChannelContext"]).toEqual({
26
33
  schema:
27
34
  "iglu:com.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
28
35
  data: { service_channel_identifier: "simplybusiness_us" },
29
36
  });
30
- expect(contexts[2]).toEqual({
31
- schema: "iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0",
37
+
38
+ expect(contexts["serviceChannelContext"]).toEqual({
39
+ schema:
40
+ "iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0",
32
41
  data: { service_channel_identifier: "simplybusiness_us" },
33
42
  });
34
43
  });
44
+
45
+ it("should extract empty contexts when config is null", () => {
46
+ const contexts = getContexts(null as unknown as TrackingProps);
47
+ expect(Object.keys(contexts)).toEqual([]);
48
+ });
49
+
50
+ it("should handle primitive values in context", () => {
51
+ const props = {
52
+ someContext: "primitive-value",
53
+ } as unknown as TrackingProps;
54
+
55
+ const contexts = getContexts(props);
56
+ expect(contexts["someContext"]).toEqual({
57
+ data: { service_channel_identifier: "primitive-value" },
58
+ });
59
+ });
60
+
61
+ describe("makeContexts", () => {
62
+ it("should return only the contexts that are present in the keys", () => {
63
+ const contexts = {
64
+ context1: {
65
+ schema: "iglu:com.simplybusiness/context1/jsonschema/1-0-0",
66
+ data: {
67
+ some_field: "some_value",
68
+ },
69
+ },
70
+ context2: {
71
+ schema: "iglu:com.simplybusiness/context2/jsonschema/1-0-0",
72
+ data: {
73
+ some_field: "some_value",
74
+ },
75
+ },
76
+ };
77
+
78
+ const keys = ["context1"];
79
+ const result = makeContexts(keys, contexts);
80
+
81
+ expect(result).toEqual([contexts.context1]);
82
+ });
83
+
84
+ it("should return multiple contexts if present in the keys", () => {
85
+ const contexts = {
86
+ context1: {
87
+ schema: "iglu:com.simplybusiness/context1/jsonschema/1-0-0",
88
+ data: {
89
+ some_field: "some_value",
90
+ },
91
+ },
92
+ context2: {
93
+ schema: "iglu:com.simplybusiness/context2/jsonschema/1-0-0",
94
+ data: {
95
+ some_field: "some_value",
96
+ },
97
+ },
98
+ };
99
+
100
+ const keys = ["context1", "context2"];
101
+ const result = makeContexts(keys, contexts);
102
+
103
+ expect(result).toEqual([contexts.context1, contexts.context2]);
104
+ });
105
+
106
+ it("should return an empty object when no contexts are present in the keys", () => {
107
+ const contexts = {
108
+ context1: {
109
+ schema: "iglu:com.simplybusiness/context1/jsonschema/1-0-0",
110
+ data: {
111
+ some_field: "some_value",
112
+ },
113
+ },
114
+ context2: {
115
+ schema: "iglu:com.simplybusiness/context2/jsonschema/1-0-0",
116
+ data: {
117
+ some_field: "some_value",
118
+ },
119
+ },
120
+ };
121
+
122
+ const result = makeContexts([], contexts);
123
+
124
+ expect(Object.keys(result)).toEqual([]);
125
+ });
126
+ });
127
+
128
+ describe("updateIdentityContext", () => {
129
+ it("should update the domain_userid when identity context exists", () => {
130
+ const contexts = {
131
+ identityContext: {
132
+ schema: "iglu:uk.co.simplybusiness/identity_context/jsonschema/1-0-0",
133
+ data: {
134
+ user_id: "123",
135
+ domain_userid: "old-uid",
136
+ },
137
+ },
138
+ };
139
+
140
+ const updatedContexts = updateIdentityContext(contexts, "new-uid");
141
+
142
+ expect(updatedContexts.identityContext.data.domain_userid).toEqual(
143
+ "new-uid",
144
+ );
145
+ expect(updatedContexts.identityContext.data.user_id).toEqual("123");
146
+ });
147
+
148
+ it("should return the original contexts when identity context doesn't exist", () => {
149
+ const contexts = {
150
+ someOtherContext: {
151
+ schema: "iglu:uk.co.simplybusiness/other_context/jsonschema/1-0-0",
152
+ data: {
153
+ some_field: "some_value",
154
+ },
155
+ },
156
+ };
157
+
158
+ const updatedContexts = updateIdentityContext(contexts, "new-uid");
159
+
160
+ expect(updatedContexts).toEqual(contexts);
161
+ });
162
+ });
35
163
  });
@@ -1,20 +1,50 @@
1
- import { SelfDescribingJson } from "@snowplow/browser-tracker";
2
- import { TrackingProps } from "./types";
3
1
  import { snakeCaseKeys } from "../utils";
2
+ import { SerialisedEvent, TrackingProps } from "./types";
4
3
 
5
4
  export const getContexts = (
6
5
  config: TrackingProps,
7
- ): SelfDescribingJson<Record<string, unknown>>[] => {
8
- const contexts =
9
- config &&
10
- Object.entries(config)
11
- .filter(([key]) => key.includes("Context") && key !== "includeGAContext")
12
- .map(([_key, value]) => {
13
- if (typeof value === "object") {
14
- return snakeCaseKeys(({ ...value }));
15
- }
16
- return snakeCaseKeys({ data: { service_channel_identifier: value } });
17
- });
6
+ ): Record<string, SerialisedEvent> => {
7
+ // Create an object that only contains Context keys
8
+ // and snake_case the keys
9
+ const contextEntries = Object.entries(config || {})
10
+ .filter(([key]) => key.includes("Context") && key !== "includeGAContext")
11
+ .reduce((acc, [key, value]) => {
12
+ if (typeof value === "object") {
13
+ return { ...acc, [key]: snakeCaseKeys({ ...value }) };
14
+ }
15
+ return {
16
+ ...acc,
17
+ [key]: snakeCaseKeys({ data: { service_channel_identifier: value } }),
18
+ };
19
+ }, {});
18
20
 
19
- return contexts as unknown as SelfDescribingJson<Record<string, unknown>>[];
21
+ return contextEntries as Record<string, SerialisedEvent>;
20
22
  };
23
+
24
+ export const updateIdentityContext = (
25
+ contexts: Record<string, SerialisedEvent>,
26
+ uid: unknown,
27
+ ): Record<string, SerialisedEvent> => {
28
+ const index = Object.keys(contexts).findIndex(ctx =>
29
+ contexts[ctx].schema?.includes("identity_context"),
30
+ );
31
+ if (index > -1) {
32
+ const key = Object.keys(contexts)[index];
33
+ return {
34
+ ...contexts,
35
+ [key]: {
36
+ ...contexts[key],
37
+ data: {
38
+ ...contexts[key].data,
39
+ domain_userid: uid,
40
+ },
41
+ },
42
+ };
43
+ }
44
+ return contexts;
45
+ };
46
+
47
+ export const makeContexts = (
48
+ keys: string[],
49
+ config: Record<string, SerialisedEvent>,
50
+ ): SerialisedEvent[] => (keys || []).map(key => config[key]);