@openobserve/browser-core 0.3.2 → 0.3.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/cjs/boot/init.js +1 -1
- package/cjs/domain/configuration/endpointBuilder.js +1 -1
- package/cjs/domain/tags.js +1 -1
- package/cjs/domain/telemetry/telemetry.js +1 -1
- package/cjs/domain/telemetry/telemetryEvent.types.d.ts +1 -1
- package/esm/boot/init.js +1 -1
- package/esm/domain/configuration/endpointBuilder.js +1 -1
- package/esm/domain/tags.js +1 -1
- package/esm/domain/telemetry/telemetry.js +1 -1
- package/esm/domain/telemetry/telemetryEvent.types.d.ts +1 -1
- package/package.json +2 -2
- package/src/domain/telemetry/telemetryEvent.types.ts +1 -1
- package/src/boot/displayAlreadyInitializedError.spec.ts +0 -18
- package/src/boot/init.spec.ts +0 -50
- package/src/browser/addEventListener.spec.ts +0 -141
- package/src/browser/cookie.spec.ts +0 -53
- package/src/browser/fetchObservable.spec.ts +0 -334
- package/src/browser/pageMayExitObservable.spec.ts +0 -56
- package/src/browser/xhrObservable.spec.ts +0 -405
- package/src/domain/allowedTrackingOrigins.spec.ts +0 -210
- package/src/domain/bufferedData.spec.ts +0 -34
- package/src/domain/configuration/configuration.spec.ts +0 -236
- package/src/domain/configuration/endpointBuilder.spec.ts +0 -173
- package/src/domain/configuration/transportConfiguration.spec.ts +0 -130
- package/src/domain/connectivity/connectivity.spec.ts +0 -50
- package/src/domain/console/consoleObservable.spec.ts +0 -151
- package/src/domain/context/contextManager.spec.ts +0 -152
- package/src/domain/context/contextUtils.spec.ts +0 -26
- package/src/domain/context/storeContextManager.spec.ts +0 -103
- package/src/domain/contexts/accountContext.spec.ts +0 -99
- package/src/domain/contexts/globalContext.spec.ts +0 -88
- package/src/domain/contexts/userContext.spec.ts +0 -156
- package/src/domain/error/error.spec.ts +0 -294
- package/src/domain/error/trackRuntimeError.spec.ts +0 -343
- package/src/domain/eventRateLimiter/createEventRateLimiter.spec.ts +0 -119
- package/src/domain/extension/extensionUtils.spec.ts +0 -47
- package/src/domain/report/reportObservable.spec.ts +0 -71
- package/src/domain/session/oldCookiesMigration.spec.ts +0 -76
- package/src/domain/session/sessionManager.spec.ts +0 -684
- package/src/domain/session/sessionState.spec.ts +0 -88
- package/src/domain/session/sessionStore.spec.ts +0 -647
- package/src/domain/session/sessionStoreOperations.spec.ts +0 -234
- package/src/domain/session/storeStrategies/sessionInCookie.spec.ts +0 -228
- package/src/domain/session/storeStrategies/sessionInLocalStorage.spec.ts +0 -75
- package/src/domain/synthetics/syntheticsWorkerValues.spec.ts +0 -82
- package/src/domain/tags.spec.ts +0 -74
- package/src/domain/telemetry/telemetry.spec.ts +0 -494
- package/src/domain/trackingConsent.spec.ts +0 -44
- package/src/tools/abstractHooks.spec.ts +0 -76
- package/src/tools/abstractLifeCycle.spec.ts +0 -46
- package/src/tools/boundedBuffer.spec.ts +0 -40
- package/src/tools/catchUserErrors.spec.ts +0 -20
- package/src/tools/encoder.spec.ts +0 -112
- package/src/tools/experimentalFeatures.spec.ts +0 -60
- package/src/tools/getZoneJsOriginalValue.spec.ts +0 -36
- package/src/tools/instrumentMethod.spec.ts +0 -381
- package/src/tools/matchOption.spec.ts +0 -41
- package/src/tools/mergeInto.spec.ts +0 -198
- package/src/tools/monitor.spec.ts +0 -172
- package/src/tools/observable.spec.ts +0 -259
- package/src/tools/queueMicrotask.spec.ts +0 -24
- package/src/tools/readBytesFromStream.spec.ts +0 -62
- package/src/tools/requestIdleCallback.spec.ts +0 -65
- package/src/tools/serialisation/jsonStringify.spec.ts +0 -76
- package/src/tools/serialisation/sanitize.spec.ts +0 -284
- package/src/tools/stackTrace/capturedExceptions.specHelper.ts +0 -316
- package/src/tools/stackTrace/computeStackTrace.spec.ts +0 -1007
- package/src/tools/stackTrace/handlingStack.spec.ts +0 -20
- package/src/tools/taskQueue.spec.ts +0 -60
- package/src/tools/timer.spec.ts +0 -76
- package/src/tools/utils/browserDetection.spec.ts +0 -120
- package/src/tools/utils/byteUtils.spec.ts +0 -29
- package/src/tools/utils/functionUtils.spec.ts +0 -229
- package/src/tools/utils/numberUtils.spec.ts +0 -27
- package/src/tools/utils/stringUtils.spec.ts +0 -74
- package/src/tools/utils/typeUtils.spec.ts +0 -25
- package/src/tools/utils/urlPolyfill.spec.ts +0 -55
- package/src/tools/valueHistory.spec.ts +0 -180
- package/src/transport/batch.spec.ts +0 -261
- package/src/transport/eventBridge.spec.ts +0 -90
- package/src/transport/flushController.spec.ts +0 -267
- package/src/transport/httpRequest.spec.ts +0 -400
- package/src/transport/sendWithRetryStrategy.spec.ts +0 -393
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { disableJasmineUncaughtExceptionTracking, wait } from '../../../test'
|
|
2
|
-
import type { UnhandledErrorCallback } from './trackRuntimeError'
|
|
3
|
-
import { instrumentOnError, instrumentUnhandledRejection, trackRuntimeError } from './trackRuntimeError'
|
|
4
|
-
import type { RawError } from './error.types'
|
|
5
|
-
|
|
6
|
-
describe('trackRuntimeError', () => {
|
|
7
|
-
const ERROR_MESSAGE = 'foo'
|
|
8
|
-
|
|
9
|
-
const errorViaTrackRuntimeError = async (callback: () => void): Promise<RawError> => {
|
|
10
|
-
disableJasmineUncaughtExceptionTracking()
|
|
11
|
-
|
|
12
|
-
const errorObservable = trackRuntimeError()
|
|
13
|
-
const errorNotification = new Promise<RawError>((resolve) => {
|
|
14
|
-
errorObservable.subscribe((e: RawError) => resolve(e))
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
await invokeAndWaitForErrorHandlers(callback)
|
|
19
|
-
return await errorNotification
|
|
20
|
-
} finally {
|
|
21
|
-
stop()
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
it('should collect unhandled error', async () => {
|
|
26
|
-
const error = await errorViaTrackRuntimeError(() => {
|
|
27
|
-
throw new Error(ERROR_MESSAGE)
|
|
28
|
-
})
|
|
29
|
-
expect(error.message).toEqual(ERROR_MESSAGE)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('should collect unhandled rejection', async () => {
|
|
33
|
-
if (!('onunhandledrejection' in window)) {
|
|
34
|
-
pending('onunhandledrejection not supported')
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const error = await errorViaTrackRuntimeError(() => {
|
|
38
|
-
// Reject with a string instead of an Error here because Jasmine forwards the
|
|
39
|
-
// unhandled rejection to the onerror handler with the wrong argument structure if
|
|
40
|
-
// you use an Error. (It uses the argument structure you'd use for
|
|
41
|
-
// addEventListener('error'). We could make our error processing code robust to
|
|
42
|
-
// that, but since it's a Jasmine-specific issue with a simple workaround, it makes
|
|
43
|
-
// sense to just work around it here.
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
45
|
-
void Promise.reject(ERROR_MESSAGE)
|
|
46
|
-
})
|
|
47
|
-
expect(error.message).toEqual(jasmine.stringContaining(ERROR_MESSAGE))
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
describe('instrumentOnError', () => {
|
|
52
|
-
const testLineNo = 1337
|
|
53
|
-
const testColNo = 42
|
|
54
|
-
const ERROR_MESSAGE = 'foo'
|
|
55
|
-
|
|
56
|
-
const spyViaInstrumentOnError = async (callback: () => void) => {
|
|
57
|
-
const onErrorSpy = spyOn(window as any, 'onerror')
|
|
58
|
-
const callbackSpy = jasmine.createSpy<UnhandledErrorCallback>()
|
|
59
|
-
const { stop } = instrumentOnError(callbackSpy)
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
await invokeAndWaitForErrorHandlers(callback)
|
|
63
|
-
expect(onErrorSpy).toHaveBeenCalled()
|
|
64
|
-
return callbackSpy
|
|
65
|
-
} finally {
|
|
66
|
-
stop()
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
it('should call original error handler', async () => {
|
|
71
|
-
// withInstrumentOnError() asserts that the original error handler has been called for
|
|
72
|
-
// every test, so we don't need an explicit expectation here.
|
|
73
|
-
await spyViaInstrumentOnError(() => {
|
|
74
|
-
throw new Error(ERROR_MESSAGE)
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('should notify unhandled error instance', async () => {
|
|
79
|
-
const error = new Error(ERROR_MESSAGE)
|
|
80
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
81
|
-
throw error
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const [originalError, stack] = spy.calls.mostRecent().args
|
|
85
|
-
expect(originalError).toBe(error)
|
|
86
|
-
expect(stack).toBeUndefined()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('should notify unhandled string', async () => {
|
|
90
|
-
const error = 'foo' as any
|
|
91
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
92
|
-
throw error
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const [originalError, stack] = spy.calls.mostRecent().args
|
|
96
|
-
expect(originalError).toBe(error)
|
|
97
|
-
expect(stack).toBeDefined()
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should notify unhandled object', async () => {
|
|
101
|
-
const error = { a: 'foo' } as any
|
|
102
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
103
|
-
throw error
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const [originalError, stack] = spy.calls.mostRecent().args
|
|
107
|
-
expect(originalError).toBe(error)
|
|
108
|
-
expect(stack).toBeDefined()
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
describe('uncaught exception handling', () => {
|
|
112
|
-
it('should not go into an infinite loop', async () => {
|
|
113
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
114
|
-
throw new Error('expected error')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
expect(spy).toHaveBeenCalledTimes(1)
|
|
118
|
-
await wait(1000)
|
|
119
|
-
expect(spy).toHaveBeenCalledTimes(1)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('should get extra arguments (isWindowError and exception)', async () => {
|
|
123
|
-
const exception = new Error('expected error')
|
|
124
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
125
|
-
throw exception
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
expect(spy).toHaveBeenCalledTimes(1)
|
|
129
|
-
await wait(1000)
|
|
130
|
-
expect(spy).toHaveBeenCalledTimes(1)
|
|
131
|
-
const [reportedError] = spy.calls.mostRecent().args
|
|
132
|
-
expect(reportedError).toEqual(exception)
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
describe('should handle direct onerror calls', () => {
|
|
137
|
-
it('with objects', async () => {
|
|
138
|
-
const error = { foo: 'bar' } as any
|
|
139
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
140
|
-
window.onerror!(error, 'http://example.com', testLineNo, testColNo)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const [originalError, stack] = spy.calls.mostRecent().args
|
|
144
|
-
expect(originalError).toBe(error)
|
|
145
|
-
expect(stack).toBeDefined()
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
describe('with undefined arguments', () => {
|
|
149
|
-
it('discards the stack', async () => {
|
|
150
|
-
// this is probably not good behavior; just writing this test to verify
|
|
151
|
-
// that it doesn't change unintentionally
|
|
152
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
153
|
-
window.onerror!(undefined!, undefined, testLineNo)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
157
|
-
expect(stack).toBeUndefined()
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
describe('when no 5th argument (error object)', () => {
|
|
162
|
-
it('should separate name, message for default error types (e.g. ReferenceError)', async () => {
|
|
163
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
164
|
-
window.onerror!('ReferenceError: foo is undefined', 'http://example.com', testLineNo)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
168
|
-
expect(stack!.name).toEqual('ReferenceError')
|
|
169
|
-
expect(stack!.message).toEqual('foo is undefined')
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('should separate name, message for default error types (e.g. Uncaught ReferenceError)', async () => {
|
|
173
|
-
// should work with/without 'Uncaught'
|
|
174
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
175
|
-
window.onerror!('Uncaught ReferenceError: foo is undefined', 'http://example.com', testLineNo)
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
179
|
-
expect(stack!.name).toEqual('ReferenceError')
|
|
180
|
-
expect(stack!.message).toEqual('foo is undefined')
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
it('should separate name, message for default error types on Opera Mini', async () => {
|
|
184
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
185
|
-
window.onerror!(
|
|
186
|
-
'Uncaught exception: ReferenceError: Undefined variable: foo',
|
|
187
|
-
'http://example.com',
|
|
188
|
-
testLineNo
|
|
189
|
-
)
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
193
|
-
expect(stack!.name).toEqual('ReferenceError')
|
|
194
|
-
expect(stack!.message).toEqual('Undefined variable: foo')
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('should separate name, message for error with multiline message', async () => {
|
|
198
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
199
|
-
window.onerror!(
|
|
200
|
-
"TypeError: foo is not a function. (In 'my.function(\n foo)",
|
|
201
|
-
'http://example.com',
|
|
202
|
-
testLineNo
|
|
203
|
-
)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
207
|
-
expect(stack!.message).toEqual("foo is not a function. (In 'my.function(\n foo)")
|
|
208
|
-
expect(stack!.name).toEqual('TypeError')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('should ignore unknown error types', async () => {
|
|
212
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
213
|
-
window.onerror!('CustomError: woo scary', 'http://example.com', testLineNo)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
// TODO: should we attempt to parse this?
|
|
217
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
218
|
-
expect(stack!.name).toEqual(undefined)
|
|
219
|
-
expect(stack!.message).toEqual('CustomError: woo scary')
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('should ignore arbitrary messages passed through onerror', async () => {
|
|
223
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
224
|
-
window.onerror!('all work and no play makes homer: something something', 'http://example.com', testLineNo)
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
const [, stack] = spy.calls.mostRecent().args
|
|
228
|
-
expect(stack!.name).toEqual(undefined)
|
|
229
|
-
expect(stack!.message).toEqual('all work and no play makes homer: something something')
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('should handle object message passed through onerror', async () => {
|
|
233
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
234
|
-
window.onerror!({ foo: 'bar' } as any, 'http://example.com', testLineNo, testColNo)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
const [error, stack] = spy.calls.mostRecent().args
|
|
238
|
-
expect(stack!.message).toBeUndefined()
|
|
239
|
-
expect(error).toEqual({ foo: 'bar' }) // consider the message as initial error
|
|
240
|
-
})
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
describe('when 5th argument (errorObj) is not of type Error', () => {
|
|
244
|
-
it('should handle strings', async () => {
|
|
245
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
246
|
-
window.onerror!(
|
|
247
|
-
'Any error message',
|
|
248
|
-
'https://example.com',
|
|
249
|
-
testLineNo,
|
|
250
|
-
testColNo,
|
|
251
|
-
'Actual Error Message' as any
|
|
252
|
-
)
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
const [error, stack] = spy.calls.mostRecent().args
|
|
256
|
-
expect(stack!.message).toBe('Any error message')
|
|
257
|
-
expect(stack!.stack).toEqual([{ url: 'https://example.com', column: testColNo, line: testLineNo }])
|
|
258
|
-
expect(error).toEqual('Actual Error Message')
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should handle objects', async () => {
|
|
262
|
-
const spy = await spyViaInstrumentOnError(() => {
|
|
263
|
-
window.onerror!('Any error message', 'https://example.com', testLineNo, testColNo, {
|
|
264
|
-
message: 'SyntaxError',
|
|
265
|
-
data: 'foo',
|
|
266
|
-
} as any)
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const [error, stack] = spy.calls.mostRecent().args
|
|
270
|
-
expect(stack!.message).toBe('Any error message')
|
|
271
|
-
expect(stack!.stack).toEqual([{ url: 'https://example.com', column: testColNo, line: testLineNo }])
|
|
272
|
-
expect(error).toEqual({ message: 'SyntaxError', data: 'foo' })
|
|
273
|
-
})
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
describe('instrumentUnhandledRejection', () => {
|
|
279
|
-
const ERROR_MESSAGE = 'foo'
|
|
280
|
-
|
|
281
|
-
const spyViaInstrumentOnUnhandledRejection = async (callback: () => void) => {
|
|
282
|
-
if (!('onunhandledrejection' in window)) {
|
|
283
|
-
pending('onunhandledrejection not supported')
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const onUnhandledRejectionSpy = spyOn(window as any, 'onunhandledrejection')
|
|
287
|
-
const callbackSpy = jasmine.createSpy<UnhandledErrorCallback>()
|
|
288
|
-
const { stop } = instrumentUnhandledRejection(callbackSpy)
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
await invokeAndWaitForErrorHandlers(callback)
|
|
292
|
-
expect(onUnhandledRejectionSpy).toHaveBeenCalled()
|
|
293
|
-
return callbackSpy
|
|
294
|
-
} finally {
|
|
295
|
-
stop()
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
it('should call original unhandled rejection handler', async () => {
|
|
300
|
-
// withInstrumentOnUnhandledRejection() asserts that the original unhandled
|
|
301
|
-
// rejection handler has been called for every test, so we don't need an
|
|
302
|
-
// explicit expectation here.
|
|
303
|
-
await spyViaInstrumentOnUnhandledRejection(() => {
|
|
304
|
-
window.onunhandledrejection!({
|
|
305
|
-
reason: new Error(ERROR_MESSAGE),
|
|
306
|
-
} as PromiseRejectionEvent)
|
|
307
|
-
})
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
it('should notify unhandled rejection', async () => {
|
|
311
|
-
const reason = new Error(ERROR_MESSAGE)
|
|
312
|
-
const spy = await spyViaInstrumentOnUnhandledRejection(() => {
|
|
313
|
-
window.onunhandledrejection!({ reason } as PromiseRejectionEvent)
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
const [originalError, stack] = spy.calls.mostRecent().args
|
|
317
|
-
expect(originalError).toBe(reason)
|
|
318
|
-
expect(stack).toBeUndefined()
|
|
319
|
-
})
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Invokes the given callback, which is expected to be a function that throws or generates
|
|
324
|
-
* an unhandled promise rejection, and returns a promise that resolves after the callback
|
|
325
|
-
* has finished running and any global error handlers that run as a result are complete.
|
|
326
|
-
*/
|
|
327
|
-
function invokeAndWaitForErrorHandlers(callback: () => void): Promise<void> {
|
|
328
|
-
return new Promise<void>((resolve) => {
|
|
329
|
-
setTimeout(() => {
|
|
330
|
-
try {
|
|
331
|
-
// Invoke the callback.
|
|
332
|
-
callback()
|
|
333
|
-
} finally {
|
|
334
|
-
// The callback has generated an error here, but global error handlers
|
|
335
|
-
// have not yet run. The global unhandledrejection handler will run at
|
|
336
|
-
// the end of the next microtask checkpoint; the global error handler
|
|
337
|
-
// will run in a later macrotask. So, schedule a new task to resolve
|
|
338
|
-
// the promise after both of those things have happened.
|
|
339
|
-
setTimeout(resolve)
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
})
|
|
343
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { Clock } from '../../../test'
|
|
2
|
-
import { mockClock } from '../../../test'
|
|
3
|
-
import { relativeToClocks, ONE_MINUTE } from '../../tools/utils/timeUtils'
|
|
4
|
-
import { noop } from '../../tools/utils/functionUtils'
|
|
5
|
-
import type { RawError } from '../error/error.types'
|
|
6
|
-
import { createEventRateLimiter } from './createEventRateLimiter'
|
|
7
|
-
import type { EventRateLimiter } from './createEventRateLimiter'
|
|
8
|
-
|
|
9
|
-
describe('createEventRateLimiter', () => {
|
|
10
|
-
let eventLimiter: EventRateLimiter | undefined
|
|
11
|
-
let clock: Clock
|
|
12
|
-
const limit = 1
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
clock = mockClock()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('returns false if the limit is not reached', () => {
|
|
18
|
-
eventLimiter = createEventRateLimiter('error', noop, limit)
|
|
19
|
-
|
|
20
|
-
expect(eventLimiter.isLimitReached()).toBe(false)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('returns true if the limit is reached', () => {
|
|
24
|
-
eventLimiter = createEventRateLimiter('error', noop, limit)
|
|
25
|
-
|
|
26
|
-
eventLimiter.isLimitReached()
|
|
27
|
-
expect(eventLimiter.isLimitReached()).toBe(true)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('returns false again when one minute is passed after the first counted error', () => {
|
|
31
|
-
eventLimiter = createEventRateLimiter('error', noop, limit)
|
|
32
|
-
|
|
33
|
-
eventLimiter.isLimitReached()
|
|
34
|
-
eventLimiter.isLimitReached()
|
|
35
|
-
clock.tick(ONE_MINUTE)
|
|
36
|
-
expect(eventLimiter.isLimitReached()).toBe(false)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('calls the "onLimitReached" callback with the raw "limit reached" error when the limit is reached', () => {
|
|
40
|
-
const onLimitReachedSpy = jasmine.createSpy<(rawError: RawError) => void>()
|
|
41
|
-
eventLimiter = createEventRateLimiter('error', onLimitReachedSpy, limit)
|
|
42
|
-
|
|
43
|
-
eventLimiter.isLimitReached()
|
|
44
|
-
eventLimiter.isLimitReached()
|
|
45
|
-
expect(onLimitReachedSpy).toHaveBeenCalledOnceWith({
|
|
46
|
-
message: 'Reached max number of errors by minute: 1',
|
|
47
|
-
source: 'agent',
|
|
48
|
-
startClocks: relativeToClocks(clock.relative(0)),
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('returns false when called from the "onLimitReached" callback to bypass the limit for the "limit reached" error', () => {
|
|
53
|
-
eventLimiter = createEventRateLimiter(
|
|
54
|
-
'error',
|
|
55
|
-
() => {
|
|
56
|
-
expect(eventLimiter!.isLimitReached()).toBe(false)
|
|
57
|
-
},
|
|
58
|
-
limit
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
eventLimiter.isLimitReached()
|
|
62
|
-
eventLimiter.isLimitReached()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('does not call the "onLimitReached" callback more than once when the limit is reached', () => {
|
|
66
|
-
const onLimitReachedSpy = jasmine.createSpy<(rawError: RawError) => void>()
|
|
67
|
-
eventLimiter = createEventRateLimiter('error', onLimitReachedSpy, limit)
|
|
68
|
-
|
|
69
|
-
eventLimiter.isLimitReached()
|
|
70
|
-
eventLimiter.isLimitReached()
|
|
71
|
-
eventLimiter.isLimitReached()
|
|
72
|
-
eventLimiter.isLimitReached()
|
|
73
|
-
eventLimiter.isLimitReached()
|
|
74
|
-
expect(onLimitReachedSpy).toHaveBeenCalledTimes(1)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('returns true after reaching the limit even if the "onLimitReached" callback throws', () => {
|
|
78
|
-
eventLimiter = createEventRateLimiter(
|
|
79
|
-
'error',
|
|
80
|
-
() => {
|
|
81
|
-
throw new Error('oops')
|
|
82
|
-
},
|
|
83
|
-
limit
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
eventLimiter.isLimitReached()
|
|
87
|
-
expect(() => eventLimiter!.isLimitReached()).toThrow()
|
|
88
|
-
expect(eventLimiter.isLimitReached()).toBe(true)
|
|
89
|
-
expect(eventLimiter.isLimitReached()).toBe(true)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('returns true when the limit is reached and the "onLimitReached" callback does not call "isLimitReached" (ex: excluded by beforeSend)', () => {
|
|
93
|
-
eventLimiter = createEventRateLimiter(
|
|
94
|
-
'error',
|
|
95
|
-
() => {
|
|
96
|
-
// do not call isLimitReached
|
|
97
|
-
},
|
|
98
|
-
limit
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
eventLimiter.isLimitReached()
|
|
102
|
-
eventLimiter.isLimitReached()
|
|
103
|
-
expect(eventLimiter.isLimitReached()).toBe(true)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('returns false only once when called from the "onLimitReached" callback (edge case)', () => {
|
|
107
|
-
eventLimiter = createEventRateLimiter(
|
|
108
|
-
'error',
|
|
109
|
-
() => {
|
|
110
|
-
expect(eventLimiter!.isLimitReached()).toBe(false)
|
|
111
|
-
expect(eventLimiter!.isLimitReached()).toBe(true)
|
|
112
|
-
},
|
|
113
|
-
limit
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
eventLimiter.isLimitReached()
|
|
117
|
-
eventLimiter.isLimitReached()
|
|
118
|
-
})
|
|
119
|
-
})
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
STACK_WITH_INIT_IN_EXTENSION,
|
|
3
|
-
STACK_WITH_INIT_IN_EXTENSION_FIREFOX,
|
|
4
|
-
STACK_WITH_INIT_IN_PAGE,
|
|
5
|
-
} from '../../../test'
|
|
6
|
-
import { containsExtensionUrl, EXTENSION_PREFIXES, isUnsupportedExtensionEnvironment } from './extensionUtils'
|
|
7
|
-
|
|
8
|
-
describe('extensionUtils', () => {
|
|
9
|
-
describe('containsExtensionUrl', () => {
|
|
10
|
-
it('should return true if string contains an extension URL', () => {
|
|
11
|
-
EXTENSION_PREFIXES.forEach((prefix) => {
|
|
12
|
-
expect(containsExtensionUrl(`${prefix}some/path`)).toBe(true)
|
|
13
|
-
})
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('should return false if string does not contain extension URL', () => {
|
|
17
|
-
expect(containsExtensionUrl('https://example.com')).toBe(false)
|
|
18
|
-
expect(containsExtensionUrl('')).toBe(false)
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
describe('isUnsupportedExtensionEnvironment', () => {
|
|
23
|
-
it('should return true when window location is a regular URL and error stack init is in an extension', () => {
|
|
24
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('should return false when both window location and error stack init are regular URLs', () => {
|
|
28
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_PAGE)).toBe(false)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should return false when error stack is empty', () => {
|
|
32
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com', '')).toBe(false)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('should handle each extension prefix in firefox', () => {
|
|
36
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION_FIREFOX)).toBe(true)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should handle case when stack trace is undefined', () => {
|
|
40
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com')).toBe(false)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should handle extension stack trace', () => {
|
|
44
|
-
expect(isUnsupportedExtensionEnvironment('https://example.com', STACK_WITH_INIT_IN_EXTENSION)).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
})
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { MockCspEventListener, MockReportingObserver } from '../../../test'
|
|
2
|
-
import { mockReportingObserver, mockCspEventListener, FAKE_CSP_VIOLATION_EVENT } from '../../../test'
|
|
3
|
-
import type { Subscription } from '../../tools/observable'
|
|
4
|
-
import type { Configuration } from '../configuration'
|
|
5
|
-
import { ErrorHandling, ErrorSource } from '../error/error.types'
|
|
6
|
-
import type { RawReportError } from './reportObservable'
|
|
7
|
-
import { initReportObservable, RawReportType } from './reportObservable'
|
|
8
|
-
|
|
9
|
-
describe('report observable', () => {
|
|
10
|
-
let reportingObserver: MockReportingObserver
|
|
11
|
-
let cspEventListener: MockCspEventListener
|
|
12
|
-
let consoleSubscription: Subscription
|
|
13
|
-
let notifyReport: jasmine.Spy<(reportError: RawReportError) => void>
|
|
14
|
-
let configuration: Configuration
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
if (!window.ReportingObserver) {
|
|
18
|
-
pending('ReportingObserver not supported')
|
|
19
|
-
}
|
|
20
|
-
configuration = {} as Configuration
|
|
21
|
-
reportingObserver = mockReportingObserver()
|
|
22
|
-
cspEventListener = mockCspEventListener()
|
|
23
|
-
notifyReport = jasmine.createSpy('notifyReport')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
consoleSubscription?.unsubscribe()
|
|
28
|
-
})
|
|
29
|
-
;[RawReportType.deprecation, RawReportType.intervention].forEach((type) => {
|
|
30
|
-
it(`should notify ${type} reports`, () => {
|
|
31
|
-
consoleSubscription = initReportObservable(configuration, [type]).subscribe(notifyReport)
|
|
32
|
-
reportingObserver.raiseReport(type)
|
|
33
|
-
|
|
34
|
-
const [report] = notifyReport.calls.mostRecent().args
|
|
35
|
-
|
|
36
|
-
expect(report).toEqual(
|
|
37
|
-
jasmine.objectContaining({
|
|
38
|
-
message: `${type}: foo bar`,
|
|
39
|
-
type: 'NavigatorVibrate',
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it(`should compute stack for ${RawReportType.intervention}`, () => {
|
|
46
|
-
consoleSubscription = initReportObservable(configuration, [RawReportType.intervention]).subscribe(notifyReport)
|
|
47
|
-
reportingObserver.raiseReport(RawReportType.intervention)
|
|
48
|
-
|
|
49
|
-
const [report] = notifyReport.calls.mostRecent().args
|
|
50
|
-
|
|
51
|
-
expect(report.stack).toEqual(`NavigatorVibrate: foo bar
|
|
52
|
-
at <anonymous> @ http://foo.bar/index.js:20:10`)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it(`should notify ${RawReportType.cspViolation}`, () => {
|
|
56
|
-
consoleSubscription = initReportObservable(configuration, [RawReportType.cspViolation]).subscribe(notifyReport)
|
|
57
|
-
cspEventListener.dispatchEvent()
|
|
58
|
-
|
|
59
|
-
expect(notifyReport).toHaveBeenCalledOnceWith({
|
|
60
|
-
startClocks: jasmine.any(Object),
|
|
61
|
-
source: ErrorSource.REPORT,
|
|
62
|
-
message: "csp_violation: 'blob' blocked by 'worker-src' directive",
|
|
63
|
-
type: 'worker-src',
|
|
64
|
-
originalError: FAKE_CSP_VIOLATION_EVENT,
|
|
65
|
-
stack: `worker-src: 'blob' blocked by 'worker-src' directive of the policy "worker-src 'none'"
|
|
66
|
-
at <anonymous> @ http://foo.bar/index.js:17:8`,
|
|
67
|
-
handling: ErrorHandling.UNHANDLED,
|
|
68
|
-
csp: { disposition: 'enforce' },
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { getCookie, resetInitCookies, setCookie } from '../../browser/cookie'
|
|
2
|
-
import { getSessionState } from '../../../test'
|
|
3
|
-
import type { Configuration } from '../configuration'
|
|
4
|
-
import {
|
|
5
|
-
OLD_LOGS_COOKIE_NAME,
|
|
6
|
-
OLD_RUM_COOKIE_NAME,
|
|
7
|
-
OLD_SESSION_COOKIE_NAME,
|
|
8
|
-
tryOldCookiesMigration,
|
|
9
|
-
} from './oldCookiesMigration'
|
|
10
|
-
import { SESSION_EXPIRATION_DELAY } from './sessionConstants'
|
|
11
|
-
import { initCookieStrategy } from './storeStrategies/sessionInCookie'
|
|
12
|
-
import type { SessionStoreStrategy } from './storeStrategies/sessionStoreStrategy'
|
|
13
|
-
import { SESSION_STORE_KEY } from './storeStrategies/sessionStoreStrategy'
|
|
14
|
-
const DEFAULT_INIT_CONFIGURATION = { trackAnonymousUser: true } as Configuration
|
|
15
|
-
|
|
16
|
-
describe('old cookies migration', () => {
|
|
17
|
-
let sessionStoreStrategy: SessionStoreStrategy
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
sessionStoreStrategy = initCookieStrategy(DEFAULT_INIT_CONFIGURATION, {})
|
|
21
|
-
resetInitCookies()
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
resetInitCookies()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('should not touch current cookie', () => {
|
|
29
|
-
setCookie(SESSION_STORE_KEY, 'id=abcde&rum=0&logs=1&expire=1234567890', SESSION_EXPIRATION_DELAY)
|
|
30
|
-
|
|
31
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
32
|
-
|
|
33
|
-
expect(getCookie(SESSION_STORE_KEY)).toBe('id=abcde&rum=0&logs=1&expire=1234567890')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should create new cookie from old cookie values', () => {
|
|
37
|
-
setCookie(OLD_SESSION_COOKIE_NAME, 'abcde', SESSION_EXPIRATION_DELAY)
|
|
38
|
-
setCookie(OLD_LOGS_COOKIE_NAME, '1', SESSION_EXPIRATION_DELAY)
|
|
39
|
-
setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY)
|
|
40
|
-
|
|
41
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
42
|
-
|
|
43
|
-
expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcde')
|
|
44
|
-
expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0')
|
|
45
|
-
expect(getSessionState(SESSION_STORE_KEY).logs).toBe('1')
|
|
46
|
-
expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should create new cookie from a single old cookie', () => {
|
|
50
|
-
setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY)
|
|
51
|
-
|
|
52
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
53
|
-
expect(getSessionState(SESSION_STORE_KEY).id).not.toBeDefined()
|
|
54
|
-
expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0')
|
|
55
|
-
expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should not create a new cookie if no old cookie is present', () => {
|
|
59
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
60
|
-
expect(getCookie(SESSION_STORE_KEY)).toBeUndefined()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('should behave correctly when performing the migration multiple times', () => {
|
|
64
|
-
setCookie(OLD_SESSION_COOKIE_NAME, 'abcde', SESSION_EXPIRATION_DELAY)
|
|
65
|
-
setCookie(OLD_LOGS_COOKIE_NAME, '1', SESSION_EXPIRATION_DELAY)
|
|
66
|
-
setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY)
|
|
67
|
-
|
|
68
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
69
|
-
tryOldCookiesMigration(sessionStoreStrategy)
|
|
70
|
-
|
|
71
|
-
expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcde')
|
|
72
|
-
expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0')
|
|
73
|
-
expect(getSessionState(SESSION_STORE_KEY).logs).toBe('1')
|
|
74
|
-
expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/)
|
|
75
|
-
})
|
|
76
|
-
})
|