@splendidlabz/tracking 0.1.21 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ import { omitEmpty } from '@splendidlabz/utils'
2
+ import { PostHog } from 'posthog-node'
3
+
4
+ // Posthog Persistence docs: https://posthog.com/docs/libraries/js/persistence
5
+
6
+ /**
7
+ * Creates a PostHog analytics client wrapper for server-side tracking
8
+ * @param {Object} config - Configuration object
9
+ * @param {string} config.apiKey - PostHog API key
10
+ * @param {string} config.host - PostHog host URL
11
+ * @param {string} config.framework - Framework name (currently only 'astro' is supported)
12
+ * @param {boolean} [config.trackEvents=true] - Whether to track events. Set to false in development mode to avoid sending events to PostHog.
13
+ * @param {...*} config.rest - Additional PostHog client options
14
+ * @return {Object} PostHog client wrapper
15
+ * @property {Function} capture - Captures analytics events
16
+ * @property {Function} setPersonProperties - Sets person properties
17
+ * @property {Function} removePersonProperties - Removes person properties
18
+ * @property {Function} shutdown - Shuts down the PostHog client
19
+ * @throws {Error} When framework is not 'astro'
20
+ */
21
+ export default function (config = {}) {
22
+ const { framework, apiKey, host, trackEvents = true, ...rest } = config
23
+ const cookieName = `ph_${apiKey}_posthog`
24
+
25
+ if (framework !== 'astro') {
26
+ throw new Error('Only Astro framework is supported at the moment')
27
+ }
28
+ const client = new PostHog(apiKey, { host, ...rest })
29
+
30
+ /**
31
+ * Gets the distinct ID for tracking, prioritizing email, then userId, then cookie-based ID
32
+ * @param {Object} params - Parameters object
33
+ * @param {string} params.email - User email address
34
+ * @param {string} params.userId - User ID
35
+ * @param {Object} params.context - Request context (for cookie access)
36
+ * @param {string} params.framework - Framework name
37
+ * @return {string|undefined} The distinct ID for tracking
38
+ */
39
+ function getDistinctId({ email, userId, context, framework }) {
40
+ if (email) return email
41
+ if (userId) return userId
42
+ return getDistinctIdFromCookie({ context, framework })
43
+ }
44
+
45
+ // This assumes posthog web is used as well.
46
+ // If not used, we need to generate and store the distinct_id in a cookie instead — maybe in the future.
47
+ // - Need a database to store distinct_id?
48
+ // - Need to use userId or email from the database?
49
+ /**
50
+ * Extracts distinct ID from PostHog cookie (assumes PostHog web client is also used)
51
+ * @param {Object} params - Parameters object
52
+ * @param {Object} params.context - Request context with cookies access
53
+ * @param {string} params.framework - Framework name
54
+ * @return {string|undefined} The distinct ID from cookie, or undefined if not found
55
+ */
56
+ function getDistinctIdFromCookie({ context, framework }) {
57
+ if (framework === 'astro') {
58
+ const cookie = context.cookies.get(cookieName)
59
+ if (cookie) {
60
+ const { distinct_id } = JSON.parse(cookie.value)
61
+ return distinct_id
62
+ }
63
+ }
64
+ }
65
+
66
+ return {
67
+ /**
68
+ * Captures an analytics event
69
+ * @param {string} event - Event name to track
70
+ * @param {Object} [properties={}] - Event properties and user identification
71
+ * @param {string} [properties.email] - User email (used for distinct ID)
72
+ * @param {string} [properties.userId] - User ID (used for distinct ID)
73
+ * @param {Object} [properties.context] - Request context (for cookie access)
74
+ * @param {...*} properties.rest - Additional event properties
75
+ * @return {Promise|undefined} PostHog capture promise, or undefined if trackDevEvents is true
76
+ */
77
+ capture(event, properties = {}) {
78
+ if (!trackEvents) return
79
+ const { email, userId, context, ...rest } = properties
80
+
81
+ const distinctId = getDistinctId({ email, userId, context, framework })
82
+
83
+ const data = omitEmpty({ event, distinctId, ...rest })
84
+ return client.capture(data)
85
+ },
86
+
87
+ /**
88
+ * Sets person properties on the current user
89
+ * @param {Object} [set={}] - Properties to set (will overwrite existing values)
90
+ * @param {Object} [once={}] - Properties to set only once (won't overwrite existing values)
91
+ * @return {Promise|undefined} PostHog capture promise, or undefined if trackDevEvents is true
92
+ */
93
+ setPersonProperties(set = {}, once = {}) {
94
+ if (!trackEvents) return
95
+ return client.capture({
96
+ event: '$set',
97
+ properties: omitEmpty({
98
+ $set: set,
99
+ $set_once: once,
100
+ }),
101
+ })
102
+ },
103
+
104
+ /**
105
+ * Removes person properties from the current user
106
+ * @param {string[]} properties - List of property names to remove
107
+ * @return {Promise|undefined} PostHog capture promise, or undefined if trackDevEvents is true
108
+ */
109
+ removePersonProperties(properties) {
110
+ if (!trackEvents) return
111
+ return client.capture({
112
+ event: '$unset',
113
+ properties: {
114
+ $unset: properties,
115
+ },
116
+ })
117
+ },
118
+
119
+ // identify(distinctId, properties = {}) {
120
+ // return client.identify({ distinctId, properties })
121
+ // },
122
+
123
+ // alias(alias, distinctId) {
124
+ // return client.alias({ alias, distinctId })
125
+ // },
126
+
127
+ // // Not sure about this yet
128
+ // group(groupType, groupKey, properties = {}) {
129
+ // return client.group({ groupType, groupKey, properties })
130
+ // },
131
+
132
+ /**
133
+ * Shuts down the PostHog client and flushes any pending events
134
+ * @return {Promise} Promise that resolves when shutdown is complete
135
+ */
136
+ shutdown() {
137
+ return client.shutdown()
138
+ },
139
+ }
140
+ }
@@ -0,0 +1,17 @@
1
+ Ok. This is quite complex. So let's break it down.
2
+
3
+ This package assumes you're using Posthog for both web and node.js.
4
+
5
+ ## Initializing
6
+
7
+ ## Steps
8
+
9
+ 1. Init on frontend — everywhere. This will allow posthog to track via their web sdk.
10
+ 2. On the backend — use ph.capture as necessary.
11
+ 3. When user logs in, or when you have a way to identify the user, use ph.identify on the frontend. (Don't identify on the backend).
12
+ 4. No need to alias. ph will replace merge any new `distinct_id` with the one you provided, if any.
13
+
14
+ ## Couple of enhancements
15
+
16
+ - Get distinct_id from cookies on the backend automatically when using Astro — just provide context.
17
+ - Use `trackEvents:false` to disable tracking completely when developing.
@@ -0,0 +1,79 @@
1
+ import { getCookie } from '@splendidlabz/utils/dom'
2
+ import posthog from 'posthog-js'
3
+
4
+ /**
5
+ * Creates a PostHog wrapper for web browser tracking
6
+ * @param {object} config - Configuration object
7
+ * @param {string} config.apiKey - PostHog API key
8
+ * @param {string} config.host - PostHog host URL
9
+ * @param {boolean} [config.trackEvents=true] - Whether to track events. Set to false in development mode to avoid sending events to PostHog.
10
+ * @returns {object} PostHog wrapper object with tracking methods
11
+ */
12
+ export default function (config = {}) {
13
+ const { apiKey, host, trackEvents = true, ...rest } = config
14
+ const cookieName = `ph_${apiKey}_posthog`
15
+
16
+ return {
17
+ ...posthog,
18
+
19
+ /**
20
+ * Initialize PostHog tracking
21
+ * @returns {void}
22
+ */
23
+ init() {
24
+ if (!trackEvents) return
25
+ posthog.init(apiKey, {
26
+ api_host: host,
27
+ ...rest,
28
+ })
29
+ },
30
+
31
+ /**
32
+ * Capture a tracking event
33
+ * @param {string} event - Name of the event to track
34
+ * @param {object} [properties={}] - Event properties to send
35
+ * @returns {void}
36
+ */
37
+ capture(event, properties = {}) {
38
+ if (!trackEvents) return
39
+ return posthog.capture(event, properties)
40
+ },
41
+
42
+ /**
43
+ * Set properties for the current person
44
+ * @param {...any} props - Properties to set for the person
45
+ * @returns {void}
46
+ */
47
+ setPersonProperties(set = {}, once = {}) {
48
+ if (!trackEvents) return
49
+ return posthog.setPersonProperties(set, once)
50
+ },
51
+
52
+ /**
53
+ * @param {string} distinctId - New unique identifier for the user (email, userId, etc.)
54
+ * @param {object} properties - The properties to set for the user
55
+ * @returns {void}
56
+ */
57
+ identify(distinctId, properties = {}) {
58
+ if (!trackEvents) return
59
+ const cookie = getCookie(cookieName)
60
+ const existingId = cookie?.distinct_id
61
+
62
+ if (existingId !== distinctId) {
63
+ return posthog.identify(distinctId, properties)
64
+ } else {
65
+ return this.setPersonProperties(properties)
66
+ }
67
+ },
68
+
69
+ /**
70
+ * @param {string} distinctId - New unique identifier for the user
71
+ * @param {string} aliasId - Alias for the user
72
+ * @returns {void}
73
+ */
74
+ alias(distinctId, aliasId) {
75
+ if (!trackEvents) return
76
+ return posthog.alias(distinctId, aliasId)
77
+ },
78
+ }
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splendidlabz/tracking",
3
- "version": "0.1.21",
3
+ "version": "0.2.0",
4
4
  "description": "Simple Tracking Helper",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,17 +20,17 @@
20
20
  },
21
21
  "author": "Zell Liew <zellwk@gmail.com> (https://zellwk.com/)",
22
22
  "dependencies": {
23
- "@splendidlabz/utils": "1.5.0",
23
+ "@splendidlabz/utils": "1.6.0",
24
24
  "posthog-js": "^1.234.6",
25
- "posthog-node": "^3.6.3",
25
+ "posthog-node": "^5.5.1",
26
26
  "zl-fetch": "^6.0.6"
27
27
  },
28
28
  "devDependencies": {
29
- "@rollup/plugin-node-resolve": "^15.2.3",
29
+ "@rollup/plugin-node-resolve": "^16.0.1",
30
30
  "@rollup/plugin-terser": "^0.4.4",
31
- "jsdom": "^24.0.0",
31
+ "jsdom": "^26.1.0",
32
32
  "np": "^10.2.0",
33
- "rollup": "^4.39.0",
33
+ "rollup": "^4.45.1",
34
34
  "rollup-plugin-commonjs": "^10.1.0",
35
35
  "rollup-plugin-filesize": "^10.0.0",
36
36
  "vitest": "^3.1.1"