@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.
- package/dist/error-tracking/coercers/promise-rejection-event.d.ts +10 -3
- package/dist/error-tracking/coercers/promise-rejection-event.d.ts.map +1 -1
- package/dist/error-tracking/coercers/promise-rejection-event.js +11 -3
- package/dist/error-tracking/coercers/promise-rejection-event.mjs +12 -4
- package/dist/utils/user-agent-utils.d.ts +7 -1
- package/dist/utils/user-agent-utils.d.ts.map +1 -1
- package/dist/utils/user-agent-utils.js +6 -1
- package/dist/utils/user-agent-utils.mjs +6 -1
- package/package.json +1 -1
- package/src/error-tracking/coercers/promise-rejection-event.ts +26 -17
- package/src/error-tracking/error-properties-builder.coerce.spec.ts +28 -2
- package/src/utils/user-agent-utils.ts +21 -3
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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;
|
|
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
|
|
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,
|
|
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,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<
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
35
|
-
return
|
|
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
|
|
45
|
-
return
|
|
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 {
|
|
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:
|
|
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 (
|
|
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
|
}
|