posthog-node 1.1.5 → 1.3.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.
package/README.md CHANGED
@@ -22,3 +22,5 @@ This library is largely based on the `analytics-node` package.
22
22
  ## Questions?
23
23
 
24
24
  ### [Join our Slack community.](https://posthog.com/slack)
25
+
26
+
@@ -22,6 +22,10 @@ function eventValidation(event, type) {
22
22
  return validateIdentifyEvent(event)
23
23
  case 'alias':
24
24
  return validateAliasEvent(event)
25
+ case 'groupIdentify':
26
+ return validateGroupIdentifyEvent(event)
27
+ case 'isFeatureEnabled':
28
+ return validateIsFeatureEnabled(event)
25
29
  default:
26
30
  assert(0, 'Invalid event type: "' + type + '"')
27
31
  }
@@ -53,6 +57,29 @@ function validateAliasEvent(event) {
53
57
  assert(event.alias, 'You must pass a "alias".')
54
58
  }
55
59
 
60
+ /**
61
+ * Validate an "groupIdentify" event.
62
+ */
63
+
64
+ function validateGroupIdentifyEvent(event) {
65
+ assert(event.groupType, 'You must pass a "groupType".')
66
+ assert(event.groupKey, 'You must pass a "groupKey".')
67
+ }
68
+
69
+ /**
70
+ * Validate a "isFeatureEnabled" call
71
+ */
72
+
73
+ function validateIsFeatureEnabled(event) {
74
+ assert(event.key, 'You must pass a "key".')
75
+ assert(event.distinctId, 'You must pass a "distinctId".')
76
+ assert(type(event.defaultResult) == 'boolean', '"defaultResult" must be a boolean.')
77
+ if (event.groups) {
78
+ assert(type(event.groups) == 'object', 'You must pass an object for "groups".')
79
+ }
80
+ }
81
+
82
+
56
83
  /**
57
84
  * Validation rules.
58
85
  */
package/feature-flags.js CHANGED
@@ -5,6 +5,17 @@ const version = require('./package.json').version
5
5
 
6
6
  const LONG_SCALE = 0xfffffffffffffff
7
7
 
8
+ class ClientError extends Error {
9
+ constructor(message, extra) {
10
+ super()
11
+ Error.captureStackTrace(this, this.constructor)
12
+ this.name = 'ClientError'
13
+ this.message = message
14
+ if (extra) {
15
+ this.extra = extra
16
+ }
17
+ }
18
+ }
8
19
 
