// Events import * as log from "loglevel"; import smartlookClient from "smartlook-client"; import { getAppsmithConfigs } from "@appsmith/configs"; import * as Sentry from "@sentry/react"; import type { User } from "constants/userConstants"; import { ANONYMOUS_USERNAME } from "constants/userConstants"; import { sha256 } from "js-sha256"; import type { EventName } from "@appsmith/utils/analyticsUtilTypes"; declare global { interface Window { // Zipy is added via script tags in index.html zipy: { identify: (uid: string, userInfo: Record) => void; anonymize: () => void; }; } } function getApplicationId(location: Location) { const pathSplit = location.pathname.split("/"); const applicationsIndex = pathSplit.findIndex( (path) => path === "applications", ); const appId = pathSplit[applicationsIndex + 1]; return appId; } class AnalyticsUtil { static cachedAnonymoustId: string; static cachedUserId: string; static user?: User = undefined; static blockTrackEvent: boolean | undefined; static instanceId?: string = ""; static initializeSmartLook(id: string) { smartlookClient.init(id); } static initializeSegment(key: string) { const initPromise = new Promise((resolve) => { (function init(window: any) { const analytics = (window.analytics = window.analytics || []); if (!analytics.initialize) { if (analytics.invoked) { log.error("Segment snippet included twice."); } else { analytics.invoked = !0; analytics.methods = [ "trackSubmit", "trackClick", "trackLink", "trackForm", "pageview", "identify", "reset", "group", "track", "ready", "alias", "debug", "page", "once", "off", "on", ]; analytics.factory = function (t: any) { return function () { const e = Array.prototype.slice.call(arguments); //eslint-disable-line prefer-rest-params e.unshift(t); analytics.push(e); return analytics; }; }; } for (let t: any = 0; t < analytics.methods.length; t++) { const e = analytics.methods[t]; analytics[e] = analytics.factory(e); } analytics.load = function (t: any, e: any) { const n = document.createElement("script"); n.type = "text/javascript"; n.async = !0; // Ref: https://www.notion.so/appsmith/530051a2083040b5bcec15a46121aea3 n.src = "https://a.appsmith.com/reroute/" + t + "/main.js"; const a: any = document.getElementsByTagName("script")[0]; a.parentNode.insertBefore(n, a); analytics._loadOptions = e; }; analytics.ready(() => { resolve(true); }); setTimeout(() => { resolve(false); }, 2000); analytics.SNIPPET_VERSION = "4.1.0"; // Ref: https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#batching analytics.load(key, { integrations: { "Segment.io": { deliveryStrategy: { strategy: "batching", // The delivery strategy used for sending events to Segment config: { size: 100, // The batch size is the threshold that forces all batched events to be sent once it’s reached. timeout: 1000, // The number of milliseconds that forces all events queued for batching to be sent, regardless of the batch size, once it’s reached }, }, }, }, }); if (!AnalyticsUtil.blockTrackEvent) { analytics.page(); } } })(window); }); return initPromise; } static logEvent(eventName: EventName, eventData: any = {}) { if (AnalyticsUtil.blockTrackEvent) { return; } const windowDoc: any = window; let finalEventData = eventData; const userData = AnalyticsUtil.user; const instanceId = AnalyticsUtil.instanceId; const appId = getApplicationId(windowDoc.location); const { appVersion, segment } = getAppsmithConfigs(); if (userData) { let user: any = {}; if (segment.apiKey) { user = { userId: userData.username, email: userData.email, appId: appId, source: "cloud", }; } else { const userId = userData.username; if (userId !== AnalyticsUtil.cachedUserId) { AnalyticsUtil.cachedAnonymoustId = sha256(userId); AnalyticsUtil.cachedUserId = userId; } user = { userId: AnalyticsUtil.cachedAnonymoustId, source: "ce", }; } finalEventData = { ...eventData, userData: user.userId === ANONYMOUS_USERNAME ? undefined : user, }; } finalEventData = { ...finalEventData, instanceId, version: appVersion.id }; if (windowDoc.analytics) { log.debug("Event fired", eventName, finalEventData); windowDoc.analytics.track(eventName, finalEventData); } else { log.debug("Event fired locally", eventName, finalEventData); } } static identifyUser(userData: User) { const { segment, sentry, smartLook } = getAppsmithConfigs(); const windowDoc: any = window; const userId = userData.username; if (windowDoc.analytics) { // This flag is only set on Appsmith Cloud. In this case, we get more detailed analytics of the user if (segment.apiKey) { const userProperties = { email: userData.email, name: userData.name, userId: userId, source: "cloud", }; AnalyticsUtil.user = userData; log.debug("Identify User " + userId); windowDoc.analytics.identify(userId, userProperties); } else if (segment.ceKey) { // This is a self-hosted instance. Only send data if the analytics are NOT disabled by the user if (userId !== AnalyticsUtil.cachedUserId) { AnalyticsUtil.cachedAnonymoustId = sha256(userId); AnalyticsUtil.cachedUserId = userId; } const userProperties = { userId: AnalyticsUtil.cachedAnonymoustId, source: "ce", }; log.debug( "Identify Anonymous User " + AnalyticsUtil.cachedAnonymoustId, ); windowDoc.analytics.identify( AnalyticsUtil.cachedAnonymoustId, userProperties, ); } } if (sentry.enabled) { Sentry.configureScope(function (scope) { scope.setUser({ id: userId, username: userData.username, email: userData.email, }); }); } if (smartLook.enabled) { smartlookClient.identify(userId, { email: userData.email }); } // If zipy was included, identify this user on the platform if (window.zipy && userId) { window.zipy.identify(userId, { email: userData.email, username: userData.username, }); } AnalyticsUtil.blockTrackEvent = false; } static initInstanceId(instanceId: string) { AnalyticsUtil.instanceId = instanceId; } static getAnonymousId() { const windowDoc: any = window; const { segment } = getAppsmithConfigs(); if (windowDoc.analytics && windowDoc.analytics.user) { return windowDoc.analytics.user().anonymousId(); } else if (segment.enabled) { return localStorage.getItem("ajs_anonymous_id")?.replaceAll('"', ""); } } static reset() { const windowDoc: any = window; if (windowDoc.Intercom) { windowDoc.Intercom("shutdown"); } windowDoc.analytics && windowDoc.analytics.reset(); windowDoc.mixpanel && windowDoc.mixpanel.reset(); window.zipy && window.zipy.anonymize(); } static removeAnalytics() { AnalyticsUtil.blockTrackEvent = false; (window as any).analytics = undefined; } } export default AnalyticsUtil;