Skip to content

Commit

Permalink
feat: add basic tracking system
Browse files Browse the repository at this point in the history
  • Loading branch information
DanSnow committed Feb 3, 2024
1 parent 3deff03 commit 9675f38
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,4 @@ dist
.moon/cache
.moon/docker

lib
/lib
4 changes: 4 additions & 0 deletions auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ declare global {
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComplexTrackEvent: typeof import('./src/utils/define-track')['defineComplexTrackEvent']
const defineComponent: typeof import('vue')['defineComponent']
const defineTrackEvent: typeof import('./src/utils/define-track')['defineTrackEvent']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
Expand Down Expand Up @@ -79,6 +81,7 @@ declare global {
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
const provideRoot: typeof import('./src/composables/portal-root')['provideRoot']
const pushEvent: typeof import('./src/stores/track-events')['pushEvent']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
Expand Down Expand Up @@ -109,6 +112,7 @@ declare global {
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const trackAtom: typeof import('./src/stores/track-events')['trackAtom']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
],
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@nanostores/persistent": "^0.9.1",
"@nanostores/vue": "^0.10.0",
"@vee-validate/zod": "^4.12.5",
"@vueuse/math": "^10.7.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"defu": "^6.1.4",
"destr": "^2.0.2",
"lucide-vue-next": "^0.320.0",
"nanostores": "^0.9.5",
"radix-vue": "^1.3.2",
Expand Down
7 changes: 7 additions & 0 deletions src/components/LeakyPaywall.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { sendTrack } from '~/lib/tracking'
const scrollLock = useScrollLock(window)
const { y } = useWindowScroll()
const { height } = useWindowSize()
Expand Down Expand Up @@ -39,6 +41,11 @@ whenever(
// When user scroll over 40% will open paywall
isNeedPaywall,
async () => {
sendTrack('paywall_triggered', {
articleId: '0',
clientId: 'client_id',
isExceedFreeLimit: true,
})
scrollLock.value = true
show.value = true
},
Expand Down
15 changes: 15 additions & 0 deletions src/lib/tracking-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as z from 'zod'
import { articleScrollBack } from './tracking-schema/article-scroll-back'
import { articleView } from './tracking-schema/article-view'
import { page } from './tracking-schema/page'
import { paywallTriggered } from './tracking-schema/paywall-triggered'

// We must import 1 by 1 or we will lost the type info here
export const trackEventSchema = z.discriminatedUnion('event', [articleScrollBack, articleView, page, paywallTriggered])

export type TrackEvent = z.infer<typeof trackEventSchema>

export type ExtractProperties<EventName extends TrackEvent['event']> = Extract<
TrackEvent,
{ event: EventName }
>['properties']
9 changes: 9 additions & 0 deletions src/lib/tracking-schema/article-scroll-back.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod'

export const articleScrollBack = defineTrackEvent({
event: 'article_scroll_back',
properties: {
clientId: z.string(),
articleId: z.string().nullable(),
},
})
11 changes: 11 additions & 0 deletions src/lib/tracking-schema/article-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { z } from 'zod'

export const articleView = defineTrackEvent({
event: 'article_view',
properties: {
pathname: z.string(),
isCustomTriggered: z.boolean(),
clientId: z.string(),
articleId: z.string().nullable(),
},
})
8 changes: 8 additions & 0 deletions src/lib/tracking-schema/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from 'zod'

export const page = defineTrackEvent({
event: 'page',
properties: {
pathname: z.string(),
},
})
10 changes: 10 additions & 0 deletions src/lib/tracking-schema/paywall-triggered.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from 'zod'

export const paywallTriggered = defineTrackEvent({
event: 'paywall_triggered',
properties: {
isExceedFreeLimit: z.boolean(),
clientId: z.string(),
articleId: z.string().nullable(),
},
})
12 changes: 12 additions & 0 deletions src/lib/tracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { pushEvent } from '~/stores/track-events'
import { ExtractProperties, TrackEvent, trackEventSchema } from './tracking-schema'

export function sendTrack<
EventName extends TrackEvent['event'],
Properties extends ExtractProperties<EventName> = ExtractProperties<EventName>,
>(event: EventName, properties: Properties) {
try {
const trackEvent = trackEventSchema.parse({ event, properties })
pushEvent(trackEvent)
} catch {}
}
40 changes: 40 additions & 0 deletions src/stores/track-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { persistentMap } from '@nanostores/persistent'
import { destr } from 'destr'
import type { TrackEvent } from '../lib/tracking-schema'

export interface BufferedEvent {
/**
* Event name
*/
e: string

/**
* Properties
*/
p: Record<string, any>

/**
* Timestamp
*/
t: number
}

export const trackAtom = persistentMap(
'storipress-paywall:',
{
lastSynced: 0,
records: [] as BufferedEvent[],
token: '',
},
{
encode: JSON.stringify,
decode: destr,
},
)

export function pushEvent(record: TrackEvent) {
trackAtom.set({
...trackAtom.get(),
records: [...trackAtom.get().records, { e: record.event, p: record.properties ?? {}, t: Date.now() }],
})
}
35 changes: 35 additions & 0 deletions src/utils/define-track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { z } from 'zod'

interface DefineTrackEventInput<EventName extends string, Properties extends z.ZodRawShape> {
event: EventName
properties?: Properties
}

export function defineTrackEvent<EventName extends string, Properties extends z.ZodRawShape>(
input: DefineTrackEventInput<EventName, Properties>,
): z.ZodObject<{
event: z.ZodLiteral<EventName>
properties: z.ZodObject<Properties> | z.ZodUndefined
}> {
return defineComplexTrackEvent({
event: input.event,
properties: input.properties ? z.object(input.properties) : z.undefined(),
})
}

interface DefineComplexTrackEventInput<EventName extends string, Properties extends z.ZodTypeAny> {
event: EventName
properties: Properties
}

export function defineComplexTrackEvent<EventName extends string, Properties extends z.ZodTypeAny>(
input: DefineComplexTrackEventInput<EventName, Properties>,
): z.ZodObject<{
event: z.ZodLiteral<EventName>
properties: Properties
}> {
return z.object({
event: z.literal(input.event),
properties: input.properties,
})
}
18 changes: 18 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,15 @@ __metadata:
languageName: node
linkType: hard

"@nanostores/persistent@npm:^0.9.1":
version: 0.9.1
resolution: "@nanostores/persistent@npm:0.9.1"
peerDependencies:
nanostores: ^0.9.0
checksum: 10c0/16b289c697d5a04319d5d85e04f0d03f039795a8ba2ef6e331fe936c90f13b79b6940cd34950cadf86799c1a8477966eb752d1d38066a14a3ed181d3e2c2486b
languageName: node
linkType: hard

"@nanostores/vue@npm:^0.10.0":
version: 0.10.0
resolution: "@nanostores/vue@npm:0.10.0"
Expand Down Expand Up @@ -1462,6 +1471,7 @@ __metadata:
"@iconify-json/lucide": "npm:^1.1.161"
"@moonrepo/cli": "npm:^1.20.1"
"@nanostores/logger": "npm:^0.2.4"
"@nanostores/persistent": "npm:^0.9.1"
"@nanostores/vue": "npm:^0.10.0"
"@size-limit/file": "npm:^11.0.2"
"@types/eslint": "npm:^8"
Expand All @@ -1476,6 +1486,7 @@ __metadata:
class-variance-authority: "npm:^0.7.0"
clsx: "npm:^2.1.0"
defu: "npm:^6.1.4"
destr: "npm:^2.0.2"
esbuild: "npm:^0.20.0"
eslint: "npm:^8.56.0"
lorem-ipsum: "npm:^2.0.8"
Expand Down Expand Up @@ -3085,6 +3096,13 @@ __metadata:
languageName: node
linkType: hard

"destr@npm:^2.0.2":
version: 2.0.2
resolution: "destr@npm:2.0.2"
checksum: 10c0/28bd8793c0507489efeb4b86c471fe9578e25439c1f7e4a4e4db9b69fe37689b68b9b205b7c317ca31590120e9c5364a31fec2eb6ec73bb425ede8f993c771d6
languageName: node
linkType: hard

"detect-libc@npm:^2.0.2":
version: 2.0.2
resolution: "detect-libc@npm:2.0.2"
Expand Down

0 comments on commit 9675f38

Please sign in to comment.