Skip to content

Commit 324591f

Browse files
STRATCONN-6430 - [Rokt] - new Hybrid, CAPI and Audience Destination (#3491)
* progress * progress * more progress * progress * multistatus * auth * throw error for non batch fail valiation * post partner call * Tiffany feedback * separating device id and ad id fields * updating dateofbirth to dob * more instructions from Tiffany * adding web plugin * removing qualified files * adding web pluging with test * adding server side unit test * adding unit tests for rokt capi * snapshot tests done * types * removing unreferenced code * reviewed with partner - tests updated
1 parent 9d8a793 commit 324591f

File tree

24 files changed

+2724
-0
lines changed

24 files changed

+2724
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# @segment/analytics-browser-actions-rokt-plugins
2+
3+
The Rokt Browser Plugin browser action destination for use with @segment/analytics-next.
4+
5+
## License
6+
7+
MIT License
8+
9+
Copyright (c) 2025 Segment
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in all
19+
copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27+
SOFTWARE.
28+
29+
## Contributing
30+
31+
All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@segment/analytics-browser-actions-rokt-plugins",
3+
"version": "1.0.0",
4+
"license": "MIT",
5+
"publishConfig": {
6+
"access": "public",
7+
"registry": "https://registry.npmjs.org"
8+
},
9+
"main": "./dist/cjs",
10+
"module": "./dist/esm",
11+
"scripts": {
12+
"build": "yarn build:esm && yarn build:cjs",
13+
"build:cjs": "tsc --module commonjs --outDir ./dist/cjs",
14+
"build:esm": "tsc --outDir ./dist/esm"
15+
},
16+
"typings": "./dist/esm",
17+
"dependencies": {
18+
"@segment/browser-destination-runtime": "^1.92.0"
19+
},
20+
"peerDependencies": {
21+
"@segment/analytics-next": ">=1.55.0"
22+
}
23+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Analytics, Context, Plugin } from '@segment/analytics-next'
2+
import { Subscription } from '@segment/browser-destination-runtime/types'
3+
import browserPluginsDestination from '..'
4+
import { rtidIntegrationFieldName, rtidQuerystringName, storageRTIDKey } from '../utils'
5+
6+
const example: Subscription[] = [
7+
{
8+
partnerAction: 'roktPlugin',
9+
name: 'Rokt Browser Plugin',
10+
enabled: true,
11+
subscribe: 'type = "track"',
12+
mapping: {}
13+
}
14+
]
15+
16+
let browserActions: Plugin[]
17+
let roktPlugin: Plugin
18+
let ajs: Analytics
19+
20+
beforeEach(async () => {
21+
browserActions = await browserPluginsDestination({ subscriptions: example })
22+
roktPlugin = browserActions[0]
23+
24+
ajs = new Analytics({
25+
writeKey: 'w_123'
26+
})
27+
28+
Object.defineProperty(window, 'location', {
29+
value: {
30+
search: ''
31+
},
32+
writable: true
33+
})
34+
35+
window.localStorage.removeItem(storageRTIDKey)
36+
})
37+
38+
describe('ajs-integration', () => {
39+
test('updates the original event with a Rokt rtid from the querystring', async () => {
40+
Object.defineProperty(window, 'location', {
41+
value: {
42+
search: `?${rtidQuerystringName}=dummyQuerystringValue`
43+
},
44+
writable: true
45+
})
46+
47+
await roktPlugin.load(Context.system(), ajs)
48+
49+
const ctx = new Context({
50+
type: 'track',
51+
event: 'Test Event',
52+
properties: {
53+
greeting: 'Yo!'
54+
}
55+
})
56+
57+
const updatedCtx = await roktPlugin.track?.(ctx)
58+
59+
const roktIntegrationsObj = updatedCtx?.event?.integrations['Rokt Conversions API']
60+
expect(roktIntegrationsObj[rtidIntegrationFieldName]).toEqual('dummyQuerystringValue')
61+
})
62+
63+
test('updates the original event with a Rokt rtid from storage', async () => {
64+
window.localStorage.setItem(storageRTIDKey, 'dummyStorageValue')
65+
66+
await roktPlugin.load(Context.system(), ajs)
67+
68+
const ctx = new Context({
69+
type: 'track',
70+
event: 'Test Event',
71+
properties: {
72+
greeting: 'Yo!'
73+
}
74+
})
75+
76+
const updatedCtx = await roktPlugin.track?.(ctx)
77+
78+
const roktIntegrationsObj = updatedCtx?.event?.integrations['Rokt Conversions API']
79+
expect(roktIntegrationsObj[rtidIntegrationFieldName]).toEqual('dummyStorageValue')
80+
})
81+
})

packages/browser-destinations/destinations/rokt-plugins/src/generated-types.ts

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 { storageFallback, storageRTIDKey, rtidQuerystringName } from './utils'
5+
import { UniversalStorage } from '@segment/analytics-next'
6+
import roktPlugin from './roktPlugin'
7+
8+
export const destination: BrowserDestinationDefinition<Settings, {}> = {
9+
name: 'Rokt Browser Plugins',
10+
mode: 'device',
11+
initialize: async ({ analytics }) => {
12+
const storage = (analytics.storage as UniversalStorage<Record<string, string>>) ?? storageFallback
13+
const urlParams = new URLSearchParams(window.location.search)
14+
const rtid: string | null = urlParams.get(rtidQuerystringName) || null
15+
if (rtid) {
16+
storage.set(storageRTIDKey, rtid)
17+
}
18+
return {}
19+
},
20+
settings: {},
21+
actions: {
22+
roktPlugin
23+
}
24+
}
25+
26+
export default browserDestination(destination)

