@posthog/core 1.23.2 → 1.23.3

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.
@@ -1,7 +1,14 @@
1
1
  import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types';
2
- export declare class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<PromiseRejectionEvent> {
3
- match(err: unknown): err is PromiseRejectionEvent;
4
- coerce(err: PromiseRejectionEvent, ctx: CoercingContext): ExceptionLike | undefined;
2
+ type EventWithDetailReason = Event & {
3
+ detail: {
4
+ reason: unknown;
5
+ };
6
+ };
7
+ export declare class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<PromiseRejectionEvent | EventWithDetailReason> {
8
+ match(err: unknown): err is PromiseRejectionEvent | EventWithDetailReason;
9
+ private isCustomEventWrappingRejection;
10
+ coerce(err: PromiseRejectionEvent | EventWithDetailReason, ctx: CoercingContext): ExceptionLike | undefined;
5
11
  private getUnhandledRejectionReason;
6
12
  }
13
+ export {};
7
14
  //# sourceMappingURL=promise-rejection-event.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"promise-rejection-event.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/promise-rejection-event.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG/E,qBAAa,4BAA6B,YAAW,oBAAoB,CAAC,qBAAqB,CAAC;IAC9F,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,qBAAqB;IAIjD,MAAM,CAAC,GAAG,EAAE,qBAAqB,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS;IAcnF,OAAO,CAAC,2BAA2B;CA6BpC"}
1
+ {"version":3,"file":"promise-rejection-event.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/promise-rejection-event.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE/E,KAAK,qBAAqB,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAA;AAGpE,qBAAa,4BAA6B,YAAW,oBAAoB,CACvE,qBAAqB,GAAG,qBAAqB,CAC9C;IACC,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,qBAAqB,GAAG,qBAAqB;IAIzE,OAAO,CAAC,8BAA8B;IAYtC,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,qBAAqB,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS;IAc3G,OAAO,CAAC,2BAA2B;CAsBpC"}
@@ -29,7 +29,16 @@ __webpack_require__.d(__webpack_exports__, {
29
29
  const index_js_namespaceObject = require("../../utils/index.js");
30
30
  class PromiseRejectionEventCoercer {
31
31
  match(err) {
32
- return (0, index_js_namespaceObject.isBuiltin)(err, 'PromiseRejectionEvent');
32
+ return (0, index_js_namespaceObject.isBuiltin)(err, 'PromiseRejectionEvent') || this.isCustomEventWrappingRejection(err);
33
+ }
34
+ isCustomEventWrappingRejection(err) {
35
+ if (!(0, index_js_namespaceObject.isEvent)(err)) return false;
36
+ try {
37
+ const detail = err.detail;
38
+ return null != detail && 'object' == typeof detail && 'reason' in detail;
39
+ } catch {
40
+ return false;
41
+ }
33
42
  }
34
43
  coerce(err, ctx) {
35
44
  const reason = this.getUnhandledRejectionReason(err);
@@ -42,10 +51,9 @@ class PromiseRejectionEventCoercer {
42
51
  return ctx.apply(reason);
43
52
  }
44
53
  getUnhandledRejectionReason(error) {
45
- if ((0, index_js_namespaceObject.isPrimitive)(error)) return error;
46
54
  try {
47
55
  if ('reason' in error) return error.reason;
48
- if ('detail' in error && 'reason' in error.detail) return error.detail.reason;
56
+ if ('detail' in error && null != error.detail && 'object' == typeof error.detail && 'reason' in error.detail) return error.detail.reason;
49
57
  } catch {}
50
58
  return error;
51
59
  }
@@ -1,7 +1,16 @@
1
- import { isBuiltin, isPrimitive } from "../../utils/index.mjs";
1
+ import { isBuiltin, isEvent, isPrimitive } from "../../utils/index.mjs";
2
2
  class PromiseRejectionEventCoercer {
3
3
  match(err) {
4
- return isBuiltin(err, 'PromiseRejectionEvent');
4
+ return isBuiltin(err, 'PromiseRejectionEvent') || this.isCustomEventWrappingRejection(err);
5
+ }
6
+ isCustomEventWrappingRejection(err) {
7
+ if (!isEvent(err)) return false;
8
+ try {
9
+ const detail = err.detail;
10
+ return null != detail && 'object' == typeof detail && 'reason' in detail;
11
+ } catch {
12
+ return false;
13
+ }
5
14
  }
6
15
  coerce(err, ctx) {
7
16
  const reason = this.getUnhandledRejectionReason(err);
@@ -14,10 +23,9 @@ class PromiseRejectionEventCoercer {
14
23
  return ctx.apply(reason);
15
24
  }
16
25
  getUnhandledRejectionReason(error) {
17
- if (isPrimitive(error)) return error;
18
26
  try {
19
27
  if ('reason' in error) return error.reason;
20
- if ('detail' in error && 'reason' in error.detail) return error.detail.reason;
28
+ if ('detail' in error && null != error.detail && 'object' == typeof error.detail && 'reason' in error.detail) return error.detail.reason;
21
29
  } catch {}
22
30
  return error;
23
31
  }
@@ -15,5 +15,11 @@ export declare const detectBrowser: (user_agent: string, vendor: string | undefi
15
15
  export declare const detectBrowserVersion: (userAgent: string, vendor: string | undefined) => number | null;
16
16
  export declare const detectOS: (user_agent: string) => [string, string];
17
17
  export declare const detectDevice: (user_agent: string) => string;
18
- export declare const detectDeviceType: (user_agent: string) => string;
18
+ export declare const detectDeviceType: (user_agent: string, options?: {
19
+ userAgentDataPlatform?: string;
20
+ maxTouchPoints?: number;
21
+ screenWidth?: number;
22
+ screenHeight?: number;
23
+ devicePixelRatio?: number;
24
+ }) => string;
19
25
  //# sourceMappingURL=user-agent-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-agent-utils.d.ts","sourceRoot":"","sources":["../../src/utils/user-agent-utils.ts"],"names":[],"mappings":"AAqFA;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAa,YAAY,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS,KAAG,MA4CvF,CAAA;AAsBD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAa,WAAW,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,IAetG,CAAA;AA0FD,eAAO,MAAM,QAAQ,GAAa,YAAY,MAAM,KAAG,CAAC,MAAM,EAAE,MAAM,CAUrE,CAAA;AAED,eAAO,MAAM,YAAY,GAAa,YAAY,MAAM,KAAG,MAuD1D,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAa,YAAY,MAAM,KAAG,MAmB9D,CAAA"}
1
+ {"version":3,"file":"user-agent-utils.d.ts","sourceRoot":"","sources":["../../src/utils/user-agent-utils.ts"],"names":[],"mappings":"AAqFA;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAa,YAAY,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS,KAAG,MA4CvF,CAAA;AAsBD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAa,WAAW,MAAM,EAAE,QAAQ,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,IAetG,CAAA;AA0FD,eAAO,MAAM,QAAQ,GAAa,YAAY,MAAM,KAAG,CAAC,MAAM,EAAE,MAAM,CAUrE,CAAA;AAED,eAAO,MAAM,YAAY,GAAa,YAAY,MAAM,KAAG,MAuD1D,CAAA;AAED,eAAO,MAAM,gBAAgB,GAC3B,YAAY,MAAM,EAClB,UAAU;IACR,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,KACA,MA4BF,CAAA"}
@@ -359,12 +359,17 @@ const detectDevice = function(user_agent) {
359
359
  else if (new RegExp(TABLET, 'i').test(user_agent) && !new RegExp(TABLET + ' pc', 'i').test(user_agent)) return GENERIC_TABLET;
360
360
  else return '';
361
361
  };
362
- const detectDeviceType = function(user_agent) {
362
+ const detectDeviceType = function(user_agent, options) {
363
363
  const device = detectDevice(user_agent);
364
364
  if (device === IPAD || device === ANDROID_TABLET || 'Kobo' === device || 'Kindle Fire' === device || device === GENERIC_TABLET) return TABLET;
365
365
  if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === OUYA) return 'Console';
366
366
  if (device === APPLE_WATCH) return 'Wearable';
367
367
  if (device) return MOBILE;
368
+ if (options?.userAgentDataPlatform === 'Android' && (options?.maxTouchPoints ?? 0) > 0) {
369
+ const shortSide = Math.min(options?.screenWidth ?? 0, options?.screenHeight ?? 0);
370
+ const shortSideDp = shortSide / (options?.devicePixelRatio ?? 1);
371
+ return shortSideDp >= 600 ? TABLET : MOBILE;
372
+ }
368
373
  return 'Desktop';
369
374
  };
370
375
  exports.detectBrowser = __webpack_exports__.detectBrowser;
@@ -327,12 +327,17 @@ const detectDevice = function(user_agent) {
327
327
  else if (new RegExp(TABLET, 'i').test(user_agent) && !new RegExp(TABLET + ' pc', 'i').test(user_agent)) return GENERIC_TABLET;
328
328
  else return '';
329
329
  };
330
- const detectDeviceType = function(user_agent) {
330
+ const detectDeviceType = function(user_agent, options) {
331
331
  const device = detectDevice(user_agent);
332
332
  if (device === IPAD || device === ANDROID_TABLET || 'Kobo' === device || 'Kindle Fire' === device || device === GENERIC_TABLET) return TABLET;
333
333
  if (device === NINTENDO || device === XBOX || device === PLAYSTATION || device === OUYA) return 'Console';
334
334
  if (device === APPLE_WATCH) return 'Wearable';
335
335
  if (device) return MOBILE;
336
+ if (options?.userAgentDataPlatform === 'Android' && (options?.maxTouchPoints ?? 0) > 0) {
337
+ const shortSide = Math.min(options?.screenWidth ?? 0, options?.screenHeight ?? 0);
338
+ const shortSideDp = shortSide / (options?.devicePixelRatio ?? 1);
339
+ return shortSideDp >= 600 ? TABLET : MOBILE;
340
+ }
336
341
  return 'Desktop';
337
342
  };
338
343
  export { detectBrowser, detectBrowserVersion, detectDevice, detectDeviceType, detectOS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/core",
3
- "version": "1.23.2",
3
+ "version": "1.23.3",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1,13 +1,29 @@
1
- import { isBuiltin, isPrimitive } from '@/utils'
1
+ import { isBuiltin, isEvent, isPrimitive } from '@/utils'
2
2
  import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types'
3
3
 
4
+ type EventWithDetailReason = Event & { detail: { reason: unknown } }
5
+
4
6
  // Web only
5
- export class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<PromiseRejectionEvent> {
6
- match(err: unknown): err is PromiseRejectionEvent {
7
- return isBuiltin(err, 'PromiseRejectionEvent')
7
+ export class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<
8
+ PromiseRejectionEvent | EventWithDetailReason
9
+ > {
10
+ match(err: unknown): err is PromiseRejectionEvent | EventWithDetailReason {
11
+ return isBuiltin(err, 'PromiseRejectionEvent') || this.isCustomEventWrappingRejection(err)
8
12
  }
9
13
 
10
- coerce(err: PromiseRejectionEvent, ctx: CoercingContext): ExceptionLike | undefined {
14
+ private isCustomEventWrappingRejection(err: unknown): err is EventWithDetailReason {
15
+ if (!isEvent(err)) {
16
+ return false
17
+ }
18
+ try {
19
+ const detail = (err as EventWithDetailReason).detail
20
+ return detail != null && typeof detail === 'object' && 'reason' in detail
21
+ } catch {
22
+ return false
23
+ }
24
+ }
25
+
26
+ coerce(err: PromiseRejectionEvent | EventWithDetailReason, ctx: CoercingContext): ExceptionLike | undefined {
11
27
  const reason = this.getUnhandledRejectionReason(err)
12
28
  if (isPrimitive(reason)) {
13
29
  return {
@@ -21,28 +37,21 @@ export class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<Promis
21
37
  }
22
38
  }
23
39
 
24
- private getUnhandledRejectionReason(error: unknown): unknown {
25
- if (isPrimitive(error)) {
26
- return error
27
- }
28
-
29
- // dig the object of the rejection out of known event types
40
+ private getUnhandledRejectionReason(error: PromiseRejectionEvent | EventWithDetailReason): unknown {
30
41
  try {
31
- type ErrorWithReason = { reason: unknown }
32
42
  // PromiseRejectionEvents store the object of the rejection under 'reason'
33
43
  // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
34
- if ('reason' in (error as ErrorWithReason)) {
35
- return (error as ErrorWithReason).reason
44
+ if ('reason' in error) {
45
+ return error.reason
36
46
  }
37
47
 
38
- type CustomEventWithDetail = { detail: { reason: unknown } }
39
48
  // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
40
49
  // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
41
50
  // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
42
51
  // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
43
52
  // https://github.com/getsentry/sentry-javascript/issues/2380
44
- if ('detail' in (error as CustomEventWithDetail) && 'reason' in (error as CustomEventWithDetail).detail) {
45
- return (error as CustomEventWithDetail).detail.reason
53
+ if ('detail' in error && error.detail != null && typeof error.detail === 'object' && 'reason' in error.detail) {
54
+ return error.detail.reason
46
55
  }
47
56
  } catch {
48
57
  // no-empty
@@ -1,4 +1,11 @@
1
- import { DOMExceptionCoercer, ErrorEventCoercer, ErrorCoercer, ObjectCoercer, StringCoercer } from './coercers'
1
+ import {
2
+ DOMExceptionCoercer,
3
+ ErrorEventCoercer,
4
+ ErrorCoercer,
5
+ ObjectCoercer,
6
+ StringCoercer,
7
+ EventCoercer,
8
+ } from './coercers'
2
9
  import { PrimitiveCoercer } from './coercers/primitive-coercer'
3
10
  import { PromiseRejectionEventCoercer } from './coercers/promise-rejection-event'
4
11
  import { ErrorPropertiesBuilder } from './error-properties-builder'
@@ -21,6 +28,7 @@ describe('ErrorPropertiesBuilder', () => {
21
28
  new ErrorEventCoercer(),
22
29
  new ErrorCoercer(),
23
30
  new PromiseRejectionEventCoercer(),
31
+ new EventCoercer(),
24
32
  new ObjectCoercer(),
25
33
  new StringCoercer(),
26
34
  new PrimitiveCoercer(),
@@ -165,7 +173,7 @@ describe('ErrorPropertiesBuilder', () => {
165
173
  const exception = coerceInput(event, syntheticError)
166
174
  expect(exception).toMatchObject({
167
175
  type: 'MouseEvent',
168
- value: "'MouseEvent' captured as exception with keys: [object has no keys]",
176
+ value: 'MouseEvent captured as exception with keys: [object has no keys]',
169
177
  stack: syntheticError.stack,
170
178
  synthetic: true,
171
179
  })
@@ -199,5 +207,23 @@ describe('ErrorPropertiesBuilder', () => {
199
207
  synthetic: false,
200
208
  })
201
209
  })
210
+
211
+ it('should extract the buried Error from a CustomEvent wrapping a PromiseRejectionEvent', () => {
212
+ const buriedError = new Error('Extension context invalidated.')
213
+ const customEvent = new CustomEvent('unhandledrejection', {
214
+ detail: {
215
+ reason: buriedError,
216
+ promise: Promise.resolve(),
217
+ },
218
+ })
219
+
220
+ const exception = coerceInput(customEvent)
221
+
222
+ expect(exception).toMatchObject({
223
+ type: 'Error',
224
+ value: 'Extension context invalidated.',
225
+ stack: buriedError.stack,
226
+ })
227
+ })
202
228
  })
203
229
  })
@@ -336,7 +336,16 @@ export const detectDevice = function (user_agent: string): string {
336
336
  }
337
337
  }
338
338
 
339
- export const detectDeviceType = function (user_agent: string): string {
339
+ export const detectDeviceType = function (
340
+ user_agent: string,
341
+ options?: {
342
+ userAgentDataPlatform?: string
343
+ maxTouchPoints?: number
344
+ screenWidth?: number
345
+ screenHeight?: number
346
+ devicePixelRatio?: number
347
+ }
348
+ ): string {
340
349
  const device = detectDevice(user_agent)
341
350
  if (
342
351
  device === IPAD ||
@@ -352,7 +361,16 @@ export const detectDeviceType = function (user_agent: string): string {
352
361
  return 'Wearable'
353
362
  } else if (device) {
354
363
  return MOBILE
355
- } else {
356
- return 'Desktop'
357
364
  }
365
+
366
+ // Chrome on Android tablets defaults to "request desktop site" mode, sending
367
+ // a desktop-like UA (e.g. "X11; Linux x86_64") indistinguishable from desktop Linux.
368
+ // The Client Hints API reports the true platform even when the UA lies.
369
+ if (options?.userAgentDataPlatform === 'Android' && (options?.maxTouchPoints ?? 0) > 0) {
370
+ const shortSide = Math.min(options?.screenWidth ?? 0, options?.screenHeight ?? 0)
371
+ const shortSideDp = shortSide / (options?.devicePixelRatio ?? 1)
372
+ return shortSideDp >= 600 ? TABLET : MOBILE
373
+ }
374
+
375
+ return 'Desktop'
358
376
  }