@antelopejs/dms-marketing 0.3.2 → 0.4.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 (287) hide show
  1. package/README.md +18 -15
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.js +4 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/data-api/funnels.d.ts +2 -0
  6. package/dist/data-api/funnels.js +8 -1
  7. package/dist/data-api/funnels.js.map +1 -1
  8. package/dist/db/models/funnels.model.d.ts +2 -0
  9. package/dist/db/models/funnels.model.js +9 -0
  10. package/dist/db/models/funnels.model.js.map +1 -1
  11. package/dist/db/models/index.d.ts +1 -0
  12. package/dist/db/models/index.js +1 -0
  13. package/dist/db/models/index.js.map +1 -1
  14. package/dist/db/models/marketing_events.model.d.ts +22 -0
  15. package/dist/db/models/marketing_events.model.js +70 -5
  16. package/dist/db/models/marketing_events.model.js.map +1 -1
  17. package/dist/db/models/marketing_preferences.model.d.ts +25 -0
  18. package/dist/db/models/marketing_preferences.model.js +29 -0
  19. package/dist/db/models/marketing_preferences.model.js.map +1 -0
  20. package/dist/db/models/marketing_sessions.model.d.ts +2 -1
  21. package/dist/db/models/marketing_sessions.model.js +11 -1
  22. package/dist/db/models/marketing_sessions.model.js.map +1 -1
  23. package/dist/db/models/page_snapshots.model.d.ts +2 -1
  24. package/dist/db/models/page_snapshots.model.js +7 -7
  25. package/dist/db/models/page_snapshots.model.js.map +1 -1
  26. package/dist/db/models/website_statistics.model.d.ts +4 -1
  27. package/dist/db/models/website_statistics.model.js +15 -1
  28. package/dist/db/models/website_statistics.model.js.map +1 -1
  29. package/dist/db/tables/index.d.ts +1 -0
  30. package/dist/db/tables/index.js +1 -0
  31. package/dist/db/tables/index.js.map +1 -1
  32. package/dist/db/tables/marketing_preferences.table.d.ts +6 -0
  33. package/dist/db/tables/marketing_preferences.table.js +31 -0
  34. package/dist/db/tables/marketing_preferences.table.js.map +1 -0
  35. package/dist/hooks/index.js +8 -0
  36. package/dist/hooks/index.js.map +1 -1
  37. package/dist/index.js +18 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/pages/acquisition/page.d.ts +9 -0
  40. package/dist/pages/acquisition/page.js +181 -0
  41. package/dist/pages/acquisition/page.js.map +1 -0
  42. package/dist/pages/all.d.ts +5 -1
  43. package/dist/pages/all.js +5 -1
  44. package/dist/pages/all.js.map +1 -1
  45. package/dist/pages/blocks.d.ts +18 -0
  46. package/dist/pages/blocks.js +56 -0
  47. package/dist/pages/blocks.js.map +1 -0
  48. package/dist/pages/funnel/page.d.ts +9 -0
  49. package/dist/pages/funnel/page.js +36 -0
  50. package/dist/pages/funnel/page.js.map +1 -0
  51. package/dist/pages/funnel-builder/page.d.ts +9 -0
  52. package/dist/pages/funnel-builder/page.js +29 -0
  53. package/dist/pages/funnel-builder/page.js.map +1 -0
  54. package/dist/pages/funnels/page.d.ts +1 -1
  55. package/dist/pages/funnels/page.js +200 -3
  56. package/dist/pages/funnels/page.js.map +1 -1
  57. package/dist/pages/index.d.ts +1 -0
  58. package/dist/pages/index.js +1 -0
  59. package/dist/pages/index.js.map +1 -1
  60. package/dist/pages/{campaigns → install}/page.d.ts +3 -3
  61. package/dist/pages/{campaigns → install}/page.js +14 -12
  62. package/dist/pages/install/page.js.map +1 -0
  63. package/dist/pages/module.d.ts +3 -0
  64. package/dist/pages/module.js +30 -3
  65. package/dist/pages/module.js.map +1 -1
  66. package/dist/pages/overview/page.d.ts +1 -1
  67. package/dist/pages/overview/page.js +122 -3
  68. package/dist/pages/overview/page.js.map +1 -1
  69. package/dist/pages/pages/page.d.ts +1 -1
  70. package/dist/pages/pages/page.js +9 -4
  71. package/dist/pages/pages/page.js.map +1 -1
  72. package/dist/pages/quick-actions.d.ts +1 -0
  73. package/dist/pages/quick-actions.js +25 -0
  74. package/dist/pages/quick-actions.js.map +1 -0
  75. package/dist/pages/settings/form.d.ts +2 -1
  76. package/dist/pages/settings/form.js +62 -20
  77. package/dist/pages/settings/form.js.map +1 -1
  78. package/dist/pages/settings/page.d.ts +4 -1
  79. package/dist/pages/settings/page.js +50 -4
  80. package/dist/pages/settings/page.js.map +1 -1
  81. package/dist/pages/websites/page.d.ts +11 -0
  82. package/dist/pages/websites/page.js +62 -0
  83. package/dist/pages/websites/page.js.map +1 -0
  84. package/dist/routes/blocks.d.ts +50 -0
  85. package/dist/routes/blocks.js +269 -0
  86. package/dist/routes/blocks.js.map +1 -0
  87. package/dist/routes/collect.d.ts +1 -2
  88. package/dist/routes/collect.js +19 -12
  89. package/dist/routes/collect.js.map +1 -1
  90. package/dist/routes/context.d.ts +8 -0
  91. package/dist/routes/context.js +54 -0
  92. package/dist/routes/context.js.map +1 -0
  93. package/dist/routes/experiments.js +2 -1
  94. package/dist/routes/experiments.js.map +1 -1
  95. package/dist/routes/funnels.d.ts +21 -3
  96. package/dist/routes/funnels.js +150 -7
  97. package/dist/routes/funnels.js.map +1 -1
  98. package/dist/routes/index.d.ts +2 -0
  99. package/dist/routes/index.js +2 -0
  100. package/dist/routes/index.js.map +1 -1
  101. package/dist/routes/settings.d.ts +6 -1
  102. package/dist/routes/settings.js +65 -4
  103. package/dist/routes/settings.js.map +1 -1
  104. package/dist/routes/snapshot.d.ts +1 -1
  105. package/dist/routes/snapshot.js +24 -25
  106. package/dist/routes/snapshot.js.map +1 -1
  107. package/dist/routes/stats.d.ts +14 -23
  108. package/dist/routes/stats.js +73 -97
  109. package/dist/routes/stats.js.map +1 -1
  110. package/dist/routes/tracker.js +6 -4
  111. package/dist/routes/tracker.js.map +1 -1
  112. package/dist/routes/websites.d.ts +4 -0
  113. package/dist/routes/websites.js +111 -12
  114. package/dist/routes/websites.js.map +1 -1
  115. package/dist/services/acquisition-report.d.ts +62 -0
  116. package/dist/services/acquisition-report.js +181 -0
  117. package/dist/services/acquisition-report.js.map +1 -0
  118. package/dist/services/acquisition.d.ts +9 -3
  119. package/dist/services/acquisition.js +35 -5
  120. package/dist/services/acquisition.js.map +1 -1
  121. package/dist/services/catalog.d.ts +3 -0
  122. package/dist/services/catalog.js +17 -0
  123. package/dist/services/catalog.js.map +1 -0
  124. package/dist/services/collect.d.ts +1 -2
  125. package/dist/services/collect.js +49 -24
  126. package/dist/services/collect.js.map +1 -1
  127. package/dist/services/composed.d.ts +9 -0
  128. package/dist/services/composed.js +62 -0
  129. package/dist/services/composed.js.map +1 -0
  130. package/dist/services/context.d.ts +20 -0
  131. package/dist/services/context.js +76 -0
  132. package/dist/services/context.js.map +1 -0
  133. package/dist/services/dashboard.d.ts +74 -0
  134. package/dist/services/dashboard.js +343 -0
  135. package/dist/services/dashboard.js.map +1 -0
  136. package/dist/services/experiments/results.d.ts +12 -1
  137. package/dist/services/experiments/results.js +19 -2
  138. package/dist/services/experiments/results.js.map +1 -1
  139. package/dist/services/experiments/stats.d.ts +7 -1
  140. package/dist/services/experiments/stats.js +43 -5
  141. package/dist/services/experiments/stats.js.map +1 -1
  142. package/dist/services/funnel-report.d.ts +64 -0
  143. package/dist/services/funnel-report.js +280 -0
  144. package/dist/services/funnel-report.js.map +1 -0
  145. package/dist/services/funnels/compute.d.ts +2 -0
  146. package/dist/services/funnels/compute.js +25 -13
  147. package/dist/services/funnels/compute.js.map +1 -1
  148. package/dist/services/funnels/results.js +4 -1
  149. package/dist/services/funnels/results.js.map +1 -1
  150. package/dist/services/funnels/step-path.d.ts +4 -0
  151. package/dist/services/funnels/step-path.js +47 -0
  152. package/dist/services/funnels/step-path.js.map +1 -0
  153. package/dist/services/funnels-table.d.ts +31 -0
  154. package/dist/services/funnels-table.js +230 -0
  155. package/dist/services/funnels-table.js.map +1 -0
  156. package/dist/services/heatmap.d.ts +15 -3
  157. package/dist/services/heatmap.js +54 -11
  158. package/dist/services/heatmap.js.map +1 -1
  159. package/dist/services/hourly-traffic.d.ts +4 -0
  160. package/dist/services/hourly-traffic.js +35 -0
  161. package/dist/services/hourly-traffic.js.map +1 -0
  162. package/dist/services/origin-guard.d.ts +9 -1
  163. package/dist/services/origin-guard.js +46 -1
  164. package/dist/services/origin-guard.js.map +1 -1
  165. package/dist/services/period.d.ts +26 -1
  166. package/dist/services/period.js +99 -9
  167. package/dist/services/period.js.map +1 -1
  168. package/dist/services/prune.d.ts +6 -0
  169. package/dist/services/prune.js +35 -0
  170. package/dist/services/prune.js.map +1 -1
  171. package/dist/services/request-body.d.ts +1 -2
  172. package/dist/services/request-body.js +10 -36
  173. package/dist/services/request-body.js.map +1 -1
  174. package/dist/services/request-meta.d.ts +0 -1
  175. package/dist/services/request-meta.js +0 -11
  176. package/dist/services/request-meta.js.map +1 -1
  177. package/dist/services/rollups.d.ts +25 -0
  178. package/dist/services/rollups.js +102 -0
  179. package/dist/services/rollups.js.map +1 -0
  180. package/dist/services/sessionize.d.ts +5 -1
  181. package/dist/services/sessionize.js +52 -10
  182. package/dist/services/sessionize.js.map +1 -1
  183. package/dist/services/settings.d.ts +41 -2
  184. package/dist/services/settings.js +219 -6
  185. package/dist/services/settings.js.map +1 -1
  186. package/dist/services/snapshots.d.ts +3 -2
  187. package/dist/services/snapshots.js +110 -9
  188. package/dist/services/snapshots.js.map +1 -1
  189. package/dist/services/tenant-website.js +1 -0
  190. package/dist/services/tenant-website.js.map +1 -1
  191. package/dist/services/url.js +12 -4
  192. package/dist/services/url.js.map +1 -1
  193. package/dist/services/website-hosts.d.ts +15 -0
  194. package/dist/services/website-hosts.js +67 -0
  195. package/dist/services/website-hosts.js.map +1 -0
  196. package/dist/services/website-report.d.ts +40 -0
  197. package/dist/services/website-report.js +84 -0
  198. package/dist/services/website-report.js.map +1 -0
  199. package/dist/types/constants.d.ts +14 -2
  200. package/dist/types/constants.js +17 -6
  201. package/dist/types/constants.js.map +1 -1
  202. package/dist/types/events.d.ts +221 -68
  203. package/dist/types/events.js +60 -11
  204. package/dist/types/events.js.map +1 -1
  205. package/dist/types/snapshots.d.ts +6 -0
  206. package/dist/types/snapshots.js +2 -0
  207. package/dist/types/snapshots.js.map +1 -1
  208. package/dist/types/statistics.d.ts +2 -1
  209. package/dist/types/statistics.js +9 -2
  210. package/dist/types/statistics.js.map +1 -1
  211. package/dist/utils/experiment-type.js +1 -1
  212. package/dist/utils/experiment-type.js.map +1 -1
  213. package/dist/utils/funnel-steps-type.d.ts +21 -3
  214. package/dist/utils/funnel-steps-type.js +7 -2
  215. package/dist/utils/funnel-steps-type.js.map +1 -1
  216. package/frontend-vue/app/components/ChannelsCard.vue +265 -0
  217. package/frontend-vue/app/components/Context.vue +479 -0
  218. package/frontend-vue/app/components/ExperimentInput.vue +3 -1
  219. package/frontend-vue/app/components/ExperimentReport.vue +736 -0
  220. package/frontend-vue/app/components/FirstRun.vue +60 -0
  221. package/frontend-vue/app/components/FunnelBuilder.vue +1595 -0
  222. package/frontend-vue/app/components/FunnelFigure.vue +640 -0
  223. package/frontend-vue/app/components/FunnelReport.vue +532 -0
  224. package/frontend-vue/app/components/FunnelStepsInput.vue +3 -1
  225. package/frontend-vue/app/components/FunnelTemplates.vue +161 -0
  226. package/frontend-vue/app/components/HeatmapCanvas.vue +11 -4
  227. package/frontend-vue/app/components/InstallGuide.vue +753 -0
  228. package/frontend-vue/app/components/PagePreview.vue +889 -416
  229. package/frontend-vue/app/components/PagesExplorer.vue +552 -0
  230. package/frontend-vue/app/components/ScrollDepthOverlay.vue +48 -20
  231. package/frontend-vue/app/components/TopListTabs.vue +317 -0
  232. package/frontend-vue/app/components/TrackerSnippet.vue +50 -16
  233. package/frontend-vue/app/components/VariationsInput.vue +15 -5
  234. package/frontend-vue/app/components/WebsitesGrid.vue +799 -0
  235. package/frontend-vue/app/composables/useContextLink.ts +16 -0
  236. package/frontend-vue/app/composables/useExperiment.ts +1 -2
  237. package/frontend-vue/app/composables/useFunnelSteps.ts +1 -2
  238. package/frontend-vue/app/composables/useLatestRequest.ts +2 -4
  239. package/frontend-vue/app/composables/useMarketingApi.ts +298 -133
  240. package/frontend-vue/app/composables/useMarketingContext.ts +55 -0
  241. package/frontend-vue/app/composables/useMarketingRoutes.ts +80 -27
  242. package/frontend-vue/app/composables/useSnapshotFrame.ts +26 -4
  243. package/frontend-vue/app/constants.ts +22 -0
  244. package/frontend-vue/app/plugins/funnel-steps-data-type.client.ts +2 -1
  245. package/frontend-vue/app/utils/channels.ts +37 -0
  246. package/frontend-vue/app/utils/format.ts +132 -0
  247. package/frontend-vue/app/utils/funnel-draft.ts +264 -0
  248. package/frontend-vue/app/utils/heatmap.ts +82 -0
  249. package/frontend-vue/app/utils/heatmapPalette.ts +10 -0
  250. package/frontend-vue/app/utils/host-conflict.ts +48 -0
  251. package/frontend-vue/app/utils/sample-size.ts +17 -0
  252. package/frontend-vue/app/utils/snapshotViewportUnits.ts +15 -5
  253. package/frontend-vue/app/utils/snippet.ts +53 -0
  254. package/frontend-vue/app/utils/step-path.ts +32 -0
  255. package/frontend-vue/dms.frontend.ts +4 -3
  256. package/frontend-vue/i18n/locales/marketing-en-GB.json +1083 -316
  257. package/frontend-vue/i18n/locales/marketing-fr-FR.json +1087 -320
  258. package/frontend-vue/package.json +3 -0
  259. package/frontend-vue/tests/drafts.test.ts +3 -3
  260. package/frontend-vue/tests/format.test.ts +73 -0
  261. package/frontend-vue/tests/funnel-draft.test.ts +194 -0
  262. package/frontend-vue/tests/heatmap.test.ts +32 -0
  263. package/frontend-vue/tests/i18n.test.ts +87 -4
  264. package/frontend-vue/tests/latest-request.test.ts +6 -3
  265. package/frontend-vue/tests/locale-typography.test.ts +115 -0
  266. package/frontend-vue/tests/registration.test.ts +21 -11
  267. package/frontend-vue/tests/routes.test.ts +67 -7
  268. package/frontend-vue/tests/server-parity.test.ts +77 -0
  269. package/frontend-vue/tests/stubs/frontend-module.ts +18 -3
  270. package/package.json +9 -7
  271. package/static/snapshot.js +46 -16
  272. package/static/tracker.js +139 -48
  273. package/dist/pages/campaigns/page.js.map +0 -1
  274. package/frontend-vue/app/components/CampaignsView.vue +0 -330
  275. package/frontend-vue/app/components/FunnelDetail.vue +0 -1026
  276. package/frontend-vue/app/components/FunnelsView.vue +0 -493
  277. package/frontend-vue/app/components/NoWebsiteState.vue +0 -18
  278. package/frontend-vue/app/components/OverviewView.vue +0 -493
  279. package/frontend-vue/app/components/PagesView.vue +0 -391
  280. package/frontend-vue/app/components/TextKpiCard.vue +0 -28
  281. package/frontend-vue/app/components/TopListTabsCard.vue +0 -162
  282. package/frontend-vue/app/composables/useChildId.ts +0 -13
  283. package/frontend-vue/app/composables/useMarketingPeriod.ts +0 -65
  284. package/frontend-vue/app/composables/useMarketingWebsiteForm.ts +0 -69
  285. package/frontend-vue/app/composables/useMarketingWebsites.ts +0 -80
  286. package/frontend-vue/app/utils/dmsComponent.ts +0 -18
  287. package/frontend-vue/tests/period.test.ts +0 -35
