posthog-node 1.3.0 → 2.0.0-alpha3
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 +5 -20
- package/index.ts +4 -0
- package/lib/index.cjs.js +1300 -0
- package/lib/index.cjs.js.map +1 -0
- package/lib/index.d.ts +120 -0
- package/lib/index.esm.js +1291 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/node_modules/tslib/tslib.es6.d.ts +35 -0
- package/lib/posthog-core/src/eventemitter.d.ts +8 -0
- package/lib/posthog-core/src/index.d.ts +84 -0
- package/lib/posthog-core/src/lz-string.d.ts +8 -0
- package/lib/posthog-core/src/types.d.ts +68 -0
- package/lib/posthog-core/src/utils.d.ts +13 -0
- package/lib/posthog-node/index.d.ts +3 -0
- package/lib/posthog-node/src/posthog-node.d.ts +22 -0
- package/lib/posthog-node/src/types.d.ts +83 -0
- package/package.json +35 -67
- package/src/posthog-node.ts +133 -0
- package/src/types.ts +99 -0
- package/test/posthog-node.spec.ts +92 -0
- package/tsconfig.json +6 -0
- package/LICENSE +0 -21
- package/cli.js +0 -72
- package/event-validation.js +0 -121
- package/feature-flags.js +0 -165
- package/index.d.ts +0 -106
- package/index.js +0 -362
package/feature-flags.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
const axios = require('axios')
|
|
2
|
-
const crypto = require('crypto')
|
|
3
|
-
const ms = require('ms')
|
|
4
|
-
const version = require('./package.json').version
|
|
5
|
-
|
|
6
|
-
const LONG_SCALE = 0xfffffffffffffff
|
|
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
|
-
}
|
|
19
|
-
|
|
20
|
-
class FeatureFlagsPoller {
|
|
21
|
-
constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host, featureFlagCalledCallback }) {
|
|
22
|
-
this.pollingInterval = pollingInterval
|
|
23
|
-
this.personalApiKey = personalApiKey
|
|
24
|
-
this.featureFlags = []
|
|
25
|
-
this.loadedSuccessfullyOnce = false
|
|
26
|
-
this.timeout = timeout
|
|
27
|
-
this.projectApiKey = projectApiKey
|
|
28
|
-
this.featureFlagCalledCallback = featureFlagCalledCallback
|
|
29
|
-
this.host = host
|
|
30
|
-
this.poller = null
|
|
31
|
-
|
|
32
|
-
void this.loadFeatureFlags()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async isFeatureEnabled(key, distinctId, defaultResult = false, groups = {}) {
|
|
36
|
-
await this.loadFeatureFlags()
|
|
37
|
-
|
|
38
|
-
if (!this.loadedSuccessfullyOnce) {
|
|
39
|
-
return defaultResult
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let featureFlag = null
|
|
43
|
-
|
|
44
|
-
for (const flag of this.featureFlags) {
|
|
45
|
-
if (key === flag.key) {
|
|
46
|
-
featureFlag = flag
|
|
47
|
-
break
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!featureFlag) {
|
|
52
|
-
return defaultResult
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let isFlagEnabledResponse
|
|
56
|
-
|
|
57
|
-
if (featureFlag.is_simple_flag) {
|
|
58
|
-
isFlagEnabledResponse = this._isSimpleFlagEnabled({
|
|
59
|
-
key,
|
|
60
|
-
distinctId,
|
|
61
|
-
rolloutPercentage: featureFlag.rolloutPercentage,
|
|
62
|
-
})
|
|
63
|
-
} else {
|
|
64
|
-
const res = await this._request({ path: 'decide', method: 'POST', data: { groups, distinct_id: distinctId } })
|
|
65
|
-
isFlagEnabledResponse = res.data.featureFlags.indexOf(key) >= 0
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this.featureFlagCalledCallback(key, distinctId, isFlagEnabledResponse)
|
|
69
|
-
return isFlagEnabledResponse
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async loadFeatureFlags(forceReload = false) {
|
|
73
|
-
if (!this.loadedSuccessfullyOnce || forceReload) {
|
|
74
|
-
await this._loadFeatureFlags()
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/* istanbul ignore next */
|
|
79
|
-
async _loadFeatureFlags() {
|
|
80
|
-
if (this.poller) {
|
|
81
|
-
clearTimeout(this.poller)
|
|
82
|
-
this.poller = null
|
|
83
|
-
}
|
|
84
|
-
this.poller = setTimeout(() => this._loadFeatureFlags(), this.pollingInterval)
|
|
85
|
-
|
|
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)
|
|
95
|
-
|
|
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
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// sha1('a.b') should equal '69f6642c9d71b463485b4faf4e989dc3fe77a8c6'
|
|
107
|
-
// integerRepresentationOfHashSubset / LONG_SCALE for sha1('a.b') should equal 0.4139158829615955
|
|
108
|
-
_isSimpleFlagEnabled({ key, distinctId, rolloutPercentage }) {
|
|
109
|
-
if (!rolloutPercentage) {
|
|
110
|
-
return true
|
|
111
|
-
}
|
|
112
|
-
const sha1Hash = crypto.createHash('sha1')
|
|
113
|
-
sha1Hash.update(`${key}.${distinctId}`)
|
|
114
|
-
const integerRepresentationOfHashSubset = parseInt(sha1Hash.digest('hex').slice(0, 15), 16)
|
|
115
|
-
return integerRepresentationOfHashSubset / LONG_SCALE <= rolloutPercentage / 100
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* istanbul ignore next */
|
|
119
|
-
async _request({ path, method = 'GET', usePersonalApiKey = false, data = {} }) {
|
|
120
|
-
let url = `${this.host}/${path}/`
|
|
121
|
-
let headers = {
|
|
122
|
-
'Content-Type': 'application/json',
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (usePersonalApiKey) {
|
|
126
|
-
headers = { ...headers, Authorization: `Bearer ${this.personalApiKey}` }
|
|
127
|
-
url = url + `?token=${this.projectApiKey}`
|
|
128
|
-
} else {
|
|
129
|
-
data = { ...data, token: this.projectApiKey }
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (typeof window === 'undefined') {
|
|
133
|
-
headers['user-agent'] = `posthog-node/${version}`
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const req = {
|
|
137
|
-
method: method,
|
|
138
|
-
url: url,
|
|
139
|
-
headers: headers,
|
|
140
|
-
data: JSON.stringify(data),
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (this.timeout) {
|
|
144
|
-
req.timeout = typeof this.timeout === 'string' ? ms(this.timeout) : this.timeout
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let res
|
|
149
|
-
try {
|
|
150
|
-
res = await axios.request(req)
|
|
151
|
-
} catch (err) {
|
|
152
|
-
throw new Error(`Request to ${path} failed with error: ${err.message}`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return res
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
stopPoller() {
|
|
159
|
-
clearTimeout(this.poller)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
module.exports = {
|
|
164
|
-
FeatureFlagsPoller,
|
|
165
|
-
}
|
package/index.d.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// Type definitions for posthog-node
|
|
2
|
-
// Project: Posthog
|
|
3
|
-
|
|
4
|
-
declare module 'posthog-node' {
|
|
5
|
-
interface Option {
|
|
6
|
-
flushAt?: number
|
|
7
|
-
flushInterval?: number
|
|
8
|
-
host?: string
|
|
9
|
-
enable?: boolean
|
|
10
|
-
personalApiKey?: string
|
|
11
|
-
featureFlagsPollingInterval?: number
|
|
12
|
-
}
|
|
13
|
-
interface IdentifyMessage {
|
|
14
|
-
distinctId: string
|
|
15
|
-
properties?: Record<string | number, any>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface EventMessage extends IdentifyMessage {
|
|
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>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default class PostHog {
|
|
33
|
-
constructor(apiKey: string, options?: Option)
|
|
34
|
-
/**
|
|
35
|
-
* @description Capture allows you to capture anything a user does within your system,
|
|
36
|
-
* which you can later use in PostHog to find patterns in usage,
|
|
37
|
-
* work out which features to improve or where people are giving up.
|
|
38
|
-
* A capture call requires:
|
|
39
|
-
* @param distinctId which uniquely identifies your user
|
|
40
|
-
* @param event We recommend using [verb] [noun], like movie played or movie updated to easily identify what your events mean later on.
|
|
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.
|
|
43
|
-
*/
|
|
44
|
-
capture({ distinctId, event, properties, groups }: EventMessage): void
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @description Identify lets you add metadata on your users so you can more easily identify who they are in PostHog,
|
|
48
|
-
* and even do things like segment users by these properties.
|
|
49
|
-
* An identify call requires:
|
|
50
|
-
* @param distinctId which uniquely identifies your user
|
|
51
|
-
* @param properties with a dict with any key: value pairs
|
|
52
|
-
*/
|
|
53
|
-
identify({ distinctId, properties }: IdentifyMessage): void
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @description To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call.
|
|
57
|
-
* This will allow you to answer questions like "Which marketing channels leads to users churning after a month?"
|
|
58
|
-
* or "What do users do on our website before signing up?"
|
|
59
|
-
* In a purely back-end implementation, this means whenever an anonymous user does something, you'll want to send a session ID with the capture call.
|
|
60
|
-
* Then, when that users signs up, you want to do an alias call with the session ID and the newly created user ID.
|
|
61
|
-
* The same concept applies for when a user logs in. If you're using PostHog in the front-end and back-end,
|
|
62
|
-
* doing the identify call in the frontend will be enough.:
|
|
63
|
-
* @param distinctId the current unique id
|
|
64
|
-
* @param alias the unique ID of the user before
|
|
65
|
-
*/
|
|
66
|
-
alias(data: { distinctId: string; alias: string }): void
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @description PostHog feature flags (https://posthog.com/docs/features/feature-flags)
|
|
71
|
-
* allow you to safely deploy and roll back new features. Once you've created a feature flag in PostHog,
|
|
72
|
-
* you can use this method to check if the flag is on for a given user, allowing you to create logic to turn
|
|
73
|
-
* features on and off for different user groups or individual users.
|
|
74
|
-
* IMPORTANT: To use this method, you need to specify `personalApiKey` in your config! More info: https://posthog.com/docs/api/overview
|
|
75
|
-
* @param key the unique key of your feature flag
|
|
76
|
-
* @param distinctId the current unique id
|
|
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)
|
|
79
|
-
*/
|
|
80
|
-
isFeatureEnabled(key: string, distinctId: string, defaultResult?: boolean, groups?: Record<GroupType, GroupKey>): Promise<boolean>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
/**
|
|
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
|
|
95
|
-
* already polled automatically at a regular interval.
|
|
96
|
-
*/
|
|
97
|
-
reloadFeatureFlags(): Promise<void>
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @description Flushes the events still in the queue and clears the feature flags poller to allow for
|
|
101
|
-
* a clean shutdown.
|
|
102
|
-
*/
|
|
103
|
-
shutdown(): void
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
}
|
package/index.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const assert = require('assert')
|
|
4
|
-
const removeSlash = require('remove-trailing-slash')
|
|
5
|
-
const axios = require('axios')
|
|
6
|
-
const axiosRetry = require('axios-retry')
|
|
7
|
-
const ms = require('ms')
|
|
8
|
-
const version = require('./package.json').version
|
|
9
|
-
const looselyValidate = require('./event-validation')
|
|
10
|
-
const { FeatureFlagsPoller } = require('./feature-flags')
|
|
11
|
-
|
|
12
|
-
const setImmediate = global.setImmediate || process.nextTick.bind(process)
|
|
13
|
-
const noop = () => {}
|
|
14
|
-
|
|
15
|
-
const FIVE_MINUTES = 5 * 60 * 1000
|
|
16
|
-
class PostHog {
|
|
17
|
-
/**
|
|
18
|
-
* Initialize a new `PostHog` with your PostHog project's `apiKey` and an
|
|
19
|
-
* optional dictionary of `options`.
|
|
20
|
-
*
|
|
21
|
-
* @param {String} apiKey
|
|
22
|
-
* @param {Object} [options] (optional)
|
|
23
|
-
* @property {Number} flushAt (default: 20)
|
|
24
|
-
* @property {Number} flushInterval (default: 10000)
|
|
25
|
-
* @property {String} host (default: 'https://app.posthog.com')
|
|
26
|
-
* @property {Boolean} enable (default: true)
|
|
27
|
-
* @property {String} featureFlagsPollingInterval (default: 300000)
|
|
28
|
-
* @property {String} personalApiKey
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
constructor(apiKey, options) {
|
|
32
|
-
options = options || {}
|
|
33
|
-
|
|
34
|
-
assert(apiKey, "You must pass your PostHog project's api key.")
|
|
35
|
-
|
|
36
|
-
this.queue = []
|
|
37
|
-
this.apiKey = apiKey
|
|
38
|
-
this.host = removeSlash(options.host || 'https://app.posthog.com')
|
|
39
|
-
this.timeout = options.timeout || false
|
|
40
|
-
this.flushAt = Math.max(options.flushAt, 1) || 20
|
|
41
|
-
this.flushInterval = typeof options.flushInterval === 'number' ? options.flushInterval : 10000
|
|
42
|
-
this.flushed = false
|
|
43
|
-
this.personalApiKey = options.personalApiKey
|
|
44
|
-
|
|
45
|
-
Object.defineProperty(this, 'enable', {
|
|
46
|
-
configurable: false,
|
|
47
|
-
writable: false,
|
|
48
|
-
enumerable: true,
|
|
49
|
-
value: typeof options.enable === 'boolean' ? options.enable : true,
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
axiosRetry(axios, {
|
|
53
|
-
retries: options.retryCount || 3,
|
|
54
|
-
retryCondition: this._isErrorRetryable,
|
|
55
|
-
retryDelay: axiosRetry.exponentialDelay,
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (this.personalApiKey) {
|
|
59
|
-
const featureFlagCalledCallback = (key, distinctId, isFlagEnabledResponse) => {
|
|
60
|
-
this.capture({
|
|
61
|
-
distinctId,
|
|
62
|
-
event: '$feature_flag_called',
|
|
63
|
-
properties: {
|
|
64
|
-
$feature_flag: key,
|
|
65
|
-
$feature_flag_response: isFlagEnabledResponse,
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.featureFlagsPoller = new FeatureFlagsPoller({
|
|
71
|
-
pollingInterval:
|
|
72
|
-
typeof options.featureFlagsPollingInterval === 'number'
|
|
73
|
-
? options.featureFlagsPollingInterval
|
|
74
|
-
: FIVE_MINUTES,
|
|
75
|
-
personalApiKey: options.personalApiKey,
|
|
76
|
-
projectApiKey: apiKey,
|
|
77
|
-
timeout: options.timeout || false,
|
|
78
|
-
host: this.host,
|
|
79
|
-
featureFlagCalledCallback,
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
_validate(message, type) {
|
|
85
|
-
try {
|
|
86
|
-
looselyValidate(message, type)
|
|
87
|
-
} catch (e) {
|
|
88
|
-
if (e.message === 'Your message must be < 32 kB.') {
|
|
89
|
-
console.log(
|
|
90
|
-
'Your message must be < 32 kB.',
|
|
91
|
-
JSON.stringify(message)
|
|
92
|
-
)
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
throw e
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Send an identify `message`.
|
|
101
|
-
*
|
|
102
|
-
* @param {Object} message
|
|
103
|
-
* @param {Function} [callback] (optional)
|
|
104
|
-
* @return {PostHog}
|
|
105
|
-
*/
|
|
106
|
-
|
|
107
|
-
identify(message, callback) {
|
|
108
|
-
this._validate(message, 'identify')
|
|
109
|
-
|
|
110
|
-
const apiMessage = Object.assign({}, message, {
|
|
111
|
-
$set: message.properties || {},
|
|
112
|
-
event: '$identify',
|
|
113
|
-
properties: {
|
|
114
|
-
$lib: 'posthog-node',
|
|
115
|
-
$lib_version: version,
|
|
116
|
-
},
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
this.enqueue('identify', apiMessage, callback)
|
|
120
|
-
return this
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Send a capture `message`.
|
|
125
|
-
*
|
|
126
|
-
* @param {Object} message
|
|
127
|
-
* @param {Function} [callback] (optional)
|
|
128
|
-
* @return {PostHog}
|
|
129
|
-
*/
|
|
130
|
-
|
|
131
|
-
capture(message, callback) {
|
|
132
|
-
this._validate(message, 'capture')
|
|
133
|
-
|
|
134
|
-
const properties = Object.assign({}, message.properties, {
|
|
135
|
-
$lib: 'posthog-node',
|
|
136
|
-
$lib_version: version,
|
|
137
|
-
})
|
|
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
|
-
|
|
146
|
-
this.enqueue('capture', apiMessage, callback)
|
|
147
|
-
return this
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Send an alias `message`.
|
|
152
|
-
*
|
|
153
|
-
* @param {Object} message
|
|
154
|
-
* @param {Function} [callback] (optional)
|
|
155
|
-
* @return {PostHog}
|
|
156
|
-
*/
|
|
157
|
-
|
|
158
|
-
alias(message, callback) {
|
|
159
|
-
this._validate(message, 'alias')
|
|
160
|
-
|
|
161
|
-
const apiMessage = Object.assign({}, message, {
|
|
162
|
-
event: '$create_alias',
|
|
163
|
-
properties: {
|
|
164
|
-
distinct_id: message.distinctId || message.distinct_id,
|
|
165
|
-
alias: message.alias,
|
|
166
|
-
$lib: 'posthog-node',
|
|
167
|
-
$lib_version: version,
|
|
168
|
-
},
|
|
169
|
-
})
|
|
170
|
-
delete apiMessage.alias
|
|
171
|
-
delete apiMessage.distinctId
|
|
172
|
-
apiMessage.distinct_id = message.distinctId || message.distinct_id
|
|
173
|
-
|
|
174
|
-
this.enqueue('alias', apiMessage, callback)
|
|
175
|
-
return this
|
|
176
|
-
}
|
|
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
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Add a `message` of type `type` to the queue and
|
|
204
|
-
* check whether it should be flushed.
|
|
205
|
-
*
|
|
206
|
-
* @param {String} type
|
|
207
|
-
* @param {Object} message
|
|
208
|
-
* @param {Function} [callback] (optional)
|
|
209
|
-
* @api private
|
|
210
|
-
*/
|
|
211
|
-
|
|
212
|
-
enqueue(type, message, callback) {
|
|
213
|
-
callback = callback || noop
|
|
214
|
-
|
|
215
|
-
if (!this.enable) {
|
|
216
|
-
return setImmediate(callback)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
message = Object.assign({}, message)
|
|
220
|
-
message.type = type
|
|
221
|
-
message.library = 'posthog-node'
|
|
222
|
-
message.library_version = version
|
|
223
|
-
|
|
224
|
-
if (!message.timestamp) {
|
|
225
|
-
message.timestamp = new Date()
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (message.distinctId) {
|
|
229
|
-
message.distinct_id = message.distinctId
|
|
230
|
-
delete message.distinctId
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
this.queue.push({ message, callback })
|
|
234
|
-
|
|
235
|
-
if (!this.flushed) {
|
|
236
|
-
this.flushed = true
|
|
237
|
-
this.flush()
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (this.queue.length >= this.flushAt) {
|
|
242
|
-
this.flush()
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (this.flushInterval && !this.timer) {
|
|
246
|
-
this.timer = setTimeout(() => this.flush(), this.flushInterval)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async isFeatureEnabled(key, distinctId, defaultResult = false, groups = {}) {
|
|
251
|
-
this._validate({ key, distinctId, defaultResult, groups }, 'isFeatureEnabled')
|
|
252
|
-
assert(this.personalApiKey, 'You have to specify the option personalApiKey to use feature flags.')
|
|
253
|
-
|
|
254
|
-
return await this.featureFlagsPoller.isFeatureEnabled(key, distinctId, defaultResult, groups)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async reloadFeatureFlags() {
|
|
258
|
-
await this.featureFlagsPoller.loadFeatureFlags(true)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Flush the current queue
|
|
263
|
-
*
|
|
264
|
-
* @param {Function} [callback] (optional)
|
|
265
|
-
* @return {PostHog}
|
|
266
|
-
*/
|
|
267
|
-
|
|
268
|
-
flush(callback) {
|
|
269
|
-
callback = callback || noop
|
|
270
|
-
|
|
271
|
-
if (!this.enable) {
|
|
272
|
-
return setImmediate(callback)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (this.timer) {
|
|
276
|
-
clearTimeout(this.timer)
|
|
277
|
-
this.timer = null
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (!this.queue.length) {
|
|
281
|
-
return setImmediate(callback)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const items = this.queue.splice(0, this.flushAt)
|
|
285
|
-
const callbacks = items.map((item) => item.callback)
|
|
286
|
-
const messages = items.map((item) => item.message)
|
|
287
|
-
|
|
288
|
-
const data = {
|
|
289
|
-
api_key: this.apiKey,
|
|
290
|
-
batch: messages,
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const done = (err) => {
|
|
294
|
-
callbacks.forEach((callback) => callback(err))
|
|
295
|
-
callback(err, data)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Don't set the user agent if we're not on a browser. The latest spec allows
|
|
299
|
-
// the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
|
|
300
|
-
// and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
|
|
301
|
-
// but browsers such as Chrome and Safari have not caught up.
|
|
302
|
-
const headers = {}
|
|
303
|
-
if (typeof window === 'undefined') {
|
|
304
|
-
headers['user-agent'] = `posthog-node/${version}`
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const req = {
|
|
308
|
-
method: 'POST',
|
|
309
|
-
url: `${this.host}/batch/`,
|
|
310
|
-
data,
|
|
311
|
-
headers,
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (this.timeout) {
|
|
315
|
-
req.timeout = typeof this.timeout === 'string' ? ms(this.timeout) : this.timeout
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
axios(req)
|
|
319
|
-
.then(() => done())
|
|
320
|
-
.catch((err) => {
|
|
321
|
-
if (err.response) {
|
|
322
|
-
const error = new Error(err.response.statusText)
|
|
323
|
-
return done(error)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
done(err)
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
shutdown() {
|
|
331
|
-
if (this.personalApiKey) {
|
|
332
|
-
this.featureFlagsPoller.stopPoller()
|
|
333
|
-
}
|
|
334
|
-
this.flush()
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
_isErrorRetryable(error) {
|
|
338
|
-
// Retry Network Errors.
|
|
339
|
-
if (axiosRetry.isNetworkError(error)) {
|
|
340
|
-
return true
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (!error.response) {
|
|
344
|
-
// Cannot determine if the request can be retried
|
|
345
|
-
return false
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Retry Server Errors (5xx).
|
|
349
|
-
if (error.response.status >= 500 && error.response.status <= 599) {
|
|
350
|
-
return true
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Retry if rate limited.
|
|
354
|
-
if (error.response.status === 429) {
|
|
355
|
-
return true
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return false
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
module.exports = PostHog
|