@amplitude/analytics-browser
Version:
Official Amplitude SDK for Web
445 lines • 25.3 kB
JavaScript
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