@segment/analytics-browser-actions-vwo 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.
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@segment/analytics-browser-actions-vwo",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "main": "./dist/cjs",
6
+ "module": "./dist/esm",
7
+ "scripts": {
8
+ "build": "yarn build:esm && yarn build:cjs",
9
+ "build:cjs": "tsc --module commonjs --outDir ./dist/cjs",
10
+ "build:esm": "tsc --outDir ./dist/esm"
11
+ },
12
+ "typings": "./dist/esm",
13
+ "dependencies": {
14
+ "@segment/actions-core": "^3.71.0",
15
+ "@segment/browser-destination-runtime": "^1.0.0"
16
+ },
17
+ "peerDependencies": {
18
+ "@segment/analytics-next": "*"
19
+ }
20
+ }
@@ -0,0 +1,43 @@
1
+ import { Subscription } from '@segment/browser-destination-runtime/types'
2
+ import { Analytics, Context } from '@segment/analytics-next'
3
+ import vwoDestination, { destination } from '../index'
4
+
5
+ const subscriptions: Subscription[] = [
6
+ {
7
+ partnerAction: 'trackEvent',
8
+ name: 'Show',
9
+ enabled: true,
10
+ subscribe: 'type = "track"',
11
+ mapping: {
12
+ eventName: {
13
+ '@path': '$.event'
14
+ },
15
+ properties: {
16
+ '@path': '$.properties'
17
+ }
18
+ }
19
+ }
20
+ ]
21
+
22
+ describe('VWO Web (Actions)', () => {
23
+ test('Loads VWO SmartCode with AccountID', async () => {
24
+ const [vwo] = await vwoDestination({
25
+ vwoAccountId: 654331,
26
+ addSmartcode: true,
27
+ subscriptions
28
+ })
29
+
30
+ jest.spyOn(destination, 'initialize')
31
+
32
+ await vwo.load(Context.system(), {} as Analytics)
33
+ expect(destination.initialize).toHaveBeenCalled()
34
+
35
+ const vwoObject = window.VWO
36
+ expect(vwoObject).toBeDefined()
37
+
38
+ const script = window.document.querySelector(
39
+ 'script[src~="https://dev.visualwebsiteoptimizer.com/j.php?a=654331]'
40
+ ) as HTMLScriptElement
41
+ expect(script).toBeDefined()
42
+ })
43
+ })
@@ -0,0 +1,24 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Settings {
4
+ /**
5
+ * Your VWO account ID, used for fetching your VWO async smart code.
6
+ */
7
+ vwoAccountId: number
8
+ /**
9
+ * The maximum amount of time (in milliseconds) to wait for test settings before VWO will simply display your original page.
10
+ */
11
+ settingsTolerance?: number
12
+ /**
13
+ * The maximum amount of time (in milliseconds) to wait for VWO’s full library to be downloaded before simply displaying your original page.
14
+ */
15
+ libraryTolerance?: number
16
+ /**
17
+ * If your page already includes JQuery, you can set this to “true”. Otherwise, VWO will include JQuery onto the page for you. VWO needs JQuery on the page to function correctly.
18
+ */
19
+ useExistingJquery?: boolean
20
+ /**
21
+ * When enabled, Segment will load the VWO SmartCode onto the webpage. When disabled, you will have to manually add SmartCode to your webpage. The setting is enabled by default, however we recommended manually adding SmartCode to the webpage to avoid flicker issues.
22
+ */
23
+ addSmartcode?: boolean
24
+ }
@@ -0,0 +1,63 @@
1
+ import { Analytics, Context } from '@segment/analytics-next'
2
+ import { Subscription } from '@segment/browser-destination-runtime'
3
+ import vwoDestination, { destination } from '../../index'
4
+
5
+ const subscriptions: Subscription[] = [
6
+ {
7
+ partnerAction: 'identifyUser',
8
+ name: 'Identify User',
9
+ enabled: true,
10
+ subscribe: 'type = "identify"',
11
+ mapping: {
12
+ attributes: {
13
+ '@path': '$.traits'
14
+ }
15
+ }
16
+ }
17
+ ]
18
+
19
+ describe('VWO.identifyUser', () => {
20
+ const settings = {
21
+ vwoAccountId: 654331
22
+ }
23
+
24
+ let identifyUser: any
25
+ beforeEach(async () => {
26
+ jest.restoreAllMocks()
27
+
28
+ const [identifyUserPlugin] = await vwoDestination({
29
+ ...settings,
30
+ subscriptions
31
+ })
32
+ identifyUser = identifyUserPlugin
33
+
34
+ jest.spyOn(destination, 'initialize').mockImplementation(() => {
35
+ window.VWO = {
36
+ push: jest.fn(),
37
+ event: jest.fn(),
38
+ visitor: jest.fn()
39
+ }
40
+ return Promise.resolve(window.VWO)
41
+ })
42
+ await identifyUser.load(Context.system(), {} as Analytics)
43
+ })
44
+
45
+ test('Visitor call with attributes', async () => {
46
+ const context = new Context({
47
+ type: 'identify',
48
+ traits: {
49
+ textAttribute: 'Hello'
50
+ }
51
+ })
52
+ await identifyUser.identify?.(context)
53
+
54
+ expect(window.VWO.visitor).toHaveBeenCalledWith(
55
+ {
56
+ 'segment.textAttribute': 'Hello'
57
+ },
58
+ {
59
+ source: 'segment.web'
60
+ }
61
+ )
62
+ })
63
+ })
@@ -0,0 +1,10 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Payload {
4
+ /**
5
+ * JSON object containing additional attributes that will be associated with the user.
6
+ */
7
+ attributes: {
8
+ [k: string]: unknown
9
+ }
10
+ }
@@ -0,0 +1,40 @@
1
+ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
2
+ import type { VWO } from '../types'
3
+ import type { Settings } from '../generated-types'
4
+ import type { Payload } from './generated-types'
5
+ import { formatAttributes } from '../utility'
6
+
7
+ // Change from unknown to the partner SDK types
8
+ const action: BrowserActionDefinition<Settings, VWO, Payload> = {
9
+ title: 'Identify User',
10
+ description: `Sends Segment's page event to VWO`,
11
+ defaultSubscription: 'type = "identify"',
12
+ platform: 'web',
13
+ fields: {
14
+ attributes: {
15
+ description: 'JSON object containing additional attributes that will be associated with the user.',
16
+ label: 'Attributes',
17
+ required: true,
18
+ type: 'object',
19
+ default: {
20
+ '@path': '$.traits'
21
+ }
22
+ }
23
+ },
24
+ perform: (_, event) => {
25
+ const { attributes } = event.payload
26
+ const formattedAttributes = formatAttributes(attributes)
27
+
28
+ window.VWO = window.VWO || []
29
+
30
+ if (!window.VWO.visitor) {
31
+ window.VWO.visitor = function (...args) {
32
+ window.VWO.push(['visitor', ...args])
33
+ }
34
+ }
35
+
36
+ window.VWO.visitor(formattedAttributes, { source: 'segment.web' })
37
+ }
38
+ }
39
+
40
+ export default action
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ import type { Settings } from './generated-types'
2
+ import type { BrowserDestinationDefinition } from '@segment/browser-destination-runtime/types'
3
+ import { browserDestination } from '@segment/browser-destination-runtime/shim'
4
+ import type { VWO } from './types'
5
+ import { initScript } from './init-script'
6
+ import { defaultValues } from '@segment/actions-core'
7
+
8
+ import trackEvent from './trackEvent'
9
+ import identifyUser from './identifyUser'
10
+
11
+ declare global {
12
+ interface Window {
13
+ VWO: VWO
14
+ }
15
+ }
16
+
17
+ // Switch from unknown to the partner SDK client types
18
+ export const destination: BrowserDestinationDefinition<Settings, VWO> = {
19
+ name: 'VWO Web Mode (Actions)',
20
+ slug: 'actions-vwo-web',
21
+ mode: 'device',
22
+ presets: [
23
+ {
24
+ name: 'Track Event',
25
+ subscribe: 'type = "track"',
26
+ partnerAction: 'trackEvent',
27
+ mapping: defaultValues(trackEvent.fields)
28
+ },
29
+ {
30
+ name: 'Identify User',
31
+ subscribe: 'type = "identify"',
32
+ partnerAction: 'identifyUser',
33
+ mapping: defaultValues(identifyUser.fields)
34
+ }
35
+ ],
36
+ settings: {
37
+ // Add any Segment destination settings required here
38
+ vwoAccountId: {
39
+ description: 'Your VWO account ID, used for fetching your VWO async smart code.',
40
+ label: 'VWO Account ID',
41
+ type: 'number',
42
+ required: true
43
+ },
44
+ settingsTolerance: {
45
+ description:
46
+ 'The maximum amount of time (in milliseconds) to wait for test settings before VWO will simply display your original page.',
47
+ label: 'Settings Tolerance',
48
+ type: 'number',
49
+ default: 2000
50
+ },
51
+ libraryTolerance: {
52
+ description:
53
+ 'The maximum amount of time (in milliseconds) to wait for VWO’s full library to be downloaded before simply displaying your original page.',
54
+ label: 'Library Tolerance',
55
+ type: 'number',
56
+ default: 2500
57
+ },
58
+ useExistingJquery: {
59
+ description:
60
+ 'If your page already includes JQuery, you can set this to “true”. Otherwise, VWO will include JQuery onto the page for you. VWO needs JQuery on the page to function correctly. ',
61
+ label: 'Use Existing JQuery',
62
+ type: 'boolean',
63
+ default: false
64
+ },
65
+ addSmartcode: {
66
+ description:
67
+ 'When enabled, Segment will load the VWO SmartCode onto the webpage. When disabled, you will have to manually add SmartCode to your webpage. The setting is enabled by default, however we recommended manually adding SmartCode to the webpage to avoid flicker issues.',
68
+ label: 'Add Asynchronous SmartCode',
69
+ type: 'boolean',
70
+ default: true
71
+ }
72
+ },
73
+
74
+ initialize: async ({ settings }, deps) => {
75
+ if (settings.addSmartcode != false) {
76
+ initScript({
77
+ vwoAccountId: settings.vwoAccountId,
78
+ settingsTolerance: settings.settingsTolerance,
79
+ libraryTolerance: settings.libraryTolerance,
80
+ useExistingJquery: settings.useExistingJquery
81
+ })
82
+ }
83
+ await deps.resolveWhen(() => Object.prototype.hasOwnProperty.call(window, 'VWO'), 100)
84
+ return window.VWO
85
+ },
86
+
87
+ actions: {
88
+ trackEvent,
89
+ identifyUser
90
+ }
91
+ }
92
+
93
+ export default browserDestination(destination)
@@ -0,0 +1,81 @@
1
+ /* eslint-disable */
2
+ // @ts-nocheck
3
+
4
+ export function initScript({
5
+ vwoAccountId,
6
+ settingsTolerance = 2000,
7
+ libraryTolerance = 2500,
8
+ useExistingJquery = false,
9
+ isSpa = 1
10
+ }) {
11
+ window._vwo_code =
12
+ window._vwo_code ||
13
+ (function () {
14
+ var account_id = vwoAccountId,
15
+ settings_tolerance = settingsTolerance,
16
+ library_tolerance = libraryTolerance,
17
+ use_existing_jquery = useExistingJquery,
18
+ is_spa = isSpa,
19
+ hide_element = 'body',
20
+ /* DO NOT EDIT BELOW THIS LINE */
21
+ f = false,
22
+ d = document,
23
+ code = {
24
+ use_existing_jquery: function () {
25
+ return use_existing_jquery
26
+ },
27
+ library_tolerance: function () {
28
+ return library_tolerance
29
+ },
30
+ finish: function () {
31
+ if (!f) {
32
+ f = true
33
+ var a = d.getElementById('_vis_opt_path_hides')
34
+ if (a) a.parentNode.removeChild(a)
35
+ }
36
+ },
37
+ finished: function () {
38
+ return f
39
+ },
40
+ load: function (a) {
41
+ var b = d.createElement('script')
42
+ b.src = a
43
+ b.type = 'text/javascript'
44
+ b.innerText
45
+ b.onerror = function () {
46
+ _vwo_code.finish()
47
+ }
48
+ d.getElementsByTagName('head')[0].appendChild(b)
49
+ },
50
+ init: function () {
51
+ window.settings_timer = setTimeout(function () {
52
+ _vwo_code.finish()
53
+ }, settings_tolerance)
54
+ var a = d.createElement('style'),
55
+ b = hide_element
56
+ ? hide_element + '{opacity:0 !important;filter:alpha(opacity=0) !important;background:none !important;}'
57
+ : '',
58
+ h = d.getElementsByTagName('head')[0]
59
+ a.setAttribute('id', '_vis_opt_path_hides')
60
+ a.setAttribute('type', 'text/css')
61
+ if (a.styleSheet) a.styleSheet.cssText = b
62
+ else a.appendChild(d.createTextNode(b))
63
+ h.appendChild(a)
64
+ this.load(
65
+ 'https://dev.visualwebsiteoptimizer.com/j.php?a=' +
66
+ account_id +
67
+ '&u=' +
68
+ encodeURIComponent(d.URL) +
69
+ '&f=' +
70
+ +is_spa +
71
+ '&r=' +
72
+ Math.random() +
73
+ '&s=segment.web'
74
+ )
75
+ return settings_timer
76
+ }
77
+ }
78
+ window._vwo_settings_timer = code.init()
79
+ return code
80
+ })()
81
+ }
@@ -0,0 +1,86 @@
1
+ import { Analytics, Context } from '@segment/analytics-next'
2
+ import { Subscription } from '@segment/browser-destination-runtime'
3
+ import vwoDestination, { destination } from '../../index'
4
+
5
+ const subscriptions: Subscription[] = [
6
+ {
7
+ partnerAction: 'trackEvent',
8
+ name: 'Show',
9
+ enabled: true,
10
+ subscribe: 'type = "track"',
11
+ mapping: {
12
+ eventName: {
13
+ '@path': '$.event'
14
+ },
15
+ properties: {
16
+ '@path': '$.properties'
17
+ }
18
+ }
19
+ }
20
+ ]
21
+
22
+ describe('VWO.trackEvent', () => {
23
+ const settings = {
24
+ vwoAccountId: 654331
25
+ }
26
+
27
+ let trackEvent: any
28
+ beforeEach(async () => {
29
+ jest.restoreAllMocks()
30
+
31
+ const [trackEventPlugin] = await vwoDestination({
32
+ ...settings,
33
+ subscriptions
34
+ })
35
+ trackEvent = trackEventPlugin
36
+
37
+ jest.spyOn(destination, 'initialize').mockImplementation(() => {
38
+ window.VWO = {
39
+ push: jest.fn(),
40
+ event: jest.fn(),
41
+ visitor: jest.fn()
42
+ }
43
+ return Promise.resolve(window.VWO)
44
+ })
45
+ await trackEvent.load(Context.system(), {} as Analytics)
46
+ })
47
+
48
+ test('Track call without parameters', async () => {
49
+ const context = new Context({
50
+ type: 'track',
51
+ event: 'ctaClick'
52
+ })
53
+ await trackEvent.track?.(context)
54
+
55
+ expect(window.VWO.event).toHaveBeenCalledWith(
56
+ 'segment.ctaClick',
57
+ {},
58
+ {
59
+ source: 'segment.web',
60
+ ogName: 'ctaClick'
61
+ }
62
+ )
63
+ })
64
+
65
+ test('Track call with parameters', async () => {
66
+ const context = new Context({
67
+ type: 'track',
68
+ event: 'buyButtonClick',
69
+ properties: {
70
+ amount: 1000
71
+ }
72
+ })
73
+ await trackEvent.track?.(context)
74
+
75
+ expect(window.VWO.event).toHaveBeenCalledWith(
76
+ 'segment.buyButtonClick',
77
+ {
78
+ amount: 1000
79
+ },
80
+ {
81
+ source: 'segment.web',
82
+ ogName: 'buyButtonClick'
83
+ }
84
+ )
85
+ })
86
+ })
@@ -0,0 +1,14 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Payload {
4
+ /**
5
+ * Name of the event.
6
+ */
7
+ eventName: string
8
+ /**
9
+ * JSON object containing additional properties that will be associated with the event.
10
+ */
11
+ properties?: {
12
+ [k: string]: unknown
13
+ }
14
+ }
@@ -0,0 +1,52 @@
1
+ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
2
+ import type { VWO } from '../types'
3
+ import type { Settings } from '../generated-types'
4
+ import type { Payload } from './generated-types'
5
+ import { sanitiseEventName } from '../utility'
6
+
7
+ // Change from unknown to the partner SDK types
8
+ const action: BrowserActionDefinition<Settings, VWO, Payload> = {
9
+ title: 'Track Event',
10
+ description: `Sends Segment's track event to VWO`,
11
+ platform: 'web',
12
+ defaultSubscription: 'type = "track"',
13
+ fields: {
14
+ eventName: {
15
+ description: 'Name of the event.',
16
+ label: 'Name',
17
+ required: true,
18
+ type: 'string',
19
+ default: {
20
+ '@path': '$.event'
21
+ }
22
+ },
23
+ properties: {
24
+ description: 'JSON object containing additional properties that will be associated with the event.',
25
+ label: 'Properties',
26
+ required: false,
27
+ type: 'object',
28
+ default: {
29
+ '@path': '$.properties'
30
+ }
31
+ }
32
+ },
33
+ perform: (_, event) => {
34
+ const { eventName, properties } = event.payload
35
+ const sanitisedEventName = sanitiseEventName(eventName)
36
+ const formattedProperties = { ...properties }
37
+
38
+ window.VWO = window.VWO || []
39
+
40
+ if (!window.VWO.event) {
41
+ window.VWO.event = function (...args) {
42
+ window.VWO.push(['event', ...args])
43
+ }
44
+ }
45
+ window.VWO.event(sanitisedEventName, formattedProperties, {
46
+ source: 'segment.web',
47
+ ogName: eventName
48
+ })
49
+ }
50
+ }
51
+
52
+ export default action
package/src/types.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type VWO = {
2
+ event: (
3
+ event: string,
4
+ properties: { [k: string]: unknown } | undefined,
5
+ vwoMeta: { [k: string]: unknown } | undefined
6
+ ) => void
7
+ visitor: (attributes: { [k: string]: unknown }, vwoMeta: { [k: string]: unknown } | undefined) => void
8
+ push: (args: (string | { [k: string]: unknown } | undefined)[]) => void
9
+ }
package/src/utility.ts ADDED
@@ -0,0 +1,11 @@
1
+ export function formatAttributes(attributes: { [k: string]: unknown }) {
2
+ const formattedAttributes: { [k: string]: unknown } = {}
3
+ for (const key in attributes) {
4
+ formattedAttributes[`segment.${key}`] = attributes[key]
5
+ }
6
+ return formattedAttributes
7
+ }
8
+
9
+ export function sanitiseEventName(name: string) {
10
+ return 'segment.' + name
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.build.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "baseUrl": "."
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["dist", "**/__tests__"]
9
+ }