Folo/packages/tracker/src/points.ts

165 lines
4.0 KiB
TypeScript

import type { OpenPanel } from "./op"
type Tracker = (name: string, properties?: Record<string, unknown>) => Promise<any>
class TrackManager {
private trackFns: Tracker[] = []
setTrackFn(fn: Tracker) {
this.trackFns.push(fn)
return () => {
this.trackFns = this.trackFns.filter((t) => t !== fn)
}
}
getTrackFn(): Tracker {
if (this.trackFns.length === 0) {
console.error("[Tracker warn]: Track function not set")
}
return (name, properties) => {
return Promise.all(this.trackFns.map((fn) => fn(name, properties)))
}
}
setOpenPanelTracker(tracker: OpenPanel["track"]) {
this.trackFns.push((name, properties) => tracker(name, properties))
}
}
export const trackManager = new TrackManager()
export enum TrackerMapper {
Identify = 1000,
UserLogin = 1001,
UiRenderInit = 1002,
AppInit = 1003,
// Biz
NavigateEntry = 2000,
BoostSent = 2001,
Integration = 2002,
DailyReportModal = 2003,
SwitchToMasonry = 2004,
WideMode = 2005,
EntryContentHeaderImageGalleryClick = 2006,
SearchOpen = 2007,
QuickAddFeed = 2008,
PlayerOpenDuration = 2009,
UpdateRestart = 2010,
TipModalOpened = 2011,
FeedClaimed = 2012,
DailyRewardClaimed = 2013,
TipSent = 2014,
}
const CodeToTrackerName = Object.fromEntries(
Object.entries(TrackerMapper).map(([key, value]) => [value, key]),
) as Record<number, string>
export class TrackerPoints {
// App
identify(userId: string) {
this.track(TrackerMapper.Identify, { userId })
}
appInit(props: {
electron?: boolean
rn?: boolean
loading_time?: number
using_indexed_db?: boolean
data_hydrated_time?: number
version?: string
}) {
this.track(TrackerMapper.AppInit, props)
}
userLogin(userId: string) {
this.track(TrackerMapper.UserLogin, { userId })
}
/**
* For desktop UI only
*/
uiRenderInit(spentTime: number) {
this.track(TrackerMapper.UiRenderInit, { time: spentTime })
}
navigateEntry(props: { feedId?: string; entryId?: string; timelineId?: string }) {
this.track(TrackerMapper.NavigateEntry, props)
}
boostSent(props: { amount: string; feedId: string }) {
this.track(TrackerMapper.BoostSent, props)
}
integration(props: { type: string; event: string }) {
this.track(TrackerMapper.Integration, props)
}
dailyReportModal() {
this.track(TrackerMapper.DailyReportModal)
}
switchToMasonry() {
this.track(TrackerMapper.SwitchToMasonry)
}
wideMode(props: { mode: "wide" | "normal" }) {
this.track(TrackerMapper.WideMode, props)
}
entryContentHeaderImageGalleryClick(props: { feedId?: string }) {
this.track(TrackerMapper.EntryContentHeaderImageGalleryClick, props)
}
searchOpen() {
this.track(TrackerMapper.SearchOpen)
}
quickAddFeed(props: { type: "url" | "search"; defaultView: number }) {
this.track(TrackerMapper.QuickAddFeed, props)
}
playerOpenDuration(props: {
duration: number
status?: "playing" | "loading" | "paused"
trigger?: "manual" | "beforeunload"
}) {
this.track(TrackerMapper.PlayerOpenDuration, props)
}
updateRestart(props: { type: "app" | "renderer" | "pwa" }) {
this.track(TrackerMapper.UpdateRestart, props)
}
tipModalOpened(props: { entryId?: string }) {
this.track(TrackerMapper.TipModalOpened, props)
}
feedClaimed(props: { feedId: string }) {
this.track(TrackerMapper.FeedClaimed, props)
}
dailyRewardClaimed() {
this.track(TrackerMapper.DailyRewardClaimed)
}
tipSent(props: { amount: string; entryId: string }) {
this.track(TrackerMapper.TipSent, props)
}
private track(code: TrackerMapper, properties?: Record<string, unknown>) {
if (code) {
let name = CodeToTrackerName[code]
if (name) {
name = snakeCase(name)
Reflect.apply(trackManager.getTrackFn(), null, [name, { ...properties, __code: code }])
}
}
}
}
const snakeCase = (string: string) => {
return string
.replaceAll(/\W+/g, " ")
.split(/ |\B(?=[A-Z])/)
.map((word) => word.toLowerCase())
.join("_")
}
export type AllTrackers = keyof TrackerPoints