utneque 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (431) hide show
  1. package/.editorconfig +10 -0
  2. package/.eslintrc.isomorphic.js +26 -0
  3. package/.eslintrc.js +86 -0
  4. package/.gitattributes +1 -0
  5. package/.github/workflows/ci.yml +33 -0
  6. package/.github/workflows/deploy-browser-cdn-candidate.yml +51 -0
  7. package/.github/workflows/deploy-releases.yml +178 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierrc +7 -0
  10. package/.vscode/extensions.json +3 -0
  11. package/.vscode/launch.json +81 -0
  12. package/.vscode/settings.json +41 -0
  13. package/.yarn/plugins/@yarnpkg/plugin-constraints.cjs +52 -0
  14. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +546 -0
  15. package/.yarn/plugins/@yarnpkg/plugin-typescript.cjs +9 -0
  16. package/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +28 -0
  17. package/.yarn/releases/yarn-3.4.1.cjs +873 -0
  18. package/.yarnrc.yml +15 -0
  19. package/jest.config.js +18 -0
  20. package/package.json +59 -0
  21. package/packages/browser/.eslintrc.js +9 -0
  22. package/packages/browser/.lintstagedrc.js +1 -0
  23. package/packages/browser/ARCHITECTURE.md +48 -0
  24. package/packages/browser/LICENSE.MD +45 -0
  25. package/packages/browser/Makefile +64 -0
  26. package/packages/browser/README.md +227 -0
  27. package/packages/browser/e2e-tests/local-server.ts +28 -0
  28. package/packages/browser/e2e-tests/performance/ajs-perf-browser.test.ts +75 -0
  29. package/packages/browser/jest.config.js +15 -0
  30. package/packages/browser/jest.setup.js +10 -0
  31. package/packages/browser/package.json +106 -0
  32. package/packages/browser/qa/README.md +41 -0
  33. package/packages/browser/qa/__fixtures__/snippets.ts +148 -0
  34. package/packages/browser/qa/__tests__/backwards-compatibility.test.ts +180 -0
  35. package/packages/browser/qa/__tests__/destinations.test.ts +101 -0
  36. package/packages/browser/qa/__tests__/smoke.test.ts +170 -0
  37. package/packages/browser/qa/lib/benchmark.ts +36 -0
  38. package/packages/browser/qa/lib/browser.ts +28 -0
  39. package/packages/browser/qa/lib/jest-reporter.js +57 -0
  40. package/packages/browser/qa/lib/runner.ts +142 -0
  41. package/packages/browser/qa/lib/schema.ts +59 -0
  42. package/packages/browser/qa/lib/server.ts +54 -0
  43. package/packages/browser/qa/lib/stats.ts +52 -0
  44. package/packages/browser/scripts/build-prep.sh +7 -0
  45. package/packages/browser/scripts/ci.sh +15 -0
  46. package/packages/browser/scripts/release.js +121 -0
  47. package/packages/browser/scripts/release.sh +9 -0
  48. package/packages/browser/scripts/run.sh +8 -0
  49. package/packages/browser/src/browser/__tests__/analytics-lazy-init.integration.test.ts +51 -0
  50. package/packages/browser/src/browser/__tests__/analytics-pre-init.integration.test.ts +440 -0
  51. package/packages/browser/src/browser/__tests__/anon-id-and-reset.integration.test.ts +73 -0
  52. package/packages/browser/src/browser/__tests__/cdn.test.ts +53 -0
  53. package/packages/browser/src/browser/__tests__/csp-detection.test.ts +140 -0
  54. package/packages/browser/src/browser/__tests__/inspector.integration.test.ts +121 -0
  55. package/packages/browser/src/browser/__tests__/integration.test.ts +1213 -0
  56. package/packages/browser/src/browser/__tests__/integrations.integration.test.ts +260 -0
  57. package/packages/browser/src/browser/__tests__/page-enrichment.integration.test.ts +216 -0
  58. package/packages/browser/src/browser/__tests__/query-string.integration.test.ts +116 -0
  59. package/packages/browser/src/browser/__tests__/standalone-analytics.test.ts +303 -0
  60. package/packages/browser/src/browser/__tests__/standalone-errors.test.ts +136 -0
  61. package/packages/browser/src/browser/__tests__/standalone.test.ts +97 -0
  62. package/packages/browser/src/browser/__tests__/typedef-tests/analytics-browser.ts +150 -0
  63. package/packages/browser/src/browser/__tests__/update-cdn-settings.test.ts +71 -0
  64. package/packages/browser/src/browser/browser-umd.ts +19 -0
  65. package/packages/browser/src/browser/index.ts +486 -0
  66. package/packages/browser/src/browser/standalone-analytics.ts +62 -0
  67. package/packages/browser/src/browser/standalone-interface.ts +11 -0
  68. package/packages/browser/src/browser/standalone.ts +92 -0
  69. package/packages/browser/src/core/__tests__/track-form.test.ts +193 -0
  70. package/packages/browser/src/core/__tests__/track-link.test.ts +252 -0
  71. package/packages/browser/src/core/analytics/__tests__/integration.test.ts +334 -0
  72. package/packages/browser/src/core/analytics/__tests__/test-plugins.ts +94 -0
  73. package/packages/browser/src/core/analytics/index.ts +672 -0
  74. package/packages/browser/src/core/analytics/interfaces.ts +100 -0
  75. package/packages/browser/src/core/arguments-resolver/__tests__/index.test.ts +524 -0
  76. package/packages/browser/src/core/arguments-resolver/index.ts +200 -0
  77. package/packages/browser/src/core/auto-track.ts +152 -0
  78. package/packages/browser/src/core/buffer/__tests__/index.test.ts +455 -0
  79. package/packages/browser/src/core/buffer/index.ts +371 -0
  80. package/packages/browser/src/core/callback/index.ts +1 -0
  81. package/packages/browser/src/core/connection/__tests__/index.test.ts +25 -0
  82. package/packages/browser/src/core/connection/index.ts +13 -0
  83. package/packages/browser/src/core/constants/index.ts +1 -0
  84. package/packages/browser/src/core/context/__tests__/index.test.ts +201 -0
  85. package/packages/browser/src/core/context/index.ts +21 -0
  86. package/packages/browser/src/core/environment/index.ts +7 -0
  87. package/packages/browser/src/core/events/__tests__/index.test.ts +450 -0
  88. package/packages/browser/src/core/events/index.ts +280 -0
  89. package/packages/browser/src/core/events/interfaces.ts +36 -0
  90. package/packages/browser/src/core/inspector/index.ts +14 -0
  91. package/packages/browser/src/core/page/__tests__/index.test.ts +130 -0
  92. package/packages/browser/src/core/page/add-page-context.ts +33 -0
  93. package/packages/browser/src/core/page/get-page-context.ts +140 -0
  94. package/packages/browser/src/core/page/index.ts +2 -0
  95. package/packages/browser/src/core/plugin/index.ts +12 -0
  96. package/packages/browser/src/core/query-string/__tests__/gracefulDecodeURIComponent.test.ts +17 -0
  97. package/packages/browser/src/core/query-string/__tests__/index.test.ts +149 -0
  98. package/packages/browser/src/core/query-string/__tests__/pickPrefix.test.ts +31 -0
  99. package/packages/browser/src/core/query-string/__tests__/useQueryString.test.ts +60 -0
  100. package/packages/browser/src/core/query-string/gracefulDecodeURIComponent.ts +16 -0
  101. package/packages/browser/src/core/query-string/index.ts +64 -0
  102. package/packages/browser/src/core/query-string/pickPrefix.ts +20 -0
  103. package/packages/browser/src/core/queue/__tests__/event-queue.test.ts +82 -0
  104. package/packages/browser/src/core/queue/event-queue.ts +22 -0
  105. package/packages/browser/src/core/session/__tests__/index.test.ts +41 -0
  106. package/packages/browser/src/core/session/index.ts +107 -0
  107. package/packages/browser/src/core/stats/__tests__/index.test.ts +15 -0
  108. package/packages/browser/src/core/stats/__tests__/remote-metrics.test.ts +189 -0
  109. package/packages/browser/src/core/stats/index.ts +15 -0
  110. package/packages/browser/src/core/stats/remote-metrics.ts +144 -0
  111. package/packages/browser/src/core/storage/__tests__/cookieStorage.test.ts +58 -0
  112. package/packages/browser/src/core/storage/__tests__/localStorage.test.ts +70 -0
  113. package/packages/browser/src/core/storage/__tests__/test-helpers.ts +26 -0
  114. package/packages/browser/src/core/storage/__tests__/universalStorage.test.ts +167 -0
  115. package/packages/browser/src/core/storage/cookieStorage.ts +80 -0
  116. package/packages/browser/src/core/storage/index.ts +64 -0
  117. package/packages/browser/src/core/storage/localStorage.ts +45 -0
  118. package/packages/browser/src/core/storage/memoryStorage.ts +22 -0
  119. package/packages/browser/src/core/storage/settings.ts +23 -0
  120. package/packages/browser/src/core/storage/types.ts +49 -0
  121. package/packages/browser/src/core/storage/universalStorage.ts +78 -0
  122. package/packages/browser/src/core/user/__tests__/index.test.ts +922 -0
  123. package/packages/browser/src/core/user/__tests__/migrate.test.ts +101 -0
  124. package/packages/browser/src/core/user/__tests__/session.test.ts +136 -0
  125. package/packages/browser/src/core/user/__tests__/tld.test.ts +36 -0
  126. package/packages/browser/src/core/user/index.ts +399 -0
  127. package/packages/browser/src/core/user/migrate.ts +126 -0
  128. package/packages/browser/src/core/user/tld.ts +65 -0
  129. package/packages/browser/src/core/user/vendor/crypto-es/LICENSE +53 -0
  130. package/packages/browser/src/core/user/vendor/crypto-es/lib/aes.ts +302 -0
  131. package/packages/browser/src/core/user/vendor/crypto-es/lib/cipher-core.ts +922 -0
  132. package/packages/browser/src/core/user/vendor/crypto-es/lib/core.ts +806 -0
  133. package/packages/browser/src/core/user/vendor/crypto-es/lib/enc-base64.ts +110 -0
  134. package/packages/browser/src/core/user/vendor/crypto-es/lib/evpkdf.ts +110 -0
  135. package/packages/browser/src/core/user/vendor/crypto-es/lib/md5.ts +238 -0
  136. package/packages/browser/src/core/user/vendor/readme.md +7 -0
  137. package/packages/browser/src/generated/__tests__/version.test.ts +18 -0
  138. package/packages/browser/src/generated/version.ts +2 -0
  139. package/packages/browser/src/index.ts +13 -0
  140. package/packages/browser/src/lib/__tests__/embedded-write-key.test.ts +15 -0
  141. package/packages/browser/src/lib/__tests__/fetch.test.ts +35 -0
  142. package/packages/browser/src/lib/__tests__/get-process-env.test.ts +5 -0
  143. package/packages/browser/src/lib/__tests__/group-by.test.ts +96 -0
  144. package/packages/browser/src/lib/__tests__/is-plan-event-enabled.test.ts +36 -0
  145. package/packages/browser/src/lib/__tests__/is-thenable.test.ts +39 -0
  146. package/packages/browser/src/lib/__tests__/merged-options.test.ts +123 -0
  147. package/packages/browser/src/lib/__tests__/on-page-change.test.ts +74 -0
  148. package/packages/browser/src/lib/__tests__/parse-cdn.test.ts +88 -0
  149. package/packages/browser/src/lib/__tests__/pick.test.ts +34 -0
  150. package/packages/browser/src/lib/__tests__/pick.typedef.ts +39 -0
  151. package/packages/browser/src/lib/bind-all.ts +19 -0
  152. package/packages/browser/src/lib/browser-polyfill.ts +23 -0
  153. package/packages/browser/src/lib/client-hints/__tests__/index.test.ts +66 -0
  154. package/packages/browser/src/lib/client-hints/index.ts +16 -0
  155. package/packages/browser/src/lib/client-hints/interfaces.ts +42 -0
  156. package/packages/browser/src/lib/create-deferred.ts +16 -0
  157. package/packages/browser/src/lib/csp-detection.ts +8 -0
  158. package/packages/browser/src/lib/embedded-write-key.ts +24 -0
  159. package/packages/browser/src/lib/fetch.ts +10 -0
  160. package/packages/browser/src/lib/get-global.ts +16 -0
  161. package/packages/browser/src/lib/get-process-env.ts +11 -0
  162. package/packages/browser/src/lib/global-analytics-helper.ts +31 -0
  163. package/packages/browser/src/lib/group-by.ts +30 -0
  164. package/packages/browser/src/lib/is-plan-event-enabled.ts +20 -0
  165. package/packages/browser/src/lib/is-thenable.ts +9 -0
  166. package/packages/browser/src/lib/load-script.ts +66 -0
  167. package/packages/browser/src/lib/merged-options.ts +46 -0
  168. package/packages/browser/src/lib/on-page-change.ts +29 -0
  169. package/packages/browser/src/lib/p-while.ts +12 -0
  170. package/packages/browser/src/lib/parse-cdn.ts +56 -0
  171. package/packages/browser/src/lib/pick.ts +28 -0
  172. package/packages/browser/src/lib/priority-queue/__tests__/backoff.test.ts +23 -0
  173. package/packages/browser/src/lib/priority-queue/__tests__/index.test.ts +158 -0
  174. package/packages/browser/src/lib/priority-queue/__tests__/persisted.test.ts +228 -0
  175. package/packages/browser/src/lib/priority-queue/backoff.ts +24 -0
  176. package/packages/browser/src/lib/priority-queue/index.ts +6 -0
  177. package/packages/browser/src/lib/priority-queue/persisted.ts +127 -0
  178. package/packages/browser/src/lib/sleep.ts +4 -0
  179. package/packages/browser/src/lib/to-facade.ts +53 -0
  180. package/packages/browser/src/lib/version-type.ts +10 -0
  181. package/packages/browser/src/node/__tests__/node-integration.test.ts +19 -0
  182. package/packages/browser/src/node/index.ts +36 -0
  183. package/packages/browser/src/node/node.browser.ts +7 -0
  184. package/packages/browser/src/plugins/ajs-destination/__tests__/index.test.ts +834 -0
  185. package/packages/browser/src/plugins/ajs-destination/index.ts +392 -0
  186. package/packages/browser/src/plugins/ajs-destination/loader.ts +129 -0
  187. package/packages/browser/src/plugins/ajs-destination/types.ts +44 -0
  188. package/packages/browser/src/plugins/ajs-destination/utils.ts +32 -0
  189. package/packages/browser/src/plugins/analytics-node/__tests__/index.test.ts +69 -0
  190. package/packages/browser/src/plugins/analytics-node/index.ts +67 -0
  191. package/packages/browser/src/plugins/env-enrichment/__tests__/index.test.ts +421 -0
  192. package/packages/browser/src/plugins/env-enrichment/index.ts +208 -0
  193. package/packages/browser/src/plugins/hightouchio/__tests__/batched-dispatcher.test.ts +299 -0
  194. package/packages/browser/src/plugins/hightouchio/__tests__/index.test.ts +317 -0
  195. package/packages/browser/src/plugins/hightouchio/__tests__/normalize.test.ts +181 -0
  196. package/packages/browser/src/plugins/hightouchio/__tests__/retries.test.ts +82 -0
  197. package/packages/browser/src/plugins/hightouchio/batched-dispatcher.ts +127 -0
  198. package/packages/browser/src/plugins/hightouchio/fetch-dispatcher.ts +27 -0
  199. package/packages/browser/src/plugins/hightouchio/index.ts +147 -0
  200. package/packages/browser/src/plugins/hightouchio/normalize.ts +71 -0
  201. package/packages/browser/src/plugins/hightouchio/schedule-flush.ts +58 -0
  202. package/packages/browser/src/plugins/legacy-video-plugins/__tests__/index.test.ts +48 -0
  203. package/packages/browser/src/plugins/legacy-video-plugins/index.ts +16 -0
  204. package/packages/browser/src/plugins/middleware/__tests__/index.test.ts +268 -0
  205. package/packages/browser/src/plugins/middleware/index.ts +131 -0
  206. package/packages/browser/src/plugins/remote-loader/__tests__/index.test.ts +943 -0
  207. package/packages/browser/src/plugins/remote-loader/index.ts +256 -0
  208. package/packages/browser/src/plugins/remote-middleware/__tests__/index.test.ts +116 -0
  209. package/packages/browser/src/plugins/remote-middleware/index.ts +44 -0
  210. package/packages/browser/src/plugins/routing-middleware/__tests__/index.test.ts +64 -0
  211. package/packages/browser/src/plugins/routing-middleware/index.ts +37 -0
  212. package/packages/browser/src/plugins/schema-filter/__tests__/index.test.ts +520 -0
  213. package/packages/browser/src/plugins/schema-filter/index.ts +90 -0
  214. package/packages/browser/src/plugins/validation/__tests__/index.test.ts +78 -0
  215. package/packages/browser/src/plugins/validation/index.ts +44 -0
  216. package/packages/browser/src/test-helpers/browser-storage.ts +75 -0
  217. package/packages/browser/src/test-helpers/factories.ts +18 -0
  218. package/packages/browser/src/test-helpers/fetch-parse.ts +8 -0
  219. package/packages/browser/src/test-helpers/fixtures/cdn-settings.ts +301 -0
  220. package/packages/browser/src/test-helpers/fixtures/classic-destination.ts +25 -0
  221. package/packages/browser/src/test-helpers/fixtures/client-hints.ts +28 -0
  222. package/packages/browser/src/test-helpers/fixtures/create-fetch-method.ts +30 -0
  223. package/packages/browser/src/test-helpers/fixtures/index.ts +4 -0
  224. package/packages/browser/src/test-helpers/fixtures/page-context.ts +11 -0
  225. package/packages/browser/src/test-helpers/test-writekeys.ts +5 -0
  226. package/packages/browser/src/test-helpers/type-assertions.ts +11 -0
  227. package/packages/browser/src/tester/__fixtures__/hightouch-snippet.ts +64 -0
  228. package/packages/browser/src/tester/__fixtures__/index.html +8 -0
  229. package/packages/browser/src/tester/ajs-perf.ts +30 -0
  230. package/packages/browser/src/tester/ajs-tester.ts +119 -0
  231. package/packages/browser/src/tester/server.js +16 -0
  232. package/packages/browser/tsconfig.build.json +9 -0
  233. package/packages/browser/tsconfig.json +13 -0
  234. package/packages/browser/webpack.config.js +106 -0
  235. package/packages/config/package.json +10 -0
  236. package/packages/config/src/index.js +4 -0
  237. package/packages/config/src/jest/config.js +50 -0
  238. package/packages/config/src/jest/get-module-map.js +34 -0
  239. package/packages/config/src/lint-staged/config.js +4 -0
  240. package/packages/config-webpack/package.json +20 -0
  241. package/packages/config-webpack/webpack.config.common.js +75 -0
  242. package/packages/consent/consent-tools/.eslintrc.js +7 -0
  243. package/packages/consent/consent-tools/.lintstagedrc.js +1 -0
  244. package/packages/consent/consent-tools/LICENSE +45 -0
  245. package/packages/consent/consent-tools/README.md +104 -0
  246. package/packages/consent/consent-tools/jest.config.js +6 -0
  247. package/packages/consent/consent-tools/jest.setup.js +4 -0
  248. package/packages/consent/consent-tools/package.json +48 -0
  249. package/packages/consent/consent-tools/src/domain/__tests__/assertions/integrations-assertions.ts +37 -0
  250. package/packages/consent/consent-tools/src/domain/__tests__/consent-stamping.test.ts +45 -0
  251. package/packages/consent/consent-tools/src/domain/__tests__/create-wrapper.test.ts +816 -0
  252. package/packages/consent/consent-tools/src/domain/__tests__/typedef-tests.ts +21 -0
  253. package/packages/consent/consent-tools/src/domain/consent-changed.ts +51 -0
  254. package/packages/consent/consent-tools/src/domain/consent-stamping.ts +19 -0
  255. package/packages/consent/consent-tools/src/domain/create-wrapper.ts +238 -0
  256. package/packages/consent/consent-tools/src/domain/get-initialized-analytics.ts +25 -0
  257. package/packages/consent/consent-tools/src/domain/load-cancellation.ts +31 -0
  258. package/packages/consent/consent-tools/src/domain/validation/__tests__/options-validators.test.ts +77 -0
  259. package/packages/consent/consent-tools/src/domain/validation/__tests__/validation-error.test.ts +15 -0
  260. package/packages/consent/consent-tools/src/domain/validation/common-validators.ts +19 -0
  261. package/packages/consent/consent-tools/src/domain/validation/index.ts +1 -0
  262. package/packages/consent/consent-tools/src/domain/validation/options-validators.ts +74 -0
  263. package/packages/consent/consent-tools/src/domain/validation/validation-error.ts +11 -0
  264. package/packages/consent/consent-tools/src/index.ts +16 -0
  265. package/packages/consent/consent-tools/src/types/errors.ts +14 -0
  266. package/packages/consent/consent-tools/src/types/index.ts +3 -0
  267. package/packages/consent/consent-tools/src/types/settings.ts +121 -0
  268. package/packages/consent/consent-tools/src/types/wrapper.ts +107 -0
  269. package/packages/consent/consent-tools/src/utils/index.ts +4 -0
  270. package/packages/consent/consent-tools/src/utils/pick.ts +18 -0
  271. package/packages/consent/consent-tools/src/utils/pipe.ts +14 -0
  272. package/packages/consent/consent-tools/src/utils/resolve-when.ts +32 -0
  273. package/packages/consent/consent-tools/src/utils/uniq.ts +4 -0
  274. package/packages/consent/consent-tools/tsconfig.build.json +9 -0
  275. package/packages/consent/consent-tools/tsconfig.json +12 -0
  276. package/packages/consent/consent-wrapper-onetrust/.eslintrc.js +7 -0
  277. package/packages/consent/consent-wrapper-onetrust/.lintstagedrc.js +1 -0
  278. package/packages/consent/consent-wrapper-onetrust/LICENSE +45 -0
  279. package/packages/consent/consent-wrapper-onetrust/README.md +125 -0
  280. package/packages/consent/consent-wrapper-onetrust/img/onetrust-cat-id.jpg +0 -0
  281. package/packages/consent/consent-wrapper-onetrust/img/onetrust-popup.jpg +0 -0
  282. package/packages/consent/consent-wrapper-onetrust/jest.config.js +6 -0
  283. package/packages/consent/consent-wrapper-onetrust/jest.setup.js +4 -0
  284. package/packages/consent/consent-wrapper-onetrust/package.json +60 -0
  285. package/packages/consent/consent-wrapper-onetrust/src/domain/__tests__/wrapper.test.ts +151 -0
  286. package/packages/consent/consent-wrapper-onetrust/src/domain/wrapper.ts +61 -0
  287. package/packages/consent/consent-wrapper-onetrust/src/index.ts +6 -0
  288. package/packages/consent/consent-wrapper-onetrust/src/index.umd.ts +11 -0
  289. package/packages/consent/consent-wrapper-onetrust/src/lib/__tests__/onetrust-api.test.ts +181 -0
  290. package/packages/consent/consent-wrapper-onetrust/src/lib/onetrust-api.ts +155 -0
  291. package/packages/consent/consent-wrapper-onetrust/src/lib/validation/index.ts +1 -0
  292. package/packages/consent/consent-wrapper-onetrust/src/lib/validation/onetrust-api-error.ts +11 -0
  293. package/packages/consent/consent-wrapper-onetrust/src/test-helpers/mocks.ts +23 -0
  294. package/packages/consent/consent-wrapper-onetrust/src/test-helpers/onetrust-globals.d.ts +11 -0
  295. package/packages/consent/consent-wrapper-onetrust/src/test-helpers/utils.ts +3 -0
  296. package/packages/consent/consent-wrapper-onetrust/tsconfig.build.json +9 -0
  297. package/packages/consent/consent-wrapper-onetrust/tsconfig.json +11 -0
  298. package/packages/consent/consent-wrapper-onetrust/webpack.config.js +25 -0
  299. package/packages/core/.eslintrc.js +4 -0
  300. package/packages/core/.lintstagedrc.js +1 -0
  301. package/packages/core/LICENSE.MD +45 -0
  302. package/packages/core/README.md +3 -0
  303. package/packages/core/jest.config.js +5 -0
  304. package/packages/core/jest.setup.js +10 -0
  305. package/packages/core/package.json +40 -0
  306. package/packages/core/src/analytics/__tests__/dispatch.test.ts +95 -0
  307. package/packages/core/src/analytics/dispatch.ts +58 -0
  308. package/packages/core/src/analytics/index.ts +11 -0
  309. package/packages/core/src/callback/__tests__/index.test.ts +85 -0
  310. package/packages/core/src/callback/index.ts +51 -0
  311. package/packages/core/src/context/index.ts +123 -0
  312. package/packages/core/src/emitter/__tests__/emitter.test.ts +74 -0
  313. package/packages/core/src/emitter/index.ts +65 -0
  314. package/packages/core/src/emitter/interface.ts +31 -0
  315. package/packages/core/src/events/__tests__/index.test.ts +394 -0
  316. package/packages/core/src/events/index.ts +280 -0
  317. package/packages/core/src/events/interfaces.ts +475 -0
  318. package/packages/core/src/index.ts +19 -0
  319. package/packages/core/src/logger/__tests__/index.test.ts +66 -0
  320. package/packages/core/src/logger/index.ts +74 -0
  321. package/packages/core/src/plugins/index.ts +43 -0
  322. package/packages/core/src/priority-queue/__tests__/backoff.test.ts +23 -0
  323. package/packages/core/src/priority-queue/__tests__/index.test.ts +158 -0
  324. package/packages/core/src/priority-queue/backoff.ts +24 -0
  325. package/packages/core/src/priority-queue/index.ts +103 -0
  326. package/packages/core/src/queue/__tests__/event-queue.test.ts +678 -0
  327. package/packages/core/src/queue/__tests__/extension-flushing.test.ts +416 -0
  328. package/packages/core/src/queue/delivery.ts +73 -0
  329. package/packages/core/src/queue/event-queue.ts +318 -0
  330. package/packages/core/src/stats/__tests__/index.test.ts +103 -0
  331. package/packages/core/src/stats/index.ts +88 -0
  332. package/packages/core/src/task/__tests__/task-group.test.ts +26 -0
  333. package/packages/core/src/task/task-group.ts +31 -0
  334. package/packages/core/src/user/index.ts +7 -0
  335. package/packages/core/src/utils/__tests__/group-by.test.ts +96 -0
  336. package/packages/core/src/utils/__tests__/is-plain-object.test.ts +27 -0
  337. package/packages/core/src/utils/__tests__/is-thenable.test.ts +39 -0
  338. package/packages/core/src/utils/bind-all.ts +19 -0
  339. package/packages/core/src/utils/get-global.ts +17 -0
  340. package/packages/core/src/utils/group-by.ts +30 -0
  341. package/packages/core/src/utils/has-properties.ts +7 -0
  342. package/packages/core/src/utils/is-plain-object.ts +26 -0
  343. package/packages/core/src/utils/is-thenable.ts +9 -0
  344. package/packages/core/src/utils/p-while.ts +12 -0
  345. package/packages/core/src/utils/pick.ts +8 -0
  346. package/packages/core/src/utils/ts-helpers.ts +13 -0
  347. package/packages/core/src/validation/__tests__/assertions.test.ts +155 -0
  348. package/packages/core/src/validation/assertions.ts +72 -0
  349. package/packages/core/src/validation/errors.ts +8 -0
  350. package/packages/core/src/validation/helpers.ts +23 -0
  351. package/packages/core/test-helpers/index.ts +2 -0
  352. package/packages/core/test-helpers/test-ctx.ts +7 -0
  353. package/packages/core/test-helpers/test-event-queue.ts +7 -0
  354. package/packages/core/tsconfig.build.json +9 -0
  355. package/packages/core/tsconfig.json +11 -0
  356. package/packages/node/.eslintrc.js +7 -0
  357. package/packages/node/.lintstagedrc.js +1 -0
  358. package/packages/node/LICENSE +45 -0
  359. package/packages/node/README.md +138 -0
  360. package/packages/node/jest.config.js +5 -0
  361. package/packages/node/jest.setup.js +4 -0
  362. package/packages/node/package.json +50 -0
  363. package/packages/node/scripts/version.sh +11 -0
  364. package/packages/node/src/__tests__/callback.test.ts +47 -0
  365. package/packages/node/src/__tests__/disable.integration.test.ts +42 -0
  366. package/packages/node/src/__tests__/emitter.integration.test.ts +45 -0
  367. package/packages/node/src/__tests__/graceful-shutdown-integration.test.ts +244 -0
  368. package/packages/node/src/__tests__/http-client.integration.test.ts +69 -0
  369. package/packages/node/src/__tests__/http-integration.test.ts +362 -0
  370. package/packages/node/src/__tests__/integration.test.ts +357 -0
  371. package/packages/node/src/__tests__/plugins.test.ts +16 -0
  372. package/packages/node/src/__tests__/settings.test.ts +9 -0
  373. package/packages/node/src/__tests__/test-helpers/assert-shape/http-request-event.ts +13 -0
  374. package/packages/node/src/__tests__/test-helpers/assert-shape/index.ts +2 -0
  375. package/packages/node/src/__tests__/test-helpers/assert-shape/segment-http-api.ts +43 -0
  376. package/packages/node/src/__tests__/test-helpers/create-test-analytics.ts +42 -0
  377. package/packages/node/src/__tests__/test-helpers/factories.ts +17 -0
  378. package/packages/node/src/__tests__/test-helpers/is-valid-date.ts +6 -0
  379. package/packages/node/src/__tests__/test-helpers/resolve-ctx.ts +19 -0
  380. package/packages/node/src/__tests__/test-helpers/resolve-emitter.ts +11 -0
  381. package/packages/node/src/__tests__/test-helpers/sleep.ts +3 -0
  382. package/packages/node/src/__tests__/test-helpers/test-plugin.ts +16 -0
  383. package/packages/node/src/__tests__/typedef-tests.ts +120 -0
  384. package/packages/node/src/app/analytics-node.ts +299 -0
  385. package/packages/node/src/app/context.ts +11 -0
  386. package/packages/node/src/app/dispatch-emit.ts +42 -0
  387. package/packages/node/src/app/emitter.ts +23 -0
  388. package/packages/node/src/app/event-factory.ts +20 -0
  389. package/packages/node/src/app/event-queue.ts +23 -0
  390. package/packages/node/src/app/settings.ts +49 -0
  391. package/packages/node/src/app/types/index.ts +3 -0
  392. package/packages/node/src/app/types/params.ts +76 -0
  393. package/packages/node/src/app/types/plugin.ts +5 -0
  394. package/packages/node/src/app/types/segment-event.ts +7 -0
  395. package/packages/node/src/generated/version.ts +2 -0
  396. package/packages/node/src/index.ts +26 -0
  397. package/packages/node/src/lib/__tests__/abort.test.ts +54 -0
  398. package/packages/node/src/lib/__tests__/create-url.test.ts +35 -0
  399. package/packages/node/src/lib/__tests__/env.test.ts +52 -0
  400. package/packages/node/src/lib/__tests__/get-message-id.test.ts +21 -0
  401. package/packages/node/src/lib/abort.ts +77 -0
  402. package/packages/node/src/lib/base-64-encode.ts +8 -0
  403. package/packages/node/src/lib/create-url.ts +11 -0
  404. package/packages/node/src/lib/env.ts +45 -0
  405. package/packages/node/src/lib/extract-promise-parts.ts +21 -0
  406. package/packages/node/src/lib/fetch.ts +16 -0
  407. package/packages/node/src/lib/get-message-id.ts +10 -0
  408. package/packages/node/src/lib/http-client.ts +95 -0
  409. package/packages/node/src/lib/uuid.ts +1 -0
  410. package/packages/node/src/plugins/segmentio/__tests__/methods.test.ts +223 -0
  411. package/packages/node/src/plugins/segmentio/__tests__/publisher.test.ts +411 -0
  412. package/packages/node/src/plugins/segmentio/context-batch.ts +71 -0
  413. package/packages/node/src/plugins/segmentio/index.ts +66 -0
  414. package/packages/node/src/plugins/segmentio/publisher.ts +265 -0
  415. package/packages/node/tsconfig.build.json +9 -0
  416. package/packages/node/tsconfig.json +10 -0
  417. package/packages/test-helpers/.eslintrc.js +7 -0
  418. package/packages/test-helpers/.lintstagedrc.js +1 -0
  419. package/packages/test-helpers/jest.config.js +3 -0
  420. package/packages/test-helpers/package.json +26 -0
  421. package/packages/test-helpers/src/analytics/cdn-settings-builder.ts +79 -0
  422. package/packages/test-helpers/src/analytics/index.ts +1 -0
  423. package/packages/test-helpers/src/index.ts +2 -0
  424. package/packages/test-helpers/src/utils/index.ts +1 -0
  425. package/packages/test-helpers/src/utils/sleep.ts +4 -0
  426. package/packages/test-helpers/tsconfig.build.json +9 -0
  427. package/packages/test-helpers/tsconfig.json +11 -0
  428. package/tsconfig.json +21 -0
  429. package/turbo.json +39 -0
  430. package/typings/get-monorepo-packages.d.ts +9 -0
  431. package/typings/spawn.d.ts +10 -0