@@ -1,23 +1,83 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import {
3
- campaignsLink,
3
+ acquisitionLink,
4
+ funnelBuilderLink,
5
+ funnelLink,
4
6
  funnelsLink,
7
+ installLink,
8
+ overviewLink,
5
9
  pagesLink,
10
+ withWebsite,
6
11
  } from '../app/composables/useMarketingRoutes'
7
12
 
8
13
  describe('module links', () => {
9
14
  it('stay bare when nothing is selected', () => {
10
15
  expect(pagesLink()).toBe('/modules/marketing/pages')
11
- expect(campaignsLink()).toBe('/modules/marketing/campaigns')
16
+ expect(acquisitionLink()).toBe('/modules/marketing/acquisition')
12
17
  expect(funnelsLink()).toBe('/modules/marketing/funnels')
18
+ expect(funnelBuilderLink()).toBe('/modules/marketing/funnel-builder')
19
+ expect(installLink()).toBe('/modules/marketing/install')
13
20
  })
14
21
 
15
- it('serializes the exact button route and encodes selected values', () => {
16
- expect(funnelsLink({ website: 'test-site-1' })).toBe(
17
- '/modules/marketing/funnels?website=test-site-1',
22
+ it('encode the values they carry', () => {
23
+ expect(pagesLink('/pricing?x=1')).toBe(
24
+ '/modules/marketing/pages?path=%2Fpricing%3Fx%3D1',
18
25
  )
19
- expect(pagesLink({ website: 'w 1', path: '/pricing?x=1', period: '7d' })).toBe(
20
- '/modules/marketing/pages?website=w+1&path=%2Fpricing%3Fx%3D1&period=7d',
26
+ expect(funnelLink('f 1')).toBe('/modules/marketing/funnel?id=f+1')
27
+ expect(installLink('site-1')).toBe(
28
+ '/modules/marketing/install?website=site-1',
29
+ )
30
+ })
31
+
32
+ it('carry a template and the split switch to the builder', () => {
33
+ const link = funnelBuilderLink(null, {
34
+ split: true,
35
+ steps: [{ kind: 'custom', value: 'signup' }],
36
+ })
37
+ const query = new URL(link, 'http://x').searchParams
38
+ expect(query.get('split')).toBe('1')
39
+ expect(JSON.parse(query.get('steps')!)).toEqual([
40
+ { kind: 'custom', value: 'signup' },
41
+ ])
42
+ })
43
+
44
+ it('opens the builder on a copy of a funnel (Duplicate)', () => {
45
+ expect(funnelBuilderLink(null, { copy: 'f1' })).toBe(
46
+ '/modules/marketing/funnel-builder?copy=f1',
47
+ )
48
+ })
49
+
50
+ it('open the overview on a website', () => {
51
+ expect(overviewLink()).toBe('/modules/marketing/overview')
52
+ expect(overviewLink('site-1')).toBe(
53
+ '/modules/marketing/overview?website=site-1',
54
+ )
55
+ })
56
+
57
+ it('carry the website of the page they are followed from', () => {
58
+ const query = {
59
+ website: 'site-1',
60
+ 'marketing.period': 'last-7-days',
61
+ path: '/ignored',
62
+ }
63
+ expect(withWebsite(pagesLink('/pricing'), query)).toBe(
64
+ '/modules/marketing/pages?path=%2Fpricing&website=site-1',
65
+ )
66
+ expect(withWebsite(acquisitionLink(), query)).toBe(
67
+ '/modules/marketing/acquisition?website=site-1',
68
+ )
69
+ })
70
+
71
+ it('keep the website a link names, and stay bare without one', () => {
72
+ const query = { website: 'site-1' }
73
+ expect(withWebsite(overviewLink('site-2'), query)).toBe(
74
+ '/modules/marketing/overview?website=site-2',
75
+ )
76
+ expect(withWebsite(acquisitionLink(), {})).toBe(
77
+ '/modules/marketing/acquisition',
78
+ )
79
+ expect(withWebsite(acquisitionLink(), { website: ['site-1'] })).toBe(
80
+ '/modules/marketing/acquisition',
21
81
  )
22
82
  })
23
83
  })
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ // The server's own rules, read from its sources: the builder and the website
3
+ // forms check a draft before saving it, and must refuse what the API refuses.
4
+ import * as server from '../../src/types/constants'
5
+ import { normalizeStepPath } from '../../src/services/funnels/step-path'
6
+ import * as draft from '../app/utils/funnel-draft'
7
+ import { normalizeFunnelStepPath } from '../app/utils/step-path'
8
+ import {
9
+ MAX_WEBSITE_DOMAIN_LENGTH,
10
+ MAX_WEBSITE_NAME_LENGTH,
11
+ } from '../app/constants'
12
+
13
+ describe('front rules mirror the server', () => {
14
+ it('caps a funnel draft as the server does', () => {
15
+ expect(draft.MAX_NAME_LENGTH).toBe(server.MAX_NAME_LENGTH)
16
+ expect(draft.MAX_STEPS).toBe(server.MAX_FUNNEL_STEPS)
17
+ expect(draft.MAX_STEP_VALUE_LENGTH).toBe(
18
+ server.MAX_FUNNEL_STEP_VALUE_LENGTH,
19
+ )
20
+ expect(draft.MAX_WINDOW_HOURS).toBe(server.MAX_CONVERSION_WINDOW_HOURS)
21
+ expect(draft.MIN_VARIATIONS).toBe(server.MIN_EXPERIMENT_VARIATIONS)
22
+ expect(draft.MAX_VARIATIONS).toBe(server.MAX_EXPERIMENT_VARIATIONS)
23
+ expect(draft.MAX_EXPERIMENT_KEY_LENGTH).toBe(
24
+ server.MAX_EXPERIMENT_KEY_LENGTH,
25
+ )
26
+ })
27
+
28
+ it('accepts the experiment keys the server accepts', () => {
29
+ const keys = [
30
+ 'hero-cta',
31
+ 'v2_bold',
32
+ '0day',
33
+ '-lead',
34
+ '_lead',
35
+ 'Hero',
36
+ 'with space',
37
+ 'é',
38
+ 'a'.repeat(server.MAX_EXPERIMENT_KEY_LENGTH),
39
+ 'a'.repeat(server.MAX_EXPERIMENT_KEY_LENGTH + 1),
40
+ ]
41
+ for (const key of keys) {
42
+ expect(draft.isExperimentKey(key), key).toBe(
43
+ server.EXPERIMENT_KEY_PATTERN.test(key) &&
44
+ key.length <= server.MAX_EXPERIMENT_KEY_LENGTH,
45
+ )
46
+ }
47
+ })
48
+
49
+ it('normalizes a page step as the server does', () => {
50
+ const typed = [
51
+ '/pricing',
52
+ 'pricing',
53
+ '/Sale/Chairs/',
54
+ '/',
55
+ '',
56
+ ' /cart?step=2#top ',
57
+ 'https://shop.acme.test/Sale/Chairs/?utm=x',
58
+ 'shop.acme.test/checkout/',
59
+ 'HTTP://Shop.Acme.Test/a',
60
+ '/blog/*',
61
+ 'shop.acme.test/blog/*',
62
+ '*',
63
+ '/a b',
64
+ 'http://[bad',
65
+ ]
66
+ for (const value of typed) {
67
+ expect(normalizeFunnelStepPath(value), value).toBe(
68
+ normalizeStepPath(value),
69
+ )
70
+ }
71
+ })
72
+
73
+ it('caps a website name and domain as the server does', () => {
74
+ expect(MAX_WEBSITE_NAME_LENGTH).toBe(server.MAX_NAME_LENGTH)
75
+ expect(MAX_WEBSITE_DOMAIN_LENGTH).toBe(server.MAX_DOMAIN_LENGTH)
76
+ })
77
+ })
@@ -26,6 +26,7 @@ export interface DmsFrontendSdk {
26
26
  }
27
27
 
28
28
  export interface DmsFrontendModule {
29
+ componentPrefix?: string
29
30
  setup(sdk: DmsFrontendSdk): void | Promise<void>
30
31
  }
31
32
 
@@ -33,15 +34,22 @@ export function defineDmsPlugin(setup: DmsPluginSetup): DmsPluginSetup {
33
34
  return setup
34
35
  }
35
36
 
36
- export function useI18n(): { t: (key: string) => string, te: () => boolean, locale: { value: string } } {
37
+ export function useI18n(): {
38
+ t: (key: string) => string
39
+ te: () => boolean
40
+ locale: { value: string }
41
+ } {
37
42
  return { t: (key: string) => key, te: () => false, locale: { value: 'en' } }
38
43
  }
39
44
 
40
- export function useDmsRoute(): { query: Record<string, string>, path: string } {
45
+ export function useDmsRoute(): { query: Record<string, string>; path: string } {
41
46
  return { query: {}, path: '/' }
42
47
  }
43
48
 
44
- export function useDmsRouter(): { replace: () => Promise<void>, push: () => Promise<void> } {
49
+ export function useDmsRouter(): {
50
+ replace: () => Promise<void>
51
+ push: () => Promise<void>
52
+ } {
45
53
  return { replace: async () => {}, push: async () => {} }
46
54
  }
47
55
 
@@ -52,3 +60,10 @@ export function useDmsRuntimeConfig(): { public: Record<string, unknown> } {
52
60
  export function resolveDmsComponent(): Component | undefined {
53
61
  return undefined
54
62
  }
63
+
64
+ export function useDmsCookie<T>(
65
+ _name: string,
66
+ options: { default: () => T },
67
+ ): { value: T } {
68
+ return { value: options.default() }
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-marketing",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,23 +31,25 @@
31
31
  "@antelopejs/interface-api": ">=0.0.13 <1.0.0",
32
32
  "@antelopejs/interface-api-util": ">=0.1.2 <1.0.0",
33
33
  "@antelopejs/interface-core": ">=0.1.1 <1.0.0",
34
- "@antelopejs/interface-data-api": ">=0.1.9 <1.0.0",
34
+ "@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
35
35
  "@antelopejs/interface-database": ">=0.1.8 <1.0.0",
36
- "@antelopejs/interface-database-decorators": ">=0.1.6 <1.0.0",
37
- "@antelopejs/interface-dms": ">=0.2.0 <1.0.0",
38
- "@antelopejs/interface-dms-marketing": ">=0.3.1 <0.4.0",
36
+ "@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
37
+ "@antelopejs/interface-dms": ">=0.5.11 <1.0.0",
38
+ "@antelopejs/interface-dms-marketing": ">=0.4.0 <0.5.0",
39
39
  "defu": "^6.1.4",
40
40
  "maxmind": "^5.0.7",
41
41
  "node-cron": "^4.2.1",
42
+ "sanitize-html": "^2.18.0",
42
43
  "ua-parser-js": "^2.0.9",
43
44
  "zod": "~3.24.4"
44
45
  },
45
46
  "devDependencies": {
46
- "@antelopejs/core": ">=1.9.1 <2",
47
+ "@antelopejs/core": ">=1.13.5 <2",
47
48
  "@antelopejs/interface-mongodb": ">=0.0.5 <1.0.0",
48
- "@antelopejs/mongodb": "1.3.1",
49
+ "@antelopejs/mongodb": "1.4.2",
49
50
  "@antelopejs/tooling-configs": ">=0.0.7 <1.0.0",
50
51
  "@types/node": "^22.19.15",
52
+ "@types/sanitize-html": "^2.16.2",
51
53
  "knip": "^6.34.0",
52
54
  "mongodb": "^6.21.0",
53
55
  "mongodb-memory-server-core": "^10.4.3",
@@ -11,9 +11,12 @@
11
11
  * Privacy, unconditionally: no form value, hidden input, script, event
12
12
  * handler or framed document leaves the page. `contenteditable` text is
13
13
  * masked, as is everything under `data-dms-marketing-mask` (or the
14
- * `dms-marketing-mask` class); `data-dms-marketing-block` (or the
15
- * `dms-marketing-block` class) replaces an element with an empty box of its
16
- * size. `maskText` masks every text node.
14
+ * `dms-marketing-mask` class) — text, the attributes that carry text
15
+ * (title, alt, labels, placeholders, data-*) and the path of its links;
16
+ * `data-dms-marketing-block` (or the `dms-marketing-block` class) replaces
17
+ * an element with an empty box of its size. `maskText` masks every text
18
+ * node and text attribute of the page; data-* and link paths stay, since
19
+ * styles select on them.
17
20
  */
18
21
  (() => {
19
22
  const CONFIG_GLOBAL = "__dmsMarketingSnapshotConfig";
@@ -81,6 +84,17 @@
81
84
  "background",
82
85
  ]);
83
86
  const LINK_TAGS = new Set(["a", "area"]);
87
+ /** Attributes that carry text a visitor could read, masked with it. */
88
+ const TEXT_ATTRIBUTES = new Set([
89
+ "title",
90
+ "alt",
91
+ "label",
92
+ "placeholder",
93
+ "aria-label",
94
+ "aria-description",
95
+ "aria-placeholder",
96
+ "aria-valuetext",
97
+ ]);
84
98
  const OPAQUE_URL_PATTERN = /^(#|data:|blob:|about:)/i;
85
99
  const SCRIPT_URL_PATTERN = /^javascript:/i;
86
100
  const CSS_URL_PATTERN = /url\(\s*(['"]?)([^'")]*)\1\s*\)/g;
@@ -93,10 +107,12 @@
93
107
  }
94
108
 
95
109
  const config = readGlobal(CONFIG_GLOBAL);
110
+ const maskAllText = config?.maskText === true;
96
111
  const context = readGlobal(CONTEXT_GLOBAL);
97
112
  if (
98
113
  !config ||
99
114
  !context ||
115
+ typeof config.token !== "string" ||
100
116
  typeof context.endpoint !== "string" ||
101
117
  window.top !== window
102
118
  ) {
@@ -144,10 +160,14 @@
144
160
  }
145
161
 
146
162
  /** Links keep their target, never a query or fragment: tokens and
147
- * addresses ride there, and nothing in the backdrop is clickable. */
148
- function withoutQuery(url) {
163
+ * addresses ride there, and nothing in the backdrop is clickable. A masked
164
+ * link keeps its origin alone — a profile path names its person. */
165
+ function withoutQuery(url, masked) {
149
166
  try {
150
167
  const parsed = new URL(url);
168
+ if (masked) {
169
+ return `${parsed.origin}/`;
170
+ }
151
171
  parsed.search = "";
152
172
  parsed.hash = "";
153
173
  return parsed.href;
@@ -269,7 +289,7 @@
269
289
  return element.dataset.src || current || value;
270
290
  }
271
291
 
272
- function serializeAttributes(element) {
292
+ function serializeAttributes(element, masked) {
273
293
  const tag = element.localName;
274
294
  let out = "";
275
295
  for (const attribute of element.attributes) {
@@ -278,6 +298,14 @@
278
298
  if (name.startsWith("on") || DROPPED_ATTRIBUTES.has(name)) {
279
299
  continue;
280
300
  }
301
+ if (
302
+ ((masked || maskAllText) && TEXT_ATTRIBUTES.has(name)) ||
303
+ (masked &&
304
+ name.startsWith("data-") &&
305
+ !name.startsWith(`data-${MASK_MARKER}`))
306
+ ) {
307
+ value = maskText(value);
308
+ }
281
309
  if (name === "value" && (tag === "input" || tag === "textarea")) {
282
310
  continue;
283
311
  }
@@ -309,7 +337,7 @@
309
337
  if (absolute === null) {
310
338
  continue;
311
339
  }
312
- value = LINK_TAGS.has(tag) ? withoutQuery(absolute) : absolute;
340
+ value = LINK_TAGS.has(tag) ? withoutQuery(absolute, masked) : absolute;
313
341
  }
314
342
  out += ` ${name}="${escapeAttribute(value)}"`;
315
343
  }
@@ -334,7 +362,7 @@
334
362
  return `<${tag}${attributes} style="${size};overflow:hidden"></${tag}>`;
335
363
  }
336
364
 
337
- function serializeCanvas(element) {
365
+ function serializeCanvas(element, masked) {
338
366
  let dataUrl = "";
339
367
  try {
340
368
  dataUrl = element.toDataURL();
@@ -342,9 +370,9 @@
342
370
  // Tainted by cross-origin drawing: the box stays, the pixels do not.
343
371
  }
344
372
  if (dataUrl && dataUrl.length <= MAX_CANVAS_DATA_URL_LENGTH) {
345
- return `<img${serializeAttributes(element)} src="${dataUrl}">`;
373
+ return `<img${serializeAttributes(element, masked)} src="${dataUrl}">`;
346
374
  }
347
- return `<canvas${serializeAttributes(element)}></canvas>`;
375
+ return `<canvas${serializeAttributes(element, masked)}></canvas>`;
348
376
  }
349
377
 
350
378
  function serializeChildren(node, masked) {
@@ -353,7 +381,8 @@
353
381
  if (child.nodeType === Node.ELEMENT_NODE) {
354
382
  out += serializeElement(child, masked);
355
383
  } else if (child.nodeType === Node.TEXT_NODE) {
356
- const text = masked ? maskText(child.data) : child.data;
384
+ const text =
385
+ masked || maskAllText ? maskText(child.data) : child.data;
357
386
  out += escapeText(text);
358
387
  }
359
388
  }
@@ -377,12 +406,12 @@
377
406
  if (isMarked(element, BLOCK_MARKER)) {
378
407
  return serializeBlocked(element);
379
408
  }
380
- if (tag === "canvas") {
381
- return serializeCanvas(element);
382
- }
383
409
  const mask =
384
410
  masked || isMarked(element, MASK_MARKER) || element.isContentEditable;
385
- let out = `<${tag}${serializeAttributes(element)}>`;
411
+ if (tag === "canvas") {
412
+ return serializeCanvas(element, mask);
413
+ }
414
+ let out = `<${tag}${serializeAttributes(element, mask)}>`;
386
415
  if (VOID_TAGS.has(tag)) {
387
416
  return out;
388
417
  }
@@ -403,7 +432,7 @@
403
432
  }
404
433
 
405
434
  function serializeDocument() {
406
- return `<!DOCTYPE html>${serializeElement(document.documentElement, config.maskText === true)}`;
435
+ return `<!DOCTYPE html>${serializeElement(document.documentElement, false)}`;
407
436
  }
408
437
 
409
438
  // --- Timing ---------------------------------------------------------------
@@ -466,6 +495,7 @@
466
495
  const root = document.documentElement;
467
496
  const page = {
468
497
  website: context.website,
498
+ token: config.token,
469
499
  url: (location.origin + location.pathname + location.search).slice(
470
500
  0,
471
501
  MAX_URL_LENGTH,