@segment/analytics-next
Version:
Analytics Next (aka Analytics 2.0) is the latest version of Segment’s JavaScript SDK - enabling you to send your data to any tool without having to learn, test, or use a new API every time.
130 lines (112 loc) • 3.73 kB
text/typescript
import { Analytics } from '../../core/analytics'
import { getNextIntegrationsURL } from '../../lib/parse-cdn'
import { Context } from '../../core/context'
import { User } from '../../core/user'
import { loadScript, unloadScript } from '../../lib/load-script'
import {
LegacyIntegration,
ClassicIntegrationBuilder,
ClassicIntegrationSource,
} from './types'
import { RemoteIntegrationSettings } from '../../browser/settings'
function normalizeName(name: string): string {
return name.toLowerCase().replace('.', '').replace(/\s+/g, '-')
}
function obfuscatePathName(pathName: string, obfuscate = false): string | void {
return obfuscate ? btoa(pathName).replace(/=/g, '') : undefined
}
export function resolveIntegrationNameFromSource(
integrationSource: ClassicIntegrationSource
) {
return (
'Integration' in integrationSource
? integrationSource.Integration
: integrationSource
).prototype.name
}
function recordLoadMetrics(fullPath: string, ctx: Context, name: string): void {
try {
const [metric] =
window?.performance?.getEntriesByName(fullPath, 'resource') ?? []
// we assume everything that took under 100ms is cached
metric &&
ctx.stats.gauge('legacy_destination_time', Math.round(metric.duration), [
name,
...(metric.duration < 100 ? ['cached'] : []),
])
} catch (_) {
// not available
}
}
export function buildIntegration(
integrationSource: ClassicIntegrationSource,
integrationSettings: { [key: string]: any },
analyticsInstance: Analytics
): LegacyIntegration {
let integrationCtr: ClassicIntegrationBuilder
// GA and Appcues use a different interface to instantiating integrations
if ('Integration' in integrationSource) {
const analyticsStub = {
user: (): User => analyticsInstance.user(),
addIntegration: (): void => {},
}
integrationSource(analyticsStub)
integrationCtr = integrationSource.Integration
} else {
integrationCtr = integrationSource
}
const integration = new integrationCtr(integrationSettings)
integration.analytics = analyticsInstance
return integration
}
export async function loadIntegration(
ctx: Context,
name: string,
version: string,
obfuscate?: boolean
): Promise<ClassicIntegrationSource> {
const pathName = normalizeName(name)
const obfuscatedPathName = obfuscatePathName(pathName, obfuscate)
const path = getNextIntegrationsURL()
const fullPath = `${path}/integrations/${
obfuscatedPathName ?? pathName
}/${version}/${obfuscatedPathName ?? pathName}.dynamic.js.gz`
try {
await loadScript(fullPath)
recordLoadMetrics(fullPath, ctx, name)
} catch (err) {
ctx.stats.gauge('legacy_destination_time', -1, [`plugin:${name}`, `failed`])
throw err
}
// @ts-ignore
const deps: string[] = window[`${pathName}Deps`]
await Promise.all(deps.map((dep) => loadScript(path + dep + '.gz')))
// @ts-ignore
window[`${pathName}Loader`]()
return window[
// @ts-ignore
`${pathName}Integration`
] as ClassicIntegrationSource
}
export async function unloadIntegration(
name: string,
version: string,
obfuscate?: boolean
): Promise<void> {
const path = getNextIntegrationsURL()
const pathName = normalizeName(name)
const obfuscatedPathName = obfuscatePathName(name, obfuscate)
const fullPath = `${path}/integrations/${
obfuscatedPathName ?? pathName
}/${version}/${obfuscatedPathName ?? pathName}.dynamic.js.gz`
return unloadScript(fullPath)
}
export function resolveVersion(
integrationConfig?: RemoteIntegrationSettings
): string {
return (
integrationConfig?.versionSettings?.override ??
integrationConfig?.versionSettings?.version ??
'latest'
)
}