@segment/analytics-browser-actions-commandbar 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-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
+ }