diff --git a/packages/browser/Makefile b/packages/browser/Makefile index e38f53bc4..f55ee1770 100644 --- a/packages/browser/Makefile +++ b/packages/browser/Makefile @@ -25,6 +25,18 @@ clean: ## Clean the build directory rm -rf dist generated .PHONY: clean +attribution: ## Replaces cookie names in the standalone build and remove last line + @gsed -i 's/\"ajs_group_id\"/\"_attrg\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"ajs_group_properties\"/\"attr_group_properties\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"ajs_user_id\"/\"_attru\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"ajs_user\"/\"none\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"ajs_user_traits\"/\"attr_user_traits\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"ajs_anonymous_id\"/\"_attrb\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"_sio\"/\"_attrb\"/g' ./dist/umd/standalone.js + @gsed -i 's/\"analytics.js\"/\"attribution.js\"/g' ./dist/umd/standalone.js + @gsed -i 's/\/\/# sourceMappingURL=standalone.js.map//g' ./dist/umd/standalone.js +.PHONY: attribution + ## Test Commands tdd: node_modules ## Runs unit tests in watch mode diff --git a/packages/browser/package.json b/packages/browser/package.json index e31add952..c7efcd901 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -33,7 +33,7 @@ "jest": "yarn run -T jest", "concurrently": "yarn run -T concurrently", "watch": "yarn concurrently 'NODE_ENV=production WATCH=true yarn umd --watch' 'yarn pkg --watch'", - "build": "yarn clean && yarn build-prep && yarn concurrently 'NODE_ENV=production yarn umd' 'yarn pkg' 'yarn cjs'", + "build": "yarn clean && yarn build-prep && NODE_ENV=production yarn umd && yarn pkg && yarn cjs && make attribution", "release:cdn": "yarn . build && NODE_ENV=production bash scripts/release.sh && NODE_ENV=stage bash scripts/release.sh", "pkg": "yarn tsc -p tsconfig.build.json", "cjs": "yarn tsc -p tsconfig.build.json --outDir ./dist/cjs --module commonjs", diff --git a/packages/browser/src/browser/index.ts b/packages/browser/src/browser/index.ts index 61b7b3f59..9d5847c1f 100644 --- a/packages/browser/src/browser/index.ts +++ b/packages/browser/src/browser/index.ts @@ -21,7 +21,7 @@ import { RemotePlugin, } from '../plugins/remote-loader' import type { RoutingRule } from '../plugins/routing-middleware' -import { segmentio, SegmentioSettings } from '../plugins/segmentio' +import { segmentio } from '../plugins/segmentio' import { validation } from '../plugins/validation' import { AnalyticsBuffered, @@ -36,6 +36,7 @@ import { ClassicIntegrationSource } from '../plugins/ajs-destination/types' import { attachInspector } from '../core/inspector' import { Stats } from '../core/stats' import { setGlobalAnalyticsKey } from '../lib/global-analytics-helper' +import { addAtbIntegrations } from '../plugins/attribution/attribution-integrations' export interface LegacyIntegrationConfiguration { /* @deprecated - This does not indicate browser types anymore */ @@ -273,11 +274,7 @@ async function registerPlugins( if (!shouldIgnoreSegmentio) { toRegister.push( - await segmentio( - analytics, - mergedSettings['Segment.io'] as SegmentioSettings, - legacySettings.integrations - ) + await segmentio(analytics, options, legacySettings.integrations) ) } @@ -326,9 +323,7 @@ async function loadAnalytics( preInitBuffer.push(new PreInitMethodCall('page', [])) } - let legacySettings = - settings.cdnSettings ?? - (await loadLegacySettings(settings.writeKey, settings.cdnURL)) + let legacySettings: any = { integrations: {} } if (options.updateCDNSettings) { legacySettings = options.updateCDNSettings(legacySettings) @@ -380,6 +375,10 @@ async function loadAnalytics( classicIntegrations ) + if (options.atbIntegrations) { + addAtbIntegrations(options.atbIntegrations) + } + const search = window.location.search ?? '' const hash = window.location.hash ?? '' diff --git a/packages/browser/src/browser/standalone-analytics.ts b/packages/browser/src/browser/standalone-analytics.ts index b4f52c9c1..e0592805f 100644 --- a/packages/browser/src/browser/standalone-analytics.ts +++ b/packages/browser/src/browser/standalone-analytics.ts @@ -47,14 +47,8 @@ function getWriteKey(): string | undefined { } export async function install(): Promise { - const writeKey = getWriteKey() - const options = getGlobalAnalytics()?._loadOptions ?? {} - if (!writeKey) { - console.error( - 'Failed to load Write Key. Make sure to use the latest version of the Segment snippet, which can be found in your source settings.' - ) - return - } + const options: any = getGlobalAnalytics()?._loadOptions ?? {} + const writeKey = getWriteKey() || options.projectId setGlobalAnalytics( (await AnalyticsBrowser.standalone(writeKey, options)) as AnalyticsSnippet diff --git a/packages/browser/src/core/analytics/index.ts b/packages/browser/src/core/analytics/index.ts index b396a8423..ebeb6ce01 100644 --- a/packages/browser/src/core/analytics/index.ts +++ b/packages/browser/src/core/analytics/index.ts @@ -106,6 +106,7 @@ export interface InitOptions { plan?: Plan retryQueue?: boolean obfuscate?: boolean + atbIntegrations?: Integrations /** * This callback allows you to update/mutate CDN Settings. * This is called directly after settings are fetched from the CDN. @@ -270,6 +271,63 @@ export class Analytics return this._universalStorage } + loadScript( + src: string, + attributes?: Record + ): Promise { + function findScript(src: string): HTMLScriptElement | undefined { + const scripts = Array.prototype.slice.call( + window.document.querySelectorAll('script') + ) + return scripts.find((s) => s.src === src) + } + + const found = findScript(src) + + if (found !== undefined) { + const status = found?.getAttribute('status') + + if (status === 'loaded') { + return Promise.resolve(found) + } + + if (status === 'loading') { + return new Promise((resolve, reject) => { + found.addEventListener('load', () => resolve(found)) + found.addEventListener('error', (err) => reject(err)) + }) + } + } + + return new Promise((resolve, reject) => { + const script = window.document.createElement('script') + + script.type = 'text/javascript' + script.src = src + script.async = true + + script.setAttribute('status', 'loading') + for (const [k, v] of Object.entries(attributes ?? {})) { + script.setAttribute(k, v) + } + + script.onload = (): void => { + script.onerror = script.onload = null + script.setAttribute('status', 'loaded') + resolve(script) + } + + script.onerror = (): void => { + script.onerror = script.onload = null + script.setAttribute('status', 'error') + reject(new Error(`Failed to load ${src}`)) + } + + const tag = window.document.getElementsByTagName('script')[0] + tag.parentElement?.insertBefore(script, tag) + }) + } + async track(...args: EventParams): Promise { const pageCtx = popPageContext(args) const [name, data, opts, cb] = resolveArguments(...args) diff --git a/packages/browser/src/core/constants/index.ts b/packages/browser/src/core/constants/index.ts index dfb7ff833..d9b7d4f66 100644 --- a/packages/browser/src/core/constants/index.ts +++ b/packages/browser/src/core/constants/index.ts @@ -1 +1 @@ -export const SEGMENT_API_HOST = 'api.segment.io/v1' +export const SEGMENT_API_HOST = 'track.attributionapp.com/v1' diff --git a/packages/browser/src/lib/global-analytics-helper.ts b/packages/browser/src/lib/global-analytics-helper.ts index 6ea1be085..b774d51ad 100644 --- a/packages/browser/src/lib/global-analytics-helper.ts +++ b/packages/browser/src/lib/global-analytics-helper.ts @@ -3,7 +3,7 @@ import { AnalyticsSnippet } from '../browser/standalone-interface' /** * Stores the global window analytics key */ -let _globalAnalyticsKey = 'analytics' +let _globalAnalyticsKey = 'Attribution' /** * Gets the global analytics/buffer diff --git a/packages/browser/src/plugins/attribution/attribution-integrations.ts b/packages/browser/src/plugins/attribution/attribution-integrations.ts new file mode 100644 index 000000000..21de7a1d5 --- /dev/null +++ b/packages/browser/src/plugins/attribution/attribution-integrations.ts @@ -0,0 +1,21 @@ +import { loadScript } from '../../lib/load-script' + +async function loadIntegration(name: string, params: any) { + if (params) console.log(params, name) + + const path = 'https://scripts.attributionapp.com/v3' + const fullPath = `${path}/${name}.js` + + try { + await loadScript(fullPath) + } catch (err) { + console.log(err) + } +} + +export function addAtbIntegrations(integrations: any) { + Object.keys(integrations).forEach(async (key) => { + const params = integrations[key] + await loadIntegration(key, params) + }) +} diff --git a/packages/browser/src/plugins/segmentio/index.ts b/packages/browser/src/plugins/segmentio/index.ts index b5b0bdd7f..98150c5e3 100644 --- a/packages/browser/src/plugins/segmentio/index.ts +++ b/packages/browser/src/plugins/segmentio/index.ts @@ -52,7 +52,7 @@ function onAlias(analytics: Analytics, json: JSON): JSON { export function segmentio( analytics: Analytics, - settings?: SegmentioSettings, + settings?: any, integrations?: LegacySettings['integrations'] ): Plugin { // Attach `pagehide` before buffer is created so that inflight events are added diff --git a/packages/browser/src/plugins/segmentio/normalize.ts b/packages/browser/src/plugins/segmentio/normalize.ts index afb22cf3d..2d12b8dfd 100644 --- a/packages/browser/src/plugins/segmentio/normalize.ts +++ b/packages/browser/src/plugins/segmentio/normalize.ts @@ -1,12 +1,11 @@ import { Analytics } from '../../core/analytics' import { LegacySettings } from '../../browser' import { SegmentFacade } from '../../lib/to-facade' -import { SegmentioSettings } from './index' export function normalize( analytics: Analytics, json: ReturnType, - settings?: SegmentioSettings, + settings?: any, integrations?: LegacySettings['integrations'] ): object { const user = analytics.user() @@ -14,6 +13,7 @@ export function normalize( delete json.options json.writeKey = settings?.apiKey + json.projectId = settings?.projectId json.userId = json.userId || user.id() json.anonymousId = json.anonymousId || user.anonymousId() @@ -53,7 +53,7 @@ export function normalize( const bundledConfigIds: string[] = [] bundled.sort().forEach((name) => { - ;(configIds[name] ?? []).forEach((id) => { + ;(configIds[name] ?? []).forEach((id: any) => { bundledConfigIds.push(id) }) })