@simplybusiness/services 0.14.0 → 0.14.2

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 (46) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/mocks/scripts-mock.js +3 -3
  3. package/dist/cjs/mocks/scripts-mock.js.map +1 -1
  4. package/dist/cjs/snowplow/Snowplow.js +9 -0
  5. package/dist/cjs/snowplow/Snowplow.js.map +1 -1
  6. package/dist/cjs/snowplow/SnowplowContext.js +5 -5
  7. package/dist/cjs/snowplow/SnowplowContext.js.map +1 -1
  8. package/dist/cjs/snowplow/contexts.js +5 -4
  9. package/dist/cjs/snowplow/contexts.js.map +1 -1
  10. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  11. package/dist/cjs/utils/index.js +1 -0
  12. package/dist/cjs/utils/index.js.map +1 -1
  13. package/dist/cjs/utils/isObject.js +15 -0
  14. package/dist/cjs/utils/isObject.js.map +1 -0
  15. package/dist/cjs/utils/text.js +35 -3
  16. package/dist/cjs/utils/text.js.map +1 -1
  17. package/dist/esm/mocks/scripts-mock.js +3 -3
  18. package/dist/esm/mocks/scripts-mock.js.map +1 -1
  19. package/dist/esm/snowplow/Snowplow.js +9 -0
  20. package/dist/esm/snowplow/Snowplow.js.map +1 -1
  21. package/dist/esm/snowplow/SnowplowContext.js +6 -6
  22. package/dist/esm/snowplow/SnowplowContext.js.map +1 -1
  23. package/dist/esm/snowplow/contexts.js +5 -4
  24. package/dist/esm/snowplow/contexts.js.map +1 -1
  25. package/dist/esm/utils/index.js +1 -0
  26. package/dist/esm/utils/index.js.map +1 -1
  27. package/dist/esm/utils/isObject.js +5 -0
  28. package/dist/esm/utils/isObject.js.map +1 -0
  29. package/dist/esm/utils/text.js +22 -0
  30. package/dist/esm/utils/text.js.map +1 -1
  31. package/dist/types/snowplow/Snowplow.d.ts +2 -0
  32. package/dist/types/utils/index.d.ts +1 -0
  33. package/dist/types/utils/isObject.d.ts +1 -0
  34. package/dist/types/utils/isObject.test.d.ts +1 -0
  35. package/dist/types/utils/text.d.ts +2 -0
  36. package/package.json +1 -1
  37. package/src/mocks/scripts-mock.ts +3 -3
  38. package/src/snowplow/Snowplow.ts +11 -0
  39. package/src/snowplow/SnowplowContext.tsx +6 -5
  40. package/src/snowplow/contexts.test.ts +3 -3
  41. package/src/snowplow/contexts.ts +3 -2
  42. package/src/utils/index.ts +1 -0
  43. package/src/utils/isObject.test.tsx +35 -0
  44. package/src/utils/isObject.tsx +8 -0
  45. package/src/utils/text.test.ts +60 -1
  46. package/src/utils/text.ts +39 -0
@@ -2,12 +2,44 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "snakeCase", {
6
- enumerable: true,
7
- get: function() {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ camelToSnakeCase: function() {
13
+ return camelToSnakeCase;
14
+ },
15
+ snakeCase: function() {
8
16
  return snakeCase;
17
+ },
18
+ snakeCaseKeys: function() {
19
+ return snakeCaseKeys;
9
20
  }
10
21
  });
22
+ const _ = require(".");
23
+ const snakeCaseKeys = (object)=>Object.entries(object || {}).reduce((acc, [key, value])=>{
24
+ const newKey = camelToSnakeCase(key);
25
+ if (Array.isArray(value) && value.every(_.isObject)) {
26
+ return {
27
+ ...acc,
28
+ [newKey]: value.map((v)=>snakeCaseKeys(v))
29
+ };
30
+ }
31
+ if ((0, _.isObject)(value)) {
32
+ return {
33
+ ...acc,
34
+ [newKey]: snakeCaseKeys(value)
35
+ };
36
+ }
37
+ return {
38
+ ...acc,
39
+ [newKey]: value
40
+ };
41
+ }, {});
11
42
  const snakeCase = (text = "")=>text.toLowerCase().replace(/ /g, "_");
43
+ const camelToSnakeCase = (text)=>text.charAt(0).toLowerCase() + text.slice(1).replace(/(\[.*?\])|[A-Z]/g, (match, group)=>group ? match : `_${match.toLowerCase()}`);
12
44
 
13
45
  //# sourceMappingURL=text.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/text.ts"],"sourcesContent":["export const snakeCase = (text = \"\"): string =>\n text.toLowerCase().replace(/ /g, \"_\");\n"],"names":["snakeCase","text","toLowerCase","replace"],"mappings":";;;;+BAAaA;;;eAAAA;;;AAAN,MAAMA,YAAY,CAACC,OAAO,EAAE,GACjCA,KAAKC,WAAW,GAAGC,OAAO,CAAC,MAAM"}
