import { AnalyticsCollector as KoalaSDK, type Options } from './analytics/collector' import { getUserId, user } from './analytics/user' import { bootstrap, BootstrapData } from './api/bootstrap' import { validate } from './lib/validate-uuid' export type { KoalaSDK, Options as Settings } export interface ProjectSettings extends BootstrapData { project: string } /** * Send all buffered operations + arguments to the SDK. */ function flushBuffered(ko: KoalaSDK, ns = 'ko') { // @ts-expect-error the types are wrong const existing = window[ns] const buffer: Array<[string, ...unknown[]]> = Array.isArray(existing) && existing[0] ? [...existing] : [] for (const [operation, ...args] of buffer) { // @ts-expect-error dynamic code if (typeof ko[operation] === 'function') { // flush individual operations so they don't block each other on await setTimeout(async () => { try { // @ts-expect-error dynamic code await ko[operation].call(ko, ...args) } catch (err) { console.warn(err) } }, 0) } } } export function mountWidget() { // no-op kept to prevent breaking installs } export function getNamespace(): string { return window.globalKoalaKey || 'ko' } /** * Fetches settings for public key */ async function fetchSettings(publicKey: string, profileId: string | undefined): Promise { try { const settings = await bootstrap(publicKey, profileId) // Update the profile id in case it changed (or wasn't set yet) if (settings.profile_id) { user(settings).setId(settings.profile_id) } else if (!profileId) { // if no profile_id returned from server, // and we didn't already have one, let's generate a new one user(settings).id() } // merge settings from server with local settings settings.sdk_settings = { ...(settings.sdk_settings || {}), ...(window.koalaSettings?.sdk_settings || {}) } return { ...settings, project: publicKey } } catch (error) { console.warn('[KOALA]', 'Failed to load project settings', error) throw error } } export async function load(options: Options) { const ns = getNamespace() // @ts-expect-error the types are wrong const existing = window[ns] // Ensure the SDK is loaded only once if (existing && !Array.isArray(existing)) { console.warn('[KOALA]', 'The Koala SDK is already loaded. Calling `load` again will have no effect.') return existing } // Ignore bots and automations since they are not useful const userAgent = navigator?.userAgent?.toLowerCase() const bots = [ 'googlebot', 'google web preview', 'adsbot', 'headlesschrome', 'lighthouse', 'speedindex', 'vercelbot', 'hubspot', 'yandex', 'ahrefsbot', 'ev-crawl', 'facebookexternalhit', 'facebookcatalog', 'sightbulb', 'slackbot', 'yahoo', 'bingbot', 'applebot', 'discordbot', 'baidu', 'screaming', 'pingdom', 'phantomjs' ] if (navigator?.webdriver || bots.some((bot) => userAgent?.includes(bot))) { return existing } let validationError = false // Allow users to set the profile id before loading the SDK const incomingProfileId = options.profileId if (incomingProfileId) { delete options.profileId if (validate(incomingProfileId)) { user().setId(incomingProfileId) } else { validationError = true console.warn('[KOALA]', 'The profileId provided on initialization is invalid. Please provide a valid UUID.') } } const settings = await fetchSettings(options.project, getUserId()) const ko = new KoalaSDK({ ...options, ...settings }) flushBuffered(ko, ns) ko.emit('initialized', settings) // Subscribe to profile updates over the websocket ko.subscribe() // report validation errors later, we can't do this until after initialization if (validationError) { const data_type = typeof incomingProfileId ko.stats.increment('sdk.error', { method: 'load', message: 'Invalid profileId provided on initialization', profileId: data_type === 'string' ? incomingProfileId : data_type }) } // Set global for debugging and to ensure loading // from different sources (npm, cdn/umd.js, cdn/sdk.js) // wont cause duplicate instances // @ts-expect-error the types are wrong window[ns] = ko window.koala = ko return ko } declare global { interface Window { KoalaSDK?: { load: typeof load mountWidget: typeof mountWidget } } } // Handles the case where the SDK is loaded from a CDN using the UMD bundle // and the user has overridden the global `exports` variable with their own implementation // in order to deal with CommonJS modules. if (typeof exports !== 'undefined' && typeof window !== 'undefined' && typeof window['KoalaSDK'] === 'undefined') { window.KoalaSDK = { load, mountWidget } }