autotel-subscribers 10.0.0 → 12.0.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 +68 -13
- package/dist/{event-subscriber-base-CnF3V56W.d.cts → event-subscriber-base-uT-C_zrL.d.cts} +39 -3
- package/dist/{event-subscriber-base-CnF3V56W.d.ts → event-subscriber-base-uT-C_zrL.d.ts} +39 -3
- package/dist/factories.cjs +127 -24
- package/dist/factories.cjs.map +1 -1
- package/dist/factories.d.cts +1 -1
- package/dist/factories.d.ts +1 -1
- package/dist/factories.js +127 -24
- package/dist/factories.js.map +1 -1
- package/dist/index.cjs +127 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +127 -24
- package/dist/index.js.map +1 -1
- package/dist/mixpanel.cjs +1 -1
- package/dist/mixpanel.cjs.map +1 -1
- package/dist/mixpanel.js +1 -1
- package/dist/mixpanel.js.map +1 -1
- package/dist/posthog.cjs +116 -13
- package/dist/posthog.cjs.map +1 -1
- package/dist/posthog.d.cts +91 -2
- package/dist/posthog.d.ts +91 -2
- package/dist/posthog.js +116 -13
- package/dist/posthog.js.map +1 -1
- package/dist/segment.cjs +10 -10
- package/dist/segment.cjs.map +1 -1
- package/dist/segment.js +10 -10
- package/dist/segment.js.map +1 -1
- package/dist/slack.cjs +54 -0
- package/dist/slack.cjs.map +1 -1
- package/dist/slack.d.cts +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +54 -0
- package/dist/slack.js.map +1 -1
- package/package.json +3 -3
- package/src/event-subscriber-base.ts +83 -3
- package/src/posthog.test.ts +2 -2
- package/src/posthog.ts +184 -20
package/src/posthog.ts
CHANGED
|
@@ -86,12 +86,34 @@
|
|
|
86
86
|
* ```
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
|
-
import type { EventAttributes } from 'autotel/event-subscriber';
|
|
89
|
+
import type { EventAttributes, EventAttributesInput } from 'autotel/event-subscriber';
|
|
90
90
|
import { EventSubscriber, type EventPayload } from './event-subscriber-base';
|
|
91
91
|
|
|
92
92
|
// Type-only import to avoid runtime dependency
|
|
93
93
|
import type { PostHog } from 'posthog-node';
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Error context for enhanced error handling
|
|
97
|
+
*
|
|
98
|
+
* Provides detailed context about the event that caused an error.
|
|
99
|
+
*/
|
|
100
|
+
export interface ErrorContext {
|
|
101
|
+
/** The error that occurred */
|
|
102
|
+
error: Error;
|
|
103
|
+
|
|
104
|
+
/** Event name (if applicable) */
|
|
105
|
+
eventName?: string;
|
|
106
|
+
|
|
107
|
+
/** Event type (event, funnel, outcome, value) */
|
|
108
|
+
eventType?: 'event' | 'funnel' | 'outcome' | 'value';
|
|
109
|
+
|
|
110
|
+
/** Event attributes (filtered) */
|
|
111
|
+
attributes?: EventAttributes;
|
|
112
|
+
|
|
113
|
+
/** Subscriber name */
|
|
114
|
+
subscriberName: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
95
117
|
export interface PostHogConfig {
|
|
96
118
|
/** PostHog API key (starts with phc_) - required if not providing custom client */
|
|
97
119
|
apiKey?: string;
|
|
@@ -105,6 +127,41 @@ export interface PostHogConfig {
|
|
|
105
127
|
/** Custom PostHog client instance (bypasses apiKey/host) */
|
|
106
128
|
client?: PostHog;
|
|
107
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Use global browser client (window.posthog)
|
|
132
|
+
*
|
|
133
|
+
* When true, uses the PostHog client already loaded on the page via script tag.
|
|
134
|
+
* This is useful for Next.js apps that initialize PostHog in _app.tsx.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Browser - uses window.posthog
|
|
139
|
+
* const subscriber = new PostHogSubscriber({
|
|
140
|
+
* useGlobalClient: true,
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
useGlobalClient?: boolean;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Serverless mode preset (AWS Lambda, Vercel Functions, Next.js API routes)
|
|
148
|
+
*
|
|
149
|
+
* When true, auto-configures for serverless environments:
|
|
150
|
+
* - flushAt: 1 (send immediately, don't batch)
|
|
151
|
+
* - flushInterval: 0 (disable interval-based flushing)
|
|
152
|
+
* - requestTimeout: 3000 (shorter timeout for fast responses)
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* // Vercel / Next.js API route
|
|
157
|
+
* const subscriber = new PostHogSubscriber({
|
|
158
|
+
* apiKey: 'phc_...',
|
|
159
|
+
* serverless: true,
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
serverless?: boolean;
|
|
164
|
+
|
|
108
165
|
// Serverless optimizations
|
|
109
166
|
/** Flush batch when it reaches this size (default: 20, set to 1 for immediate send) */
|
|
110
167
|
flushAt?: number;
|
|
@@ -122,10 +179,49 @@ export interface PostHogConfig {
|
|
|
122
179
|
/** Send feature flag evaluation events (default: true) */
|
|
123
180
|
sendFeatureFlags?: boolean;
|
|
124
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Automatically filter out undefined and null values from attributes
|
|
184
|
+
*
|
|
185
|
+
* When true (default), undefined and null values are removed before sending.
|
|
186
|
+
* This improves DX when passing objects with optional properties.
|
|
187
|
+
*
|
|
188
|
+
* @default true
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* // With filterUndefinedValues: true (default)
|
|
193
|
+
* subscriber.trackEvent('user.action', {
|
|
194
|
+
* userId: user.id,
|
|
195
|
+
* email: user.email, // might be undefined - will be filtered
|
|
196
|
+
* plan: user.subscription, // might be null - will be filtered
|
|
197
|
+
* });
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
filterUndefinedValues?: boolean;
|
|
201
|
+
|
|
125
202
|
// Error handling
|
|
126
203
|
/** Error callback for debugging and monitoring */
|
|
127
204
|
onError?: (error: Error) => void;
|
|
128
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Enhanced error callback with event context
|
|
208
|
+
*
|
|
209
|
+
* Provides detailed context about the event that caused the error.
|
|
210
|
+
* If both onError and onErrorWithContext are provided, both are called.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const subscriber = new PostHogSubscriber({
|
|
215
|
+
* apiKey: 'phc_...',
|
|
216
|
+
* onErrorWithContext: (ctx) => {
|
|
217
|
+
* console.error(`Failed to track ${ctx.eventType}: ${ctx.eventName}`, ctx.error);
|
|
218
|
+
* Sentry.captureException(ctx.error, { extra: ctx });
|
|
219
|
+
* }
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
onErrorWithContext?: (context: ErrorContext) => void;
|
|
224
|
+
|
|
129
225
|
/** Enable debug logging (default: false) */
|
|
130
226
|
debug?: boolean;
|
|
131
227
|
}
|
|
@@ -171,16 +267,35 @@ export class PostHogSubscriber extends EventSubscriber {
|
|
|
171
267
|
private posthog: PostHog | null = null;
|
|
172
268
|
private config: PostHogConfig;
|
|
173
269
|
private initPromise: Promise<void> | null = null;
|
|
270
|
+
/** True when using browser's window.posthog (different API signature) */
|
|
271
|
+
private isBrowserClient = false;
|
|
174
272
|
|
|
175
273
|
constructor(config: PostHogConfig) {
|
|
176
274
|
super();
|
|
177
275
|
|
|
178
|
-
|
|
179
|
-
|
|
276
|
+
// Apply serverless preset first (can be overridden by explicit config)
|
|
277
|
+
if (config.serverless) {
|
|
278
|
+
config = {
|
|
279
|
+
flushAt: 1,
|
|
280
|
+
flushInterval: 0,
|
|
281
|
+
requestTimeout: 3000,
|
|
282
|
+
...config, // User config overrides serverless defaults
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Validate: need either apiKey, client, or useGlobalClient
|
|
287
|
+
if (!config.apiKey && !config.client && !config.useGlobalClient) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
'PostHogSubscriber requires either apiKey, client, or useGlobalClient to be provided',
|
|
290
|
+
);
|
|
180
291
|
}
|
|
181
292
|
|
|
182
293
|
this.enabled = config.enabled ?? true;
|
|
183
|
-
|
|
294
|
+
// Default filterUndefinedValues to true
|
|
295
|
+
this.config = {
|
|
296
|
+
filterUndefinedValues: true,
|
|
297
|
+
...config,
|
|
298
|
+
};
|
|
184
299
|
|
|
185
300
|
if (this.enabled) {
|
|
186
301
|
// Start initialization immediately but don't block constructor
|
|
@@ -190,14 +305,29 @@ export class PostHogSubscriber extends EventSubscriber {
|
|
|
190
305
|
|
|
191
306
|
private async initialize(): Promise<void> {
|
|
192
307
|
try {
|
|
193
|
-
// Use
|
|
308
|
+
// Option 1: Use global browser client (window.posthog)
|
|
309
|
+
if (this.config.useGlobalClient) {
|
|
310
|
+
const globalWindow = typeof globalThis === 'undefined' ? undefined : (globalThis as Record<string, unknown>);
|
|
311
|
+
if (globalWindow?.posthog) {
|
|
312
|
+
this.posthog = globalWindow.posthog as PostHog;
|
|
313
|
+
this.isBrowserClient = true;
|
|
314
|
+
this.setupErrorHandling();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
throw new Error(
|
|
318
|
+
'useGlobalClient enabled but window.posthog not found. ' +
|
|
319
|
+
'Ensure PostHog script is loaded before initializing the subscriber.',
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Option 2: Use custom client if provided
|
|
194
324
|
if (this.config.client) {
|
|
195
325
|
this.posthog = this.config.client;
|
|
196
326
|
this.setupErrorHandling();
|
|
197
327
|
return;
|
|
198
328
|
}
|
|
199
329
|
|
|
200
|
-
//
|
|
330
|
+
// Option 3: Create new PostHog client with dynamic import
|
|
201
331
|
const { PostHog } = await import('posthog-node');
|
|
202
332
|
|
|
203
333
|
this.posthog = new PostHog(this.config.apiKey!, {
|
|
@@ -247,25 +377,46 @@ export class PostHogSubscriber extends EventSubscriber {
|
|
|
247
377
|
protected async sendToDestination(payload: EventPayload): Promise<void> {
|
|
248
378
|
await this.ensureInitialized();
|
|
249
379
|
|
|
250
|
-
//
|
|
251
|
-
|
|
380
|
+
// Filter attributes if enabled (default: true)
|
|
381
|
+
const filteredAttributes =
|
|
382
|
+
this.config.filterUndefinedValues === false
|
|
383
|
+
? payload.attributes
|
|
384
|
+
: this.filterAttributes(payload.attributes as EventAttributesInput);
|
|
385
|
+
|
|
386
|
+
// Build properties object, including value and funnel metadata if present
|
|
387
|
+
const properties: Record<string, unknown> = { ...filteredAttributes };
|
|
252
388
|
if (payload.value !== undefined) {
|
|
253
|
-
properties =
|
|
389
|
+
properties.value = payload.value;
|
|
390
|
+
}
|
|
391
|
+
// Add funnel progression metadata
|
|
392
|
+
if (payload.stepNumber !== undefined) {
|
|
393
|
+
properties.step_number = payload.stepNumber;
|
|
394
|
+
}
|
|
395
|
+
if (payload.stepName !== undefined) {
|
|
396
|
+
properties.step_name = payload.stepName;
|
|
254
397
|
}
|
|
255
398
|
|
|
256
|
-
|
|
257
|
-
const capturePayload: any = {
|
|
258
|
-
distinctId: this.extractDistinctId(payload.attributes),
|
|
259
|
-
event: payload.name,
|
|
260
|
-
properties,
|
|
261
|
-
};
|
|
399
|
+
const distinctId = this.extractDistinctId(filteredAttributes);
|
|
262
400
|
|
|
263
|
-
//
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
|
|
401
|
+
// Browser client has different API signature
|
|
402
|
+
if (this.isBrowserClient) {
|
|
403
|
+
// Browser API: capture(eventName, properties)
|
|
404
|
+
(this.posthog as any)?.capture(payload.name, properties);
|
|
405
|
+
} else {
|
|
406
|
+
// Server API: capture({ distinctId, event, properties, groups })
|
|
407
|
+
const capturePayload: any = {
|
|
408
|
+
distinctId,
|
|
409
|
+
event: payload.name,
|
|
410
|
+
properties,
|
|
411
|
+
};
|
|
267
412
|
|
|
268
|
-
|
|
413
|
+
// Add groups if present in attributes
|
|
414
|
+
if (filteredAttributes?.groups) {
|
|
415
|
+
capturePayload.groups = filteredAttributes.groups;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.posthog?.capture(capturePayload);
|
|
419
|
+
}
|
|
269
420
|
}
|
|
270
421
|
|
|
271
422
|
// Feature Flag Methods
|
|
@@ -524,7 +675,20 @@ export class PostHogSubscriber extends EventSubscriber {
|
|
|
524
675
|
* Handle errors with custom error handler
|
|
525
676
|
*/
|
|
526
677
|
protected handleError(error: Error, payload: EventPayload): void {
|
|
678
|
+
// Call basic onError if provided
|
|
527
679
|
this.config.onError?.(error);
|
|
680
|
+
|
|
681
|
+
// Call enhanced onErrorWithContext if provided
|
|
682
|
+
if (this.config.onErrorWithContext) {
|
|
683
|
+
this.config.onErrorWithContext({
|
|
684
|
+
error,
|
|
685
|
+
eventName: payload.name,
|
|
686
|
+
eventType: payload.type,
|
|
687
|
+
attributes: payload.attributes,
|
|
688
|
+
subscriberName: this.name,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
528
692
|
super.handleError(error, payload);
|
|
529
693
|
}
|
|
530
694
|
}
|