@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.
- package/README.md +18 -15
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/data-api/funnels.d.ts +2 -0
- package/dist/data-api/funnels.js +8 -1
- package/dist/data-api/funnels.js.map +1 -1
- package/dist/db/models/funnels.model.d.ts +2 -0
- package/dist/db/models/funnels.model.js +9 -0
- package/dist/db/models/funnels.model.js.map +1 -1
- package/dist/db/models/index.d.ts +1 -0
- package/dist/db/models/index.js +1 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/marketing_events.model.d.ts +22 -0
- package/dist/db/models/marketing_events.model.js +70 -5
- package/dist/db/models/marketing_events.model.js.map +1 -1
- package/dist/db/models/marketing_preferences.model.d.ts +25 -0
- package/dist/db/models/marketing_preferences.model.js +29 -0
- package/dist/db/models/marketing_preferences.model.js.map +1 -0
- package/dist/db/models/marketing_sessions.model.d.ts +2 -1
- package/dist/db/models/marketing_sessions.model.js +11 -1
- package/dist/db/models/marketing_sessions.model.js.map +1 -1
- package/dist/db/models/page_snapshots.model.d.ts +2 -1
- package/dist/db/models/page_snapshots.model.js +7 -7
- package/dist/db/models/page_snapshots.model.js.map +1 -1
- package/dist/db/models/website_statistics.model.d.ts +4 -1
- package/dist/db/models/website_statistics.model.js +15 -1
- package/dist/db/models/website_statistics.model.js.map +1 -1
- package/dist/db/tables/index.d.ts +1 -0
- package/dist/db/tables/index.js +1 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/marketing_preferences.table.d.ts +6 -0
- package/dist/db/tables/marketing_preferences.table.js +31 -0
- package/dist/db/tables/marketing_preferences.table.js.map +1 -0
- package/dist/hooks/index.js +8 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/acquisition/page.d.ts +9 -0
- package/dist/pages/acquisition/page.js +181 -0
- package/dist/pages/acquisition/page.js.map +1 -0
- package/dist/pages/all.d.ts +5 -1
- package/dist/pages/all.js +5 -1
- package/dist/pages/all.js.map +1 -1
- package/dist/pages/blocks.d.ts +18 -0
- package/dist/pages/blocks.js +56 -0
- package/dist/pages/blocks.js.map +1 -0
- package/dist/pages/funnel/page.d.ts +9 -0
- package/dist/pages/funnel/page.js +36 -0
- package/dist/pages/funnel/page.js.map +1 -0
- package/dist/pages/funnel-builder/page.d.ts +9 -0
- package/dist/pages/funnel-builder/page.js +29 -0
- package/dist/pages/funnel-builder/page.js.map +1 -0
- package/dist/pages/funnels/page.d.ts +1 -1
- package/dist/pages/funnels/page.js +200 -3
- package/dist/pages/funnels/page.js.map +1 -1
- package/dist/pages/index.d.ts +1 -0
- package/dist/pages/index.js +1 -0
- package/dist/pages/index.js.map +1 -1
- package/dist/pages/{campaigns → install}/page.d.ts +3 -3
- package/dist/pages/{campaigns → install}/page.js +14 -12
- package/dist/pages/install/page.js.map +1 -0
- package/dist/pages/module.d.ts +3 -0
- package/dist/pages/module.js +30 -3
- package/dist/pages/module.js.map +1 -1
- package/dist/pages/overview/page.d.ts +1 -1
- package/dist/pages/overview/page.js +122 -3
- package/dist/pages/overview/page.js.map +1 -1
- package/dist/pages/pages/page.d.ts +1 -1
- package/dist/pages/pages/page.js +9 -4
- package/dist/pages/pages/page.js.map +1 -1
- package/dist/pages/quick-actions.d.ts +1 -0
- package/dist/pages/quick-actions.js +25 -0
- package/dist/pages/quick-actions.js.map +1 -0
- package/dist/pages/settings/form.d.ts +2 -1
- package/dist/pages/settings/form.js +62 -20
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/settings/page.d.ts +4 -1
- package/dist/pages/settings/page.js +50 -4
- package/dist/pages/settings/page.js.map +1 -1
- package/dist/pages/websites/page.d.ts +11 -0
- package/dist/pages/websites/page.js +62 -0
- package/dist/pages/websites/page.js.map +1 -0
- package/dist/routes/blocks.d.ts +50 -0
- package/dist/routes/blocks.js +269 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/collect.d.ts +1 -2
- package/dist/routes/collect.js +19 -12
- package/dist/routes/collect.js.map +1 -1
- package/dist/routes/context.d.ts +8 -0
- package/dist/routes/context.js +54 -0
- package/dist/routes/context.js.map +1 -0
- package/dist/routes/experiments.js +2 -1
- package/dist/routes/experiments.js.map +1 -1
- package/dist/routes/funnels.d.ts +21 -3
- package/dist/routes/funnels.js +150 -7
- package/dist/routes/funnels.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/settings.d.ts +6 -1
- package/dist/routes/settings.js +65 -4
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/snapshot.d.ts +1 -1
- package/dist/routes/snapshot.js +24 -25
- package/dist/routes/snapshot.js.map +1 -1
- package/dist/routes/stats.d.ts +14 -23
- package/dist/routes/stats.js +73 -97
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/tracker.js +6 -4
- package/dist/routes/tracker.js.map +1 -1
- package/dist/routes/websites.d.ts +4 -0
- package/dist/routes/websites.js +111 -12
- package/dist/routes/websites.js.map +1 -1
- package/dist/services/acquisition-report.d.ts +62 -0
- package/dist/services/acquisition-report.js +181 -0
- package/dist/services/acquisition-report.js.map +1 -0
- package/dist/services/acquisition.d.ts +9 -3
- package/dist/services/acquisition.js +35 -5
- package/dist/services/acquisition.js.map +1 -1
- package/dist/services/catalog.d.ts +3 -0
- package/dist/services/catalog.js +17 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/collect.d.ts +1 -2
- package/dist/services/collect.js +49 -24
- package/dist/services/collect.js.map +1 -1
- package/dist/services/composed.d.ts +9 -0
- package/dist/services/composed.js +62 -0
- package/dist/services/composed.js.map +1 -0
- package/dist/services/context.d.ts +20 -0
- package/dist/services/context.js +76 -0
- package/dist/services/context.js.map +1 -0
- package/dist/services/dashboard.d.ts +74 -0
- package/dist/services/dashboard.js +343 -0
- package/dist/services/dashboard.js.map +1 -0
- package/dist/services/experiments/results.d.ts +12 -1
- package/dist/services/experiments/results.js +19 -2
- package/dist/services/experiments/results.js.map +1 -1
- package/dist/services/experiments/stats.d.ts +7 -1
- package/dist/services/experiments/stats.js +43 -5
- package/dist/services/experiments/stats.js.map +1 -1
- package/dist/services/funnel-report.d.ts +64 -0
- package/dist/services/funnel-report.js +280 -0
- package/dist/services/funnel-report.js.map +1 -0
- package/dist/services/funnels/compute.d.ts +2 -0
- package/dist/services/funnels/compute.js +25 -13
- package/dist/services/funnels/compute.js.map +1 -1
- package/dist/services/funnels/results.js +4 -1
- package/dist/services/funnels/results.js.map +1 -1
- package/dist/services/funnels/step-path.d.ts +4 -0
- package/dist/services/funnels/step-path.js +47 -0
- package/dist/services/funnels/step-path.js.map +1 -0
- package/dist/services/funnels-table.d.ts +31 -0
- package/dist/services/funnels-table.js +230 -0
- package/dist/services/funnels-table.js.map +1 -0
- package/dist/services/heatmap.d.ts +15 -3
- package/dist/services/heatmap.js +54 -11
- package/dist/services/heatmap.js.map +1 -1
- package/dist/services/hourly-traffic.d.ts +4 -0
- package/dist/services/hourly-traffic.js +35 -0
- package/dist/services/hourly-traffic.js.map +1 -0
- package/dist/services/origin-guard.d.ts +9 -1
- package/dist/services/origin-guard.js +46 -1
- package/dist/services/origin-guard.js.map +1 -1
- package/dist/services/period.d.ts +26 -1
- package/dist/services/period.js +99 -9
- package/dist/services/period.js.map +1 -1
- package/dist/services/prune.d.ts +6 -0
- package/dist/services/prune.js +35 -0
- package/dist/services/prune.js.map +1 -1
- package/dist/services/request-body.d.ts +1 -2
- package/dist/services/request-body.js +10 -36
- package/dist/services/request-body.js.map +1 -1
- package/dist/services/request-meta.d.ts +0 -1
- package/dist/services/request-meta.js +0 -11
- package/dist/services/request-meta.js.map +1 -1
- package/dist/services/rollups.d.ts +25 -0
- package/dist/services/rollups.js +102 -0
- package/dist/services/rollups.js.map +1 -0
- package/dist/services/sessionize.d.ts +5 -1
- package/dist/services/sessionize.js +52 -10
- package/dist/services/sessionize.js.map +1 -1
- package/dist/services/settings.d.ts +41 -2
- package/dist/services/settings.js +219 -6
- package/dist/services/settings.js.map +1 -1
- package/dist/services/snapshots.d.ts +3 -2
- package/dist/services/snapshots.js +110 -9
- package/dist/services/snapshots.js.map +1 -1
- package/dist/services/tenant-website.js +1 -0
- package/dist/services/tenant-website.js.map +1 -1
- package/dist/services/url.js +12 -4
- package/dist/services/url.js.map +1 -1
- package/dist/services/website-hosts.d.ts +15 -0
- package/dist/services/website-hosts.js +67 -0
- package/dist/services/website-hosts.js.map +1 -0
- package/dist/services/website-report.d.ts +40 -0
- package/dist/services/website-report.js +84 -0
- package/dist/services/website-report.js.map +1 -0
- package/dist/types/constants.d.ts +14 -2
- package/dist/types/constants.js +17 -6
- package/dist/types/constants.js.map +1 -1
- package/dist/types/events.d.ts +221 -68
- package/dist/types/events.js +60 -11
- package/dist/types/events.js.map +1 -1
- package/dist/types/snapshots.d.ts +6 -0
- package/dist/types/snapshots.js +2 -0
- package/dist/types/snapshots.js.map +1 -1
- package/dist/types/statistics.d.ts +2 -1
- package/dist/types/statistics.js +9 -2
- package/dist/types/statistics.js.map +1 -1
- package/dist/utils/experiment-type.js +1 -1
- package/dist/utils/experiment-type.js.map +1 -1
- package/dist/utils/funnel-steps-type.d.ts +21 -3
- package/dist/utils/funnel-steps-type.js +7 -2
- package/dist/utils/funnel-steps-type.js.map +1 -1
- package/frontend-vue/app/components/ChannelsCard.vue +265 -0
- package/frontend-vue/app/components/Context.vue +479 -0
- package/frontend-vue/app/components/ExperimentInput.vue +3 -1
- package/frontend-vue/app/components/ExperimentReport.vue +736 -0
- package/frontend-vue/app/components/FirstRun.vue +60 -0
- package/frontend-vue/app/components/FunnelBuilder.vue +1595 -0
- package/frontend-vue/app/components/FunnelFigure.vue +640 -0
- package/frontend-vue/app/components/FunnelReport.vue +532 -0
- package/frontend-vue/app/components/FunnelStepsInput.vue +3 -1
- package/frontend-vue/app/components/FunnelTemplates.vue +161 -0
- package/frontend-vue/app/components/HeatmapCanvas.vue +11 -4
- package/frontend-vue/app/components/InstallGuide.vue +753 -0
- package/frontend-vue/app/components/PagePreview.vue +889 -416
- package/frontend-vue/app/components/PagesExplorer.vue +552 -0
- package/frontend-vue/app/components/ScrollDepthOverlay.vue +48 -20
- package/frontend-vue/app/components/TopListTabs.vue +317 -0
- package/frontend-vue/app/components/TrackerSnippet.vue +50 -16
- package/frontend-vue/app/components/VariationsInput.vue +15 -5
- package/frontend-vue/app/components/WebsitesGrid.vue +799 -0
- package/frontend-vue/app/composables/useContextLink.ts +16 -0
- package/frontend-vue/app/composables/useExperiment.ts +1 -2
- package/frontend-vue/app/composables/useFunnelSteps.ts +1 -2
- package/frontend-vue/app/composables/useLatestRequest.ts +2 -4
- package/frontend-vue/app/composables/useMarketingApi.ts +298 -133
- package/frontend-vue/app/composables/useMarketingContext.ts +55 -0
- package/frontend-vue/app/composables/useMarketingRoutes.ts +80 -27
- package/frontend-vue/app/composables/useSnapshotFrame.ts +26 -4
- package/frontend-vue/app/constants.ts +22 -0
- package/frontend-vue/app/plugins/funnel-steps-data-type.client.ts +2 -1
- package/frontend-vue/app/utils/channels.ts +37 -0
- package/frontend-vue/app/utils/format.ts +132 -0
- package/frontend-vue/app/utils/funnel-draft.ts +264 -0
- package/frontend-vue/app/utils/heatmap.ts +82 -0
- package/frontend-vue/app/utils/heatmapPalette.ts +10 -0
- package/frontend-vue/app/utils/host-conflict.ts +48 -0
- package/frontend-vue/app/utils/sample-size.ts +17 -0
- package/frontend-vue/app/utils/snapshotViewportUnits.ts +15 -5
- package/frontend-vue/app/utils/snippet.ts +53 -0
- package/frontend-vue/app/utils/step-path.ts +32 -0
- package/frontend-vue/dms.frontend.ts +4 -3
- package/frontend-vue/i18n/locales/marketing-en-GB.json +1083 -316
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +1087 -320
- package/frontend-vue/package.json +3 -0
- package/frontend-vue/tests/drafts.test.ts +3 -3
- package/frontend-vue/tests/format.test.ts +73 -0
- package/frontend-vue/tests/funnel-draft.test.ts +194 -0
- package/frontend-vue/tests/heatmap.test.ts +32 -0
- package/frontend-vue/tests/i18n.test.ts +87 -4
- package/frontend-vue/tests/latest-request.test.ts +6 -3
- package/frontend-vue/tests/locale-typography.test.ts +115 -0
- package/frontend-vue/tests/registration.test.ts +21 -11
- package/frontend-vue/tests/routes.test.ts +67 -7
- package/frontend-vue/tests/server-parity.test.ts +77 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +18 -3
- package/package.json +9 -7
- package/static/snapshot.js +46 -16
- package/static/tracker.js +139 -48
- package/dist/pages/campaigns/page.js.map +0 -1
- package/frontend-vue/app/components/CampaignsView.vue +0 -330
- package/frontend-vue/app/components/FunnelDetail.vue +0 -1026
- package/frontend-vue/app/components/FunnelsView.vue +0 -493
- package/frontend-vue/app/components/NoWebsiteState.vue +0 -18
- package/frontend-vue/app/components/OverviewView.vue +0 -493
- package/frontend-vue/app/components/PagesView.vue +0 -391
- package/frontend-vue/app/components/TextKpiCard.vue +0 -28
- package/frontend-vue/app/components/TopListTabsCard.vue +0 -162
- package/frontend-vue/app/composables/useChildId.ts +0 -13
- package/frontend-vue/app/composables/useMarketingPeriod.ts +0 -65
- package/frontend-vue/app/composables/useMarketingWebsiteForm.ts +0 -69
- package/frontend-vue/app/composables/useMarketingWebsites.ts +0 -80
- package/frontend-vue/app/utils/dmsComponent.ts +0 -18
- 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
|
-
|
|
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(
|
|
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('
|
|
16
|
-
expect(
|
|
17
|
-
'/modules/marketing/
|
|
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(
|
|
20
|
-
|
|
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(): {
|
|
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
|
|
45
|
+
export function useDmsRoute(): { query: Record<string, string>; path: string } {
|
|
41
46
|
return { query: {}, path: '/' }
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
export function useDmsRouter(): {
|
|
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
|
+
"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.
|
|
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.
|
|
37
|
-
"@antelopejs/interface-dms": ">=0.
|
|
38
|
-
"@antelopejs/interface-dms-marketing": ">=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.
|
|
47
|
+
"@antelopejs/core": ">=1.13.5 <2",
|
|
47
48
|
"@antelopejs/interface-mongodb": ">=0.0.5 <1.0.0",
|
|
48
|
-
"@antelopejs/mongodb": "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",
|
package/static/snapshot.js
CHANGED
|
@@ -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)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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,
|