packages/browser-destinations/destinations/rokt-plugins/src/roktPlugin/generated-types.ts

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
2+
import type { Settings } from '../generated-types'
3+
import type { Payload } from './generated-types'
4+
import {
5+
storageRTIDKey,
6+
rtidIntegrationFieldName,
7+
storageFallback
8+
} from '../utils'
9+
import { UniversalStorage } from '@segment/analytics-next'
10+
11+
const action: BrowserActionDefinition<Settings, {}, Payload> = {
12+
title: 'Rokt Browser Plugin',
13+
description: 'Enriches all Segment payloads with Rokt rtid Querystring value',
14+
platform: 'web',
15+
hidden: false,
16+
defaultSubscription: 'type = "track" or type = "identify" or type = "page" or type = "group" or type = "alias"',
17+
fields: {},
18+
lifecycleHook: 'enrichment',
19+
perform: (_, { context, analytics }) => {
20+
const storage = (analytics.storage as UniversalStorage<Record<string, string>>) ?? storageFallback
21+
const rtid: string | null = storage.get(storageRTIDKey)
22+
if (rtid) {
23+
const integrationsData: Record<string, string> = {}
24+
integrationsData[rtidIntegrationFieldName] = rtid
25+
if (context.event.integrations?.All !== false || context.event.integrations['Rokt Conversions API']) {
26+
context.updateEvent(`integrations.Rokt Conversions API`, integrationsData)
27+
}
28+
}
29+
30+
return
31+
}
32+
}
33+
34+
export default action
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// The name of the storage location where we'll cache the Rokt rtid Querystring value
2+
export const storageRTIDKey = 'analytics_rokt_capi_rtid'
3+
4+
// The name of the Rokt rtid querystring to retrieve when the page loads
5+
export const rtidQuerystringName = 'rtid'
6+
7+
// The field name to include for the Rokt rtid cookie in the "context.integrations.Rokt Conversions API" object
8+
export const rtidIntegrationFieldName = 'rtid'
9+
10+
export const storageFallback = {
11+
get: (key: string) => {
12+
const data = window.localStorage.getItem(key)
13+
return data
14+
},
15+
set: (key: string, value: string) => {
16+
return window.localStorage.setItem(key, value)
17+
}
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Testing snapshot for actions-rokt-capi destination: send action - all fields 1`] = `
4+
Object {
5+
"device_info": Object {
6+
"android_advertising_id": "xI%Z3kWrnFUD^fL(n8@",
7+
"android_uuid": "xI%Z3kWrnFUD^fL(n8@",
8+
"http_header_user_agent": "xI%Z3kWrnFUD^fL(n8@",
9+
"ios_advertising_id": "xI%Z3kWrnFUD^fL(n8@",
10+
"ios_idfv": "xI%Z3kWrnFUD^fL(n8@",
11+
},
12+
"environment": "production",
13+
"events": Array [
14+
Object {
15+
"data": Object {
16+
"custom_attributes": Object {
17+
"audience_name": "xI%Z3kWrnFUD^fL(n8@",
18+
"status": "drop",
19+
},
20+
"custom_event_type": "other",
21+
"event_name": "audiencemembershipupdate",
22+
"source_message_id": "xI%Z3kWrnFUD^fL(n8@",
23+
"timestamp_unixtime_ms": null,
24+
},
25+
"event_type": "custom_event",
26+
},
27+
Object {
28+
"data": Object {
29+
"custom_attributes": Object {
30+
"amount": 72258322505400.31,
31+
"confirmationref": "xI%Z3kWrnFUD^fL(n8@",
32+
"conversiontype": "xI%Z3kWrnFUD^fL(n8@",
33+
"currency": "CAD",
34+
"testType": "xI%Z3kWrnFUD^fL(n8@",
35+
},
36+
"custom_event_type": "transaction",
37+
"event_name": "conversion",
38+
"source_message_id": "xI%Z3kWrnFUD^fL(n8@",
39+
"timestamp_unixtime_ms": null,
40+
},
41+
"event_type": "custom_event",
42+
},
43+
],
44+
"integration_attributes": Object {
45+
"1277": Object {
46+
"passbackconversiontrackingid": "xI%Z3kWrnFUD^fL(n8@",
47+
},
48+
},
49+
"ip": "xI%Z3kWrnFUD^fL(n8@",
50+
"user_attributes": Object {
51+
"billingzipcode": "xI%Z3kWrnFUD^fL(n8@",
52+
"dob": "20581114",
53+
"firstname": "xI%Z3kWrnFUD^fL(n8@",
54+
"gender": "f",
55+
"lastname": "xI%Z3kWrnFUD^fL(n8@",
56+
"mobile": "xI%Z3kWrnFUD^fL(n8@",
57+
"segment_xI%Z3kWrnFUD^fL(n8@": false,
58+
},
59+
"user_identities": Object {
60+
"customerid": "test-user-id-456",
61+
"email": "[email protected]",
62+
"other2": "xI%Z3kWrnFUD^fL(n8@",
63+
},
64+
}
65+
`;
66+
67+
exports[`Testing snapshot for actions-rokt-capi destination: send action - required fields 1`] = `
68+
Object {
69+
"device_info": Object {},
70+
"environment": "production",
71+
"integration_attributes": Object {
72+
"1277": Object {
73+
"passbackconversiontrackingid": "xI%Z3kWrnFUD^fL(n8@",
74+
},
75+
},
76+
"user_attributes": Object {},
77+
"user_identities": Object {
78+
"customerid": "test-user-id-123",
79+
"other2": "xI%Z3kWrnFUD^fL(n8@",
80+
},
81+
}
82+
`;

0 commit comments

Comments
 (0)