@segment/analytics-browser-actions-commandbar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@segment/analytics-browser-actions-commandbar",
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,46 @@
1
+ import { Subscription } from '@segment/browser-destination-runtime/types'
2
+ import { Analytics, Context } from '@segment/analytics-next'
3
+ import commandBarDestination, { 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
+ event_name: {
13
+ '@path': '$.event'
14
+ },
15
+ event_metadata: {
16
+ '@path': '$.properties'
17
+ }
18
+ }
19
+ }
20
+ ]
21
+
22
+ describe('CommandBar initialization', () => {
23
+ test('can load CommandBar with just orgId', async () => {
24
+ const [event] = await commandBarDestination({
25
+ orgId: '05f077f2',
26
+ subscriptions
27
+ })
28
+ jest.spyOn(destination, 'initialize')
29
+
30
+ await event.load(Context.system(), {} as Analytics)
31
+ expect(destination.initialize).toHaveBeenCalled()
32
+
33
+ const scripts = window.document.querySelectorAll('script')
34
+ expect(scripts).toMatchInlineSnapshot(`
35
+ NodeList [
36
+ <script>
37
+ // the emptiness
38
+ </script>,
39
+ <script
40
+ src="https://api.commandbar.com/latest/05f077f2?version=2"
41
+ type="text/javascript"
42
+ />,
43
+ ]
44
+ `)
45
+ })
46
+ })
@@ -0,0 +1,12 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Settings {
4
+ /**
5
+ * The ID of your CommandBar organization.
6
+ */
7
+ orgId: string
8
+ /**
9
+ * If enabled, CommandBar will be deployed to your site automatically and you can remove the snippet from your source code.
10
+ */
11
+ deploy?: boolean
12
+ }
@@ -0,0 +1,153 @@
1
+ import { Analytics, Context } from '@segment/analytics-next'
2
+ import { Subscription } from '@segment/browser-destination-runtime'
3
+ import commandBarDestination, { 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
+ userId: {
13
+ '@path': '$.userId'
14
+ },
15
+ traits: {
16
+ '@path': '$.traits'
17
+ },
18
+ hmac: {
19
+ '@path': '$.context.CommandBar.hmac'
20
+ },
21
+ formFactor: {
22
+ '@path': '$.context.CommandBar.formFactor'
23
+ }
24
+ }
25
+ }
26
+ ]
27
+
28
+ describe('CommandBar.boot when with deploy enabled', () => {
29
+ const settings = {
30
+ orgId: 'xxxxxxxx'
31
+ }
32
+ let mockCommandBarBoot: jest.Mock<any, any>
33
+ let mockCommandBarMetadataBatch: jest.Mock<any, any>
34
+
35
+ let identifyUser: any
36
+
37
+ beforeEach(async () => {
38
+ const [identifyUserPlugin] = await commandBarDestination({
39
+ ...settings,
40
+ subscriptions
41
+ })
42
+
43
+ identifyUser = identifyUserPlugin
44
+
45
+ mockCommandBarBoot = jest.fn()
46
+ mockCommandBarMetadataBatch = jest.fn()
47
+ jest.spyOn(destination, 'initialize').mockImplementation(() => {
48
+ const mockedWithBoot = {
49
+ boot: mockCommandBarBoot,
50
+ addMetadataBatch: mockCommandBarMetadataBatch,
51
+ trackEvent: jest.fn()
52
+ }
53
+ return Promise.resolve(mockedWithBoot)
54
+ })
55
+ await identifyUser.load(Context.system(), {} as Analytics)
56
+ })
57
+
58
+ it('Can add metadata via identify', async () => {
59
+ await identifyUser.identify?.(
60
+ new Context({
61
+ type: 'identify',
62
+ userId: 'test-user',
63
+ traits: {
64
+ foo: 'bar'
65
+ }
66
+ })
67
+ )
68
+
69
+ expect(mockCommandBarMetadataBatch).toHaveBeenCalledWith({ foo: 'bar' }, true)
70
+ expect(mockCommandBarBoot).not.toHaveBeenCalled()
71
+ })
72
+ })
73
+
74
+ describe('CommandBar.addMetadataBatch with deploy disabled', () => {
75
+ const settings = {
76
+ orgId: 'xxxxxxxx',
77
+ deploy: true
78
+ }
79
+ let mockCommandBarBoot: jest.Mock<any, any>
80
+ let mockCommandBarMetadataBatch: jest.Mock<any, any>
81
+
82
+ let identifyUser: any
83
+
84
+ beforeEach(async () => {
85
+ const [identifyUserPlugin] = await commandBarDestination({
86
+ ...settings,
87
+ subscriptions
88
+ })
89
+
90
+ identifyUser = identifyUserPlugin
91
+
92
+ mockCommandBarBoot = jest.fn()
93
+ mockCommandBarMetadataBatch = jest.fn()
94
+ jest.spyOn(destination, 'initialize').mockImplementation(() => {
95
+ const mockedWithBoot = {
96
+ boot: mockCommandBarBoot,
97
+ addMetadataBatch: mockCommandBarMetadataBatch,
98
+ trackEvent: jest.fn()
99
+ }
100
+ return Promise.resolve(mockedWithBoot)
101
+ })
102
+ await identifyUser.load(Context.system(), {} as Analytics)
103
+ })
104
+
105
+ it('Can boot via identify', async () => {
106
+ await identifyUser.identify?.(
107
+ new Context({
108
+ type: 'identify',
109
+ userId: 'test-user',
110
+ traits: {
111
+ foo: 'bar'
112
+ }
113
+ })
114
+ )
115
+
116
+ expect(mockCommandBarBoot).toHaveBeenCalledWith('test-user', { foo: 'bar' }, {})
117
+ expect(mockCommandBarMetadataBatch).not.toHaveBeenCalled()
118
+ })
119
+
120
+ it('Can pass instanceAttributes to boot', async () => {
121
+ await identifyUser.identify?.(
122
+ new Context({
123
+ type: 'identify',
124
+ userId: 'test-user',
125
+ traits: {
126
+ foo: 'bar'
127
+ },
128
+ context: {
129
+ CommandBar: {
130
+ hmac: 'x',
131
+ formFactor: {
132
+ type: 'inline',
133
+ rootElement: 'commandbar-inline-root'
134
+ }
135
+ }
136
+ }
137
+ })
138
+ )
139
+
140
+ expect(mockCommandBarBoot).toHaveBeenCalledWith(
141
+ 'test-user',
142
+ { foo: 'bar' },
143
+ {
144
+ hmac: 'x',
145
+ formFactor: {
146
+ type: 'inline',
147
+ rootElement: 'commandbar-inline-root'
148
+ }
149
+ }
150
+ )
151
+ expect(mockCommandBarMetadataBatch).not.toHaveBeenCalled()
152
+ })
153
+ })
@@ -0,0 +1,24 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Payload {
4
+ /**
5
+ * The user's id
6
+ */
7
+ userId: string
8
+ /**
9
+ * Identify users with an HMAC of their user ID; this enables end user customizable shortcuts and other features. [Learn about identity verification](https://app.commandbar.com/identity-verification).
10
+ */
11
+ hmac?: string
12
+ /**
13
+ * Configures the way the bar is displayed. An 'inline' bar is always visible and hosted within an element on your page. A 'modal' bar will display in a modal dialog when open.
14
+ */
15
+ formFactor?: {
16
+ [k: string]: unknown
17
+ }
18
+ /**
19
+ * The Segment traits to be forwarded to CommandBar
20
+ */
21
+ traits?: {
22
+ [k: string]: unknown
23
+ }
24
+ }
@@ -0,0 +1,66 @@
1
+ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
2
+ import type { Settings } from '../generated-types'
3
+ import { CommandBarClientSDK, FormFactorConfig } from '../types'
4
+ import type { Payload } from './generated-types'
5
+
6
+ const action: BrowserActionDefinition<Settings, CommandBarClientSDK, Payload> = {
7
+ title: 'Identify User',
8
+ description:
9
+ 'Set attributes for the user in CommandBar. If "Deploy via Segment" is enabled, then also boot CommandBar for the user, which makes CommandBar available to the user.',
10
+ platform: 'web',
11
+ defaultSubscription: 'type = "identify"',
12
+ fields: {
13
+ userId: {
14
+ type: 'string',
15
+ required: true,
16
+ description: "The user's id",
17
+ label: 'User ID',
18
+ default: {
19
+ '@path': '$.userId'
20
+ }
21
+ },
22
+ hmac: {
23
+ description:
24
+ 'Identify users with an HMAC of their user ID; this enables end user customizable shortcuts and other features. [Learn about identity verification](https://app.commandbar.com/identity-verification).',
25
+ label: 'HMAC',
26
+ type: 'string',
27
+ required: false,
28
+ default: {
29
+ '@path': '$.context.CommandBar.hmac'
30
+ }
31
+ },
32
+ formFactor: {
33
+ description:
34
+ "Configures the way the bar is displayed. An 'inline' bar is always visible and hosted within an element on your page. A 'modal' bar will display in a modal dialog when open.",
35
+ label: 'Event Metadata',
36
+ type: 'object',
37
+ required: false,
38
+ default: {
39
+ '@path': '$.context.CommandBar.formFactor'
40
+ }
41
+ },
42
+ traits: {
43
+ type: 'object',
44
+ required: false,
45
+ description: 'The Segment traits to be forwarded to CommandBar',
46
+ label: 'Traits',
47
+ default: {
48
+ '@path': '$.traits'
49
+ }
50
+ }
51
+ },
52
+ perform: (CommandBar, event) => {
53
+ const traits = event.payload.traits || {}
54
+
55
+ if (event.settings.deploy) {
56
+ void CommandBar.boot(event.payload.userId, traits, {
57
+ ...(!!event.payload.hmac && { hmac: event.payload.hmac }),
58
+ ...(!!event.payload.formFactor && { formFactor: event.payload.formFactor as FormFactorConfig })
59
+ })
60
+ } else {
61
+ CommandBar.addMetadataBatch(traits, true)
62
+ }
63
+ }
64
+ }
65
+
66
+ export default action
package/src/index.ts ADDED
@@ -0,0 +1,75 @@
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 { initScript } from './init-script'
5
+ import { CommandBarClientSDK } from './types'
6
+
7
+ import identifyUser from './identifyUser'
8
+ import { defaultValues } from '@segment/actions-core'
9
+
10
+ import trackEvent from './trackEvent'
11
+
12
+ declare global {
13
+ interface Window {
14
+ CommandBar: CommandBarClientSDK
15
+ }
16
+ }
17
+
18
+ export const destination: BrowserDestinationDefinition<Settings, CommandBarClientSDK> = {
19
+ name: 'CommandBar',
20
+ slug: 'actions-commandbar',
21
+ mode: 'device',
22
+
23
+ settings: {
24
+ orgId: {
25
+ description: 'The ID of your CommandBar organization.',
26
+ label: 'Organization ID',
27
+ type: 'string',
28
+ required: true
29
+ },
30
+ deploy: {
31
+ description:
32
+ 'If enabled, CommandBar will be deployed to your site automatically and you can remove the snippet from your source code.',
33
+ label: 'Deploy via Segment',
34
+ type: 'boolean',
35
+ required: false
36
+ }
37
+ },
38
+
39
+ presets: [
40
+ {
41
+ name: 'Track Event',
42
+ subscribe: 'type = "track"',
43
+ partnerAction: 'trackEvent',
44
+ mapping: defaultValues(trackEvent.fields)
45
+ },
46
+ {
47
+ name: 'Identify User',
48
+ subscribe: 'type = "identify"',
49
+ partnerAction: 'identifyUser',
50
+ mapping: defaultValues(identifyUser.fields)
51
+ }
52
+ ],
53
+
54
+ initialize: async ({ settings }, deps) => {
55
+ if (!window.CommandBar) {
56
+ initScript(settings.orgId)
57
+ }
58
+
59
+ await deps.resolveWhen(() => Object.prototype.hasOwnProperty.call(window, 'CommandBar'), 100)
60
+
61
+ // Older CommandBar snippets initialize a proxy that traps all field access including `then`
62
+ // `then` is called implicitly on the return value of an async function on await
63
+ // So, we need to remove that behavior for the promise to resolve
64
+ Object.assign(window.CommandBar, { then: undefined })
65
+
66
+ return window.CommandBar
67
+ },
68
+
69
+ actions: {
70
+ identifyUser,
71
+ trackEvent
72
+ }
73
+ }
74
+
75
+ export default browserDestination(destination)
@@ -0,0 +1,86 @@
1
+ /* eslint-disable */
2
+ // @ts-nocheck
3
+
4
+ export function initScript(orgId) {
5
+ var o = orgId,
6
+ n = ['Object.assign', 'Symbol', 'Symbol.for'].join('%2C'),
7
+ a = window
8
+ function t(o, n) {
9
+ void 0 === n && (n = !1),
10
+ 'complete' !== document.readyState &&
11
+ window.addEventListener('load', t.bind(null, o, n), { capture: !1, once: !0 })
12
+ var a = document.createElement('script')
13
+ ;(a.type = 'text/javascript'), (a.async = n), (a.src = o), document.head.appendChild(a)
14
+ }
15
+ function r() {
16
+ var n
17
+ if (void 0 === a.CommandBar) {
18
+ delete a.__CommandBarBootstrap__
19
+ var r = Symbol.for('CommandBar::configuration'),
20
+ e = Symbol.for('CommandBar::orgConfig'),
21
+ c = Symbol.for('CommandBar::disposed'),
22
+ i = Symbol.for('CommandBar::isProxy'),
23
+ m = Symbol.for('CommandBar::queue'),
24
+ l = Symbol.for('CommandBar::unwrap'),
25
+ d = [],
26
+ s = localStorage.getItem('commandbar.lc'),
27
+ u = s && s.includes('local') ? 'http://localhost:8000' : 'https://api.commandbar.com',
28
+ f = Object.assign(
29
+ (((n = {})[r] = { uuid: o }),
30
+ (n[e] = {}),
31
+ (n[c] = !1),
32
+ (n[i] = !0),
33
+ (n[m] = new Array()),
34
+ (n[l] = function () {
35
+ return f
36
+ }),
37
+ n),
38
+ a.CommandBar
39
+ ),
40
+ p = ['addCommand', 'boot'],
41
+ y = f
42
+ Object.assign(f, {
43
+ shareCallbacks: function () {
44
+ return {}
45
+ },
46
+ shareContext: function () {
47
+ return {}
48
+ }
49
+ }),
50
+ (a.CommandBar = new Proxy(f, {
51
+ get: function (o, n) {
52
+ if (
53
+ n === 'then' ||
54
+ n === 'toJSON' ||
55
+ n === '$$typeof' ||
56
+ n === '@@__IMMUTABLE_RECORD__@@' ||
57
+ n === 'hasAttribute' ||
58
+ n === 'asymmetricMatch'
59
+ ) {
60
+ return Reflect.get(...arguments)
61
+ }
62
+ return n in y
63
+ ? f[n]
64
+ : p.includes(n)
65
+ ? function () {
66
+ var o = Array.prototype.slice.call(arguments)
67
+ return new Promise(function (a, t) {
68
+ o.unshift(n, a, t), f[m].push(o)
69
+ })
70
+ }
71
+ : function () {
72
+ var o = Array.prototype.slice.call(arguments)
73
+ o.unshift(n), f[m].push(o)
74
+ }
75
+ }
76
+ })),
77
+ null !== s && d.push('lc='.concat(s)),
78
+ d.push('version=2'),
79
+ t(''.concat(u, '/latest/').concat(o, '?').concat(d.join('&')), !0)
80
+ }
81
+ }
82
+ void 0 === Object.assign || 'undefined' == typeof Symbol || void 0 === Symbol.for
83
+ ? ((a.__CommandBarBootstrap__ = r),
84
+ t('https://polyfill.io/v3/polyfill.min.js?version=3.101.0&callback=__CommandBarBootstrap__&features=' + n))
85
+ : r()
86
+ }
@@ -0,0 +1,63 @@
1
+ import { Analytics, Context } from '@segment/analytics-next'
2
+ import { Subscription } from '@segment/browser-destination-runtime'
3
+ import commandBarDestination, { destination } from '../../index'
4
+
5
+ const subscriptions: Subscription[] = [
6
+ {
7
+ partnerAction: 'trackEvent',
8
+ name: 'Track Event',
9
+ enabled: true,
10
+ subscribe: 'type = "track"',
11
+ mapping: {
12
+ event_name: {
13
+ '@path': '$.event'
14
+ },
15
+ event_metadata: {
16
+ '@path': '$.properties'
17
+ }
18
+ }
19
+ }
20
+ ]
21
+
22
+ describe('CommandBar.trackEvent', () => {
23
+ const settings = {
24
+ orgId: 'xxxxxxxx'
25
+ }
26
+ let mockCommandBarTrackEvent: jest.Mock<any, any>
27
+
28
+ let plugin: any
29
+
30
+ beforeEach(async () => {
31
+ const [commandBarPlugin] = await commandBarDestination({
32
+ ...settings,
33
+ subscriptions
34
+ })
35
+
36
+ plugin = commandBarPlugin
37
+
38
+ mockCommandBarTrackEvent = jest.fn()
39
+ jest.spyOn(destination, 'initialize').mockImplementation(() => {
40
+ const mockedWithTrack = {
41
+ boot: jest.fn(),
42
+ addMetadataBatch: jest.fn(),
43
+ trackEvent: mockCommandBarTrackEvent
44
+ }
45
+ return Promise.resolve(mockedWithTrack)
46
+ })
47
+ await plugin.load(Context.system(), {} as Analytics)
48
+ })
49
+
50
+ it('Sends events to CommandBar', async () => {
51
+ await plugin.track?.(
52
+ new Context({
53
+ type: 'track',
54
+ event: 'example-event',
55
+ properties: {
56
+ foo: 'bar'
57
+ }
58
+ })
59
+ )
60
+
61
+ expect(mockCommandBarTrackEvent).toHaveBeenCalledWith('example-event', { foo: 'bar' })
62
+ })
63
+ })
@@ -0,0 +1,14 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Payload {
4
+ /**
5
+ * The name of the event.
6
+ */
7
+ event_name: string
8
+ /**
9
+ * Optional metadata describing the event.
10
+ */
11
+ event_metadata?: {
12
+ [k: string]: unknown
13
+ }
14
+ }
@@ -0,0 +1,40 @@
1
+ import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
2
+ import type { Settings } from '../generated-types'
3
+ import { CommandBarClientSDK } from '../types'
4
+ import type { Payload } from './generated-types'
5
+
6
+ const action: BrowserActionDefinition<Settings, CommandBarClientSDK, Payload> = {
7
+ title: 'Track Event',
8
+ description: "Submit an event's properties as CommandBar metaData.",
9
+ platform: 'web',
10
+ defaultSubscription: 'type = "track"',
11
+
12
+ fields: {
13
+ event_name: {
14
+ description: 'The name of the event.',
15
+ label: 'Event Name',
16
+ type: 'string',
17
+ required: true,
18
+ default: {
19
+ '@path': '$.event'
20
+ }
21
+ },
22
+ event_metadata: {
23
+ description: 'Optional metadata describing the event.',
24
+ label: 'Event Metadata',
25
+ type: 'object',
26
+ required: false,
27
+ default: {
28
+ '@path': '$.properties'
29
+ }
30
+ }
31
+ },
32
+
33
+ perform: (CommandBar, event) => {
34
+ if (event.payload.event_name) {
35
+ CommandBar.trackEvent(event.payload.event_name, event.payload.event_metadata || {})
36
+ }
37
+ }
38
+ }
39
+
40
+ export default action
package/src/types.ts ADDED
@@ -0,0 +1,24 @@
1
+ export type ModalFormFactor = { type: 'modal' }
2
+ export type InlineFormFactor = { type: 'inline'; rootElement: string | HTMLElement }
3
+
4
+ export type FormFactorConfig = ModalFormFactor | InlineFormFactor
5
+
6
+ export type UserAttributes = Record<string, unknown>
7
+
8
+ export type InstanceAttributes = {
9
+ canOpenEditor: boolean
10
+ hmac?: string
11
+ formFactor: FormFactorConfig
12
+ }
13
+
14
+ export type ContextLoader = (chosenValues?: undefined | Record<string, unknown[]>) => unknown
15
+
16
+ export type Metadata = Record<string, unknown>
17
+
18
+ export type CommandBarClientSDK = {
19
+ boot(id: string, userAttributes?: UserAttributes, instanceAttributes?: Partial<InstanceAttributes>): Promise<void>
20
+
21
+ addMetadataBatch(data: Metadata, addToUserProperties?: boolean): void
22
+
23
+ trackEvent(key: string, properties: Metadata): void
24
+ }
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
+ }