@@ -0,0 +1,816 @@
1
+ import * as ConsentStamping from '../consent-stamping'
2
+ import * as ConsentChanged from '../consent-changed'
3
+ import { createWrapper } from '../create-wrapper'
4
+ import { AbortLoadError, LoadContext } from '../load-cancellation'
5
+ import type {
6
+ CreateWrapperSettings,
7
+ AnyAnalytics,
8
+ CDNSettings,
9
+ HtEventsBrowserSettings,
10
+ Categories,
11
+ } from '../../types'
12
+ import { CDNSettingsBuilder } from '@internal/test-helpers'
13
+ import { assertIntegrationsContainOnly } from './assertions/integrations-assertions'
14
+
15
+ const DEFAULT_LOAD_SETTINGS = {
16
+ writeKey: 'foo',
17
+ cdnSettings: { integrations: {} },
18
+ }
19
+ /**
20
+ * Create consent settings for integrations
21
+ */
22
+ const createConsentSettings = (categories: string[] = []) => ({
23
+ consentSettings: {
24
+ categories,
25
+ },
26
+ })
27
+
28
+ const mockGetCategories: jest.MockedFn<CreateWrapperSettings['getCategories']> =
29
+ jest.fn().mockImplementation(() => ({ Advertising: true }))
30
+
31
+ const analyticsLoadSpy: jest.MockedFn<AnyAnalytics['load']> = jest.fn()
32
+ const addSourceMiddlewareSpy = jest.fn()
33
+ let analyticsOnSpy: jest.MockedFn<AnyAnalytics['on']>
34
+ const analyticsTrackSpy: jest.MockedFn<AnyAnalytics['track']> = jest.fn()
35
+ let consoleErrorSpy: jest.SpiedFunction<typeof console['error']>
36
+
37
+ const getAnalyticsLoadLastCall = () => {
38
+ const [arg1, arg2] = analyticsLoadSpy.mock.lastCall
39
+ const cdnSettings = (arg1 as any).cdnSettings as CDNSettings
40
+ const updateCDNSettings = arg2!.updateCDNSettings || ((id) => id)
41
+ const updatedCDNSettings = updateCDNSettings(cdnSettings) as CDNSettings
42
+ return {
43
+ args: [arg1 as HtEventsBrowserSettings, arg2!] as const,
44
+ cdnSettings,
45
+ updatedCDNSettings,
46
+ }
47
+ }
48
+
49
+ let analytics: AnyAnalytics, settingsBuilder: CDNSettingsBuilder
50
+ beforeEach(() => {
51
+ consoleErrorSpy = jest.spyOn(console, 'error')
52
+
53
+ settingsBuilder = new CDNSettingsBuilder().addActionDestinationSettings({
54
+ // add a default plugin just for safety
55
+ creationName: 'nope',
56
+ ...createConsentSettings(['Nope', 'Never']),
57
+ })
58
+ analyticsOnSpy = jest.fn().mockImplementation((event, fn) => {
59
+ if (event === 'initialize') {
60
+ fn(settingsBuilder.build())
61
+ } else {
62
+ console.error('event not recognized')
63
+ }
64
+ })
65
+
66
+ class MockAnalytics implements AnyAnalytics {
67
+ track = analyticsTrackSpy
68
+ on = analyticsOnSpy
69
+ load = analyticsLoadSpy
70
+ addSourceMiddleware = addSourceMiddlewareSpy
71
+ }
72
+ analytics = new MockAnalytics()
73
+ })
74
+
75
+ const wrapTestAnalytics = (overrides: Partial<CreateWrapperSettings> = {}) =>
76
+ createWrapper({
77
+ getCategories: mockGetCategories,
78
+ ...overrides,
79
+ })(analytics)
80
+
81
+ describe(createWrapper, () => {
82
+ it('should allow load arguments to be forwarded correctly from the patched analytics.load to the underlying load method', async () => {
83
+ const mockCdnSettings = settingsBuilder.build()
84
+
85
+ wrapTestAnalytics()
86
+
87
+ const loadSettings1 = {
88
+ ...DEFAULT_LOAD_SETTINGS,
89
+ cdnSettings: mockCdnSettings,
90
+ }
91
+ const loadSettings2 = {
92
+ anyOption: 'foo',
93
+ updateCDNSettings: (cdnSettings: any) => ({
94
+ ...cdnSettings,
95
+ some_new_key: 123,
96
+ }),
97
+ }
98
+ await analytics.load(loadSettings1, loadSettings2)
99
+ const { args: loadCallArgs, updatedCDNSettings } =
100
+ getAnalyticsLoadLastCall()
101
+ const [loadedSettings1, loadedSettings2] = loadCallArgs
102
+ expect(loadCallArgs.length).toBe(2)
103
+ expect(loadedSettings1).toEqual(loadSettings1)
104
+
105
+ expect(Object.keys(loadedSettings1)).toEqual(Object.keys(loadSettings1))
106
+
107
+ expect(Object.keys(loadedSettings2)).toEqual(Object.keys(loadSettings2))
108
+ expect(loadSettings2).toEqual(expect.objectContaining({ anyOption: 'foo' }))
109
+ expect(updatedCDNSettings).toEqual(
110
+ expect.objectContaining({ some_new_key: 123 })
111
+ )
112
+ })
113
+
114
+ it('should invoke addSourceMiddleware in order to stamp the event', async () => {
115
+ wrapTestAnalytics()
116
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
117
+ expect(addSourceMiddlewareSpy).toBeCalledWith(expect.any(Function))
118
+ })
119
+
120
+ it('should be chainable', async () => {
121
+ await wrapTestAnalytics().load(DEFAULT_LOAD_SETTINGS)
122
+ const { args } = getAnalyticsLoadLastCall()
123
+ expect(args.length).toBeTruthy()
124
+ })
125
+
126
+ describe('shouldLoad', () => {
127
+ describe('Throwing errors / aborting load', () => {
128
+ const createShouldLoadThatThrows = (
129
+ ...args: Parameters<LoadContext['abort']>
130
+ ) => {
131
+ let err: Error
132
+ const shouldLoad = jest.fn().mockImplementation((ctx: LoadContext) => {
133
+ try {
134
+ ctx.abort(...args)
135
+ throw new Error('Fail')
136
+ } catch (_err: any) {
137
+ err = _err
138
+ }
139
+ })
140
+ return { shouldLoad, getError: () => err }
141
+ }
142
+
143
+ it('should throw a special error if ctx.abort is called', async () => {
144
+ const { shouldLoad, getError } = createShouldLoadThatThrows({
145
+ loadHightouchNormally: true,
146
+ })
147
+ wrapTestAnalytics({
148
+ shouldLoad,
149
+ })
150
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
151
+ expect(getError() instanceof AbortLoadError).toBeTruthy()
152
+ })
153
+
154
+ it.each([
155
+ { loadHightouchNormally: true },
156
+ { loadHightouchNormally: false },
157
+ ])(
158
+ `should not log a console error or throw an error if ctx.abort is called (%p)`,
159
+ async (args) => {
160
+ wrapTestAnalytics({
161
+ shouldLoad: (ctx) => ctx.abort(args),
162
+ })
163
+ const result = await analytics.load(DEFAULT_LOAD_SETTINGS)
164
+ expect(result).toBeUndefined()
165
+ expect(consoleErrorSpy).not.toBeCalled()
166
+ }
167
+ )
168
+
169
+ it('should allow hightouch to be loaded normally (with all consent wrapper behavior disabled) via ctx.abort', async () => {
170
+ wrapTestAnalytics({
171
+ shouldLoad: (ctx) => {
172
+ ctx.abort({
173
+ loadHightouchNormally: true, // magic config option
174
+ })
175
+ },
176
+ })
177
+
178
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
179
+ expect(analyticsLoadSpy).toBeCalled()
180
+ })
181
+
182
+ it('should allow hightouch loading to be completely aborted via ctx.abort', async () => {
183
+ wrapTestAnalytics({
184
+ shouldLoad: (ctx) => {
185
+ ctx.abort({
186
+ loadHightouchNormally: false, // magic config option
187
+ })
188
+ throw new Error('Fail')
189
+ },
190
+ })
191
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
192
+ expect(analyticsLoadSpy).not.toBeCalled()
193
+ })
194
+ it('should throw a validation error if ctx.abort is called incorrectly', async () => {
195
+ const { getError, shouldLoad } = createShouldLoadThatThrows(
196
+ undefined as any
197
+ )
198
+ wrapTestAnalytics({
199
+ shouldLoad,
200
+ })
201
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
202
+ expect(getError().message).toMatch(/validation/i)
203
+ })
204
+
205
+ it('An unrecognized Error (non-consent) error should bubble up, but we should not log any additional console error', async () => {
206
+ const err = new Error('hello')
207
+ wrapTestAnalytics({
208
+ shouldLoad: () => {
209
+ throw err
210
+ },
211
+ })
212
+
213
+ await expect(() =>
214
+ analytics.load(DEFAULT_LOAD_SETTINGS)
215
+ ).rejects.toThrow(err)
216
+
217
+ expect(consoleErrorSpy).not.toBeCalled()
218
+ expect(analyticsLoadSpy).not.toBeCalled()
219
+ })
220
+ })
221
+ it('should first call shouldLoad(), then wait for it to resolve/return before calling analytics.load()', async () => {
222
+ const fnCalls: string[] = []
223
+ analyticsLoadSpy.mockImplementationOnce(() => {
224
+ fnCalls.push('analytics.load')
225
+ })
226
+
227
+ const shouldLoadMock: jest.Mock<undefined> = jest
228
+ .fn()
229
+ .mockImplementationOnce(async () => {
230
+ fnCalls.push('shouldLoad')
231
+ })
232
+
233
+ wrapTestAnalytics({
234
+ shouldLoad: shouldLoadMock,
235
+ })
236
+
237
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
238
+ expect(fnCalls).toEqual(['shouldLoad', 'analytics.load'])
239
+ })
240
+ })
241
+
242
+ describe('getCategories', () => {
243
+ test.each([
244
+ {
245
+ shouldLoad: () => undefined,
246
+ returnVal: 'undefined',
247
+ },
248
+ {
249
+ shouldLoad: () => Promise.resolve(undefined),
250
+ returnVal: 'Promise<undefined>',
251
+ },
252
+ ])(
253
+ 'if shouldLoad() returns nil ($returnVal), intial categories will come from getCategories()',
254
+ async ({ shouldLoad }) => {
255
+ const mockCdnSettings = {
256
+ integrations: {
257
+ mockIntegration: {
258
+ ...createConsentSettings(['Advertising']),
259
+ },
260
+ },
261
+ }
262
+
263
+ wrapTestAnalytics({
264
+ shouldLoad: shouldLoad,
265
+ })
266
+ await analytics.load({
267
+ ...DEFAULT_LOAD_SETTINGS,
268
+ cdnSettings: mockCdnSettings,
269
+ })
270
+
271
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
272
+ expect(analyticsLoadSpy).toBeCalled()
273
+ expect(updatedCDNSettings).toBeTruthy()
274
+ expect(mockGetCategories).toBeCalled()
275
+ }
276
+ )
277
+
278
+ test.each([
279
+ {
280
+ getCategories: () => ({ Advertising: true }),
281
+ returnVal: 'Categories',
282
+ },
283
+ {
284
+ getCategories: () => Promise.resolve({ Advertising: true }),
285
+ returnVal: 'Promise<Categories>',
286
+ },
287
+ ])(
288
+ 'if shouldLoad() returns categories ($returnVal), those will be the initial categories',
289
+ async ({ getCategories }) => {
290
+ const mockCdnSettings = {
291
+ integrations: {
292
+ mockIntegration: {
293
+ ...createConsentSettings(['Advertising']),
294
+ },
295
+ },
296
+ }
297
+
298
+ mockGetCategories.mockImplementationOnce(getCategories)
299
+
300
+ wrapTestAnalytics({
301
+ getCategories: mockGetCategories,
302
+ shouldLoad: () => undefined,
303
+ })
304
+ await analytics.load({
305
+ ...DEFAULT_LOAD_SETTINGS,
306
+ cdnSettings: mockCdnSettings,
307
+ })
308
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
309
+ expect(analyticsLoadSpy).toBeCalled()
310
+ expect(mockGetCategories).toBeCalled()
311
+ expect(updatedCDNSettings).toBeTruthy()
312
+ }
313
+ )
314
+ })
315
+
316
+ describe('Settings Validation', () => {
317
+ /* NOTE: This test suite is meant to be minimal -- please see validation/__tests__ */
318
+
319
+ test('createWrapper should throw if user-defined settings/configuration/options are invalid', () => {
320
+ expect(() =>
321
+ wrapTestAnalytics({ getCategories: {} as any })
322
+ ).toThrowError(/validation/i)
323
+ })
324
+
325
+ test('analytics.load should reject if categories are in the wrong format', async () => {
326
+ wrapTestAnalytics({
327
+ shouldLoad: () => Promise.resolve('sup' as any),
328
+ })
329
+ await expect(() => analytics.load(DEFAULT_LOAD_SETTINGS)).rejects.toThrow(
330
+ /validation/i
331
+ )
332
+ })
333
+
334
+ test('analytics.load should reject if categories are undefined', async () => {
335
+ wrapTestAnalytics({
336
+ getCategories: () => undefined as any,
337
+ shouldLoad: () => undefined,
338
+ })
339
+ await expect(() => analytics.load(DEFAULT_LOAD_SETTINGS)).rejects.toThrow(
340
+ /validation/i
341
+ )
342
+ })
343
+ })
344
+
345
+ describe('Disabling/Enabling integrations before analytics initialization (device mode gating)', () => {
346
+ it('should filter remote plugins based on consent settings', async () => {
347
+ wrapTestAnalytics()
348
+ const creationNameNoConsentData =
349
+ 'should.be.enabled.bc.no.consent.settings'
350
+ const creationNameWithConsentMatch = 'should.be.enabled.bc.consent.match'
351
+ const creationNameWithConsentMismatch = 'should.be.disabled'
352
+
353
+ const mockCdnSettings = settingsBuilder
354
+ .addActionDestinationSettings(
355
+ {
356
+ creationName: creationNameWithConsentMismatch,
357
+ ...createConsentSettings(['Foo']),
358
+ },
359
+ {
360
+ creationName: creationNameNoConsentData,
361
+ },
362
+ {
363
+ creationName: creationNameWithConsentMatch,
364
+ ...createConsentSettings(['Advertising']),
365
+ }
366
+ )
367
+ .build()
368
+
369
+ await analytics.load({
370
+ ...DEFAULT_LOAD_SETTINGS,
371
+ cdnSettings: mockCdnSettings,
372
+ })
373
+
374
+ expect(analyticsLoadSpy).toBeCalled()
375
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
376
+
377
+ expect(typeof updatedCDNSettings.remotePlugins).toBe('object')
378
+
379
+ // remote plugins should be filtered based on consent settings
380
+ assertIntegrationsContainOnly(
381
+ [creationNameNoConsentData, creationNameWithConsentMatch],
382
+ mockCdnSettings,
383
+ updatedCDNSettings
384
+ )
385
+ })
386
+
387
+ it('should allow integration if it has one category and user has consented to that category', async () => {
388
+ const mockCdnSettings = settingsBuilder
389
+ .addActionDestinationSettings({
390
+ creationName: 'mockIntegration',
391
+ ...createConsentSettings(['Foo']),
392
+ })
393
+ .build()
394
+
395
+ wrapTestAnalytics({
396
+ shouldLoad: () => ({ Foo: true }),
397
+ })
398
+ await analytics.load({
399
+ ...DEFAULT_LOAD_SETTINGS,
400
+ cdnSettings: mockCdnSettings,
401
+ })
402
+ expect(analyticsLoadSpy).toBeCalled()
403
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
404
+ // remote plugins should be filtered based on consent settings
405
+ assertIntegrationsContainOnly(
406
+ ['mockIntegration'],
407
+ mockCdnSettings,
408
+ updatedCDNSettings
409
+ )
410
+ })
411
+
412
+ it('should allow integration if it has multiple categories and user consents to all of them.', async () => {
413
+ const mockCdnSettings = settingsBuilder
414
+ .addActionDestinationSettings({
415
+ creationName: 'mockIntegration',
416
+ ...createConsentSettings(['Foo', 'Bar']),
417
+ })
418
+ .build()
419
+
420
+ wrapTestAnalytics({
421
+ shouldLoad: () => ({ Foo: true, Bar: true }),
422
+ })
423
+ await analytics.load({
424
+ ...DEFAULT_LOAD_SETTINGS,
425
+ cdnSettings: mockCdnSettings,
426
+ })
427
+ expect(analyticsLoadSpy).toBeCalled()
428
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
429
+ // remote plugins should be filtered based on consent settings
430
+ assertIntegrationsContainOnly(
431
+ ['mockIntegration'],
432
+ mockCdnSettings,
433
+ updatedCDNSettings
434
+ )
435
+ })
436
+
437
+ it('should disable integration if it has multiple categories but user has only consented to one', async () => {
438
+ const mockCdnSettings = settingsBuilder
439
+ .addActionDestinationSettings({
440
+ creationName: 'mockIntegration',
441
+ ...createConsentSettings(['Foo', 'Bar']),
442
+ })
443
+ .build()
444
+
445
+ wrapTestAnalytics({
446
+ shouldLoad: () => ({ Foo: true }),
447
+ })
448
+ await analytics.load({
449
+ ...DEFAULT_LOAD_SETTINGS,
450
+ cdnSettings: mockCdnSettings,
451
+ })
452
+
453
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
454
+ assertIntegrationsContainOnly(
455
+ ['mockIntegation'],
456
+ mockCdnSettings,
457
+ updatedCDNSettings
458
+ )
459
+ })
460
+ })
461
+
462
+ describe('shouldDisableConsentRequirement', () => {
463
+ describe('if true on wrapper initialization', () => {
464
+ it('should load analytics as usual', async () => {
465
+ wrapTestAnalytics({
466
+ shouldDisableConsentRequirement: () => true,
467
+ })
468
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
469
+ expect(analyticsLoadSpy).toBeCalled()
470
+ })
471
+
472
+ it('should not call shouldLoad if called on first', async () => {
473
+ const shouldLoad = jest.fn()
474
+ wrapTestAnalytics({
475
+ shouldDisableConsentRequirement: () => true,
476
+ shouldLoad,
477
+ })
478
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
479
+ expect(shouldLoad).not.toBeCalled()
480
+ })
481
+
482
+ it('should work with promises if false', async () => {
483
+ const shouldLoad = jest.fn()
484
+ wrapTestAnalytics({
485
+ shouldDisableConsentRequirement: () => Promise.resolve(false),
486
+ shouldLoad,
487
+ })
488
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
489
+ expect(shouldLoad).toBeCalled()
490
+ })
491
+
492
+ it('should work with promises if true', async () => {
493
+ const shouldLoad = jest.fn()
494
+ wrapTestAnalytics({
495
+ shouldDisableConsentRequirement: () => Promise.resolve(true),
496
+ shouldLoad,
497
+ })
498
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
499
+ expect(shouldLoad).not.toBeCalled()
500
+ })
501
+
502
+ it('should forward all arguments to the original analytics.load method', async () => {
503
+ const mockCdnSettings = settingsBuilder.build()
504
+
505
+ wrapTestAnalytics({
506
+ shouldDisableConsentRequirement: () => true,
507
+ })
508
+
509
+ const loadArgs: [any, any] = [
510
+ {
511
+ ...DEFAULT_LOAD_SETTINGS,
512
+ cdnSettings: mockCdnSettings,
513
+ },
514
+ {},
515
+ ]
516
+ await analytics.load(...loadArgs)
517
+ expect(analyticsLoadSpy).toBeCalled()
518
+ expect(getAnalyticsLoadLastCall().args).toEqual(loadArgs)
519
+ })
520
+
521
+ it('should not stamp the event with consent info', async () => {
522
+ wrapTestAnalytics({
523
+ shouldDisableConsentRequirement: () => true,
524
+ })
525
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
526
+ expect(addSourceMiddlewareSpy).not.toBeCalled()
527
+ })
528
+ })
529
+ })
530
+
531
+ describe('shouldDisableHightouch', () => {
532
+ it('should load analytics if disableAll returns false', async () => {
533
+ wrapTestAnalytics({
534
+ shouldDisableHightouch: () => false,
535
+ })
536
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
537
+ expect(analyticsLoadSpy).toBeCalled()
538
+ })
539
+
540
+ it('should not load analytics if disableAll returns true', async () => {
541
+ wrapTestAnalytics({
542
+ shouldDisableHightouch: () => true,
543
+ })
544
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
545
+ expect(mockGetCategories).not.toBeCalled()
546
+ expect(addSourceMiddlewareSpy).not.toBeCalled()
547
+ expect(analyticsLoadSpy).not.toBeCalled()
548
+ })
549
+ })
550
+ test.each([
551
+ {
552
+ getCategories: () =>
553
+ ({
554
+ invalidCategory: 'hello',
555
+ } as any),
556
+ returnVal: 'Categories',
557
+ },
558
+ {
559
+ getCategories: () =>
560
+ Promise.resolve({
561
+ invalidCategory: 'hello',
562
+ }) as any,
563
+ returnVal: 'Promise<Categories>',
564
+ },
565
+ ])(
566
+ 'should throw an error if getCategories() returns invalid categories during consent stamping ($returnVal))',
567
+ async ({ getCategories }) => {
568
+ const fn = jest.spyOn(ConsentStamping, 'createConsentStampingMiddleware')
569
+ const mockCdnSettings = settingsBuilder.build()
570
+
571
+ wrapTestAnalytics({
572
+ getCategories,
573
+ shouldLoad: () => {
574
+ // on first load, we should not get an error because this is a valid category setting
575
+ return { invalidCategory: true }
576
+ },
577
+ })
578
+ await analytics.load({
579
+ ...DEFAULT_LOAD_SETTINGS,
580
+ cdnSettings: mockCdnSettings,
581
+ })
582
+
583
+ const getCategoriesFn = fn.mock.lastCall[0]
584
+ await expect(getCategoriesFn()).rejects.toMatchInlineSnapshot(
585
+ `[ValidationError: [Validation] Consent Categories should be {[categoryName: string]: boolean} (Received: {"invalidCategory":"hello"})]`
586
+ )
587
+ }
588
+ )
589
+
590
+ describe('shouldEnableIntegration', () => {
591
+ it('should let user customize the logic that determines whether or not a destination is enabled', async () => {
592
+ const disabledDestinationCreationName = 'DISABLED'
593
+ const mockCdnSettings = settingsBuilder
594
+ .addActionDestinationSettings(
595
+ {
596
+ creationName: disabledDestinationCreationName,
597
+ },
598
+ { creationName: 'ENABLED' }
599
+ )
600
+ .build()
601
+
602
+ wrapTestAnalytics({
603
+ shouldEnableIntegration: (categories, consentedCategories, plugin) => {
604
+ if (plugin.creationName === disabledDestinationCreationName)
605
+ return false
606
+ if (!categories.length) return true
607
+ return categories.some((c) => consentedCategories[c])
608
+ },
609
+ })
610
+ await analytics.load({
611
+ ...DEFAULT_LOAD_SETTINGS,
612
+ cdnSettings: mockCdnSettings,
613
+ })
614
+ const { updatedCDNSettings } = getAnalyticsLoadLastCall()
615
+ assertIntegrationsContainOnly(
616
+ ['ENABLED'],
617
+ mockCdnSettings,
618
+ updatedCDNSettings
619
+ )
620
+ })
621
+ })
622
+
623
+ describe('Consent Stamping', () => {
624
+ test.each([
625
+ {
626
+ getCategories: () => ({
627
+ Something: true,
628
+ SomethingElse: false,
629
+ }),
630
+ returnVal: 'Categories',
631
+ },
632
+ {
633
+ getCategories: () =>
634
+ Promise.resolve({
635
+ Something: true,
636
+ SomethingElse: false,
637
+ }),
638
+ returnVal: 'Promise<Categories>',
639
+ },
640
+ ])(
641
+ 'should, by default, stamp the event with _all_ consent info if getCategories returns $returnVal',
642
+ async ({ getCategories }) => {
643
+ const fn = jest.spyOn(
644
+ ConsentStamping,
645
+ 'createConsentStampingMiddleware'
646
+ )
647
+ const mockCdnSettings = settingsBuilder.build()
648
+
649
+ wrapTestAnalytics({
650
+ getCategories,
651
+ })
652
+ await analytics.load({
653
+ ...DEFAULT_LOAD_SETTINGS,
654
+ cdnSettings: mockCdnSettings,
655
+ })
656
+
657
+ const getCategoriesFn = fn.mock.lastCall[0]
658
+ await expect(getCategoriesFn()).resolves.toEqual({
659
+ Something: true,
660
+ SomethingElse: false,
661
+ })
662
+ }
663
+ )
664
+
665
+ describe('pruneUnmappedCategories', () => {
666
+ it('should throw an error if there are no configured categories', async () => {
667
+ const fn = jest.spyOn(
668
+ ConsentStamping,
669
+ 'createConsentStampingMiddleware'
670
+ )
671
+ const mockCdnSettings = settingsBuilder
672
+ .addActionDestinationSettings({
673
+ creationName: 'Some Other Plugin',
674
+ })
675
+ .build()
676
+
677
+ wrapTestAnalytics({ pruneUnmappedCategories: true })
678
+ await analytics.load({
679
+ ...DEFAULT_LOAD_SETTINGS,
680
+ cdnSettings: mockCdnSettings,
681
+ })
682
+
683
+ const getCategoriesFn = fn.mock.lastCall[0]
684
+ await expect(() =>
685
+ getCategoriesFn()
686
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
687
+ `"[Validation] Invariant: No consent categories defined in Segment (Received: [])"`
688
+ )
689
+ })
690
+
691
+ it('should exclude properties that are not configured based on the allCategories array', async () => {
692
+ const fn = jest.spyOn(
693
+ ConsentStamping,
694
+ 'createConsentStampingMiddleware'
695
+ )
696
+ const mockCdnSettings = settingsBuilder
697
+ .addActionDestinationSettings({
698
+ creationName: 'Some Other Plugin',
699
+ ...createConsentSettings(['Foo']),
700
+ })
701
+ .build()
702
+
703
+ ;(mockCdnSettings as any).consentSettings = {
704
+ allCategories: ['Foo', 'Bar'],
705
+ }
706
+
707
+ wrapTestAnalytics({
708
+ pruneUnmappedCategories: true,
709
+ getCategories: () => ({
710
+ Foo: true,
711
+ Bar: false,
712
+ Rand1: false,
713
+ Rand2: true,
714
+ }),
715
+ })
716
+ await analytics.load({
717
+ ...DEFAULT_LOAD_SETTINGS,
718
+ cdnSettings: mockCdnSettings,
719
+ })
720
+
721
+ const getCategoriesFn = fn.mock.lastCall[0]
722
+ await expect(getCategoriesFn()).resolves.toEqual({
723
+ Foo: true,
724
+ Bar: false,
725
+ })
726
+ })
727
+
728
+ it('should exclude properties that are not configured if integrationCategoryMappings are passed', async () => {
729
+ const fn = jest.spyOn(
730
+ ConsentStamping,
731
+ 'createConsentStampingMiddleware'
732
+ )
733
+ const mockCdnSettings = settingsBuilder
734
+ .addActionDestinationSettings({
735
+ creationName: 'Some Other Plugin',
736
+ })
737
+ .build()
738
+
739
+ wrapTestAnalytics({
740
+ pruneUnmappedCategories: true,
741
+ getCategories: () => ({
742
+ Foo: true,
743
+ Rand1: true,
744
+ Rand2: false,
745
+ }),
746
+ integrationCategoryMappings: {
747
+ 'Some Other Plugin': ['Foo'],
748
+ },
749
+ })
750
+ await analytics.load({
751
+ ...DEFAULT_LOAD_SETTINGS,
752
+ cdnSettings: mockCdnSettings,
753
+ })
754
+
755
+ const getCategoriesFn = fn.mock.lastCall[0]
756
+ await expect(getCategoriesFn()).resolves.toEqual({ Foo: true })
757
+ })
758
+ })
759
+ })
760
+
761
+ describe('registerOnConsentChanged', () => {
762
+ const sendConsentChangedEventSpy = jest.spyOn(
763
+ ConsentChanged,
764
+ 'sendConsentChangedEvent'
765
+ )
766
+
767
+ let categoriesChangedCb: (categories: Categories) => void = () => {
768
+ throw new Error('Not implemented')
769
+ }
770
+
771
+ const registerOnConsentChanged = jest.fn(
772
+ (consentChangedCb: (c: Categories) => void) => {
773
+ // simulate a OneTrust.onConsentChanged event callback
774
+ categoriesChangedCb = jest.fn((categories: Categories) =>
775
+ consentChangedCb(categories)
776
+ )
777
+ }
778
+ )
779
+ it('should expect a callback', async () => {
780
+ wrapTestAnalytics({
781
+ registerOnConsentChanged: registerOnConsentChanged,
782
+ })
783
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
784
+
785
+ expect(sendConsentChangedEventSpy).not.toBeCalled()
786
+ expect(registerOnConsentChanged).toBeCalledTimes(1)
787
+ categoriesChangedCb({ C0001: true, C0002: false })
788
+ expect(registerOnConsentChanged).toBeCalledTimes(1)
789
+ expect(sendConsentChangedEventSpy).toBeCalledTimes(1)
790
+
791
+ // if OnConsentChanged callback is called with categories, it should send event
792
+ expect(analyticsTrackSpy).toBeCalledWith(
793
+ 'Hightouch Consent Preference',
794
+ undefined,
795
+ { consent: { categoryPreferences: { C0001: true, C0002: false } } }
796
+ )
797
+ })
798
+ it('should throw an error if categories are invalid', async () => {
799
+ consoleErrorSpy.mockImplementationOnce(() => undefined)
800
+
801
+ wrapTestAnalytics({
802
+ registerOnConsentChanged: registerOnConsentChanged,
803
+ })
804
+
805
+ await analytics.load(DEFAULT_LOAD_SETTINGS)
806
+ expect(consoleErrorSpy).not.toBeCalled()
807
+ categoriesChangedCb(['OOPS'] as any)
808
+ expect(consoleErrorSpy).toBeCalledTimes(1)
809
+ const err = consoleErrorSpy.mock.lastCall[0]
810
+ expect(err.toString()).toMatch(/validation/i)
811
+ // if OnConsentChanged callback is called with categories, it should send event
812
+ expect(sendConsentChangedEventSpy).not.toBeCalled()
813
+ expect(analyticsTrackSpy).not.toBeCalled()
814
+ })
815
+ })
816
+ })