feat(analytics): add click extension link event tracking for banner and offerwall#683
feat(analytics): add click extension link event tracking for banner and offerwall#683kjmitchelljr wants to merge 29 commits intomainfrom
Conversation
This reverts commit 51274ac.
Deployment results
Logs #25474495805 |
| type ClickLinkEvent = `click_link_${ExtensionLinkSource}` | ||
|
|
||
| export type CdnEventMap = { | ||
| [K in ClickLinkEvent]: undefined |
There was a problem hiding this comment.
Should the event payload include { link: string } (the resolved extension store URL) to allow slicing by browser/store in the dashboard or is the tracking from Posthog fine on this front?
sidvishnoi
left a comment
There was a problem hiding this comment.
This will end up injecting umami into the website. It'll track the website's page views etc. Also, what if the site already has umami? Which window.track() will get used? Also, what if site's CSP doesn't allow unami script/API?
We should instead proxy the events from CDN to go via a custom endpoint in API. That'll mean calling umami API from our API, and not using umami script.
sidvishnoi
left a comment
There was a problem hiding this comment.
Moving in right direction now..
| 'Content-Type': 'application/json', | ||
| 'User-Agent': req.header('user-agent') ?? '', | ||
| 'X-Forwarded-For': req.header('cf-connecting-ip') ?? '', // do we need this? |
There was a problem hiding this comment.
We might want to pass through most of the headers here, not just these.
There was a problem hiding this comment.
Still debating whether to just pass-through all here. Went with allowlist that made sense to me, but we could include more. Let me know your thoughts.
There was a problem hiding this comment.
Umami docs shows these headers: https://docs.umami.is/docs/enable-cloudflare-headers
I think we want to skip the Host as headers for sure, but we do want to pass through the website domain to be available in our event payload/data, so we know which sites are embedding our tools.
| payload: { | ||
| ...event.payload, | ||
| website: env.UMAMI_WEBSITE_ID, | ||
| hostname: env.UMAMI_HOSTNAME, |
There was a problem hiding this comment.
@DarianM @sidvishnoi would you be able to provide clarity on where the environment variables are kept? Would need to have one for UMAMI_HOSTNAME if I understand correctly, but please feel free to provide more insight
There was a problem hiding this comment.
Replied here: #683 (comment)
These build time vars are stored in https://github.com/interledger/publisher-tools/settings/variables/actions
sidvishnoi
left a comment
There was a problem hiding this comment.
Nearly there, but little broken..
| export function trackEvent(event: TrackArgs): void { | ||
| if (!API_URL) return | ||
| // assumes all event names follow 'embed.click_link_<source>' | ||
| const url = `/embed/${event.name.replace('embed.click_link_', '')}` |
There was a problem hiding this comment.
Alternative: How about we use a higher-order function like this:
export function trackEventFactory(tool: Tool) {
return (event: TrackArgs) => {
// ...
}
}
const trackEvent = trackEventFactory('banner') // at some top-level in each of our tool script in CDN
trackEvent({name, /* ... */ })| onExtensionLinkClick(e) { | ||
| const { link } = (e as CustomEvent<{ link: string }>).detail |
There was a problem hiding this comment.
Nit: Let's update the Controller interface to denote what the type of e is.
| UMAMI_HOST?: string | ||
| UMAMI_WEBSITE_ID?: string | ||
| UMAMI_HOSTNAME?: string |
There was a problem hiding this comment.
These 3 won't be available in env vars, but rather available as build time vars:
Lines 5 to 7 in 8e215c8
Then, we'd use them via @shared/defines:
publisher-tools/api/src/index.ts
Line 1 in 8e215c8
There was a problem hiding this comment.
Would need to update CI a bit as well:
publisher-tools/.github/workflows/deploy.yml
Line 140 in 8e215c8
| UMAMI_HOST?: string | ||
| UMAMI_WEBSITE_ID?: string | ||
| UMAMI_HOSTNAME?: string |
There was a problem hiding this comment.
Nit: Maybe call it UMAMI_API_HOST
| import type { TrackFn } from 'publisher-tools-api' | ||
| import { API_URL } from '@shared/defines' | ||
|
|
||
| type TrackArgs = Omit<TrackFn, 'url'> |
There was a problem hiding this comment.
Perhaps call TrackFn as something like: TrackPayload?
| const blob = new Blob( | ||
| [JSON.stringify({ type: 'event', payload: { ...event, url } })], | ||
| { type: 'text/plain' }, | ||
| ) | ||
| navigator.sendBeacon?.(`${API_URL}/events`, blob) |
There was a problem hiding this comment.
This gave me a HTTP 400 validation error as I used the PR preview as well as checked out locally.
{
"error": {
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"details": {
"issues": [
{
"path": "type",
"message": "Invalid input: expected \"event\"",
"code": "invalid_value"
},
{
"path": "payload",
"message": "Invalid input",
"code": "invalid_union"
}
]
}
}
}There was a problem hiding this comment.
I think it could be because we're sending text/plain, but server validator is validating on zValidator('json', schema)`?
There was a problem hiding this comment.
This worked locally:
diff --git a/api/src/routes/events.ts b/api/src/routes/events.ts
index 582bcfff..163f1278 100644
--- a/api/src/routes/events.ts
+++ b/api/src/routes/events.ts
@@ -24,14 +24,13 @@ export type TrackFn = z.infer<typeof payloadSchema>
app.post(
'/events',
- zValidator('json', eventSchema),
async ({ req, env, body }) => {
+ const event = z.parse(eventSchema, await req.json())
+
if (!env.UMAMI_HOST || !env.UMAMI_WEBSITE_ID || !env.UMAMI_HOSTNAME) {
return body(null, 204)
}
- const event = req.valid('json')
-
const headers = new Headers({ 'content-type': 'application/json' })
for (const h of ['user-agent', 'accept-language', 'referer']) {
const v = req.header(h)
Summary
Adds
embed.click_link_bannerandembed.click_link_offerwallevent tracking when visitors click "Install the Web Monetization Extension" in the banner or offerwall embeds. Events are proxied CDN → API → UmamiImplementation
api/src/routes/events.ts—POST /eventsproxy: validates event name, injectswebsite/hostnamefrom env, forwards to Umamicdn/src/lib/analytics.ts— builds the Umami payload and sends vianavigator.sendBeaconcomponents/src/banner.ts+ offerwall install-required screen — dispatchclick-extension-linkwith{ link }Follow Ups
Part of
#625 / #682
Three small departures from your original: