UNPKG

@amplitude/analytics-browser

Version:
445 lines 25.3 kB
import { __assign, __awaiter, __extends, __generator, __rest } from "tslib"; import { AmplitudeCore, Destination, Identify, returnWrapper, Revenue, UUID, getAnalyticsConnector, setConnectorDeviceId, setConnectorUserId, isNewSession, IdentityEventSender, getQueryParams, OfflineDisabled, SpecialEventType, RemoteConfigClient, DiagnosticsClient, } from '@amplitude/analytics-core'; import { getAttributionTrackingConfig, getPageViewTrackingConfig, getElementInteractionsConfig, getNetworkTrackingConfig, isAttributionTrackingEnabled, isSessionTrackingEnabled, isFileDownloadTrackingEnabled, isFormInteractionTrackingEnabled, isElementInteractionsEnabled, isPageViewTrackingEnabled, isNetworkTrackingEnabled, isWebVitalsEnabled, isFrustrationInteractionsEnabled, getFrustrationInteractionsConfig, isPageUrlEnrichmentEnabled, } from './default-tracking'; import { convertProxyObjectToRealObject, isInstanceProxy } from './utils/snippet-helper'; import { Context } from './plugins/context'; import { useBrowserConfig, createTransport } from './config'; import { pageViewTrackingPlugin } from '@amplitude/plugin-page-view-tracking-browser'; import { formInteractionTracking } from './plugins/form-interaction-tracking'; import { fileDownloadTracking } from './plugins/file-download-tracking'; import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_START_EVENT } from './constants'; import { detNotify } from './det-notification'; import { networkConnectivityCheckerPlugin } from './plugins/network-connectivity-checker'; import { updateBrowserConfigWithRemoteConfig } from './config/joined-config'; import { autocapturePlugin, frustrationPlugin } from '@amplitude/plugin-autocapture-browser'; import { plugin as networkCapturePlugin } from '@amplitude/plugin-network-capture-browser'; import { webVitalsPlugin } from '@amplitude/plugin-web-vitals-browser'; import { WebAttribution } from './attribution/web-attribution'; import { LIBPREFIX } from './lib-prefix'; import { VERSION } from './version'; import { pageUrlEnrichmentPlugin } from '@amplitude/plugin-page-url-enrichment-browser'; /** * Exported for `@amplitude/unified` or integration with blade plugins. * If you only use `@amplitude/analytics-browser`, use `amplitude.init()` or `amplitude.createInstance()` instead. */ var AmplitudeBrowser = /** @class */ (function (_super) { __extends(AmplitudeBrowser, _super); function AmplitudeBrowser() { var _this = _super !== null && _super.apply(this, arguments) || this; // Backdoor to set diagnostics sample rate // by calling amplitude._setDiagnosticsSampleRate(1); before amplitude.init() _this._diagnosticsSampleRate = 0; return _this; } AmplitudeBrowser.prototype.init = function (apiKey, userIdOrOptions, maybeOptions) { if (apiKey === void 0) { apiKey = ''; } var userId; var options; if (arguments.length > 2) { userId = userIdOrOptions; options = maybeOptions; } else { if (typeof userIdOrOptions === 'string') { userId = userIdOrOptions; options = undefined; } else { userId = userIdOrOptions === null || userIdOrOptions === void 0 ? void 0 : userIdOrOptions.userId; options = userIdOrOptions; } } return returnWrapper(this._init(__assign(__assign({}, options), { userId: userId, apiKey: apiKey }))); }; AmplitudeBrowser.prototype._init = function (options) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function () { var browserOptions, remoteConfigClient, diagnosticsClient, attributionTrackingOptions, queryParams, ampTimestamp, isWithinTimeLimit, querySessionId, connector; var _this = this; return __generator(this, function (_f) { switch (_f.label) { case 0: // Step 1: Block concurrent initialization if (this.initializing) { return [2 /*return*/]; } this.initializing = true; return [4 /*yield*/, useBrowserConfig(options.apiKey, options, this)]; case 1: browserOptions = _f.sent(); if (!((_a = browserOptions.remoteConfig) === null || _a === void 0 ? void 0 : _a.fetchRemoteConfig)) return [3 /*break*/, 3]; remoteConfigClient = new RemoteConfigClient(browserOptions.apiKey, browserOptions.loggerProvider, browserOptions.serverZone, /* istanbul ignore next */ (_b = browserOptions.remoteConfig) === null || _b === void 0 ? void 0 : _b.serverUrl); // Wait for initial remote config before proceeding. // Delivery mode is all, meaning it calls the callback with whoever (cache or remote) returns first. return [4 /*yield*/, new Promise(function (resolve) { // Disable coverage for this line because remote config client will always be defined in this case. // istanbul ignore next remoteConfigClient === null || remoteConfigClient === void 0 ? void 0 : remoteConfigClient.subscribe('configs.analyticsSDK.browserSDK', 'all', function (remoteConfig, source, lastFetch) { browserOptions.loggerProvider.debug('Remote configuration received:', JSON.stringify({ remoteConfig: remoteConfig, source: source, lastFetch: lastFetch, }, null, 2)); if (remoteConfig) { updateBrowserConfigWithRemoteConfig(remoteConfig, browserOptions); } // Resolve the promise on first callback (initial config) resolve(); }); })]; case 2: // Wait for initial remote config before proceeding. // Delivery mode is all, meaning it calls the callback with whoever (cache or remote) returns first. _f.sent(); _f.label = 3; case 3: diagnosticsClient = new DiagnosticsClient(browserOptions.apiKey, browserOptions.loggerProvider, browserOptions.serverZone, { enabled: browserOptions.enableDiagnostics, sampleRate: browserOptions.diagnosticsSampleRate || this._diagnosticsSampleRate, }); diagnosticsClient.setTag('library', "".concat(LIBPREFIX, "/").concat(VERSION)); return [4 /*yield*/, _super.prototype._init.call(this, browserOptions)]; case 4: _f.sent(); this.logBrowserOptions(browserOptions); this.config.diagnosticsClient = diagnosticsClient; this.config.remoteConfigClient = remoteConfigClient; if (!isAttributionTrackingEnabled(this.config.defaultTracking)) return [3 /*break*/, 6]; attributionTrackingOptions = getAttributionTrackingConfig(this.config); this.webAttribution = new WebAttribution(attributionTrackingOptions, this.config); // Fetch the current campaign, check if need to track web attribution later return [4 /*yield*/, this.webAttribution.init()]; case 5: // Fetch the current campaign, check if need to track web attribution later _f.sent(); _f.label = 6; case 6: queryParams = getQueryParams(); ampTimestamp = queryParams.ampTimestamp ? Number(queryParams.ampTimestamp) : undefined; isWithinTimeLimit = ampTimestamp ? Date.now() < ampTimestamp : true; // if an identify object is provided, call it on init if (this.config.identify) { this.identify(this.config.identify); } querySessionId = isWithinTimeLimit && !Number.isNaN(Number(queryParams.ampSessionId)) ? Number(queryParams.ampSessionId) : undefined; this.setSessionId((_e = (_d = (_c = options.sessionId) !== null && _c !== void 0 ? _c : querySessionId) !== null && _d !== void 0 ? _d : this.config.sessionId) !== null && _e !== void 0 ? _e : Date.now()); connector = getAnalyticsConnector(options.instanceName); connector.identityStore.setIdentity({ userId: this.config.userId, deviceId: this.config.deviceId, }); if (!(this.config.offline !== OfflineDisabled)) return [3 /*break*/, 8]; return [4 /*yield*/, this.add(networkConnectivityCheckerPlugin()).promise]; case 7: _f.sent(); _f.label = 8; case 8: return [4 /*yield*/, this.add(new Destination({ diagnosticsClient: diagnosticsClient })).promise]; case 9: _f.sent(); return [4 /*yield*/, this.add(new Context()).promise]; case 10: _f.sent(); return [4 /*yield*/, this.add(new IdentityEventSender()).promise]; case 11: _f.sent(); // Notify if DET is enabled detNotify(this.config); if (!isFileDownloadTrackingEnabled(this.config.defaultTracking)) return [3 /*break*/, 13]; this.config.loggerProvider.debug('Adding file download tracking plugin'); return [4 /*yield*/, this.add(fileDownloadTracking()).promise]; case 12: _f.sent(); _f.label = 13; case 13: if (!isFormInteractionTrackingEnabled(this.config.defaultTracking)) return [3 /*break*/, 15]; this.config.loggerProvider.debug('Adding form interaction plugin'); return [4 /*yield*/, this.add(formInteractionTracking()).promise]; case 14: _f.sent(); _f.label = 15; case 15: if (!isPageViewTrackingEnabled(this.config.defaultTracking)) return [3 /*break*/, 17]; this.config.loggerProvider.debug('Adding page view tracking plugin'); return [4 /*yield*/, this.add(pageViewTrackingPlugin(getPageViewTrackingConfig(this.config))).promise]; case 16: _f.sent(); _f.label = 17; case 17: if (!isElementInteractionsEnabled(this.config.autocapture)) return [3 /*break*/, 19]; this.config.loggerProvider.debug('Adding user interactions plugin (autocapture plugin)'); return [4 /*yield*/, this.add(autocapturePlugin(getElementInteractionsConfig(this.config), { diagnosticsClient: diagnosticsClient })).promise]; case 18: _f.sent(); _f.label = 19; case 19: if (!isFrustrationInteractionsEnabled(this.config.autocapture)) return [3 /*break*/, 21]; this.config.loggerProvider.debug('Adding frustration interactions plugin'); return [4 /*yield*/, this.add(frustrationPlugin(getFrustrationInteractionsConfig(this.config))).promise]; case 20: _f.sent(); _f.label = 21; case 21: if (!isNetworkTrackingEnabled(this.config.autocapture)) return [3 /*break*/, 23]; this.config.loggerProvider.debug('Adding network tracking plugin'); return [4 /*yield*/, this.add(networkCapturePlugin(getNetworkTrackingConfig(this.config))).promise]; case 22: _f.sent(); _f.label = 23; case 23: if (!isWebVitalsEnabled(this.config.autocapture)) return [3 /*break*/, 25]; this.config.loggerProvider.debug('Adding web vitals plugin'); return [4 /*yield*/, this.add(webVitalsPlugin()).promise]; case 24: _f.sent(); _f.label = 25; case 25: if (!isPageUrlEnrichmentEnabled(this.config.autocapture)) return [3 /*break*/, 27]; this.config.loggerProvider.debug('Adding referrer page url plugin'); return [4 /*yield*/, this.add(pageUrlEnrichmentPlugin()).promise]; case 26: _f.sent(); _f.label = 27; case 27: this.initializing = false; // Step 6: Run queued dispatch functions return [4 /*yield*/, this.runQueuedFunctions('dispatchQ')]; case 28: // Step 6: Run queued dispatch functions _f.sent(); // Step 7: Add the event receiver after running remaining queued functions. connector.eventBridge.setEventReceiver(function (event) { var _a = event.eventProperties || {}, time = _a.time, cleanEventProperties = __rest(_a, ["time"]); var eventOptions = typeof time === 'number' ? { time: time } : undefined; void _this.track(event.eventType, cleanEventProperties, eventOptions); }); return [2 /*return*/]; } }); }); }; AmplitudeBrowser.prototype.getUserId = function () { var _a; return (_a = this.config) === null || _a === void 0 ? void 0 : _a.userId; }; AmplitudeBrowser.prototype.setUserId = function (userId) { if (!this.config) { this.q.push(this.setUserId.bind(this, userId)); return; } this.config.loggerProvider.debug('function setUserId: ', userId); if (userId !== this.config.userId || userId === undefined) { this.config.userId = userId; // eslint-disable-next-line @typescript-eslint/no-unsafe-call this.timeline.onIdentityChanged({ userId: userId }); setConnectorUserId(userId, this.config.instanceName); } }; AmplitudeBrowser.prototype.getDeviceId = function () { var _a; return (_a = this.config) === null || _a === void 0 ? void 0 : _a.deviceId; }; AmplitudeBrowser.prototype.setDeviceId = function (deviceId) { if (!this.config) { this.q.push(this.setDeviceId.bind(this, deviceId)); return; } this.config.loggerProvider.debug('function setDeviceId: ', deviceId); if (deviceId !== this.config.deviceId) { this.config.deviceId = deviceId; // eslint-disable-next-line @typescript-eslint/no-unsafe-call this.timeline.onIdentityChanged({ deviceId: deviceId }); setConnectorDeviceId(deviceId, this.config.instanceName); } }; AmplitudeBrowser.prototype.reset = function () { this.setDeviceId(UUID()); this.setUserId(undefined); }; AmplitudeBrowser.prototype.getIdentity = function () { var _a, _b; return { deviceId: (_a = this.config) === null || _a === void 0 ? void 0 : _a.deviceId, userId: (_b = this.config) === null || _b === void 0 ? void 0 : _b.userId, userProperties: this.userProperties, }; }; AmplitudeBrowser.prototype.getOptOut = function () { var _a; return (_a = this.config) === null || _a === void 0 ? void 0 : _a.optOut; }; AmplitudeBrowser.prototype.getSessionId = function () { var _a; return (_a = this.config) === null || _a === void 0 ? void 0 : _a.sessionId; }; AmplitudeBrowser.prototype.setSessionId = function (sessionId) { var _a; var promises = []; if (!this.config) { this.q.push(this.setSessionId.bind(this, sessionId)); return returnWrapper(Promise.resolve()); } // Prevents starting a new session with the same session ID if (sessionId === this.config.sessionId) { return returnWrapper(Promise.resolve()); } this.config.loggerProvider.debug('function setSessionId: ', sessionId); var previousSessionId = this.getSessionId(); if (previousSessionId !== sessionId) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call this.timeline.onSessionIdChanged(sessionId); } var lastEventTime = this.config.lastEventTime; var lastEventId = (_a = this.config.lastEventId) !== null && _a !== void 0 ? _a : -1; this.config.sessionId = sessionId; this.config.lastEventTime = undefined; this.config.pageCounter = 0; if (isSessionTrackingEnabled(this.config.defaultTracking)) { if (previousSessionId && lastEventTime) { promises.push(this.track(DEFAULT_SESSION_END_EVENT, undefined, { device_id: this.previousSessionDeviceId, event_id: ++lastEventId, session_id: previousSessionId, time: lastEventTime + 1, user_id: this.previousSessionUserId, }).promise); } this.config.lastEventTime = this.config.sessionId; } // Fire web attribution event when enable webAttribution tracking // 1. has new campaign (call setSessionId from init function) // 2. or shouldTrackNewCampaign (call setSessionId from async process(event) when there has new campaign and resetSessionOnNewCampaign = true ) var isCampaignEventTracked = this.trackCampaignEventIfNeeded(++lastEventId, promises); if (isSessionTrackingEnabled(this.config.defaultTracking)) { promises.push(this.track(DEFAULT_SESSION_START_EVENT, undefined, { event_id: isCampaignEventTracked ? ++lastEventId : lastEventId, session_id: this.config.sessionId, time: this.config.lastEventTime, }).promise); } this.previousSessionDeviceId = this.config.deviceId; this.previousSessionUserId = this.config.userId; return returnWrapper(Promise.all(promises)); }; AmplitudeBrowser.prototype.extendSession = function () { if (!this.config) { this.q.push(this.extendSession.bind(this)); return; } this.config.lastEventTime = Date.now(); }; AmplitudeBrowser.prototype.setTransport = function (transport) { if (!this.config) { this.q.push(this.setTransport.bind(this, transport)); return; } this.config.transportProvider = createTransport(transport); }; AmplitudeBrowser.prototype.identify = function (identify, eventOptions) { if (isInstanceProxy(identify)) { var queue = identify._q; identify._q = []; identify = convertProxyObjectToRealObject(new Identify(), queue); } if (eventOptions === null || eventOptions === void 0 ? void 0 : eventOptions.user_id) { this.setUserId(eventOptions.user_id); } if (eventOptions === null || eventOptions === void 0 ? void 0 : eventOptions.device_id) { this.setDeviceId(eventOptions.device_id); } return _super.prototype.identify.call(this, identify, eventOptions); }; AmplitudeBrowser.prototype.groupIdentify = function (groupType, groupName, identify, eventOptions) { if (isInstanceProxy(identify)) { var queue = identify._q; identify._q = []; identify = convertProxyObjectToRealObject(new Identify(), queue); } return _super.prototype.groupIdentify.call(this, groupType, groupName, identify, eventOptions); }; AmplitudeBrowser.prototype.revenue = function (revenue, eventOptions) { if (isInstanceProxy(revenue)) { var queue = revenue._q; revenue._q = []; revenue = convertProxyObjectToRealObject(new Revenue(), queue); } return _super.prototype.revenue.call(this, revenue, eventOptions); }; AmplitudeBrowser.prototype.trackCampaignEventIfNeeded = function (lastEventId, promises) { if (!this.webAttribution || !this.webAttribution.shouldTrackNewCampaign) { return false; } var campaignEvent = this.webAttribution.generateCampaignEvent(lastEventId); if (promises) { promises.push(this.track(campaignEvent).promise); } else { this.track(campaignEvent); } this.config.loggerProvider.log('Tracking attribution.'); return true; }; AmplitudeBrowser.prototype.process = function (event) { return __awaiter(this, void 0, void 0, function () { var currentTime, isEventInNewSession, shouldSetSessionIdOnNewCampaign; return __generator(this, function (_a) { currentTime = Date.now(); isEventInNewSession = isNewSession(this.config.sessionTimeout, this.config.lastEventTime); shouldSetSessionIdOnNewCampaign = this.webAttribution && this.webAttribution.shouldSetSessionIdOnNewCampaign(); if (event.event_type !== DEFAULT_SESSION_START_EVENT && event.event_type !== DEFAULT_SESSION_END_EVENT && (!event.session_id || event.session_id === this.getSessionId())) { if (isEventInNewSession || shouldSetSessionIdOnNewCampaign) { this.setSessionId(currentTime); if (shouldSetSessionIdOnNewCampaign) { this.config.loggerProvider.log('Created a new session for new campaign.'); } } else if (!isEventInNewSession) { // Web attribution should be tracked during the middle of a session // if there has been a chance in the campaign information. this.trackCampaignEventIfNeeded(); } } // Set user properties if (event.event_type === SpecialEventType.IDENTIFY && event.user_properties) { this.userProperties = this.getOperationAppliedUserProperties(event.user_properties); } return [2 /*return*/, _super.prototype.process.call(this, event)]; }); }); }; AmplitudeBrowser.prototype.logBrowserOptions = function (browserConfig) { try { var browserConfigCopy = __assign(__assign({}, browserConfig), { apiKey: browserConfig.apiKey.substring(0, 10) + '********' }); this.config.loggerProvider.debug('Initialized Amplitude with BrowserConfig:', JSON.stringify(browserConfigCopy)); } catch (e) { /* istanbul ignore next */ this.config.loggerProvider.error('Error logging browser config', e); } }; /** * @experimental * WARNING: This method is for internal testing only and is not part of the public API. * It may be changed or removed at any time without notice. * * Sets the diagnostics sample rate before amplitude.init() * @param sampleRate - The sample rate to set */ AmplitudeBrowser.prototype._setDiagnosticsSampleRate = function (sampleRate) { if (sampleRate > 1 || sampleRate < 0) { return; } // Set diagnostics sample rate before initializing the config if (!this.config) { this._diagnosticsSampleRate = sampleRate; return; } }; return AmplitudeBrowser; }(AmplitudeCore)); export { AmplitudeBrowser }; //# sourceMappingURL=browser-client.js.map