posthog-node 4.2.2 → 4.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/CHANGELOG.md +8 -0
- package/lib/index.cjs.js +95 -50
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +20 -28
- package/lib/index.esm.js +93 -51
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +1 -1
- package/lib/posthog-node/src/extensions/sentry-integration.d.ts +26 -14
- package/package.json +1 -1
- package/src/extensions/sentry-integration.ts +142 -76
- package/src/posthog-node.ts +9 -2
- package/test/extensions/sentry-integration.spec.ts +13 -3
- package/test/posthog-node.spec.ts +29 -3
|
@@ -6,7 +6,7 @@ import { SimpleEventEmitter } from './eventemitter';
|
|
|
6
6
|
export declare abstract class PostHogCoreStateless {
|
|
7
7
|
readonly apiKey: string;
|
|
8
8
|
readonly host: string;
|
|
9
|
-
|
|
9
|
+
readonly flushAt: number;
|
|
10
10
|
private maxBatchSize;
|
|
11
11
|
private maxQueueSize;
|
|
12
12
|
private flushInterval;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
|
|
3
3
|
*/
|
|
4
|
-
import { type PostHog } from '../posthog-node';
|
|
5
|
-
type _SentryEventProcessor = any;
|
|
6
|
-
type _SentryHub = any;
|
|
7
|
-
interface _SentryIntegration {
|
|
8
|
-
name: string;
|
|
9
|
-
setupOnce(addGlobalEventProcessor: (callback: _SentryEventProcessor) => void, getCurrentHub: () => _SentryHub): void;
|
|
10
|
-
}
|
|
11
4
|
/**
|
|
12
5
|
* Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
|
|
13
6
|
*
|
|
@@ -26,15 +19,34 @@ interface _SentryIntegration {
|
|
|
26
19
|
* @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
|
|
27
20
|
* @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
|
|
28
21
|
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
22
|
+
* @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
|
|
29
23
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
import { type PostHog } from '../posthog-node';
|
|
25
|
+
export declare const severityLevels: readonly ["fatal", "error", "warning", "log", "info", "debug"];
|
|
26
|
+
export declare type SeverityLevel = (typeof severityLevels)[number];
|
|
27
|
+
type _SentryEvent = any;
|
|
28
|
+
type _SentryEventProcessor = any;
|
|
29
|
+
type _SentryHub = any;
|
|
30
|
+
interface _SentryIntegration {
|
|
31
|
+
name: string;
|
|
32
|
+
processEvent(event: _SentryEvent): _SentryEvent;
|
|
33
|
+
}
|
|
34
|
+
interface _SentryIntegrationClass {
|
|
35
|
+
name: string;
|
|
36
|
+
setupOnce(addGlobalEventProcessor: (callback: _SentryEventProcessor) => void, getCurrentHub: () => _SentryHub): void;
|
|
37
|
+
}
|
|
38
|
+
export type SentryIntegrationOptions = {
|
|
39
|
+
organization?: string;
|
|
40
|
+
projectId?: number;
|
|
41
|
+
prefix?: string;
|
|
42
|
+
severityAllowList?: SeverityLevel[] | '*';
|
|
43
|
+
};
|
|
44
|
+
export declare function createEventProcessor(_posthog: PostHog, { organization, projectId, prefix, severityAllowList }?: SentryIntegrationOptions): (event: _SentryEvent) => _SentryEvent;
|
|
45
|
+
export declare function sentryIntegration(_posthog: PostHog, options?: SentryIntegrationOptions): _SentryIntegration;
|
|
46
|
+
export declare class PostHogSentryIntegration implements _SentryIntegrationClass {
|
|
35
47
|
readonly name = "posthog-node";
|
|
36
48
|
static readonly POSTHOG_ID_TAG = "posthog_distinct_id";
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
setupOnce: (addGlobalEventProcessor: (callback: _SentryEventProcessor) => void, getCurrentHub: () => _SentryHub) => void;
|
|
50
|
+
constructor(_posthog: PostHog, organization?: string, prefix?: string, severityAllowList?: SeverityLevel[] | '*');
|
|
39
51
|
}
|
|
40
52
|
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
|
|
6
|
+
*
|
|
7
|
+
* ### Usage
|
|
8
|
+
*
|
|
9
|
+
* Sentry.init({
|
|
10
|
+
* dsn: 'https://example',
|
|
11
|
+
* integrations: [
|
|
12
|
+
* new PostHogSentryIntegration(posthog)
|
|
13
|
+
* ]
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} [posthog] The posthog object
|
|
19
|
+
* @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
|
|
20
|
+
* @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
|
|
21
|
+
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
22
|
+
* @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
|
|
23
|
+
*/
|
|
24
|
+
|
|
4
25
|
import { type PostHog } from '../posthog-node'
|
|
5
26
|
|
|
27
|
+
export const severityLevels = ['fatal', 'error', 'warning', 'log', 'info', 'debug'] as const
|
|
28
|
+
export declare type SeverityLevel = (typeof severityLevels)[number]
|
|
29
|
+
|
|
6
30
|
// NOTE - we can't import from @sentry/types because it changes frequently and causes clashes
|
|
7
31
|
// We only use a small subset of the types, so we can just define the integration overall and use any for the rest
|
|
8
32
|
|
|
@@ -11,115 +35,157 @@ import { type PostHog } from '../posthog-node'
|
|
|
11
35
|
// EventProcessor as _SentryEventProcessor,
|
|
12
36
|
// Exception as _SentryException,
|
|
13
37
|
// Hub as _SentryHub,
|
|
14
|
-
// Integration as _SentryIntegration,
|
|
15
38
|
// Primitive as _SentryPrimitive,
|
|
39
|
+
// Integration as _SentryIntegration,
|
|
40
|
+
// IntegrationClass as _SentryIntegrationClass,
|
|
16
41
|
// } from '@sentry/types'
|
|
17
42
|
|
|
18
43
|
// Uncomment the above and comment the below to get type checking for development
|
|
19
44
|
|
|
20
45
|
type _SentryEvent = any
|
|
21
46
|
type _SentryEventProcessor = any
|
|
22
|
-
type _SentryHub = any
|
|
23
47
|
type _SentryException = any
|
|
48
|
+
type _SentryHub = any
|
|
24
49
|
type _SentryPrimitive = any
|
|
25
50
|
|
|
26
51
|
interface _SentryIntegration {
|
|
52
|
+
name: string
|
|
53
|
+
processEvent(event: _SentryEvent): _SentryEvent
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface _SentryIntegrationClass {
|
|
27
57
|
name: string
|
|
28
58
|
setupOnce(addGlobalEventProcessor: (callback: _SentryEventProcessor) => void, getCurrentHub: () => _SentryHub): void
|
|
29
59
|
}
|
|
30
60
|
|
|
31
|
-
interface
|
|
61
|
+
interface SentryExceptionProperties {
|
|
32
62
|
$sentry_event_id?: string
|
|
33
63
|
$sentry_exception?: { values?: _SentryException[] }
|
|
34
64
|
$sentry_exception_message?: string
|
|
35
65
|
$sentry_exception_type?: string
|
|
36
66
|
$sentry_tags: { [key: string]: _SentryPrimitive }
|
|
37
67
|
$sentry_url?: string
|
|
38
|
-
$exception_type?: string
|
|
39
|
-
$exception_message?: string
|
|
40
|
-
$exception_personURL?: string
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
* dsn: 'https://example',
|
|
50
|
-
* integrations: [
|
|
51
|
-
* new PostHogSentryIntegration(posthog)
|
|
52
|
-
* ]
|
|
53
|
-
* })
|
|
54
|
-
*
|
|
55
|
-
* Sentry.setTag(PostHogSentryIntegration.POSTHOG_ID_TAG, 'some distinct id');
|
|
56
|
-
*
|
|
57
|
-
* @param {Object} [posthog] The posthog object
|
|
58
|
-
* @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
|
|
59
|
-
* @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
|
|
60
|
-
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
61
|
-
*/
|
|
62
|
-
export class PostHogSentryIntegration implements _SentryIntegration {
|
|
63
|
-
public readonly name = 'posthog-node'
|
|
70
|
+
export type SentryIntegrationOptions = {
|
|
71
|
+
organization?: string
|
|
72
|
+
projectId?: number
|
|
73
|
+
prefix?: string
|
|
74
|
+
severityAllowList?: SeverityLevel[] | '*'
|
|
75
|
+
}
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
const NAME = 'posthog-node'
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
export function createEventProcessor(
|
|
80
|
+
_posthog: PostHog,
|
|
81
|
+
{ organization, projectId, prefix, severityAllowList = ['error'] }: SentryIntegrationOptions = {}
|
|
82
|
+
): (event: _SentryEvent) => _SentryEvent {
|
|
83
|
+
return (event) => {
|
|
84
|
+
const shouldProcessLevel = severityAllowList === '*' || severityAllowList.includes(event.level)
|
|
85
|
+
if (!shouldProcessLevel) {
|
|
86
|
+
return event
|
|
87
|
+
}
|
|
88
|
+
if (!event.tags) {
|
|
89
|
+
event.tags = {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Get the PostHog user ID from a specific tag, which users can set on their Sentry scope as they need.
|
|
93
|
+
const userId = event.tags[PostHogSentryIntegration.POSTHOG_ID_TAG]
|
|
94
|
+
if (userId === undefined) {
|
|
95
|
+
// If we can't find a user ID, don't bother linking the event. We won't be able to send anything meaningful to PostHog without it.
|
|
96
|
+
return event
|
|
97
|
+
}
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
getCurrentHub: () => _SentryHub
|
|
79
|
-
): void {
|
|
80
|
-
addGlobalEventProcessor((event: _SentryEvent): _SentryEvent => {
|
|
81
|
-
if (event.exception?.values === undefined || event.exception.values.length === 0) {
|
|
82
|
-
return event
|
|
83
|
-
}
|
|
99
|
+
const uiHost = _posthog.options.host ?? 'https://us.i.posthog.com'
|
|
100
|
+
const personUrl = new URL(`/project/${_posthog.apiKey}/person/${userId}`, uiHost).toString()
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
event.tags = {}
|
|
87
|
-
}
|
|
102
|
+
event.tags['PostHog Person URL'] = personUrl
|
|
88
103
|
|
|
89
|
-
|
|
104
|
+
const exceptions: _SentryException[] = event.exception?.values || []
|
|
90
105
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// If we can't find a user ID, don't bother linking the event. We won't be able to send anything meaningful to PostHog without it.
|
|
95
|
-
return event
|
|
106
|
+
exceptions.map((exception) => {
|
|
107
|
+
if (exception.stacktrace) {
|
|
108
|
+
exception.stacktrace.type = 'raw'
|
|
96
109
|
}
|
|
110
|
+
})
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
const properties: SentryExceptionProperties & {
|
|
113
|
+
// two properties added to match any exception auto-capture
|
|
114
|
+
// added manually to avoid any dependency on the lazily loaded content
|
|
115
|
+
$exception_message: any
|
|
116
|
+
$exception_type: any
|
|
117
|
+
$exception_list: any
|
|
118
|
+
$exception_personURL: string
|
|
119
|
+
$exception_level: SeverityLevel
|
|
120
|
+
} = {
|
|
121
|
+
// PostHog Exception Properties,
|
|
122
|
+
$exception_message: exceptions[0]?.value || event.message,
|
|
123
|
+
$exception_type: exceptions[0]?.type,
|
|
124
|
+
$exception_personURL: personUrl,
|
|
125
|
+
$exception_level: event.level,
|
|
126
|
+
$exception_list: exceptions,
|
|
127
|
+
// Sentry Exception Properties
|
|
128
|
+
$sentry_event_id: event.event_id,
|
|
129
|
+
$sentry_exception: event.exception,
|
|
130
|
+
$sentry_exception_message: exceptions[0]?.value || event.message,
|
|
131
|
+
$sentry_exception_type: exceptions[0]?.type,
|
|
132
|
+
$sentry_tags: event.tags,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (organization && projectId) {
|
|
136
|
+
properties['$sentry_url'] =
|
|
137
|
+
(prefix || 'https://sentry.io/organizations/') +
|
|
138
|
+
organization +
|
|
139
|
+
'/issues/?project=' +
|
|
140
|
+
projectId +
|
|
141
|
+
'&query=' +
|
|
142
|
+
event.event_id
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_posthog.capture({ event: '$exception', distinctId: userId, properties })
|
|
146
|
+
|
|
147
|
+
return event
|
|
148
|
+
}
|
|
149
|
+
}
|
|
112
150
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
// V8 integration - function based
|
|
152
|
+
export function sentryIntegration(_posthog: PostHog, options?: SentryIntegrationOptions): _SentryIntegration {
|
|
153
|
+
const processor = createEventProcessor(_posthog, options)
|
|
154
|
+
return {
|
|
155
|
+
name: NAME,
|
|
156
|
+
processEvent(event) {
|
|
157
|
+
return processor(event)
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|
|
119
161
|
|
|
120
|
-
|
|
162
|
+
// V7 integration - class based
|
|
163
|
+
export class PostHogSentryIntegration implements _SentryIntegrationClass {
|
|
164
|
+
public readonly name = NAME
|
|
121
165
|
|
|
122
|
-
|
|
123
|
-
|
|
166
|
+
public static readonly POSTHOG_ID_TAG = 'posthog_distinct_id'
|
|
167
|
+
|
|
168
|
+
public setupOnce: (
|
|
169
|
+
addGlobalEventProcessor: (callback: _SentryEventProcessor) => void,
|
|
170
|
+
getCurrentHub: () => _SentryHub
|
|
171
|
+
) => void
|
|
172
|
+
|
|
173
|
+
constructor(_posthog: PostHog, organization?: string, prefix?: string, severityAllowList?: SeverityLevel[] | '*') {
|
|
174
|
+
// setupOnce gets called by Sentry when it intializes the plugin
|
|
175
|
+
this.name = NAME
|
|
176
|
+
this.setupOnce = function (
|
|
177
|
+
addGlobalEventProcessor: (callback: _SentryEventProcessor) => void,
|
|
178
|
+
getCurrentHub: () => _SentryHub
|
|
179
|
+
) {
|
|
180
|
+
const projectId = getCurrentHub()?.getClient()?.getDsn()?.projectId
|
|
181
|
+
addGlobalEventProcessor(
|
|
182
|
+
createEventProcessor(_posthog, {
|
|
183
|
+
organization,
|
|
184
|
+
projectId,
|
|
185
|
+
prefix,
|
|
186
|
+
severityAllowList,
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
}
|
|
124
190
|
}
|
|
125
191
|
}
|
package/src/posthog-node.ts
CHANGED
|
@@ -174,12 +174,19 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
|
|
|
174
174
|
|
|
175
175
|
identify({ distinctId, properties, disableGeoip }: IdentifyMessage): void {
|
|
176
176
|
// Catch properties passed as $set and move them to the top level
|
|
177
|
-
|
|
177
|
+
|
|
178
|
+
// promote $set and $set_once to top level
|
|
179
|
+
const userPropsOnce = properties?.$set_once
|
|
180
|
+
delete properties?.$set_once
|
|
181
|
+
|
|
182
|
+
// if no $set is provided we assume all properties are $set
|
|
183
|
+
const userProps = properties?.$set || properties
|
|
178
184
|
|
|
179
185
|
super.identifyStateless(
|
|
180
186
|
distinctId,
|
|
181
187
|
{
|
|
182
|
-
$set:
|
|
188
|
+
$set: userProps,
|
|
189
|
+
$set_once: userPropsOnce,
|
|
183
190
|
},
|
|
184
191
|
{ disableGeoip }
|
|
185
192
|
)
|
|
@@ -39,6 +39,7 @@ const createMockSentryException = (): any => ({
|
|
|
39
39
|
server_name: 'localhost',
|
|
40
40
|
timestamp: 1704203482.356,
|
|
41
41
|
environment: 'production',
|
|
42
|
+
level: 'error',
|
|
42
43
|
tags: { posthog_distinct_id: 'EXAMPLE_APP_GLOBAL' },
|
|
43
44
|
breadcrumbs: [
|
|
44
45
|
{
|
|
@@ -119,16 +120,25 @@ describe('PostHogSentryIntegration', () => {
|
|
|
119
120
|
distinct_id: 'EXAMPLE_APP_GLOBAL',
|
|
120
121
|
event: '$exception',
|
|
121
122
|
properties: {
|
|
123
|
+
$exception_level: 'error',
|
|
124
|
+
$exception_list: [
|
|
125
|
+
{
|
|
126
|
+
mechanism: { handled: true, type: 'generic' },
|
|
127
|
+
stacktrace: { frames: [], type: 'raw' },
|
|
128
|
+
type: 'Error',
|
|
129
|
+
value: 'example error',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
122
132
|
$exception_message: 'example error',
|
|
123
133
|
$exception_type: 'Error',
|
|
124
|
-
$exception_personURL: 'http://example.com/person/EXAMPLE_APP_GLOBAL',
|
|
134
|
+
$exception_personURL: 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
|
|
125
135
|
$sentry_event_id: '80a7023ac32c47f7acb0adaed600d149',
|
|
126
136
|
$sentry_exception: {
|
|
127
137
|
values: [
|
|
128
138
|
{
|
|
129
139
|
type: 'Error',
|
|
130
140
|
value: 'example error',
|
|
131
|
-
stacktrace: { frames: [] },
|
|
141
|
+
stacktrace: { frames: [], type: 'raw' },
|
|
132
142
|
mechanism: { type: 'generic', handled: true },
|
|
133
143
|
},
|
|
134
144
|
],
|
|
@@ -137,7 +147,7 @@ describe('PostHogSentryIntegration', () => {
|
|
|
137
147
|
$sentry_exception_type: 'Error',
|
|
138
148
|
$sentry_tags: {
|
|
139
149
|
posthog_distinct_id: 'EXAMPLE_APP_GLOBAL',
|
|
140
|
-
'PostHog Person URL': 'http://example.com/person/EXAMPLE_APP_GLOBAL',
|
|
150
|
+
'PostHog Person URL': 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
|
|
141
151
|
},
|
|
142
152
|
$lib: 'posthog-node',
|
|
143
153
|
$lib_version: '1.2.3',
|
|
@@ -154,9 +154,9 @@ describe('PostHog Node.js', () => {
|
|
|
154
154
|
])
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
it('should handle identify
|
|
157
|
+
it('should handle identify using $set and $set_once', async () => {
|
|
158
158
|
expect(mockedFetch).toHaveBeenCalledTimes(0)
|
|
159
|
-
posthog.identify({ distinctId: '123', properties: { foo: 'bar', $
|
|
159
|
+
posthog.identify({ distinctId: '123', properties: { $set: { foo: 'bar' }, $set_once: { vip: true } } })
|
|
160
160
|
jest.runOnlyPendingTimers()
|
|
161
161
|
await waitForPromises()
|
|
162
162
|
const batchEvents = getLastBatchEvents()
|
|
@@ -166,7 +166,33 @@ describe('PostHog Node.js', () => {
|
|
|
166
166
|
event: '$identify',
|
|
167
167
|
properties: {
|
|
168
168
|
$set: {
|
|
169
|
-
foo: '
|
|
169
|
+
foo: 'bar',
|
|
170
|
+
},
|
|
171
|
+
$set_once: {
|
|
172
|
+
vip: true,
|
|
173
|
+
},
|
|
174
|
+
$geoip_disable: true,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should handle identify using $set_once', async () => {
|
|
181
|
+
expect(mockedFetch).toHaveBeenCalledTimes(0)
|
|
182
|
+
posthog.identify({ distinctId: '123', properties: { foo: 'bar', $set_once: { vip: true } } })
|
|
183
|
+
jest.runOnlyPendingTimers()
|
|
184
|
+
await waitForPromises()
|
|
185
|
+
const batchEvents = getLastBatchEvents()
|
|
186
|
+
expect(batchEvents).toMatchObject([
|
|
187
|
+
{
|
|
188
|
+
distinct_id: '123',
|
|
189
|
+
event: '$identify',
|
|
190
|
+
properties: {
|
|
191
|
+
$set: {
|
|
192
|
+
foo: 'bar',
|
|
193
|
+
},
|
|
194
|
+
$set_once: {
|
|
195
|
+
vip: true,
|
|
170
196
|
},
|
|
171
197
|
$geoip_disable: true,
|
|
172
198
|
},
|