posthog-node 4.2.3 → 4.3.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Next
2
2
 
3
+ # 4.3.1 - 2024-11-26
4
+
5
+ 1. Fix bug where this SDK incorrectly sent `$feature_flag_called` events with null values when using `getFeatureFlagPayload`.
6
+
7
+ # 4.3.0 - 2024-11-25
8
+
9
+ 1. Add Sentry v8 support to the Sentry integration
10
+
3
11
  # 4.2.3 - 2024-11-21
4
12
 
5
13
  1. fix: identify method allows passing a $set_once object
package/lib/index.cjs.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var rusha = require('rusha');
6
6
 
7
- var version = "4.2.3";
7
+ var version = "4.3.1";
8
8
 
9
9
  var PostHogPersistedProperty;
10
10
  (function (PostHogPersistedProperty) {
@@ -2234,44 +2234,70 @@ class PostHog extends PostHogCoreStateless {
2234
2234
  async getFeatureFlagPayload(key, distinctId, matchValue, options) {
2235
2235
  const {
2236
2236
  groups,
2237
- disableGeoip
2238
- } = options || {};
2239
- let {
2240
- onlyEvaluateLocally,
2241
- sendFeatureFlagEvents,
2237
+ disableGeoip,
2238
+ onlyEvaluateLocally = false,
2242
2239
  personProperties,
2243
2240
  groupProperties
2244
2241
  } = options || {};
2245
- const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2246
- personProperties = adjustedProperties.allPersonProperties;
2247
- groupProperties = adjustedProperties.allGroupProperties;
2248
- let response = undefined;
2249
- // Try to get match value locally if not provided
2250
- if (!matchValue) {
2242
+ const {
2243
+ allPersonProperties,
2244
+ allGroupProperties
2245
+ } = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2246
+ if (matchValue === undefined) {
2251
2247
  matchValue = await this.getFeatureFlag(key, distinctId, {
2252
2248
  ...options,
2253
- onlyEvaluateLocally: true
2249
+ onlyEvaluateLocally: true,
2250
+ sendFeatureFlagEvents: false
2254
2251
  });
2255
2252
  }
2253
+ let response;
2254
+ let payload;
2256
2255
  if (matchValue) {
2257
- response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
2258
- }
2259
- // set defaults
2260
- if (onlyEvaluateLocally == undefined) {
2261
- onlyEvaluateLocally = false;
2262
- }
2263
- if (sendFeatureFlagEvents == undefined) {
2264
- sendFeatureFlagEvents = true;
2265
- }
2266
- // set defaults
2267
- if (onlyEvaluateLocally == undefined) {
2268
- onlyEvaluateLocally = false;
2256
+ response = matchValue;
2257
+ payload = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue);
2258
+ } else {
2259
+ response = undefined;
2260
+ payload = undefined;
2269
2261
  }
2270
- const payloadWasLocallyEvaluated = response !== undefined;
2271
- if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
2272
- response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
2262
+ // Determine if the payload was evaluated locally
2263
+ const payloadWasLocallyEvaluated = payload !== undefined;
2264
+ // Fetch final flags and payloads either locally or from the remote server
2265
+ let fetchedOrLocalFlags;
2266
+ let fetchedOrLocalPayloads;
2267
+ if (payloadWasLocallyEvaluated || onlyEvaluateLocally) {
2268
+ if (response !== undefined) {
2269
+ fetchedOrLocalFlags = {
2270
+ [key]: response
2271
+ };
2272
+ fetchedOrLocalPayloads = {
2273
+ [key]: payload
2274
+ };
2275
+ } else {
2276
+ fetchedOrLocalFlags = {};
2277
+ fetchedOrLocalPayloads = {};
2278
+ }
2279
+ } else {
2280
+ const fetchedData = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, allPersonProperties, allGroupProperties, disableGeoip);
2281
+ fetchedOrLocalFlags = fetchedData.flags || {};
2282
+ fetchedOrLocalPayloads = fetchedData.payloads || {};
2273
2283
  }
2274
- return response;
2284
+ const finalResponse = fetchedOrLocalFlags[key];
2285
+ const finalPayload = fetchedOrLocalPayloads[key];
2286
+ const finalLocallyEvaluated = payloadWasLocallyEvaluated;
2287
+ this.capture({
2288
+ distinctId,
2289
+ event: '$feature_flag_called',
2290
+ properties: {
2291
+ $feature_flag: key,
2292
+ $feature_flag_response: finalResponse,
2293
+ $feature_flag_payload: finalPayload,
2294
+ locally_evaluated: finalLocallyEvaluated,
2295
+ [`$feature/${key}`]: finalResponse
2296
+ },
2297
+ groups,
2298
+ disableGeoip
2299
+ });
2300
+ return finalPayload;
2275
2301
  }
2276
2302
  async isFeatureEnabled(key, distinctId, options) {
2277
2303
  const feat = await this.getFeatureFlag(key, distinctId, options);
@@ -2365,6 +2391,9 @@ class PostHog extends PostHogCoreStateless {
2365
2391
  }
2366
2392
  }
2367
2393
 
2394
+ /**
2395
+ * @file Adapted from [posthog-js](https://github.com/PostHog/posthog-js/blob/8157df935a4d0e71d2fefef7127aa85ee51c82d1/src/extensions/sentry-integration.ts) with modifications for the Node SDK.
2396
+ */
2368
2397
  /**
2369
2398
  * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog.
2370
2399
  *
@@ -2383,59 +2412,96 @@ class PostHog extends PostHogCoreStateless {
2383
2412
  * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry
2384
2413
  * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry
2385
2414
  * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
2415
+ * @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
2386
2416
  */
2387
- class PostHogSentryIntegration {
2388
- constructor(posthog, posthogHost, organization, prefix) {
2389
- this.posthog = posthog;
2390
- this.posthogHost = posthogHost;
2391
- this.organization = organization;
2392
- this.prefix = prefix;
2393
- this.name = 'posthog-node';
2394
- this.posthogHost = posthog.options.host ?? 'https://us.i.posthog.com';
2395
- }
2396
- setupOnce(addGlobalEventProcessor, getCurrentHub) {
2397
- addGlobalEventProcessor(event => {
2398
- if (event.exception?.values === undefined || event.exception.values.length === 0) {
2399
- return event;
2400
- }
2401
- if (!event.tags) {
2402
- event.tags = {};
2403
- }
2404
- const sentry = getCurrentHub();
2405
- // Get the PostHog user ID from a specific tag, which users can set on their Sentry scope as they need.
2406
- const userId = event.tags[PostHogSentryIntegration.POSTHOG_ID_TAG];
2407
- if (userId === undefined) {
2408
- // 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.
2409
- return event;
2410
- }
2411
- event.tags['PostHog Person URL'] = new URL(`/person/${userId}`, this.posthogHost).toString();
2412
- const properties = {
2413
- // PostHog Exception Properties
2414
- $exception_message: event.exception.values[0]?.value,
2415
- $exception_type: event.exception.values[0]?.type,
2416
- $exception_personURL: event.tags['PostHog Person URL'],
2417
- // Sentry Exception Properties
2418
- $sentry_event_id: event.event_id,
2419
- $sentry_exception: event.exception,
2420
- $sentry_exception_message: event.exception.values[0]?.value,
2421
- $sentry_exception_type: event.exception.values[0]?.type,
2422
- $sentry_tags: event.tags
2423
- };
2424
- const projectId = sentry.getClient()?.getDsn()?.projectId;
2425
- if (this.organization !== undefined && projectId !== undefined && event.event_id !== undefined) {
2426
- properties.$sentry_url = `${this.prefix ?? 'https://sentry.io/organizations'}/${this.organization}/issues/?project=${projectId}&query=${event.event_id}`;
2427
- }
2428
- this.posthog.capture({
2429
- event: '$exception',
2430
- distinctId: userId,
2431
- properties
2432
- });
2417
+ const severityLevels = ['fatal', 'error', 'warning', 'log', 'info', 'debug'];
2418
+ const NAME = 'posthog-node';
2419
+ function createEventProcessor(_posthog, {
2420
+ organization,
2421
+ projectId,
2422
+ prefix,
2423
+ severityAllowList = ['error']
2424
+ } = {}) {
2425
+ return event => {
2426
+ const shouldProcessLevel = severityAllowList === '*' || severityAllowList.includes(event.level);
2427
+ if (!shouldProcessLevel) {
2433
2428
  return event;
2429
+ }
2430
+ if (!event.tags) {
2431
+ event.tags = {};
2432
+ }
2433
+ // Get the PostHog user ID from a specific tag, which users can set on their Sentry scope as they need.
2434
+ const userId = event.tags[PostHogSentryIntegration.POSTHOG_ID_TAG];
2435
+ if (userId === undefined) {
2436
+ // 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.
2437
+ return event;
2438
+ }
2439
+ const uiHost = _posthog.options.host ?? 'https://us.i.posthog.com';
2440
+ const personUrl = new URL(`/project/${_posthog.apiKey}/person/${userId}`, uiHost).toString();
2441
+ event.tags['PostHog Person URL'] = personUrl;
2442
+ const exceptions = event.exception?.values || [];
2443
+ exceptions.map(exception => {
2444
+ if (exception.stacktrace) {
2445
+ exception.stacktrace.type = 'raw';
2446
+ }
2447
+ });
2448
+ const properties = {
2449
+ // PostHog Exception Properties,
2450
+ $exception_message: exceptions[0]?.value || event.message,
2451
+ $exception_type: exceptions[0]?.type,
2452
+ $exception_personURL: personUrl,
2453
+ $exception_level: event.level,
2454
+ $exception_list: exceptions,
2455
+ // Sentry Exception Properties
2456
+ $sentry_event_id: event.event_id,
2457
+ $sentry_exception: event.exception,
2458
+ $sentry_exception_message: exceptions[0]?.value || event.message,
2459
+ $sentry_exception_type: exceptions[0]?.type,
2460
+ $sentry_tags: event.tags
2461
+ };
2462
+ if (organization && projectId) {
2463
+ properties['$sentry_url'] = (prefix || 'https://sentry.io/organizations/') + organization + '/issues/?project=' + projectId + '&query=' + event.event_id;
2464
+ }
2465
+ _posthog.capture({
2466
+ event: '$exception',
2467
+ distinctId: userId,
2468
+ properties
2434
2469
  });
2470
+ return event;
2471
+ };
2472
+ }
2473
+ // V8 integration - function based
2474
+ function sentryIntegration(_posthog, options) {
2475
+ const processor = createEventProcessor(_posthog, options);
2476
+ return {
2477
+ name: NAME,
2478
+ processEvent(event) {
2479
+ return processor(event);
2480
+ }
2481
+ };
2482
+ }
2483
+ // V7 integration - class based
2484
+ class PostHogSentryIntegration {
2485
+ constructor(_posthog, organization, prefix, severityAllowList) {
2486
+ this.name = NAME;
2487
+ // setupOnce gets called by Sentry when it intializes the plugin
2488
+ this.name = NAME;
2489
+ this.setupOnce = function (addGlobalEventProcessor, getCurrentHub) {
2490
+ const projectId = getCurrentHub()?.getClient()?.getDsn()?.projectId;
2491
+ addGlobalEventProcessor(createEventProcessor(_posthog, {
2492
+ organization,
2493
+ projectId,
2494
+ prefix,
2495
+ severityAllowList
2496
+ }));
2497
+ };
2435
2498
  }
2436
2499
  }
2437
2500
  PostHogSentryIntegration.POSTHOG_ID_TAG = 'posthog_distinct_id';
2438
2501
 
2439
2502
  exports.PostHog = PostHog;
2440
2503
  exports.PostHogSentryIntegration = PostHogSentryIntegration;
2504
+ exports.createEventProcessor = createEventProcessor;
2505
+ exports.sentryIntegration = sentryIntegration;
2506
+ exports.severityLevels = severityLevels;
2441
2507
  //# sourceMappingURL=index.cjs.js.map