9
20
  class FeatureFlagsPoller {
10
21
  constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host, featureFlagCalledCallback }) {
@@ -21,7 +32,7 @@ class FeatureFlagsPoller {
21
32
  void this.loadFeatureFlags()
22
33
  }
23
34
 
24
- async isFeatureEnabled(key, distinctId, defaultResult = false) {
35
+ async isFeatureEnabled(key, distinctId, defaultResult = false, groups = {}) {
25
36
  await this.loadFeatureFlags()
26
37
 
27
38
  if (!this.loadedSuccessfullyOnce) {
@@ -50,7 +61,7 @@ class FeatureFlagsPoller {
50
61
  rolloutPercentage: featureFlag.rolloutPercentage,
51
62
  })
52
63
  } else {
53
- const res = await this._request({ path: 'decide', method: 'POST', data: { distinct_id: distinctId } })
64
+ const res = await this._request({ path: 'decide', method: 'POST', data: { groups, distinct_id: distinctId } })
54
65
  isFlagEnabledResponse = res.data.featureFlags.indexOf(key) >= 0
55
66
  }
56
67
 
@@ -72,16 +83,24 @@ class FeatureFlagsPoller {
72
83
  }
73
84
  this.poller = setTimeout(() => this._loadFeatureFlags(), this.pollingInterval)
74
85
 
75
- const res = await this._request({ path: 'api/feature_flag', usePersonalApiKey: true })
86
+ try {
87
+ const res = await this._request({ path: 'api/feature_flag', usePersonalApiKey: true })
88
+ if (res && res.status === 401) {
89
+ throw new ClientError(
90
+ `Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview`
91
+ )
92
+ }
93
+
94
+ this.featureFlags = res.data.results.filter(flag => flag.active)
76
95
 
77
- if (res && res.status === 401) {
78
- throw new Error(
79
- `Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview`
80
- )
96
+ this.loadedSuccessfullyOnce = true
97
+ } catch (err) {
98
+ // if an error that is not an instance of ClientError is thrown
99
+ // we silently ignore the error when reloading feature flags
100
+ if (err instanceof ClientError) {
101
+ throw err
102
+ }
81
103
  }
82
-
83
- this.featureFlags = res.data.results.filter(flag => flag.active)
84
- this.loadedSuccessfullyOnce = true
85
104
  }
86
105
 
87
106
  // sha1('a.b') should equal '69f6642c9d71b463485b4faf4e989dc3fe77a8c6'
@@ -125,9 +144,10 @@ class FeatureFlagsPoller {
125
144
  req.timeout = typeof this.timeout === 'string' ? ms(this.timeout) : this.timeout
126
145
  }
127
146
 
147
+
128
148
  let res
129
149
  try {
130
- res = await axios(req)
150
+ res = await axios.request(req)
131
151
  } catch (err) {
132
152
  throw new Error(`Request to ${path} failed with error: ${err.message}`)
133
153
  }
package/index.d.ts CHANGED
@@ -17,6 +17,16 @@ declare module 'posthog-node' {
17
17
 
18
18
  interface EventMessage extends IdentifyMessage {
19
19
  event: string
20
+ groups?: Record<string, string | number> // Mapping of group type to group id
21
+ }
22
+
23
+ type GroupType = string
24
+ type GroupKey = string
25
+
26
+ interface GroupIdentifyMessage {
27
+ groupType: GroupType
28
+ groupKey: GroupKey // Unique identifier for the group
29
+ properties?: Record<string | number, any>
20
30
  }
21
31
 
22
32
  export default class PostHog {
@@ -28,9 +38,10 @@ declare module 'posthog-node' {
28
38
  * A capture call requires:
29
39
  * @param distinctId which uniquely identifies your user
30
40
  * @param event We recommend using [verb] [noun], like movie played or movie updated to easily identify what your events mean later on.
31
- * @param properties OPTIONAL | which can be a dict with any information you'd like to add
41
+ * @param properties OPTIONAL | which can be a object with any information you'd like to add
42
+ * @param groups OPTIONAL | object of what groups are related to this event, example: { company: 'id:5' }. Can be used to analyze companies instead of users.
32
43
  */
33
- capture({ distinctId, event, properties }: EventMessage): void
44
+ capture({ distinctId, event, properties, groups }: EventMessage): void
34
45
 
35
46
  /**
36
47
  * @description Identify lets you add metadata on your users so you can more easily identify who they are in PostHog,
@@ -56,20 +67,31 @@ declare module 'posthog-node' {
56
67
 
57
68
 
58
69
  /**
59
- * @description PostHog feature flags (https://posthog.com/docs/features/feature-flags)
70
+ * @description PostHog feature flags (https://posthog.com/docs/features/feature-flags)
60
71
  * allow you to safely deploy and roll back new features. Once you've created a feature flag in PostHog,
61
- * you can use this method to check if the flag is on for a given user, allowing you to create logic to turn
72
+ * you can use this method to check if the flag is on for a given user, allowing you to create logic to turn
62
73
  * features on and off for different user groups or individual users.
63
74
  * IMPORTANT: To use this method, you need to specify `personalApiKey` in your config! More info: https://posthog.com/docs/api/overview
64
75
  * @param key the unique key of your feature flag
65
76
  * @param distinctId the current unique id
66
77
  * @param defaultResult optional - default value to be returned if the feature flag is not on for the user
78
+ * @param groups optional - what groups are currently active (group analytics)
67
79
  */
68
- isFeatureEnabled(key: string, distinctId: string, defaultResult?: boolean): Promise<boolean>
80
+ isFeatureEnabled(key: string, distinctId: string, defaultResult?: boolean, groups?: Record<GroupType, GroupKey>): Promise<boolean>
69
81
 
70
82
 
71
83
  /**
72
- * @description Force an immediate reload of the polled feature flags. Please note that they are
84
+ * @description Sets a groups properties, which allows asking questions like "Who are the most active companies"
85
+ * using my product in PostHog.
86
+ *
87
+ * @param groupType Type of group (ex: 'company'). Limited to 5 per project
88
+ * @param groupKey Unique identifier for that type of group (ex: 'id:5')
89
+ * @param properties OPTIONAL | which can be a object with any information you'd like to add
90
+ */
91
+ groupIdentify({ groupType, groupKey, properties }: GroupIdentifyMessage): void
92
+
93
+ /**
94
+ * @description Force an immediate reload of the polled feature flags. Please note that they are
73
95
  * already polled automatically at a regular interval.
74
96
  */
75
97
  reloadFeatureFlags(): Promise<void>
package/index.js CHANGED
@@ -131,13 +131,18 @@ class PostHog {
131
131
  capture(message, callback) {
132
132
  this._validate(message, 'capture')
133
133
 
134
- const apiMessage = Object.assign({}, message, {
135
- properties: Object.assign({}, message.properties, {
136
- $lib: 'posthog-node',
137
- $lib_version: version,
138
- }),
134
+ const properties = Object.assign({}, message.properties, {
135
+ $lib: 'posthog-node',
136
+ $lib_version: version,
139
137
  })
140
138
 
139
+ if ('groups' in message) {
140
+ properties.$groups = message.groups
141
+ delete message.groups
142
+ }
143
+
144
+ const apiMessage = Object.assign({}, message, { properties })
145
+
141
146
  this.enqueue('capture', apiMessage, callback)
142
147
  return this
143
148
  }
@@ -170,6 +175,30 @@ class PostHog {
170
175
  return this
171
176
  }
172
177
 
178
+ /**
179
+ * @description Sets a groups properties, which allows asking questions like "Who are the most active companies"
180
+ * using my product in PostHog.
181
+ *
182
+ * @param groupType Type of group (ex: 'company'). Limited to 5 per project
183
+ * @param groupKey Unique identifier for that type of group (ex: 'id:5')
184
+ * @param properties OPTIONAL | which can be a object with any information you'd like to add
185
+ */
186
+ groupIdentify(message, callback) {
187
+ this._validate(message, 'groupIdentify')
188
+
189
+ const captureMessage = {
190
+ event: '$groupidentify',
191
+ distinctId: `\$${message.groupType}_${message.groupKey}`,
192
+ properties: {
193
+ $group_type: message.groupType,
194
+ $group_key: message.groupKey,
195
+ $group_set: message.properties || {}
196
+ }
197
+ }
198
+
199
+ return this.capture(captureMessage, callback)
200
+ }
201
+
173
202
  /**
174
203
  * Add a `message` of type `type` to the queue and
175
204
  * check whether it should be flushed.
@@ -218,9 +247,11 @@ class PostHog {
218
247
  }
219
248
  }
220
249
 
221
- async isFeatureEnabled(key, distinctId, defaultResult) {
250
+ async isFeatureEnabled(key, distinctId, defaultResult = false, groups = {}) {
251
+ this._validate({ key, distinctId, defaultResult, groups }, 'isFeatureEnabled')
222
252
  assert(this.personalApiKey, 'You have to specify the option personalApiKey to use feature flags.')
223
- return await this.featureFlagsPoller.isFeatureEnabled(key, distinctId, defaultResult)
253
+
254
+ return await this.featureFlagsPoller.isFeatureEnabled(key, distinctId, defaultResult, groups)
224
255
  }
225
256
 
226
257
  async reloadFeatureFlags() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posthog-node",
3
- "version": "1.1.5",
3
+ "version": "1.3.0",
4
4
  "description": "PostHog Node.js integration",
5
5
  "license": "MIT",
6
6
  "repository": "PostHog/posthog-node",
@@ -42,7 +42,7 @@
42
42
  "funnels"
43
43
  ],
44
44
  "dependencies": {
45
- "axios": "^0.21.1",
45
+ "axios": "0.24.0",
46
46
  "axios-retry": "^3.1.9",
47
47
  "component-type": "^1.2.1",
48
48
  "join-component": "^1.1.0",
@@ -52,7 +52,7 @@
52
52
  "uuid": "^8.3.2"
53
53
  },
54
54
  "devDependencies": {
55
- "ava": "^0.25.0",
55
+ "ava": "^3.15.0",
56
56
  "basic-auth": "^2.0.1",
57
57
  "body-parser": "^1.17.1",
58
58
  "codecov": "^3.0.0",