@open-condo/miniapp-utils
Version:
A set of helper functions / components / hooks used to build new condo apps fast
1 lines • 9.11 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/helpers/analytics/instance.ts","../../../src/helpers/analytics/middlewares.ts"],"sourcesContent":["import { Analytics as DefaultAnalytics } from 'analytics'\n\nimport { GroupingMiddlewarePlugin } from './middlewares'\n\nimport type { AnyPayload, AnalyticsConfig, AnalyticsInstanceWithGroups, PageData } from './types'\n\n/**\n * Type-safe wrapper on top of \"[analytics](https://www.npmjs.com/package/analytics)\" npm package\n * with support of group method (Posthog inspired) to group users into cohorts\n *\n * @example Init analytics in your app with type-guards\n * import { Analytics } from '@open-condo/miniapp-utils/helpers/analytics'\n * import { isDebug } from '@open-condo/miniapp-utils/helpers/environment'\n *\n * type MyAppEvents = {\n * 'order_create': { orderId: string, itemIds: Array<string> }\n * 'user_register': { userId: string, utmSource?: string }\n * }\n *\n * type MyUserData = {\n * 'name'?: string\n * 'age'?: number\n * }\n *\n * type MyAppGroups = 'organization' | 'country'\n *\n * export const analytics = new Analytics<MyAppEvents, MyUserData, MyAppGroups>({\n * app: appName,\n * version: revision,\n * debug: isDebug(),\n * })\n *\n * @example Use initialized analytics in your-app\n * import { Router } from 'next/router'\n * import { useEffect } from 'react'\n *\n * import { isValidCondoUIMessage } from '@open-condo/ui/events'\n *\n * import { analytics } from '@/domains/common/utils/analytics'\n * import { useAuth } from '@/domains/user/utils/auth'\n *\n * import type { FC } from 'react'\n *\n * export const ResidentAppEventsHandler: FC = () => {\n * const user = useAuth()\n * const { activeResident } = useActiveResident()\n *\n * // User change tracking\n * useEffect(() => {\n * if (user) {\n * analytics.identify(user.id, { name: user.name, type: user.type })\n * }\n * }, [user])\n *\n * // Page views tracking\n * useEffect(() => {\n * const handleRouteChange = () => analytics.pageView()\n * Router.events.on('routeChangeComplete', handleRouteChange)\n *\n * return () => {\n * Router.events.off('routeChangeComplete', handleRouteChange)\n * }\n * }, [])\n *\n * // Condo UI events tracking\n * useEffect(() => {\n * if (typeof window !== 'undefined') {\n * const handleMessage = async (e: MessageEvent) => {\n * if (isValidCondoUIMessage(e)) {\n * const { params: { event, ...eventData } } = e.data\n * await analytics.trackUntyped(event, eventData)\n * }\n *\n * }\n *\n * window.addEventListener('message', handleMessage)\n *\n * return () => {\n * window.removeEventListener('message', handleMessage)\n * }\n * }\n * }, [])\n *\n * return null\n * }\n */\nexport class Analytics<\n Events extends Record<string, AnyPayload> = Record<string, never>,\n UserData extends AnyPayload = Record<string, never>,\n GroupNames extends string = never,\n> {\n private readonly _analytics: AnalyticsInstanceWithGroups<GroupNames>\n private readonly _groups = new Set<GroupNames>()\n\n constructor (config: AnalyticsConfig) {\n this._analytics = DefaultAnalytics({\n ...config,\n plugins: [\n GroupingMiddlewarePlugin,\n ...(config.plugins || []),\n ],\n }) as AnalyticsInstanceWithGroups<GroupNames>\n this._analytics.groups = this._groups\n }\n\n /**\n * Tracks type-safe business events. Recommended to use in most cases in app's codebase.\n * To add an event, modify \"Events\" generic.\n */\n async track<EventName extends Extract<keyof Events, string>>(eventName: EventName, eventData: Events[EventName]): Promise<void> {\n await this._analytics.track(eventName, eventData)\n }\n\n /**\n * Tracks untyped analytics events, used mainly for external sources (bridge / ui-kit / messages, etc.)\n * @deprecated It's not recommended to use this in your business logic, consider using typed \"track\" instead\n */\n async trackUntyped (eventName: string, eventData: AnyPayload): Promise<void> {\n await this._analytics.track(eventName, eventData)\n }\n\n /**\n * Tracks page changing in SPAs\n */\n async pageView (data?: PageData): Promise<void> {\n await this._analytics.page(data)\n }\n\n /**\n * Identifies user in analytics provider.\n * To specify all possible shape of user's data, modify \"UserData\" generic\n *\n * NOTE: Analytics plugins don't have a fixed behavior on how to handle consecutive identify calls.\n * Some of them affect only subsequent events, others affect all user events.\n * Therefore, it is not recommended to put cohort-specific data (organization, address, language, etc.) here.\n * Instead, use something like \"group\" method if your plugins supports it.\n */\n async identify<Key extends keyof UserData> (userId: string, userData?: Pick<UserData, Key>): Promise<void> {\n await this._analytics.identify(userId, userData)\n }\n\n /**\n * Resets analytics providers\n */\n async reset (): Promise<void> {\n for (const groupName of this._groups) {\n const groupKey = Analytics.getGroupKey(groupName)\n this._analytics.storage.removeItem(groupKey)\n }\n this._groups.clear()\n await this._analytics.reset()\n }\n\n static getGroupKey (groupName: string): string {\n return ['analytics', 'groups', groupName].join(':')\n }\n\n /**\n * Associates the user with a group, adding the attributes `groups.${groupName} = groupId`\n * to all subsequent analytic queries for the user\n * @example\n * analytics.setGroup('organization', organizationId)\n */\n setGroup (groupName: GroupNames, groupId: string): void {\n const groupKey = Analytics.getGroupKey(groupName)\n this._groups.add(groupName)\n this._analytics.storage.setItem(groupKey, groupId)\n }\n\n /**\n * Removes the current user from the group, stripping the “groups.${groupName}”\n * attribute from all subsequent eventualities\n * @example\n * deleteOrganization()\n * .then(() => analytics.removeGroup('organization'))\n */\n removeGroup (groupName: GroupNames): void {\n const groupKey = Analytics.getGroupKey(groupName)\n this._analytics.storage.removeItem(groupKey)\n this._groups.delete(groupName)\n }\n}\n","import { Analytics } from './instance'\n\nimport type { AnalyticsPlugin, PluginTrackData } from './types'\n\nfunction _addGroupingProperties (data: PluginTrackData): PluginTrackData {\n const { instance } = data\n for (const groupName of instance.groups) {\n const groupKey = Analytics.getGroupKey(groupName)\n const groupValue = instance.storage.getItem(groupKey)\n\n if (typeof groupValue === 'string') {\n const groupAttrName = `groups.${groupName}`\n data.payload.properties[groupAttrName] = groupValue\n }\n }\n return data\n}\n\nexport const GroupingMiddlewarePlugin: AnalyticsPlugin = {\n name: 'analytics-plugin-grouping',\n track: _addGroupingProperties,\n page: _addGroupingProperties,\n}"],"mappings":";AAAA,SAAS,aAAa,wBAAwB;;;ACI9C,SAAS,uBAAwB,MAAwC;AACrE,QAAM,EAAE,SAAS,IAAI;AACrB,aAAW,aAAa,SAAS,QAAQ;AACrC,UAAM,WAAW,UAAU,YAAY,SAAS;AAChD,UAAM,aAAa,SAAS,QAAQ,QAAQ,QAAQ;AAEpD,QAAI,OAAO,eAAe,UAAU;AAChC,YAAM,gBAAgB,UAAU,SAAS;AACzC,WAAK,QAAQ,WAAW,aAAa,IAAI;AAAA,IAC7C;AAAA,EACJ;AACA,SAAO;AACX;AAEO,IAAM,2BAA4C;AAAA,EACrD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACV;;;ADgEO,IAAM,YAAN,MAAM,WAIX;AAAA,EAIE,YAAa,QAAyB;AAFtC,SAAiB,UAAU,oBAAI,IAAgB;AAG3C,SAAK,aAAa,iBAAiB;AAAA,MAC/B,GAAG;AAAA,MACH,SAAS;AAAA,QACL;AAAA,QACA,GAAI,OAAO,WAAW,CAAC;AAAA,MAC3B;AAAA,IACJ,CAAC;AACD,SAAK,WAAW,SAAS,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAuD,WAAsB,WAA6C;AAC5H,UAAM,KAAK,WAAW,MAAM,WAAW,SAAS;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAc,WAAmB,WAAsC;AACzE,UAAM,KAAK,WAAW,MAAM,WAAW,SAAS;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAU,MAAgC;AAC5C,UAAM,KAAK,WAAW,KAAK,IAAI;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,SAAsC,QAAgB,UAA+C;AACvG,UAAM,KAAK,WAAW,SAAS,QAAQ,QAAQ;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAwB;AAC1B,eAAW,aAAa,KAAK,SAAS;AAClC,YAAM,WAAW,WAAU,YAAY,SAAS;AAChD,WAAK,WAAW,QAAQ,WAAW,QAAQ;AAAA,IAC/C;AACA,SAAK,QAAQ,MAAM;AACnB,UAAM,KAAK,WAAW,MAAM;AAAA,EAChC;AAAA,EAEA,OAAO,YAAa,WAA2B;AAC3C,WAAO,CAAC,aAAa,UAAU,SAAS,EAAE,KAAK,GAAG;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAU,WAAuB,SAAuB;AACpD,UAAM,WAAW,WAAU,YAAY,SAAS;AAChD,SAAK,QAAQ,IAAI,SAAS;AAC1B,SAAK,WAAW,QAAQ,QAAQ,UAAU,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,YAAa,WAA6B;AACtC,UAAM,WAAW,WAAU,YAAY,SAAS;AAChD,SAAK,WAAW,QAAQ,WAAW,QAAQ;AAC3C,SAAK,QAAQ,OAAO,SAAS;AAAA,EACjC;AACJ;","names":[]}