1
+ {"version":3,"sources":["../../../src/utils/text.ts"],"sourcesContent":["import { isObject } from \".\";\n\n// Deeply converts keys in an object to snake_case\nexport const snakeCaseKeys = (object: Record<string, unknown>) =>\n Object.entries(object || {}).reduce(\n (acc: Record<string, unknown>, [key, value]): Record<string, unknown> => {\n const newKey = camelToSnakeCase(key);\n\n if (Array.isArray(value) && value.every(isObject)) {\n return {\n ...acc,\n [newKey]: value.map(v => snakeCaseKeys(v)),\n };\n }\n\n if (isObject(value)) {\n return {\n ...acc,\n [newKey]: snakeCaseKeys(value),\n };\n }\n\n return {\n ...acc,\n [newKey]: value,\n };\n },\n {},\n );\n\nexport const snakeCase = (text = \"\"): string =>\n text.toLowerCase().replace(/ /g, \"_\");\n\nexport const camelToSnakeCase = (text: string) =>\n text.charAt(0).toLowerCase() +\n text\n .slice(1)\n .replace(/(\\[.*?\\])|[A-Z]/g, (match, group) =>\n group ? match : `_${match.toLowerCase()}`,\n );\n\n"],"names":["camelToSnakeCase","snakeCase","snakeCaseKeys","object","Object","entries","reduce","acc","key","value","newKey","Array","isArray","every","isObject","map","v","text","toLowerCase","replace","charAt","slice","match","group"],"mappings":";;;;;;;;;;;IAiCaA,gBAAgB;eAAhBA;;IAHAC,SAAS;eAATA;;IA3BAC,aAAa;eAAbA;;;kBAHY;AAGlB,MAAMA,gBAAgB,CAACC,SAC5BC,OAAOC,OAAO,CAACF,UAAU,CAAC,GAAGG,MAAM,CACjC,CAACC,KAA8B,CAACC,KAAKC,MAAM;QACzC,MAAMC,SAASV,iBAAiBQ;QAEhC,IAAIG,MAAMC,OAAO,CAACH,UAAUA,MAAMI,KAAK,CAACC,UAAQ,GAAG;YACjD,OAAO;gBACL,GAAGP,GAAG;gBACN,CAACG,OAAO,EAAED,MAAMM,GAAG,CAACC,CAAAA,IAAKd,cAAcc;YACzC;QACF;QAEA,IAAIF,IAAAA,UAAQ,EAACL,QAAQ;YACnB,OAAO;gBACL,GAAGF,GAAG;gBACN,CAACG,OAAO,EAAER,cAAcO;YAC1B;QACF;QAEA,OAAO;YACL,GAAGF,GAAG;YACN,CAACG,OAAO,EAAED;QACZ;IACF,GACA,CAAC;AAGE,MAAMR,YAAY,CAACgB,OAAO,EAAE,GACjCA,KAAKC,WAAW,GAAGC,OAAO,CAAC,MAAM;AAE5B,MAAMnB,mBAAmB,CAACiB,OAC/BA,KAAKG,MAAM,CAAC,GAAGF,WAAW,KAC1BD,KACGI,KAAK,CAAC,GACNF,OAAO,CAAC,oBAAoB,CAACG,OAAOC,QACnCA,QAAQD,QAAQ,CAAC,CAAC,EAAEA,MAAMJ,WAAW,IAAI"}
@@ -16,7 +16,7 @@ export const pageData = {
16
16
  trackActivity: true,
17
17
  trackPageView: true,
18
18
  pageViewContext: {
19
- schema: "igluuk.co.simplybusiness/journey_context/jsonschema/1-0-0",
19
+ schema: "iglu:uk.co.simplybusiness/journey_context/jsonschema/1-0-0",
20
20
  data: {
21
21
  site: "simplybusiness_us",
22
22
  vertical: "usa",
@@ -29,13 +29,13 @@ export const pageData = {
29
29
  }
30
30
  },
31
31
  distributionChannelContext: {
32
- schema: "iglucom.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
32
+ schema: "iglu:com.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
33
33
  data: {
34
34
  service_channel_identifier: "simplybusiness_us"
35
35
  }
36
36
  },
37
37
  serviceChannelContext: {
38
- schema: "iglucom.simplybusiness/service_channel_context/jsonschema/1-0-0",
38
+ schema: "iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0",
39
39
  data: {
40
40
  service_channel_identifier: "simplybusiness_us"
41
41
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/mocks/scripts-mock.ts"],"sourcesContent":["export const pageData = {\n scripts: [\n {\n metadata: { name: \"snowplow\" },\n props: {\n uid: \"49a449d8aaa9dd58f90a623d4b9dbcae235bf92a\",\n cookieDomain: \"\",\n // TODO: Change this url to \"http://localhost:8000\" for local development\n avalancheCollector:\n \"https://snowplow-collector-staging.simplybusiness.com\",\n appId: \"us-chopin\",\n includeGAContext: true,\n eventMethod: \"post\",\n postPath: \"/com.simplybusiness/events\",\n trackActivity: true,\n trackPageView: true,\n pageViewContext: {\n schema: \"igluuk.co.simplybusiness/journey_context/jsonschema/1-0-0\",\n data: {\n site: \"simplybusiness_us\",\n vertical: \"usa\",\n super_segment: \"Unknown\",\n primary_detail: \"Lawn care services\",\n journey_name: \"usa\",\n journey_id: \"666ff79d90abbc3582e496da\",\n page_step_name: \"thank_you_ssr\",\n page_step_depth: -1,\n },\n },\n distributionChannelContext: {\n schema:\n \"iglucom.simplybusiness/distribution_channel_context/jsonschema/1-0-0\",\n data: { service_channel_identifier: \"simplybusiness_us\" },\n },\n serviceChannelContext: {\n schema:\n \"iglucom.simplybusiness/service_channel_context/jsonschema/1-0-0\",\n data: { service_channel_identifier: \"simplybusiness_us\" },\n },\n forceSecureTracker: true,\n },\n },\n ],\n};\n"],"names":["pageData","scripts","metadata","name","props","uid","cookieDomain","avalancheCollector","appId","includeGAContext","eventMethod","postPath","trackActivity","trackPageView","pageViewContext","schema","data","site","vertical","super_segment","primary_detail","journey_name","journey_id","page_step_name","page_step_depth","distributionChannelContext","service_channel_identifier","serviceChannelContext","forceSecureTracker"],"mappings":"AAAA,OAAO,MAAMA,WAAW;IACtBC,SAAS;QACP;YACEC,UAAU;gBAAEC,MAAM;YAAW;YAC7BC,OAAO;gBACLC,KAAK;gBACLC,cAAc;gBACd,yEAAyE;gBACzEC,oBACE;gBACFC,OAAO;gBACPC,kBAAkB;gBAClBC,aAAa;gBACbC,UAAU;gBACVC,eAAe;gBACfC,eAAe;gBACfC,iBAAiB;oBACfC,QAAQ;oBACRC,MAAM;wBACJC,MAAM;wBACNC,UAAU;wBACVC,eAAe;wBACfC,gBAAgB;wBAChBC,cAAc;wBACdC,YAAY;wBACZC,gBAAgB;wBAChBC,iBAAiB,CAAC;oBACpB;gBACF;gBACAC,4BAA4B;oBAC1BV,QACE;oBACFC,MAAM;wBAAEU,4BAA4B;oBAAoB;gBAC1D;gBACAC,uBAAuB;oBACrBZ,QACE;oBACFC,MAAM;wBAAEU,4BAA4B;oBAAoB;gBAC1D;gBACAE,oBAAoB;YACtB;QACF;KACD;AACH,EAAE"}
1
+ {"version":3,"sources":["../../../src/mocks/scripts-mock.ts"],"sourcesContent":["export const pageData = {\n scripts: [\n {\n metadata: { name: \"snowplow\" },\n props: {\n uid: \"49a449d8aaa9dd58f90a623d4b9dbcae235bf92a\",\n cookieDomain: \"\",\n // TODO: Change this url to \"http://localhost:8000\" for local development\n avalancheCollector:\n \"https://snowplow-collector-staging.simplybusiness.com\",\n appId: \"us-chopin\",\n includeGAContext: true,\n eventMethod: \"post\",\n postPath: \"/com.simplybusiness/events\",\n trackActivity: true,\n trackPageView: true,\n pageViewContext: {\n schema: \"iglu:uk.co.simplybusiness/journey_context/jsonschema/1-0-0\",\n data: {\n site: \"simplybusiness_us\",\n vertical: \"usa\",\n super_segment: \"Unknown\",\n primary_detail: \"Lawn care services\",\n journey_name: \"usa\",\n journey_id: \"666ff79d90abbc3582e496da\",\n page_step_name: \"thank_you_ssr\",\n page_step_depth: -1,\n },\n },\n distributionChannelContext: {\n schema:\n \"iglu:com.simplybusiness/distribution_channel_context/jsonschema/1-0-0\",\n data: { service_channel_identifier: \"simplybusiness_us\" },\n },\n serviceChannelContext: {\n schema:\n \"iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0\",\n data: { service_channel_identifier: \"simplybusiness_us\" },\n },\n forceSecureTracker: true,\n },\n },\n ],\n};\n"],"names":["pageData","scripts","metadata","name","props","uid","cookieDomain","avalancheCollector","appId","includeGAContext","eventMethod","postPath","trackActivity","trackPageView","pageViewContext","schema","data","site","vertical","super_segment","primary_detail","journey_name","journey_id","page_step_name","page_step_depth","distributionChannelContext","service_channel_identifier","serviceChannelContext","forceSecureTracker"],"mappings":"AAAA,OAAO,MAAMA,WAAW;IACtBC,SAAS;QACP;YACEC,UAAU;gBAAEC,MAAM;YAAW;YAC7BC,OAAO;gBACLC,KAAK;gBACLC,cAAc;gBACd,yEAAyE;gBACzEC,oBACE;gBACFC,OAAO;gBACPC,kBAAkB;gBAClBC,aAAa;gBACbC,UAAU;gBACVC,eAAe;gBACfC,eAAe;gBACfC,iBAAiB;oBACfC,QAAQ;oBACRC,MAAM;wBACJC,MAAM;wBACNC,UAAU;wBACVC,eAAe;wBACfC,gBAAgB;wBAChBC,cAAc;wBACdC,YAAY;wBACZC,gBAAgB;wBAChBC,iBAAiB,CAAC;oBACpB;gBACF;gBACAC,4BAA4B;oBAC1BV,QACE;oBACFC,MAAM;wBAAEU,4BAA4B;oBAAoB;gBAC1D;gBACAC,uBAAuB;oBACrBZ,QACE;oBACFC,MAAM;wBAAEU,4BAA4B;oBAAoB;gBAC1D;gBACAE,oBAAoB;YACtB;QACF;KACD;AACH,EAAE"}
@@ -100,6 +100,12 @@ import { newTracker, setCookiePath, setUserId, trackPageView, trackSelfDescribin
100
100
  }
101
101
  return this;
102
102
  }
103
+ static getInstance(props) {
104
+ if (!Snowplow.instance) {
105
+ Snowplow.instance = new Snowplow(props);
106
+ }
107
+ return Snowplow.instance;
108
+ }
103
109
  constructor(props){
104
110
  var _props_pageViewContext;
105
111
  _define_property(this, "avalancheTrackerName", "sb-ava");
@@ -138,7 +144,10 @@ import { newTracker, setCookiePath, setUserId, trackPageView, trackSelfDescribin
138
144
  if (uid) {
139
145
  setUserId(uid);
140
146
  }
147
+ // Create a singleton instance
148
+ Snowplow.instance = this;
141
149
  }
142
150
  }
151
+ _define_property(Snowplow, "instance", void 0);
143
152
 
144
153
  //# sourceMappingURL=Snowplow.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/snowplow/Snowplow.ts"],"sourcesContent":["import {\n SelfDescribingJson,\n StructuredEvent,\n TrackerConfiguration,\n newTracker,\n setCookiePath,\n setUserId,\n trackPageView,\n trackSelfDescribingEvent,\n trackStructEvent,\n} from \"@snowplow/browser-tracker\";\nimport { EventDefinition, TrackingProps } from \"./types\";\n\nexport type FrontOfficeStructuredEvent = StructuredEvent & {\n serviceChannelIdentifier: string;\n};\n\n/**\n * This class is an abstraction which wraps Snowplow\n * and exposes common methods with other services:\n * - trackEvent : sends a standard payload\n * - trackUnstructEvent : sends a payload for custom schema\n */\nexport class Snowplow {\n avalancheTrackerName = \"sb-ava\";\n\n bronzeAvalancheTrackerName = \"sb-ava-br\";\n\n pvAvalancheTrackerName = \"sb-ava-pv\";\n\n uid: unknown = \"\";\n\n trackPageView: boolean = false;\n\n contexts: SelfDescribingJson<Record<string, unknown>>[] = [];\n\n serverData: Record<string, unknown> = {};\n\n eventHandlers: Record<string, (params?: Record<string, unknown>) => void> =\n {};\n\n constructor(props?: TrackingProps) {\n if (!props) return;\n\n const {\n appId,\n cookieDomain,\n avalancheCollector,\n eventMethod,\n uid,\n postPath,\n // includeGAContext,\n // trackActivity,\n trackPageView: tpv,\n } = props;\n this.uid = uid;\n this.trackPageView = tpv;\n this.serverData = props?.pageViewContext?.data || {};\n\n // Set options\n const stateStorageStrategy = \"cookieAndLocalStorage\";\n const baseOptions: TrackerConfiguration = {\n appId,\n cookieDomain,\n eventMethod,\n stateStorageStrategy,\n postPath,\n };\n // Initialize trackers\n newTracker(this.avalancheTrackerName, avalancheCollector, baseOptions);\n\n newTracker(\n this.bronzeAvalancheTrackerName,\n avalancheCollector,\n baseOptions,\n );\n\n // Page view tracker\n newTracker(this.pvAvalancheTrackerName, avalancheCollector, {\n ...baseOptions,\n eventMethod: eventMethod === \"post\" ? \"beacon\" : eventMethod,\n });\n\n setCookiePath(\"/\");\n if (uid) {\n setUserId(uid);\n }\n }\n\n setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]) {\n this.contexts = contexts;\n // Update identity context\n const index = this.contexts?.findIndex(ctx =>\n ctx.schema?.includes(\"identity_context\"),\n );\n if (index > -1) {\n this.contexts[index].data.domain_userid = this.uid;\n }\n return this;\n }\n\n // Send a page view event\n trackView() {\n if (this.trackPageView) {\n trackPageView({ context: this.contexts });\n }\n return this;\n }\n\n // Send a structured event with contexts\n async trackEvent(event: StructuredEvent) {\n await trackStructEvent({ ...event, context: this.contexts }, [\n this.bronzeAvalancheTrackerName,\n ]);\n return this;\n }\n\n // Send a custom event with defined schema and optional contexts\n async trackUnstructEvent(event: SelfDescribingJson<Record<string, unknown>>) {\n if (!event) {\n return this;\n }\n await trackSelfDescribingEvent({ event, context: this.contexts }, [\n this.avalancheTrackerName,\n ]);\n return this;\n }\n\n addEventHandlers(eventDefinitions: EventDefinition[]) {\n // Add server context to makePayload functions\n const context = this.serverData;\n\n eventDefinitions.forEach(({ name, type, makePayload }) => {\n // Convert type into relevant function\n if (type === \"structured\") {\n this.addEventHandler(name, (params?: Record<string, unknown>) => {\n this.trackEvent(\n makePayload({\n ...params,\n context,\n }) as StructuredEvent,\n );\n });\n } else {\n this.addEventHandler(name, (params?: Record<string, unknown>) => {\n this.trackUnstructEvent(\n makePayload({\n ...params,\n context,\n }) as SelfDescribingJson<Record<string, unknown>>,\n );\n });\n }\n });\n return this;\n }\n\n private addEventHandler(\n name: string,\n handler: (params?: Record<string, unknown>) => void,\n ) {\n this.eventHandlers[name] = handler;\n return this;\n }\n\n private removeEventHandler(name: string) {\n delete this.eventHandlers[name];\n return this;\n }\n\n trigger(name: string, params?: Record<string, unknown>) {\n if (this.eventHandlers[name]) {\n this.eventHandlers[name](params);\n }\n return this;\n }\n}\n"],"names":["newTracker","setCookiePath","setUserId","trackPageView","trackSelfDescribingEvent","trackStructEvent","Snowplow","setContexts","contexts","index","findIndex","ctx","schema","includes","data","domain_userid","uid","trackView","context","trackEvent","event","bronzeAvalancheTrackerName","trackUnstructEvent","avalancheTrackerName","addEventHandlers","eventDefinitions","serverData","forEach","name","type","makePayload","addEventHandler","params","handler","eventHandlers","removeEventHandler","trigger","constructor","props","pvAvalancheTrackerName","appId","cookieDomain","avalancheCollector","eventMethod","postPath","tpv","pageViewContext","stateStorageStrategy","baseOptions"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/snowplow/Snowplow.ts"],"sourcesContent":["import {\n SelfDescribingJson,\n StructuredEvent,\n TrackerConfiguration,\n newTracker,\n setCookiePath,\n setUserId,\n trackPageView,\n trackSelfDescribingEvent,\n trackStructEvent,\n} from \"@snowplow/browser-tracker\";\nimport { EventDefinition, TrackingProps } from \"./types\";\n\nexport type FrontOfficeStructuredEvent = StructuredEvent & {\n serviceChannelIdentifier: string;\n};\n\n/**\n * This class is an abstraction which wraps Snowplow\n * and exposes common methods with other services:\n * - trackEvent : sends a standard payload\n * - trackUnstructEvent : sends a payload for custom schema\n */\nexport class Snowplow {\n avalancheTrackerName = \"sb-ava\";\n\n bronzeAvalancheTrackerName = \"sb-ava-br\";\n\n pvAvalancheTrackerName = \"sb-ava-pv\";\n\n uid: unknown = \"\";\n\n trackPageView: boolean = false;\n\n contexts: SelfDescribingJson<Record<string, unknown>>[] = [];\n\n serverData: Record<string, unknown> = {};\n\n eventHandlers: Record<string, (params?: Record<string, unknown>) => void> =\n {};\n\n static instance: Snowplow | undefined;\n\n constructor(props?: TrackingProps) {\n if (!props) return;\n\n const {\n appId,\n cookieDomain,\n avalancheCollector,\n eventMethod,\n uid,\n postPath,\n // includeGAContext,\n // trackActivity,\n trackPageView: tpv,\n } = props;\n this.uid = uid;\n this.trackPageView = tpv;\n this.serverData = props?.pageViewContext?.data || {};\n\n // Set options\n const stateStorageStrategy = \"cookieAndLocalStorage\";\n const baseOptions: TrackerConfiguration = {\n appId,\n cookieDomain,\n eventMethod,\n stateStorageStrategy,\n postPath,\n };\n // Initialize trackers\n newTracker(this.avalancheTrackerName, avalancheCollector, baseOptions);\n\n newTracker(\n this.bronzeAvalancheTrackerName,\n avalancheCollector,\n baseOptions,\n );\n\n // Page view tracker\n newTracker(this.pvAvalancheTrackerName, avalancheCollector, {\n ...baseOptions,\n eventMethod: eventMethod === \"post\" ? \"beacon\" : eventMethod,\n });\n\n setCookiePath(\"/\");\n if (uid) {\n setUserId(uid);\n }\n // Create a singleton instance\n Snowplow.instance = this;\n }\n\n setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]) {\n this.contexts = contexts;\n // Update identity context\n const index = this.contexts?.findIndex(ctx =>\n ctx.schema?.includes(\"identity_context\"),\n );\n if (index > -1) {\n this.contexts[index].data.domain_userid = this.uid;\n }\n return this;\n }\n\n // Send a page view event\n trackView() {\n if (this.trackPageView) {\n trackPageView({ context: this.contexts });\n }\n return this;\n }\n\n // Send a structured event with contexts\n async trackEvent(event: StructuredEvent) {\n await trackStructEvent({ ...event, context: this.contexts }, [\n this.bronzeAvalancheTrackerName,\n ]);\n return this;\n }\n\n // Send a custom event with defined schema and optional contexts\n async trackUnstructEvent(event: SelfDescribingJson<Record<string, unknown>>) {\n if (!event) {\n return this;\n }\n await trackSelfDescribingEvent({ event, context: this.contexts }, [\n this.avalancheTrackerName,\n ]);\n return this;\n }\n\n addEventHandlers(eventDefinitions: EventDefinition[]) {\n // Add server context to makePayload functions\n const context = this.serverData;\n\n eventDefinitions.forEach(({ name, type, makePayload }) => {\n // Convert type into relevant function\n if (type === \"structured\") {\n this.addEventHandler(name, (params?: Record<string, unknown>) => {\n this.trackEvent(\n makePayload({\n ...params,\n context,\n }) as StructuredEvent,\n );\n });\n } else {\n this.addEventHandler(name, (params?: Record<string, unknown>) => {\n this.trackUnstructEvent(\n makePayload({\n ...params,\n context,\n }) as SelfDescribingJson<Record<string, unknown>>,\n );\n });\n }\n });\n return this;\n }\n\n private addEventHandler(\n name: string,\n handler: (params?: Record<string, unknown>) => void,\n ) {\n this.eventHandlers[name] = handler;\n return this;\n }\n\n private removeEventHandler(name: string) {\n delete this.eventHandlers[name];\n return this;\n }\n\n trigger(name: string, params?: Record<string, unknown>) {\n if (this.eventHandlers[name]) {\n this.eventHandlers[name](params);\n }\n return this;\n }\n\n static getInstance(props?: TrackingProps) {\n if (!Snowplow.instance) {\n Snowplow.instance = new Snowplow(props);\n }\n return Snowplow.instance;\n }\n}\n"],"names":["newTracker","setCookiePath","setUserId","trackPageView","trackSelfDescribingEvent","trackStructEvent","Snowplow","setContexts","contexts","index","findIndex","ctx","schema","includes","data","domain_userid","uid","trackView","context","trackEvent","event","bronzeAvalancheTrackerName","trackUnstructEvent","avalancheTrackerName","addEventHandlers","eventDefinitions","serverData","forEach","name","type","makePayload","addEventHandler","params","handler","eventHandlers","removeEventHandler","trigger","getInstance","props","instance","constructor","pvAvalancheTrackerName","appId","cookieDomain","avalancheCollector","eventMethod","postPath","tpv","pageViewContext","stateStorageStrategy","baseOptions"],"mappings":"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"}
@@ -1,19 +1,19 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */ import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect, useMemo, useState } from "react";
2
+ import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { getContexts } from "./contexts";
4
4
  import { eventDefinitions } from "./event-definitions";
5
5
  import { Snowplow } from "./Snowplow";
6
6
  const SnowplowContext = /*#__PURE__*/ createContext(null);
7
7
  export const SnowplowProvider = ({ scripts, children })=>{
8
8
  const [config, _setConfig] = useState(scripts);
9
- const [snowplow, setSnowplow] = useState(new Snowplow(config));
9
+ const snowplow = useRef(Snowplow.getInstance(config));
10
10
  // Attach event handlers and set contexts
11
11
  useEffect(()=>{
12
- if (snowplow && scripts) {
12
+ if (snowplow.current && scripts) {
13
13
  const contexts = getContexts(config);
14
- snowplow.setContexts(contexts).addEventHandlers(eventDefinitions);
14
+ snowplow.current.setContexts(contexts).addEventHandlers(eventDefinitions);
15
15
  // Send page view event
16
- if (config.trackPageView) snowplow.trackView();
16
+ if (config.trackPageView) snowplow.current.trackView();
17
17
  }
18
18
  }, [
19
19
  config,
@@ -22,7 +22,7 @@ export const SnowplowProvider = ({ scripts, children })=>{
22
22
  ]);
23
23
  const value = useMemo(()=>({
24
24
  config,
25
- snowplow
25
+ snowplow: snowplow.current
26
26
  }), [
27
27
  config,
28
28
  snowplow
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/snowplow/SnowplowContext.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\nimport { getContexts } from \"./contexts\";\nimport { eventDefinitions } from \"./event-definitions\";\nimport { Snowplow } from \"./Snowplow\";\nimport { EventDefinition, TrackingProps } from \"./types\";\n\nexport interface SnowplowContextInterface {\n config: TrackingProps;\n snowplow?: Snowplow;\n}\n\nconst SnowplowContext = createContext<SnowplowContextInterface | null>(null);\n\ntype ProviderProps = {\n scripts: TrackingProps;\n children: ReactNode;\n};\n\nexport const SnowplowProvider = ({ scripts, children }: ProviderProps) => {\n const [config, _setConfig] = useState<TrackingProps>(scripts);\n const [snowplow, setSnowplow] = useState<Snowplow>(new Snowplow(config));\n\n // Attach event handlers and set contexts\n useEffect(() => {\n if (snowplow && scripts) {\n const contexts = getContexts(config);\n\n snowplow\n .setContexts(contexts)\n .addEventHandlers(eventDefinitions as EventDefinition[]);\n // Send page view event\n if (config.trackPageView) snowplow.trackView();\n }\n }, [config, snowplow, scripts]);\n\n const value: SnowplowContextInterface = useMemo(\n () => ({\n config,\n snowplow,\n }),\n [config, snowplow],\n );\n\n return (\n <SnowplowContext.Provider value={value}>\n {children}\n </SnowplowContext.Provider>\n );\n};\n\nexport function useSnowplowContext() {\n const context = useContext(SnowplowContext);\n\n if (!context) {\n throw new Error(\n \"useSnowplowContext must be used inside a `SnowplowProvider`\",\n );\n }\n return context;\n}\n"],"names":["createContext","useContext","useEffect","useMemo","useState","getContexts","eventDefinitions","Snowplow","SnowplowContext","SnowplowProvider","scripts","children","config","_setConfig","snowplow","setSnowplow","contexts","setContexts","addEventHandlers","trackPageView","trackView","value","Provider","useSnowplowContext","context","Error"],"mappings":"AAAA,oDAAoD;AACpD,SACEA,aAAa,EACbC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,QAAQ,QAEH,QAAQ;AACf,SAASC,WAAW,QAAQ,aAAa;AACzC,SAASC,gBAAgB,QAAQ,sBAAsB;AACvD,SAASC,QAAQ,QAAQ,aAAa;AAQtC,MAAMC,gCAAkBR,cAA+C;AAOvE,OAAO,MAAMS,mBAAmB,CAAC,EAAEC,OAAO,EAAEC,QAAQ,EAAiB;IACnE,MAAM,CAACC,QAAQC,WAAW,GAAGT,SAAwBM;IACrD,MAAM,CAACI,UAAUC,YAAY,GAAGX,SAAmB,IAAIG,SAASK;IAEhE,yCAAyC;IACzCV,UAAU;QACR,IAAIY,YAAYJ,SAAS;YACvB,MAAMM,WAAWX,YAAYO;YAE7BE,SACGG,WAAW,CAACD,UACZE,gBAAgB,CAACZ;YACpB,uBAAuB;YACvB,IAAIM,OAAOO,aAAa,EAAEL,SAASM,SAAS;QAC9C;IACF,GAAG;QAACR;QAAQE;QAAUJ;KAAQ;IAE9B,MAAMW,QAAkClB,QACtC,IAAO,CAAA;YACLS;YACAE;QACF,CAAA,GACA;QAACF;QAAQE;KAAS;IAGpB,qBACE,KAACN,gBAAgBc,QAAQ;QAACD,OAAOA;kBAC9BV;;AAGP,EAAE;AAEF,OAAO,SAASY;IACd,MAAMC,UAAUvB,WAAWO;IAE3B,IAAI,CAACgB,SAAS;QACZ,MAAM,IAAIC,MACR;IAEJ;IACA,OAAOD;AACT"}
1
+ {"version":3,"sources":["../../../src/snowplow/SnowplowContext.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { getContexts } from \"./contexts\";\nimport { eventDefinitions } from \"./event-definitions\";\nimport { Snowplow } from \"./Snowplow\";\nimport { EventDefinition, TrackingProps } from \"./types\";\n\nexport interface SnowplowContextInterface {\n config: TrackingProps;\n snowplow?: Snowplow;\n}\n\nconst SnowplowContext = createContext<SnowplowContextInterface | null>(null);\n\ntype ProviderProps = {\n scripts: TrackingProps;\n children: ReactNode;\n};\n\nexport const SnowplowProvider = ({ scripts, children }: ProviderProps) => {\n const [config, _setConfig] = useState<TrackingProps>(scripts);\n const snowplow = useRef<Snowplow>(Snowplow.getInstance(config));\n\n // Attach event handlers and set contexts\n useEffect(() => {\n if (snowplow.current && scripts) {\n const contexts = getContexts(config);\n\n snowplow.current\n .setContexts(contexts)\n .addEventHandlers(eventDefinitions as EventDefinition[]);\n // Send page view event\n if (config.trackPageView) snowplow.current.trackView();\n }\n }, [config, snowplow, scripts]);\n\n const value: SnowplowContextInterface = useMemo(\n () => ({\n config,\n snowplow: snowplow.current,\n }),\n [config, snowplow],\n );\n\n return (\n <SnowplowContext.Provider value={value}>\n {children}\n </SnowplowContext.Provider>\n );\n};\n\nexport function useSnowplowContext() {\n const context = useContext(SnowplowContext);\n\n if (!context) {\n throw new Error(\n \"useSnowplowContext must be used inside a `SnowplowProvider`\",\n );\n }\n return context;\n}\n"],"names":["createContext","useContext","useEffect","useMemo","useRef","useState","getContexts","eventDefinitions","Snowplow","SnowplowContext","SnowplowProvider","scripts","children","config","_setConfig","snowplow","getInstance","current","contexts","setContexts","addEventHandlers","trackPageView","trackView","value","Provider","useSnowplowContext","context","Error"],"mappings":"AAAA,oDAAoD;AACpD,SACEA,aAAa,EACbC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,MAAM,EACNC,QAAQ,QAEH,QAAQ;AACf,SAASC,WAAW,QAAQ,aAAa;AACzC,SAASC,gBAAgB,QAAQ,sBAAsB;AACvD,SAASC,QAAQ,QAAQ,aAAa;AAQtC,MAAMC,gCAAkBT,cAA+C;AAOvE,OAAO,MAAMU,mBAAmB,CAAC,EAAEC,OAAO,EAAEC,QAAQ,EAAiB;IACnE,MAAM,CAACC,QAAQC,WAAW,GAAGT,SAAwBM;IACrD,MAAMI,WAAWX,OAAiBI,SAASQ,WAAW,CAACH;IAEvD,yCAAyC;IACzCX,UAAU;QACR,IAAIa,SAASE,OAAO,IAAIN,SAAS;YAC/B,MAAMO,WAAWZ,YAAYO;YAE7BE,SAASE,OAAO,CACbE,WAAW,CAACD,UACZE,gBAAgB,CAACb;YACpB,uBAAuB;YACvB,IAAIM,OAAOQ,aAAa,EAAEN,SAASE,OAAO,CAACK,SAAS;QACtD;IACF,GAAG;QAACT;QAAQE;QAAUJ;KAAQ;IAE9B,MAAMY,QAAkCpB,QACtC,IAAO,CAAA;YACLU;YACAE,UAAUA,SAASE,OAAO;QAC5B,CAAA,GACA;QAACJ;QAAQE;KAAS;IAGpB,qBACE,KAACN,gBAAgBe,QAAQ;QAACD,OAAOA;kBAC9BX;;AAGP,EAAE;AAEF,OAAO,SAASa;IACd,MAAMC,UAAUzB,WAAWQ;IAE3B,IAAI,CAACiB,SAAS;QACZ,MAAM,IAAIC,MACR;IAEJ;IACA,OAAOD;AACT"}
@@ -1,15 +1,16 @@
1
+ import { snakeCaseKeys } from "../utils";
1
2
  export const getContexts = (config)=>{
2
3
  const contexts = config && Object.entries(config).filter(([key])=>key.includes("Context") && key !== "includeGAContext").map(([_key, value])=>{
3
4
  if (typeof value === "object") {
4
- return {
5
+ return snakeCaseKeys({
5
6
  ...value
6
- };
7
+ });
7
8
  }
8
- return {
9
+ return snakeCaseKeys({
9
10
  data: {
10
11
  service_channel_identifier: value
11
12
  }
12
- };
13
+ });
13
14
  });
14
15
  return contexts;
15
16
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/snowplow/contexts.ts"],"sourcesContent":["import { SelfDescribingJson } from \"@snowplow/browser-tracker\";\nimport { TrackingProps } from \"./types\";\n\nexport const getContexts = (\n config: TrackingProps,\n): SelfDescribingJson<Record<string, unknown>>[] => {\n const contexts =\n config &&\n Object.entries(config)\n .filter(([key]) => key.includes(\"Context\") && key !== \"includeGAContext\")\n .map(([_key, value]) => {\n if (typeof value === \"object\") {\n return { ...value };\n }\n return { data: { service_channel_identifier: value } };\n });\n\n return contexts as unknown as SelfDescribingJson<Record<string, unknown>>[];\n};\n"],"names":["getContexts","config","contexts","Object","entries","filter","key","includes","map","_key","value","data","service_channel_identifier"],"mappings":"AAGA,OAAO,MAAMA,cAAc,CACzBC;IAEA,MAAMC,WACJD,UACAE,OAAOC,OAAO,CAACH,QACZI,MAAM,CAAC,CAAC,CAACC,IAAI,GAAKA,IAAIC,QAAQ,CAAC,cAAcD,QAAQ,oBACrDE,GAAG,CAAC,CAAC,CAACC,MAAMC,MAAM;QACjB,IAAI,OAAOA,UAAU,UAAU;YAC7B,OAAO;gBAAE,GAAGA,KAAK;YAAC;QACpB;QACA,OAAO;YAAEC,MAAM;gBAAEC,4BAA4BF;YAAM;QAAE;IACvD;IAEJ,OAAOR;AACT,EAAE"}
1
+ {"version":3,"sources":["../../../src/snowplow/contexts.ts"],"sourcesContent":["import { SelfDescribingJson } from \"@snowplow/browser-tracker\";\nimport { TrackingProps } from \"./types\";\nimport { snakeCaseKeys } from \"../utils\";\n\nexport const getContexts = (\n config: TrackingProps,\n): SelfDescribingJson<Record<string, unknown>>[] => {\n const contexts =\n config &&\n Object.entries(config)\n .filter(([key]) => key.includes(\"Context\") && key !== \"includeGAContext\")\n .map(([_key, value]) => {\n if (typeof value === \"object\") {\n return snakeCaseKeys(({ ...value }));\n }\n return snakeCaseKeys({ data: { service_channel_identifier: value } });\n });\n\n return contexts as unknown as SelfDescribingJson<Record<string, unknown>>[];\n};\n"],"names":["snakeCaseKeys","getContexts","config","contexts","Object","entries","filter","key","includes","map","_key","value","data","service_channel_identifier"],"mappings":"AAEA,SAASA,aAAa,QAAQ,WAAW;AAEzC,OAAO,MAAMC,cAAc,CACzBC;IAEA,MAAMC,WACJD,UACAE,OAAOC,OAAO,CAACH,QACZI,MAAM,CAAC,CAAC,CAACC,IAAI,GAAKA,IAAIC,QAAQ,CAAC,cAAcD,QAAQ,oBACrDE,GAAG,CAAC,CAAC,CAACC,MAAMC,MAAM;QACjB,IAAI,OAAOA,UAAU,UAAU;YAC7B,OAAOX,cAAe;gBAAE,GAAGW,KAAK;YAAC;QACnC;QACA,OAAOX,cAAc;YAAEY,MAAM;gBAAEC,4BAA4BF;YAAM;QAAE;IACrE;IAEJ,OAAOR;AACT,EAAE"}
@@ -1,3 +1,4 @@
1
1
  export * from "./text";
2
+ export * from "./isObject";
2
3
 
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/index.ts"],"sourcesContent":["export * from \"./text\";\n"],"names":[],"mappings":"AAAA,cAAc,SAAS"}
1
+ {"version":3,"sources":["../../../src/utils/index.ts"],"sourcesContent":["export * from \"./text\";\nexport * from \"./isObject\";\n"],"names":[],"mappings":"AAAA,cAAc,SAAS;AACvB,cAAc,aAAa"}
@@ -0,0 +1,5 @@
1
+ export function isObject(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
3
+ }
4
+
5
+ //# sourceMappingURL=isObject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/isObject.tsx"],"sourcesContent":["export function isObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype\n );\n }\n"],"names":["isObject","value","Array","isArray","Object","getPrototypeOf","prototype"],"mappings":"AAAA,OAAO,SAASA,SAASC,KAAc;IACnC,OACE,OAAOA,UAAU,YACjBA,UAAU,QACV,CAACC,MAAMC,OAAO,CAACF,UACfG,OAAOC,cAAc,CAACJ,WAAWG,OAAOE,SAAS;AAErD"}
@@ -1,3 +1,25 @@
1
+ import { isObject } from ".";
2
+ // Deeply converts keys in an object to snake_case
3
+ export const snakeCaseKeys = (object)=>Object.entries(object || {}).reduce((acc, [key, value])=>{
4
+ const newKey = camelToSnakeCase(key);
5
+ if (Array.isArray(value) && value.every(isObject)) {
6
+ return {
7
+ ...acc,
8
+ [newKey]: value.map((v)=>snakeCaseKeys(v))
9
+ };
10
+ }
11
+ if (isObject(value)) {
12
+ return {
13
+ ...acc,
14
+ [newKey]: snakeCaseKeys(value)
15
+ };
16
+ }
17
+ return {
18
+ ...acc,
19
+ [newKey]: value
20
+ };
21
+ }, {});
1
22
  export const snakeCase = (text = "")=>text.toLowerCase().replace(/ /g, "_");
23
+ export const camelToSnakeCase = (text)=>text.charAt(0).toLowerCase() + text.slice(1).replace(/(\[.*?\])|[A-Z]/g, (match, group)=>group ? match : `_${match.toLowerCase()}`);
2
24
 
3
25
  //# sourceMappingURL=text.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/text.ts"],"sourcesContent":["export const snakeCase = (text = \"\"): string =>\n text.toLowerCase().replace(/ /g, \"_\");\n"],"names":["snakeCase","text","toLowerCase","replace"],"mappings":"AAAA,OAAO,MAAMA,YAAY,CAACC,OAAO,EAAE,GACjCA,KAAKC,WAAW,GAAGC,OAAO,CAAC,MAAM,KAAK"}
1
+ {"version":3,"sources":["../../../src/utils/text.ts"],"sourcesContent":["import { isObject } from \".\";\n\n// Deeply converts keys in an object to snake_case\nexport const snakeCaseKeys = (object: Record<string, unknown>) =>\n Object.entries(object || {}).reduce(\n (acc: Record<string, unknown>, [key, value]): Record<string, unknown> => {\n const newKey = camelToSnakeCase(key);\n\n if (Array.isArray(value) && value.every(isObject)) {\n return {\n ...acc,\n [newKey]: value.map(v => snakeCaseKeys(v)),\n };\n }\n\n if (isObject(value)) {\n return {\n ...acc,\n [newKey]: snakeCaseKeys(value),\n };\n }\n\n return {\n ...acc,\n [newKey]: value,\n };\n },\n {},\n );\n\nexport const snakeCase = (text = \"\"): string =>\n text.toLowerCase().replace(/ /g, \"_\");\n\nexport const camelToSnakeCase = (text: string) =>\n text.charAt(0).toLowerCase() +\n text\n .slice(1)\n .replace(/(\\[.*?\\])|[A-Z]/g, (match, group) =>\n group ? match : `_${match.toLowerCase()}`,\n );\n\n"],"names":["isObject","snakeCaseKeys","object","Object","entries","reduce","acc","key","value","newKey","camelToSnakeCase","Array","isArray","every","map","v","snakeCase","text","toLowerCase","replace","charAt","slice","match","group"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,IAAI;AAE7B,kDAAkD;AAClD,OAAO,MAAMC,gBAAgB,CAACC,SAC5BC,OAAOC,OAAO,CAACF,UAAU,CAAC,GAAGG,MAAM,CACjC,CAACC,KAA8B,CAACC,KAAKC,MAAM;QACzC,MAAMC,SAASC,iBAAiBH;QAEhC,IAAII,MAAMC,OAAO,CAACJ,UAAUA,MAAMK,KAAK,CAACb,WAAW;YACjD,OAAO;gBACL,GAAGM,GAAG;gBACN,CAACG,OAAO,EAAED,MAAMM,GAAG,CAACC,CAAAA,IAAKd,cAAcc;YACzC;QACF;QAEA,IAAIf,SAASQ,QAAQ;YACnB,OAAO;gBACL,GAAGF,GAAG;gBACN,CAACG,OAAO,EAAER,cAAcO;YAC1B;QACF;QAEA,OAAO;YACL,GAAGF,GAAG;YACN,CAACG,OAAO,EAAED;QACZ;IACF,GACA,CAAC,GACD;AAEJ,OAAO,MAAMQ,YAAY,CAACC,OAAO,EAAE,GACjCA,KAAKC,WAAW,GAAGC,OAAO,CAAC,MAAM,KAAK;AAExC,OAAO,MAAMT,mBAAmB,CAACO,OAC/BA,KAAKG,MAAM,CAAC,GAAGF,WAAW,KAC1BD,KACGI,KAAK,CAAC,GACNF,OAAO,CAAC,oBAAoB,CAACG,OAAOC,QACnCA,QAAQD,QAAQ,CAAC,CAAC,EAAEA,MAAMJ,WAAW,IAAI,EACzC"}
@@ -18,6 +18,7 @@ export declare class Snowplow {
18
18
  contexts: SelfDescribingJson<Record<string, unknown>>[];
19
19
  serverData: Record<string, unknown>;
20
20
  eventHandlers: Record<string, (params?: Record<string, unknown>) => void>;
21
+ static instance: Snowplow | undefined;
21
22
  constructor(props?: TrackingProps);
22
23
  setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]): this;
23
24
  trackView(): this;
@@ -27,4 +28,5 @@ export declare class Snowplow {
27
28
  private addEventHandler;
28
29
  private removeEventHandler;
29
30
  trigger(name: string, params?: Record<string, unknown>): this;
31
+ static getInstance(props?: TrackingProps): Snowplow;
30
32
  }
@@ -1 +1,2 @@
1
1
  export * from "./text";
2
+ export * from "./isObject";
@@ -0,0 +1 @@
1
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,3 @@
1
+ export declare const snakeCaseKeys: (object: Record<string, unknown>) => Record<string, unknown>;
1
2
  export declare const snakeCase: (text?: string) => string;
3
+ export declare const camelToSnakeCase: (text: string) => string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/services",
3
3
  "license": "UNLICENSED",
4
- "version": "0.14.0",
4
+ "version": "0.14.2",
5
5
  "description": "Internal library for services",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,7 +15,7 @@ export const pageData = {
15
15
  trackActivity: true,
16
16
  trackPageView: true,
17
17
  pageViewContext: {
18
- schema: "igluuk.co.simplybusiness/journey_context/jsonschema/1-0-0",
18
+ schema: "iglu:uk.co.simplybusiness/journey_context/jsonschema/1-0-0",
19
19
  data: {
20
20
  site: "simplybusiness_us",
21
21
  vertical: "usa",
@@ -29,12 +29,12 @@ export const pageData = {
29
29
  },
30
30
  distributionChannelContext: {
31
31
  schema:
32
- "iglucom.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
32
+ "iglu:com.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
33
33
  data: { service_channel_identifier: "simplybusiness_us" },
34
34
  },
35
35
  serviceChannelContext: {
36
36
  schema:
37
- "iglucom.simplybusiness/service_channel_context/jsonschema/1-0-0",
37
+ "iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0",
38
38
  data: { service_channel_identifier: "simplybusiness_us" },
39
39
  },
40
40
  forceSecureTracker: true,
@@ -39,6 +39,8 @@ export class Snowplow {
39
39
  eventHandlers: Record<string, (params?: Record<string, unknown>) => void> =
40
40
  {};
41
41
 
42
+ static instance: Snowplow | undefined;
43
+
42
44
  constructor(props?: TrackingProps) {
43
45
  if (!props) return;
44
46
 
@@ -85,6 +87,8 @@ export class Snowplow {
85
87
  if (uid) {
86
88
  setUserId(uid);
87
89
  }
90
+ // Create a singleton instance
91
+ Snowplow.instance = this;
88
92
  }
89
93
 
90
94
  setContexts(contexts: SelfDescribingJson<Record<string, unknown>>[]) {
@@ -174,4 +178,11 @@ export class Snowplow {
174
178
  }
175
179
  return this;
176
180
  }
181
+
182
+ static getInstance(props?: TrackingProps) {
183
+ if (!Snowplow.instance) {
184
+ Snowplow.instance = new Snowplow(props);
185
+ }
186
+ return Snowplow.instance;
187
+ }
177
188
  }
@@ -4,6 +4,7 @@ import {
4
4
  useContext,
5
5
  useEffect,
6
6
  useMemo,
7
+ useRef,
7
8
  useState,
8
9
  type ReactNode,
9
10
  } from "react";
@@ -26,25 +27,25 @@ type ProviderProps = {
26
27
 
27
28
  export const SnowplowProvider = ({ scripts, children }: ProviderProps) => {
28
29
  const [config, _setConfig] = useState<TrackingProps>(scripts);
29
- const [snowplow, setSnowplow] = useState<Snowplow>(new Snowplow(config));
30
+ const snowplow = useRef<Snowplow>(Snowplow.getInstance(config));
30
31
 
31
32
  // Attach event handlers and set contexts
32
33
  useEffect(() => {
33
- if (snowplow && scripts) {
34
+ if (snowplow.current && scripts) {
34
35
  const contexts = getContexts(config);
35
36
 
36
- snowplow
37
+ snowplow.current
37
38
  .setContexts(contexts)
38
39
  .addEventHandlers(eventDefinitions as EventDefinition[]);
39
40
  // Send page view event
40
- if (config.trackPageView) snowplow.trackView();
41
+ if (config.trackPageView) snowplow.current.trackView();
41
42
  }
42
43
  }, [config, snowplow, scripts]);
43
44
 
44
45
  const value: SnowplowContextInterface = useMemo(
45
46
  () => ({
46
47
  config,
47
- snowplow,
48
+ snowplow: snowplow.current,
48
49
  }),
49
50
  [config, snowplow],
50
51
  );
@@ -10,7 +10,7 @@ describe("Snowplow Contexts", () => {
10
10
 
11
11
  expect(contexts).toHaveLength(3);
12
12
  expect(contexts[0]).toEqual({
13
- schema: "igluuk.co.simplybusiness/journey_context/jsonschema/1-0-0",
13
+ schema: "iglu:uk.co.simplybusiness/journey_context/jsonschema/1-0-0",
14
14
  data: {
15
15
  site: "simplybusiness_us",
16
16
  vertical: "usa",
@@ -24,11 +24,11 @@ describe("Snowplow Contexts", () => {
24
24
  });
25
25
  expect(contexts[1]).toEqual({
26
26
  schema:
27
- "iglucom.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
27
+ "iglu:com.simplybusiness/distribution_channel_context/jsonschema/1-0-0",
28
28
  data: { service_channel_identifier: "simplybusiness_us" },
29
29
  });
30
30
  expect(contexts[2]).toEqual({
31
- schema: "iglucom.simplybusiness/service_channel_context/jsonschema/1-0-0",
31
+ schema: "iglu:com.simplybusiness/service_channel_context/jsonschema/1-0-0",
32
32
  data: { service_channel_identifier: "simplybusiness_us" },
33
33
  });
34
34
  });
@@ -1,5 +1,6 @@
1
1
  import { SelfDescribingJson } from "@snowplow/browser-tracker";
2
2
  import { TrackingProps } from "./types";
3
+ import { snakeCaseKeys } from "../utils";
3
4
 
4
5
  export const getContexts = (
5
6
  config: TrackingProps,
@@ -10,9 +11,9 @@ export const getContexts = (
10
11
  .filter(([key]) => key.includes("Context") && key !== "includeGAContext")
11
12
  .map(([_key, value]) => {
12
13
  if (typeof value === "object") {
13
- return { ...value };
14
+ return snakeCaseKeys(({ ...value }));
14
15
  }
15
- return { data: { service_channel_identifier: value } };
16
+ return snakeCaseKeys({ data: { service_channel_identifier: value } });
16
17
  });
17
18
 
18
19
  return contexts as unknown as SelfDescribingJson<Record<string, unknown>>[];
@@ -1 +1,2 @@
1
1
  export * from "./text";
2
+ export * from "./isObject";
@@ -0,0 +1,35 @@
1
+ import { isObject } from "./isObject";
2
+
3
+ describe("isObject", () => {
4
+ it("should return true for plain objects", () => {
5
+ expect(isObject({})).toBeTruthy();
6
+ expect(isObject({ key: "value" })).toBeTruthy();
7
+ });
8
+
9
+ it("should return false for arrays", () => {
10
+ expect(isObject([])).toBeFalsy();
11
+ expect(isObject([1, 2, 3])).toBeFalsy();
12
+ });
13
+
14
+ it("should return false for null", () => {
15
+ expect(isObject(null)).toBeFalsy();
16
+ });
17
+
18
+ it("should return false for non-object types", () => {
19
+ expect(isObject("string")).toBeFalsy();
20
+ expect(isObject(123)).toBeFalsy();
21
+ expect(isObject(true)).toBeFalsy();
22
+ expect(isObject(undefined)).toBeFalsy();
23
+ });
24
+
25
+ it("should return false for instances of classes", () => {
26
+ class MyClass {}
27
+ const instance = new MyClass();
28
+ expect(isObject(instance)).toBeFalsy();
29
+ });
30
+
31
+ it("should return false for objects with different prototypes", () => {
32
+ const obj = Object.create(null);
33
+ expect(isObject(obj)).toBeFalsy();
34
+ });
35
+ });
@@ -0,0 +1,8 @@
1
+ export function isObject(value: unknown): value is Record<string, unknown> {
2
+ return (
3
+ typeof value === "object" &&
4
+ value !== null &&
5
+ !Array.isArray(value) &&
6
+ Object.getPrototypeOf(value) === Object.prototype
7
+ );
8
+ }
@@ -1,4 +1,8 @@
1
- import { snakeCase } from "./text";
1
+ import {
2
+ camelToSnakeCase,
3
+ snakeCase,
4
+ snakeCaseKeys,
5
+ } from "./text";
2
6
 
3
7
  describe("text utils", () => {
4
8
  test("snakeCase", () => {
@@ -6,4 +10,59 @@ describe("text utils", () => {
6
10
  expect(snakeCase("Hello")).toBe("hello");
7
11
  expect(snakeCase("")).toBe("");
8
12
  });
13
+
14
+ test("camelToSnakeCase", () => {
15
+ expect(camelToSnakeCase("")).toBe("");
16
+ expect(camelToSnakeCase("helloWorld")).toBe("hello_world");
17
+ expect(camelToSnakeCase("coverTogglesData")).toBe("cover_toggles_data");
18
+ expect(camelToSnakeCase("hello")).toBe("hello");
19
+ expect(camelToSnakeCase("Address 1")).toBe("address 1");
20
+ expect(camelToSnakeCase("uk_address_lookup[Address 2]")).toBe(
21
+ "uk_address_lookup[Address 2]",
22
+ );
23
+ expect(
24
+ camelToSnakeCase("uk_correspondence_address_lookup[Customer[address_2]]"),
25
+ ).toBe("uk_correspondence_address_lookup[Customer[address_2]]");
26
+ });
27
+
28
+ test("snakeCaseKeys", () => {
29
+ expect(
30
+ snakeCaseKeys({
31
+ authToken: "1234",
32
+ coverTogglesData: {
33
+ fromValue: "f",
34
+ toValue: "t",
35
+ },
36
+ hello: "world",
37
+ }),
38
+ ).toEqual({
39
+ auth_token: "1234",
40
+ cover_toggles_data: {
41
+ from_value: "f",
42
+ to_value: "t",
43
+ },
44
+ hello: "world",
45
+ });
46
+
47
+ // With Arrays
48
+ expect(
49
+ snakeCaseKeys({
50
+ authToken: "1234",
51
+ coverTogglesData: [
52
+ {
53
+ fromValue: "f",
54
+ toValue: "t",
55
+ },
56
+ ],
57
+ }),
58
+ ).toEqual({
59
+ auth_token: "1234",
60
+ cover_toggles_data: [
61
+ {
62
+ from_value: "f",
63
+ to_value: "t",
64
+ },
65
+ ],
66
+ });
67
+ });
9
68
  });