UNPKG

@amplitude/analytics-browser

Version:
487 lines 28.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AmplitudeBrowser = void 0; var tslib_1 = require("tslib"); var analytics_core_1 = require("@amplitude/analytics-core"); var default_tracking_1 = require("./default-tracking"); var snippet_helper_1 = require("./utils/snippet-helper"); var context_1 = require("./plugins/context"); var config_1 = require("./config"); var plugin_page_view_tracking_browser_1 = require("@amplitude/plugin-page-view-tracking-browser"); var form_interaction_tracking_1 = require("./plugins/form-interaction-tracking"); var file_download_tracking_1 = require("./plugins/file-download-tracking"); var constants_1 = require("./constants"); var det_notification_1 = require("./det-notification"); var network_connectivity_checker_1 = require("./plugins/network-connectivity-checker"); var joined_config_1 = require("./config/joined-config"); var plugin_autocapture_browser_1 = require("@amplitude/plugin-autocapture-browser"); var plugin_network_capture_browser_1 = require("@amplitude/plugin-network-capture-browser"); var plugin_web_vitals_browser_1 = require("@amplitude/plugin-web-vitals-browser"); var web_attribution_1 = require("./attribution/web-attribution"); var lib_prefix_1 = require("./lib-prefix"); var version_1 = require("./version"); var plugin_page_url_enrichment_browser_1 = require("@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) { tslib_1.__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 (0, analytics_core_1.returnWrapper)(this._init(tslib_1.__assign(tslib_1.__assign({}, options), { userId: userId, apiKey: apiKey }))); }; AmplitudeBrowser.prototype._init = function (options) { var _a, _b, _c, _d, _e, _f, _g; return tslib_1.__awaiter(this, void 0, void 0, function () { var fetchRemoteConfig, loggerProvider, serverZone, remoteConfigClient, diagnosticsSampleRate, enableDiagnostics, diagnosticsClient, browserOptions, attributionTrackingOptions, queryParams, ampTimestamp, isWithinTimeLimit, querySessionId, connector; var _this = this; return tslib_1.__generator(this, function (_h) { switch (_h.label) { case 0: // Step 1: Block concurrent initialization if (this.initializing) { return [2 /*return*/]; } this.initializing = true; fetchRemoteConfig = (0, config_1.shouldFetchRemoteConfig)(options); loggerProvider = (_a = options.loggerProvider) !== null && _a !== void 0 ? _a : new analytics_core_1.Logger(); serverZone = (_b = options.serverZone) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_SERVER_ZONE; diagnosticsSampleRate = this._diagnosticsSampleRate; enableDiagnostics = (_c = options.enableDiagnostics) !== null && _c !== void 0 ? _c : true; if (!fetchRemoteConfig) return [3 /*break*/, 2]; remoteConfigClient = new analytics_core_1.RemoteConfigClient(options.apiKey, loggerProvider, serverZone, /* istanbul ignore next */ (_d = options.remoteConfig) === null || _d === void 0 ? void 0 : _d.serverUrl); // Fetch diagnostics config first to get sample rate 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.diagnostics.browserSDK', 'all', function (remoteConfig, source, lastFetch) { loggerProvider.debug('Diagnostics remote configuration received:', JSON.stringify({ remoteConfig: remoteConfig, source: source, lastFetch: lastFetch, }, null, 2)); if (remoteConfig) { // Validate and set sampleRate (must be a valid number) var sampleRate = remoteConfig.sampleRate; if (typeof sampleRate === 'number' && !isNaN(sampleRate)) { diagnosticsSampleRate = sampleRate; } // Validate and set enabled (must be a boolean) var enabled = remoteConfig.enabled; if (typeof enabled === 'boolean') { enableDiagnostics = enabled; } } resolve(); }); })]; case 1: // Fetch diagnostics config first to get sample rate _h.sent(); _h.label = 2; case 2: diagnosticsClient = new analytics_core_1.DiagnosticsClient(options.apiKey, loggerProvider, serverZone, { enabled: enableDiagnostics, sampleRate: diagnosticsSampleRate, }); diagnosticsClient.setTag('library', "".concat(lib_prefix_1.LIBPREFIX, "/").concat(version_1.VERSION)); if (typeof navigator !== 'undefined') { diagnosticsClient.setTag('user_agent', navigator.userAgent); } return [4 /*yield*/, (0, config_1.useBrowserConfig)(options.apiKey, options, this, diagnosticsClient, { loggerProvider: loggerProvider, serverZone: serverZone, enableDiagnostics: enableDiagnostics, diagnosticsSampleRate: diagnosticsSampleRate, })]; case 3: browserOptions = _h.sent(); if (!(fetchRemoteConfig && remoteConfigClient)) return [3 /*break*/, 5]; 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) { (0, joined_config_1.updateBrowserConfigWithRemoteConfig)(remoteConfig, browserOptions); } // Resolve the promise on first callback (initial config) resolve(); }); })]; case 4: _h.sent(); _h.label = 5; case 5: return [4 /*yield*/, _super.prototype._init.call(this, browserOptions)]; case 6: _h.sent(); this.logBrowserOptions(browserOptions); this.config.remoteConfigClient = remoteConfigClient; if (!(0, default_tracking_1.isAttributionTrackingEnabled)(this.config.defaultTracking)) return [3 /*break*/, 8]; attributionTrackingOptions = (0, default_tracking_1.getAttributionTrackingConfig)(this.config); this.webAttribution = new web_attribution_1.WebAttribution(attributionTrackingOptions, this.config); // Fetch the current campaign, check if need to track web attribution later return [4 /*yield*/, this.webAttribution.init()]; case 7: // Fetch the current campaign, check if need to track web attribution later _h.sent(); _h.label = 8; case 8: queryParams = (0, analytics_core_1.getQueryParams)(); ampTimestamp = queryParams.ampTimestamp ? Number(queryParams.ampTimestamp) : undefined; isWithinTimeLimit = ampTimestamp ? Date.now() < ampTimestamp : true; querySessionId = isWithinTimeLimit && !Number.isNaN(Number(queryParams.ampSessionId)) ? Number(queryParams.ampSessionId) : undefined; this.setSessionId((_g = (_f = (_e = options.sessionId) !== null && _e !== void 0 ? _e : querySessionId) !== null && _f !== void 0 ? _f : this.config.sessionId) !== null && _g !== void 0 ? _g : Date.now()); connector = (0, analytics_core_1.getAnalyticsConnector)(options.instanceName); connector.identityStore.setIdentity({ userId: this.config.userId, deviceId: this.config.deviceId, }); if (!(this.config.offline !== analytics_core_1.OfflineDisabled)) return [3 /*break*/, 10]; return [4 /*yield*/, this.add((0, network_connectivity_checker_1.networkConnectivityCheckerPlugin)()).promise]; case 9: _h.sent(); _h.label = 10; case 10: return [4 /*yield*/, this.add(new analytics_core_1.Destination({ diagnosticsClient: diagnosticsClient })).promise]; case 11: _h.sent(); return [4 /*yield*/, this.add(new context_1.Context()).promise]; case 12: _h.sent(); return [4 /*yield*/, this.add(new analytics_core_1.IdentityEventSender()).promise]; case 13: _h.sent(); // Notify if DET is enabled (0, det_notification_1.detNotify)(this.config); if (!(0, default_tracking_1.isFileDownloadTrackingEnabled)(this.config.defaultTracking)) return [3 /*break*/, 15]; this.config.loggerProvider.debug('Adding file download tracking plugin'); return [4 /*yield*/, this.add((0, file_download_tracking_1.fileDownloadTracking)()).promise]; case 14: _h.sent(); _h.label = 15; case 15: if (!(0, default_tracking_1.isFormInteractionTrackingEnabled)(this.config.defaultTracking)) return [3 /*break*/, 17]; this.config.loggerProvider.debug('Adding form interaction plugin'); return [4 /*yield*/, this.add((0, form_interaction_tracking_1.formInteractionTracking)()).promise]; case 16: _h.sent(); _h.label = 17; case 17: if (!(0, default_tracking_1.isPageViewTrackingEnabled)(this.config.defaultTracking)) return [3 /*break*/, 19]; this.config.loggerProvider.debug('Adding page view tracking plugin'); return [4 /*yield*/, this.add((0, plugin_page_view_tracking_browser_1.pageViewTrackingPlugin)((0, default_tracking_1.getPageViewTrackingConfig)(this.config))).promise]; case 18: _h.sent(); _h.label = 19; case 19: if (!(0, default_tracking_1.isElementInteractionsEnabled)(this.config.autocapture)) return [3 /*break*/, 21]; this.config.loggerProvider.debug('Adding user interactions plugin (autocapture plugin)'); return [4 /*yield*/, this.add((0, plugin_autocapture_browser_1.autocapturePlugin)((0, default_tracking_1.getElementInteractionsConfig)(this.config), { diagnosticsClient: diagnosticsClient })).promise]; case 20: _h.sent(); _h.label = 21; case 21: if (!(0, default_tracking_1.isFrustrationInteractionsEnabled)(this.config.autocapture)) return [3 /*break*/, 23]; this.config.loggerProvider.debug('Adding frustration interactions plugin'); return [4 /*yield*/, this.add((0, plugin_autocapture_browser_1.frustrationPlugin)((0, default_tracking_1.getFrustrationInteractionsConfig)(this.config))).promise]; case 22: _h.sent(); _h.label = 23; case 23: if (!(0, default_tracking_1.isNetworkTrackingEnabled)(this.config.autocapture)) return [3 /*break*/, 25]; this.config.loggerProvider.debug('Adding network tracking plugin'); return [4 /*yield*/, this.add((0, plugin_network_capture_browser_1.plugin)((0, default_tracking_1.getNetworkTrackingConfig)(this.config))).promise]; case 24: _h.sent(); _h.label = 25; case 25: if (!(0, default_tracking_1.isWebVitalsEnabled)(this.config.autocapture)) return [3 /*break*/, 27]; this.config.loggerProvider.debug('Adding web vitals plugin'); return [4 /*yield*/, this.add((0, plugin_web_vitals_browser_1.webVitalsPlugin)()).promise]; case 26: _h.sent(); _h.label = 27; case 27: if (!(0, default_tracking_1.isPageUrlEnrichmentEnabled)(this.config.autocapture)) return [3 /*break*/, 29]; this.config.loggerProvider.debug('Adding referrer page url plugin'); return [4 /*yield*/, this.add((0, plugin_page_url_enrichment_browser_1.pageUrlEnrichmentPlugin)()).promise]; case 28: _h.sent(); _h.label = 29; case 29: this.initializing = false; // Step 6: Run queued dispatch functions return [4 /*yield*/, this.runQueuedFunctions('dispatchQ')]; case 30: // Step 6: Run queued dispatch functions _h.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 = tslib_1.__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 }); (0, analytics_core_1.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 }); (0, analytics_core_1.setConnectorDeviceId)(deviceId, this.config.instanceName); } }; AmplitudeBrowser.prototype.reset = function () { this.setDeviceId((0, analytics_core_1.UUID)()); this.setUserId(undefined); this.timeline.onReset(); }; 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 (0, analytics_core_1.returnWrapper)(Promise.resolve()); } // Prevents starting a new session with the same session ID if (sessionId === this.config.sessionId) { return (0, analytics_core_1.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 ((0, default_tracking_1.isSessionTrackingEnabled)(this.config.defaultTracking)) { if (previousSessionId && lastEventTime) { promises.push(this.track(constants_1.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); // track the identify event if an Identify object is provided in the config if (this.config.identify) { promises.push(this.track((0, analytics_core_1.createIdentifyEvent)(this.config.identify)).promise); } if ((0, default_tracking_1.isSessionTrackingEnabled)(this.config.defaultTracking)) { promises.push(this.track(constants_1.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 (0, analytics_core_1.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 = (0, config_1.createTransport)(transport); }; AmplitudeBrowser.prototype.identify = function (identify, eventOptions) { if ((0, snippet_helper_1.isInstanceProxy)(identify)) { var queue = identify._q; identify._q = []; identify = (0, snippet_helper_1.convertProxyObjectToRealObject)(new analytics_core_1.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 ((0, snippet_helper_1.isInstanceProxy)(identify)) { var queue = identify._q; identify._q = []; identify = (0, snippet_helper_1.convertProxyObjectToRealObject)(new analytics_core_1.Identify(), queue); } return _super.prototype.groupIdentify.call(this, groupType, groupName, identify, eventOptions); }; AmplitudeBrowser.prototype.revenue = function (revenue, eventOptions) { if ((0, snippet_helper_1.isInstanceProxy)(revenue)) { var queue = revenue._q; revenue._q = []; revenue = (0, snippet_helper_1.convertProxyObjectToRealObject)(new analytics_core_1.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 tslib_1.__awaiter(this, void 0, void 0, function () { var currentTime, isEventInNewSession, shouldSetSessionIdOnNewCampaign; return tslib_1.__generator(this, function (_a) { currentTime = Date.now(); isEventInNewSession = (0, analytics_core_1.isNewSession)(this.config.sessionTimeout, this.config.lastEventTime); shouldSetSessionIdOnNewCampaign = this.webAttribution && this.webAttribution.shouldSetSessionIdOnNewCampaign(); if (event.event_type !== constants_1.DEFAULT_SESSION_START_EVENT && event.event_type !== constants_1.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 === analytics_core_1.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 = tslib_1.__assign(tslib_1.__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; }(analytics_core_1.AmplitudeCore)); exports.AmplitudeBrowser = AmplitudeBrowser; //# sourceMappingURL=browser-client.js.map