UNPKG

express-addon-analytics

Version:
329 lines (275 loc) 11.6 kB
/** Express Analytics * Copyright (c) 2025 Scherotter Enterprises */ /** User ID for anonymous users */ const AnonymousId = "_anonymous"; /** Interface from Adobe Express addon SDK "@types/adobe__ccweb-add-on-sdk": "^1.3.0", */ export interface IAdobeExpressPlatform{ deviceClass: string; inAppPurchaseAllowed: boolean; platform: string; } /** Interface from Adobe Express addon SDK "@types/adobe__ccweb-add-on-sdk": "^1.3.0", */ export interface IAdobeExpressAddOnSDKAPI{ /** the API version */ apiVersion:string, /** the app */ app: { /** the current user */ currentUser: { /** the User Id * @returns an async promise with a string */ userId(): Promise<string>, /** is the user premium * @returns an async promise with a boolean value */ isPremiumUser() : Promise<boolean>, /** is the current user is anonymous * @returns an async promise with a boolean value */ isAnonymousUser(): Promise<boolean> }, /** the developer flags */ devFlags : { /** True to simulated a free user */ simulateFreeUser: boolean }, /** Gets the current platform * @returns an async promise with the Adobe Express Platform */ getCurrentPlatform() : Promise<IAdobeExpressPlatform>, /** The user interface */ ui:{ /** the format */ format:string, /** the locale */ locale:string, /** the theme name */ theme: string } }, /** The add-on instance */ instance: { /** The add-on manifest */ manifest: Record<string, unknown> } } /** Adobe Express Add-on Analytics */ export class ExpressAnalytics{ private _addOnSDK: IAdobeExpressAddOnSDKAPI; private _endpoint: string; private _devEndpoint: string; private _addOnName: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any private _timeout?: any; /** whether to log errors to the browser console */ static LogErrors = false; /** The pulse interval in milliseconds (default is 15 seconds) */ static PulseInterval = 15000; /** Create an analytics object * @param addOnSDK the Adobe Express add-on SDK * @param endpoint the https:// production endpoint * @param devEndpoint the https:// development endpoint, if not specified the * endpoint will be used when in development */ constructor(addOnSDK: IAdobeExpressAddOnSDKAPI, endpoint: string, devEndpoint?: string){ if (!addOnSDK) throw new Error("Express Analytics: addOnSDK is undefined."); if (!endpoint) throw new Error("Express Analytics: endpoint cannot be empty."); if (!endpoint.startsWith("https://")) throw new Error("Express Analytics: endpoint must start with https://"); if (devEndpoint && !devEndpoint.startsWith("https://")) throw new Error("Express Analytics: devEndpoint must start with https://"); this._addOnSDK = addOnSDK; this._addOnName = encodeURIComponent(addOnSDK.instance.manifest.name as string); this._endpoint = endpoint; this._devEndpoint = devEndpoint ? devEndpoint : endpoint; if (!this._timeout){ this._timeout = setInterval(ExpressAnalytics.onPulseAsync, ExpressAnalytics.PulseInterval, this); } } /** stop the pulse interval */ dispose(){ if (this._timeout){ clearInterval(this._timeout); this._timeout = undefined; } } /** track a user * @param extra extra fields to add * @returns an async promise with a boolean value indicating whether the tracking POST succeeded */ async trackUserAsync(extra?: Record<string,string>): Promise<boolean>{ try{ const userId = await this._addOnSDK.app.currentUser.userId(); const isPremiumUser = await this._addOnSDK.app.currentUser.isPremiumUser(); const isAnonymousUser = await this._addOnSDK.app.currentUser.isAnonymousUser(); const platform = await this._addOnSDK.app.getCurrentPlatform(); const screen = window.screen; const parameters = [ `a=${this._addOnSDK.apiVersion}`, `an=${isAnonymousUser}`, `c=${screen.colorDepth}`, `d=${platform.deviceClass}`, `e=_user`, `f=${this._addOnSDK.app.ui.format}`, `h=${screen.height}`, `i=${platform.inAppPurchaseAllowed}`, `l=${this._addOnSDK.app.ui.locale}`, `n=${this._addOnName}`, `p=${isPremiumUser}`, `pd=${screen.pixelDepth}`, `pl=${platform.platform}`, `t=${this._addOnSDK.app.ui.theme}`, `u=${userId || AnonymousId}`, `v=${this._addOnSDK.instance.manifest.version}`, `w=${screen.width}` ]; if (ExpressAnalytics.isDevelopment){ parameters.push(`s=${this._addOnSDK.app.devFlags.simulateFreeUser}`); } if (extra){ // add extra parameters Object.entries(extra).forEach(ExpressAnalytics.addExtra, parameters); } const url = this.getUrl(parameters); const response = await fetch(url, { method:"POST" }); if (response.ok){ //const textResponse = await response.text(); //console.info(`Express Analytics user tracked: ${textResponse}`); } else { if (ExpressAnalytics.LogErrors){ const textResponse = await response.text(); console.error(`Express Analytics user tracking error: ${textResponse}`); } } return response.ok; } catch(error:unknown){ if (ExpressAnalytics.LogErrors){ const err = error as Error; console.error(`Express Analytics user tracking error: ${err.message}`); } return false; } } /** track an event * @param eventName: the event name * @param extra: extra parameters to record * @returns an async promise with a boolean value indicating whether the tracking * was successful. */ async trackEventAsync(eventName: string, extra?: Record<string,string>) : Promise<boolean>{ try{ if (!eventName) throw new Error("Express Analytics: eventName cannot be blank"); const reservedNames = ["_user", "_error"]; if (reservedNames.includes(eventName)) throw new Error(`Express Analytics: Cannot track a ${eventName} event using trackEventAsync(), use trackUserAsync() or trackErrorAsync() instead.`); const userId = await this._addOnSDK.app.currentUser.userId(); const parameters = [ `e=${encodeURIComponent(eventName)}`, `n=${encodeURIComponent(this._addOnName)}`, `u=${userId || AnonymousId}` ]; if (extra){ // add extra parameters Object.entries(extra).forEach(ExpressAnalytics.addExtra, parameters); } const url = this.getUrl(parameters); const response = await fetch(url, { method:"post" }); if (!response.ok){ if (ExpressAnalytics.LogErrors){ const text = await response.text(); console.error(`Express Analytics error tracking event ${eventName}: ${text}.`); } } return response.ok; } catch (error: unknown){ if (ExpressAnalytics.LogErrors){ const err = error as Error; console.error(`Express Analytics event tracking event: ${err.message}`); } return false; } } /** Track an error * @param error an Error object * @param extra the extra parameters * @returns an ansyc promise with a boolean value indicating whether the tracking was successful * @example * try{ * ... * // code that throws an exception * ... * } catch(error:any) { * await this.analytics.trackErrorAsync(error as Error); * } */ async trackErrorAsync(error: Error, extra?: Record<string,string>) : Promise<boolean>{ try{ const userId = await this._addOnSDK.app.currentUser.userId(); const parameters = [ `e=_error`, `en=${encodeURIComponent(error.name)}`, `m=${encodeURIComponent(error.message)}`, `n=${encodeURIComponent(this._addOnName)}`, `u=${userId || AnonymousId}` ]; if (error.cause && typeof error.cause === 'string'){ parameters.push(`c=${encodeURIComponent(error.cause)}`) } if (extra){ // add extra parameters Object.entries(extra).forEach(ExpressAnalytics.addExtra, parameters); } const url = this.getUrl(parameters); let response; if (error.stack){ response = await fetch(url, { method:"post", headers: { 'Content-Type': 'text/plain' }, body: error.stack }); } else { response = await fetch(url, { method:"post" }); } if (!response.ok){ if (ExpressAnalytics.LogErrors){ const text = await response.text(); console.error(`Express Analytics error tracking error: ${text}.`); } } return response.ok; } catch (error: unknown){ if (ExpressAnalytics.LogErrors){ const err = error as Error; console.error(`Express Analytics event tracking error: ${err.message}`); } return false; } } private static get isDevelopment() { return process.env.NODE_ENV == "development"; } private static addExtra(this: string[], value: [string,string]){ const entry = `ex-${encodeURIComponent(value[0])}=${encodeURIComponent(value[1])}`; this.push(entry); } private static async onPulseAsync(analytics: ExpressAnalytics){ await analytics.trackEventAsync("_pulse"); } private getUrl(parameters: string[]) { let url = this._endpoint; if (ExpressAnalytics.isDevelopment){ url = this._devEndpoint; } if (url.includes("?")){ return `${url}&${parameters.join("&")}`; } return `${url}?${parameters.join("&")}`; } }