@cloudcare/browser-core 2.0.4 → 2.0.6
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/browser/addEventListener.js +57 -0
- package/cjs/browser/fetchObservable.js +4 -5
- package/cjs/browser/pageExitObservable.js +4 -2
- package/cjs/browser/runOnReadyState.js +21 -0
- package/cjs/browser/xhrObservable.js +7 -5
- package/cjs/configuration/configuration.js +5 -3
- package/cjs/console/consoleObservable.js +3 -1
- package/cjs/dataMap.js +1 -0
- package/cjs/helper/byteUtils.js +26 -0
- package/cjs/helper/contextHistory.js +4 -2
- package/cjs/helper/createEventRateLimiter.js +3 -1
- package/cjs/helper/errorTools.js +4 -2
- package/cjs/helper/instrumentMethod.js +2 -4
- package/cjs/helper/serialisation/contextManager.js +84 -0
- package/cjs/helper/serialisation/heavyCustomerDataWarning.js +34 -0
- package/cjs/helper/serialisation/jsonStringify.js +57 -0
- package/cjs/helper/serialisation/rowData.js +50 -0
- package/cjs/helper/timer.js +29 -0
- package/cjs/helper/tools.js +10 -331
- package/cjs/index.js +117 -0
- package/cjs/init.js +6 -6
- package/cjs/report/reportObservable.js +4 -2
- package/cjs/session/sessionCookieStore.js +3 -1
- package/cjs/session/sessionManagement.js +10 -5
- package/cjs/session/sessionStore.js +10 -4
- package/cjs/transport/batch.js +40 -91
- package/cjs/transport/flushController.js +121 -0
- package/cjs/transport/httpRequest.js +6 -2
- package/cjs/transport/index.js +9 -1
- package/cjs/transport/sendWithRetryStrategy.js +8 -4
- package/cjs/transport/startBatchWithReplica.js +10 -2
- package/cjs/user/index.js +18 -0
- package/cjs/user/user.js +42 -0
- package/esm/browser/addEventListener.js +46 -0
- package/esm/browser/fetchObservable.js +4 -3
- package/esm/browser/pageExitObservable.js +2 -1
- package/esm/browser/runOnReadyState.js +12 -0
- package/esm/browser/xhrObservable.js +6 -5
- package/esm/configuration/configuration.js +2 -1
- package/esm/console/consoleObservable.js +2 -1
- package/esm/dataMap.js +1 -0
- package/esm/helper/byteUtils.js +16 -0
- package/esm/helper/contextHistory.js +1 -0
- package/esm/helper/createEventRateLimiter.js +1 -0
- package/esm/helper/errorTools.js +2 -1
- package/esm/helper/instrumentMethod.js +1 -3
- package/esm/helper/serialisation/contextManager.js +70 -0
- package/esm/helper/serialisation/heavyCustomerDataWarning.js +20 -0
- package/esm/helper/serialisation/jsonStringify.js +46 -0
- package/esm/helper/serialisation/rowData.js +33 -0
- package/esm/helper/timer.js +14 -0
- package/esm/helper/tools.js +1 -292
- package/esm/index.js +10 -1
- package/esm/init.js +6 -6
- package/esm/report/reportObservable.js +3 -2
- package/esm/session/sessionCookieStore.js +1 -0
- package/esm/session/sessionManagement.js +5 -2
- package/esm/session/sessionStore.js +7 -2
- package/esm/transport/batch.js +31 -85
- package/esm/transport/flushController.js +114 -0
- package/esm/transport/httpRequest.js +5 -2
- package/esm/transport/index.js +2 -1
- package/esm/transport/sendWithRetryStrategy.js +3 -1
- package/esm/transport/startBatchWithReplica.js +9 -2
- package/esm/user/index.js +1 -0
- package/esm/user/user.js +32 -0
- package/package.json +22 -22
- package/src/browser/addEventListener.js +50 -0
- package/src/browser/fetchObservable.js +5 -5
- package/src/browser/pageExitObservable.js +4 -5
- package/src/browser/runOnReadyState.js +16 -0
- package/src/browser/xhrObservable.js +57 -32
- package/src/configuration/configuration.js +1 -1
- package/src/console/consoleObservable.js +22 -12
- package/src/dataMap.js +1 -0
- package/src/helper/byteUtils.js +17 -0
- package/src/helper/contextHistory.js +1 -1
- package/src/helper/createEventRateLimiter.js +7 -9
- package/src/helper/errorTools.js +2 -1
- package/src/helper/instrumentMethod.js +1 -2
- package/src/helper/serialisation/contextManager.js +79 -0
- package/src/helper/serialisation/heavyCustomerDataWarning.js +28 -0
- package/src/helper/serialisation/jsonStringify.js +47 -0
- package/src/helper/serialisation/rowData.js +41 -0
- package/src/helper/timer.js +21 -0
- package/src/helper/tools.js +2 -301
- package/src/index.js +11 -0
- package/src/init.js +6 -6
- package/src/report/reportObservable.js +40 -25
- package/src/session/sessionCookieStore.js +24 -14
- package/src/session/sessionManagement.js +34 -17
- package/src/session/sessionStore.js +7 -2
- package/src/transport/batch.js +39 -91
- package/src/transport/flushController.js +115 -0
- package/src/transport/httpRequest.js +13 -6
- package/src/transport/index.js +1 -0
- package/src/transport/sendWithRetryStrategy.js +3 -7
- package/src/transport/startBatchWithReplica.js +11 -7
- package/src/user/index.js +1 -0
- package/src/user/user.js +31 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Observable } from '../helper/observable';
|
|
2
|
+
import { clearTimeout, setTimeout } from '../helper/timer'; // type FlushReason = PageExitReason | 'duration_limit' | 'bytes_limit' | 'messages_limit' | 'session_expire'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a "flush controller", responsible of notifying when flushing a pool of pending data needs
|
|
6
|
+
* to happen. The implementation is designed to support both synchronous and asynchronous usages,
|
|
7
|
+
* but relies on invariants described in each method documentation to keep a coherent state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function createFlushController(_ref) {
|
|
11
|
+
var {
|
|
12
|
+
messagesLimit,
|
|
13
|
+
bytesLimit,
|
|
14
|
+
durationLimit,
|
|
15
|
+
pageExitObservable,
|
|
16
|
+
sessionExpireObservable
|
|
17
|
+
} = _ref;
|
|
18
|
+
var flushObservable = new Observable();
|
|
19
|
+
pageExitObservable.subscribe(function (event) {
|
|
20
|
+
return flush(event.reason);
|
|
21
|
+
});
|
|
22
|
+
sessionExpireObservable.subscribe(function () {
|
|
23
|
+
return flush('session_expire');
|
|
24
|
+
});
|
|
25
|
+
var currentBytesCount = 0;
|
|
26
|
+
var currentMessagesCount = 0;
|
|
27
|
+
|
|
28
|
+
function flush(flushReason) {
|
|
29
|
+
if (currentMessagesCount === 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var messagesCount = currentMessagesCount;
|
|
34
|
+
var bytesCount = currentBytesCount;
|
|
35
|
+
currentMessagesCount = 0;
|
|
36
|
+
currentBytesCount = 0;
|
|
37
|
+
cancelDurationLimitTimeout();
|
|
38
|
+
flushObservable.notify({
|
|
39
|
+
reason: flushReason,
|
|
40
|
+
messagesCount: messagesCount,
|
|
41
|
+
bytesCount: bytesCount
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var durationLimitTimeoutId;
|
|
46
|
+
|
|
47
|
+
function scheduleDurationLimitTimeout() {
|
|
48
|
+
if (durationLimitTimeoutId === undefined) {
|
|
49
|
+
durationLimitTimeoutId = setTimeout(function () {
|
|
50
|
+
flush('duration_limit');
|
|
51
|
+
}, durationLimit);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cancelDurationLimitTimeout() {
|
|
56
|
+
clearTimeout(durationLimitTimeoutId);
|
|
57
|
+
durationLimitTimeoutId = undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
flushObservable: flushObservable,
|
|
62
|
+
getMessagesCount: function getMessagesCount() {
|
|
63
|
+
return currentMessagesCount;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Notifies that a message will be added to a pool of pending messages waiting to be flushed.
|
|
68
|
+
*
|
|
69
|
+
* This function needs to be called synchronously, right before adding the message, so no flush
|
|
70
|
+
* event can happen after `notifyBeforeAddMessage` and before adding the message.
|
|
71
|
+
*/
|
|
72
|
+
notifyBeforeAddMessage: function notifyBeforeAddMessage(messageBytesCount) {
|
|
73
|
+
if (currentBytesCount + messageBytesCount >= bytesLimit) {
|
|
74
|
+
flush('bytes_limit');
|
|
75
|
+
} // Consider the message to be added now rather than in `notifyAfterAddMessage`, because if no
|
|
76
|
+
// message was added yet and `notifyAfterAddMessage` is called asynchronously, we still want
|
|
77
|
+
// to notify when a flush is needed (for example on page exit).
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
currentMessagesCount += 1;
|
|
81
|
+
currentBytesCount += messageBytesCount;
|
|
82
|
+
scheduleDurationLimitTimeout();
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Notifies that a message *was* added to a pool of pending messages waiting to be flushed.
|
|
87
|
+
*
|
|
88
|
+
* This function can be called asynchronously after the message was added, but in this case it
|
|
89
|
+
* should not be called if a flush event occurred in between.
|
|
90
|
+
*/
|
|
91
|
+
notifyAfterAddMessage: function notifyAfterAddMessage() {
|
|
92
|
+
if (currentMessagesCount >= messagesLimit) {
|
|
93
|
+
flush('messages_limit');
|
|
94
|
+
} else if (currentBytesCount >= bytesLimit) {
|
|
95
|
+
flush('bytes_limit');
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Notifies that a message was removed from a pool of pending messages waiting to be flushed.
|
|
101
|
+
*
|
|
102
|
+
* This function needs to be called synchronously, right after removing the message, so no flush
|
|
103
|
+
* event can happen after removing the message and before `notifyAfterRemoveMessage`.
|
|
104
|
+
*/
|
|
105
|
+
notifyAfterRemoveMessage: function notifyAfterRemoveMessage(messageBytesCount) {
|
|
106
|
+
currentBytesCount -= messageBytesCount;
|
|
107
|
+
currentMessagesCount -= 1;
|
|
108
|
+
|
|
109
|
+
if (currentMessagesCount === 0) {
|
|
110
|
+
cancelDurationLimitTimeout();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { newRetryState, sendWithRetryStrategy } from './sendWithRetryStrategy';
|
|
2
|
+
import { addEventListener } from '../browser/addEventListener';
|
|
2
3
|
/**
|
|
3
4
|
* Use POST request without content type to:
|
|
4
5
|
* - avoid CORS preflight requests
|
|
@@ -95,12 +96,14 @@ function isKeepAliveSupported() {
|
|
|
95
96
|
function sendXHR(url, data, onResponse) {
|
|
96
97
|
var request = new XMLHttpRequest();
|
|
97
98
|
request.open('POST', url, true);
|
|
98
|
-
request
|
|
99
|
-
request.addEventListener('loadend', function () {
|
|
99
|
+
addEventListener(request, 'loadend', function () {
|
|
100
100
|
if (typeof onResponse === 'function') {
|
|
101
101
|
onResponse({
|
|
102
102
|
status: request.status
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
+
}, {
|
|
106
|
+
once: true
|
|
105
107
|
});
|
|
108
|
+
request.send(data);
|
|
106
109
|
}
|
package/esm/transport/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createHttpRequest } from './httpRequest';
|
|
2
2
|
export { Batch } from './batch';
|
|
3
|
-
export { startBatchWithReplica } from './startBatchWithReplica';
|
|
3
|
+
export { startBatchWithReplica } from './startBatchWithReplica';
|
|
4
|
+
export { createFlushController } from './flushController';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { clocksNow,
|
|
1
|
+
import { clocksNow, ONE_SECOND } from '../helper/tools';
|
|
2
|
+
import { ONE_MEBI_BYTE, ONE_KIBI_BYTE } from '../helper/byteUtils';
|
|
2
3
|
import { ErrorSource } from '../helper/errorTools';
|
|
4
|
+
import { setTimeout } from '../helper/timer';
|
|
3
5
|
export var MAX_ONGOING_BYTES_COUNT = 80 * ONE_KIBI_BYTE;
|
|
4
6
|
export var MAX_ONGOING_REQUESTS = 32;
|
|
5
7
|
export var MAX_QUEUE_BYTES_COUNT = 3 * ONE_MEBI_BYTE;
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Batch } from './batch';
|
|
2
2
|
import { createHttpRequest } from './httpRequest';
|
|
3
|
-
|
|
3
|
+
import { createFlushController } from './flushController';
|
|
4
|
+
export function startBatchWithReplica(configuration, endpointUrl, reportError, pageExitObservable, sessionExpireObservable) {
|
|
4
5
|
var primaryBatch = createBatch(endpointUrl);
|
|
5
6
|
|
|
6
7
|
function createBatch(endpointUrl) {
|
|
7
|
-
return new Batch(createHttpRequest(endpointUrl, configuration.batchBytesLimit, reportError),
|
|
8
|
+
return new Batch(createHttpRequest(endpointUrl, configuration.batchBytesLimit, reportError), createFlushController({
|
|
9
|
+
messagesLimit: configuration.batchMessagesLimit,
|
|
10
|
+
bytesLimit: configuration.batchBytesLimit,
|
|
11
|
+
durationLimit: configuration.flushTimeout,
|
|
12
|
+
pageExitObservable: pageExitObservable,
|
|
13
|
+
sessionExpireObservable: sessionExpireObservable
|
|
14
|
+
}), configuration.messageBytesLimit);
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './user';
|
package/esm/user/user.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { display } from '../helper/display';
|
|
2
|
+
import { assign, getType, each } from '../helper/tools';
|
|
3
|
+
/**
|
|
4
|
+
* Clone input data and ensure known user properties (id, name, email)
|
|
5
|
+
* are strings, as defined here:
|
|
6
|
+
* https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function sanitizeUser(newUser) {
|
|
10
|
+
// We shallow clone only to prevent mutation of user data.
|
|
11
|
+
var user = assign({}, newUser);
|
|
12
|
+
var keys = ['id', 'name', 'email'];
|
|
13
|
+
each(keys, function (key) {
|
|
14
|
+
if (key in user) {
|
|
15
|
+
user[key] = String(user[key]);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return user;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Simple check to ensure user is valid
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export function checkUser(newUser) {
|
|
25
|
+
var isValid = getType(newUser) === 'object';
|
|
26
|
+
|
|
27
|
+
if (!isValid) {
|
|
28
|
+
display.error('Unsupported user:', newUser);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return isValid;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
"name": "@cloudcare/browser-core",
|
|
3
|
+
"version": "2.0.6",
|
|
4
|
+
"main": "cjs/index.js",
|
|
5
|
+
"module": "esm/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "run-p build:cjs build:esm",
|
|
8
|
+
"build:cjs": "rm -rf cjs && babel --config-file ./babel.cjs.json --out-dir cjs ./src",
|
|
9
|
+
"build:esm": "rm -rf esm && babel --config-file ./babel.esm.json --out-dir esm ./src "
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"dataflux",
|
|
13
|
+
"logs",
|
|
14
|
+
"sdk"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"url": "https://github.com/DataFlux-cn/datakit-js",
|
|
18
|
+
"type": "git"
|
|
19
|
+
},
|
|
20
|
+
"author": "dataflux",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"description": "DataFlux RUM Web 端数据指标监控",
|
|
23
|
+
"gitHead": "ffbab227685c4ea3bf9b26e6e5e9c00233e64bb2"
|
|
24
24
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { each } from '../helper/tools'
|
|
2
|
+
import { getZoneJsOriginalValue } from '../helper/getZoneJsOriginalValue'
|
|
3
|
+
export function addEventListener(eventTarget, event, listener, options) {
|
|
4
|
+
return addEventListeners(eventTarget, [event], listener, options)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add event listeners to an event emitter object (Window, Element, mock object...). This provides
|
|
9
|
+
* a few conveniences compared to using `element.addEventListener` directly:
|
|
10
|
+
*
|
|
11
|
+
* * supports IE11 by:
|
|
12
|
+
* * using an option object only if needed
|
|
13
|
+
* * emulating the `once` option
|
|
14
|
+
*
|
|
15
|
+
* * wraps the listener with a `monitor` function
|
|
16
|
+
*
|
|
17
|
+
* * returns a `stop` function to remove the listener
|
|
18
|
+
*
|
|
19
|
+
* * with `once: true`, the listener will be called at most once, even if different events are
|
|
20
|
+
* listened
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export function addEventListeners(eventTarget, events, listener, options) {
|
|
24
|
+
var wrappedListener =
|
|
25
|
+
options && options.once
|
|
26
|
+
? function (event) {
|
|
27
|
+
stop()
|
|
28
|
+
listener(event)
|
|
29
|
+
}
|
|
30
|
+
: listener
|
|
31
|
+
|
|
32
|
+
options =
|
|
33
|
+
options && options.passive
|
|
34
|
+
? { capture: options.capture, passive: options.passive }
|
|
35
|
+
: options && options.capture
|
|
36
|
+
var add = getZoneJsOriginalValue(eventTarget, 'addEventListener')
|
|
37
|
+
|
|
38
|
+
each(events, function (event) {
|
|
39
|
+
add.call(eventTarget, event, wrappedListener, options)
|
|
40
|
+
})
|
|
41
|
+
var stop = function () {
|
|
42
|
+
var remove = getZoneJsOriginalValue(eventTarget, 'removeEventListener')
|
|
43
|
+
each(events, function (event) {
|
|
44
|
+
remove.call(eventTarget, event, wrappedListener, options)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
stop: stop
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -47,10 +47,9 @@ function createFetchObservable() {
|
|
|
47
47
|
|
|
48
48
|
function beforeSend(observable, input, init) {
|
|
49
49
|
var method =
|
|
50
|
-
(init && init.method) ||
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
var url = normalizeUrl((typeof input === 'object' && input.url) || input)
|
|
50
|
+
(init && init.method) || (input instanceof Request && input.method) || 'GET'
|
|
51
|
+
var url = input instanceof Request ? input.url : normalizeUrl(String(input))
|
|
52
|
+
|
|
54
53
|
var startClocks = clocksNow()
|
|
55
54
|
|
|
56
55
|
var context = {
|
|
@@ -80,7 +79,8 @@ function afterSend(observable, responsePromise, startContext) {
|
|
|
80
79
|
context.error = response
|
|
81
80
|
} else if ('status' in response) {
|
|
82
81
|
context.response = response
|
|
83
|
-
context.responseType =
|
|
82
|
+
context.responseType =
|
|
83
|
+
(response instanceof Response && response.type) || '' // issue The Response type getter can only be used on instances of Response
|
|
84
84
|
context.status = response.status
|
|
85
85
|
context.isAborted = false
|
|
86
86
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Observable } from '../helper/observable'
|
|
2
|
+
import { includes, values } from '../helper/tools'
|
|
3
|
+
import { DOM_EVENT } from '../helper/enums'
|
|
2
4
|
import {
|
|
3
5
|
addEventListener,
|
|
4
|
-
addEventListeners
|
|
5
|
-
|
|
6
|
-
values
|
|
7
|
-
} from '../helper/tools'
|
|
8
|
-
import { DOM_EVENT } from '../helper/enums'
|
|
6
|
+
addEventListeners
|
|
7
|
+
} from '../browser/addEventListener'
|
|
9
8
|
export var PageExitReason = {
|
|
10
9
|
HIDDEN: 'visibility_hidden',
|
|
11
10
|
UNLOADING: 'before_unload',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { addEventListener } from './addEventListener'
|
|
2
|
+
import { DOM_EVENT } from '../helper/enums'
|
|
3
|
+
export function runOnReadyState(expectedReadyState, callback) {
|
|
4
|
+
if (
|
|
5
|
+
document.readyState === expectedReadyState ||
|
|
6
|
+
document.readyState === 'complete'
|
|
7
|
+
) {
|
|
8
|
+
callback()
|
|
9
|
+
} else {
|
|
10
|
+
var eventName =
|
|
11
|
+
expectedReadyState === 'complete'
|
|
12
|
+
? DOM_EVENT.LOAD
|
|
13
|
+
: DOM_EVENT.DOM_CONTENT_LOADED
|
|
14
|
+
addEventListener(window, eventName, callback, { once: true })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { instrumentMethodAndCallOriginal } from '../helper/instrumentMethod'
|
|
2
2
|
import { Observable } from '../helper/observable'
|
|
3
3
|
import { normalizeUrl } from '../helper/urlPolyfill'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
shallowClone,
|
|
6
|
+
elapsed,
|
|
7
|
+
relativeNow,
|
|
8
|
+
clocksNow,
|
|
9
|
+
timeStampNow,
|
|
10
|
+
UUID
|
|
11
|
+
} from '../helper/tools'
|
|
12
|
+
import { addEventListener } from '../browser/addEventListener'
|
|
6
13
|
var xhrObservable
|
|
7
14
|
var xhrContexts = {}
|
|
8
15
|
var DATA_FLUX_REQUEST_ID_KEY = '_DATAFLUX_REQUEST_UUID'
|
|
@@ -14,22 +21,34 @@ export function initXhrObservable() {
|
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
function createXhrObservable() {
|
|
17
|
-
var observable = new Observable(function(){
|
|
18
|
-
var openInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
var observable = new Observable(function () {
|
|
25
|
+
var openInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
26
|
+
XMLHttpRequest.prototype,
|
|
27
|
+
'open',
|
|
28
|
+
{
|
|
29
|
+
before: openXhr
|
|
30
|
+
}
|
|
31
|
+
)
|
|
21
32
|
|
|
22
|
-
var sendInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
var sendInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
34
|
+
XMLHttpRequest.prototype,
|
|
35
|
+
'send',
|
|
36
|
+
{
|
|
37
|
+
before: function () {
|
|
38
|
+
sendXhr.call(this, observable)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
)
|
|
27
42
|
|
|
28
|
-
var abortInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
var abortInstrumentMethod = instrumentMethodAndCallOriginal(
|
|
44
|
+
XMLHttpRequest.prototype,
|
|
45
|
+
'abort',
|
|
46
|
+
{
|
|
47
|
+
before: abortXhr
|
|
48
|
+
}
|
|
49
|
+
)
|
|
31
50
|
|
|
32
|
-
return function() {
|
|
51
|
+
return function () {
|
|
33
52
|
openInstrumentMethod.stop()
|
|
34
53
|
sendInstrumentMethod.stop()
|
|
35
54
|
abortInstrumentMethod.stop()
|
|
@@ -44,7 +63,7 @@ function openXhr(method, url) {
|
|
|
44
63
|
xhrContexts[requestUUID] = {
|
|
45
64
|
state: 'open',
|
|
46
65
|
method: method,
|
|
47
|
-
url: normalizeUrl(String(url))
|
|
66
|
+
url: normalizeUrl(String(url))
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
|
|
@@ -60,34 +79,40 @@ function sendXhr(observable) {
|
|
|
60
79
|
startContext.isAborted = false
|
|
61
80
|
startContext.xhr = this
|
|
62
81
|
var hasBeenReported = false
|
|
63
|
-
var
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
var stopInstrumentingOnReadyStateChange = instrumentMethodAndCallOriginal(
|
|
83
|
+
this,
|
|
84
|
+
'onreadystatechange',
|
|
85
|
+
{
|
|
86
|
+
before: function () {
|
|
87
|
+
if (this.readyState === XMLHttpRequest.DONE) {
|
|
88
|
+
// Try to report the XHR as soon as possible, because the XHR may be mutated by the
|
|
89
|
+
// application during a future event. For example, Angular is calling .abort() on
|
|
90
|
+
// completed requests during a onreadystatechange event, so the status becomes '0'
|
|
91
|
+
// before the request is collected.
|
|
92
|
+
onEnd.call(this)
|
|
93
|
+
}
|
|
72
94
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
95
|
+
}
|
|
96
|
+
).stop
|
|
75
97
|
|
|
76
|
-
var onEnd = function() {
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
var onEnd = function () {
|
|
99
|
+
unsubscribeLoadEndListener()
|
|
100
|
+
stopInstrumentingOnReadyStateChange()
|
|
79
101
|
if (hasBeenReported) {
|
|
80
102
|
return
|
|
81
103
|
}
|
|
82
104
|
hasBeenReported = true
|
|
83
105
|
var completeContext = context
|
|
84
106
|
completeContext.state = 'complete'
|
|
85
|
-
completeContext.duration = elapsed(
|
|
107
|
+
completeContext.duration = elapsed(
|
|
108
|
+
startContext.startClocks.timeStamp,
|
|
109
|
+
timeStampNow()
|
|
110
|
+
)
|
|
86
111
|
completeContext.status = this.status
|
|
87
112
|
observable.notify(shallowClone(completeContext))
|
|
88
113
|
clearRequestId.call(this)
|
|
89
114
|
}
|
|
90
|
-
|
|
115
|
+
var unsubscribeLoadEndListener = addEventListener(this, 'loadend', onEnd).stop
|
|
91
116
|
observable.notify(startContext)
|
|
92
117
|
}
|
|
93
118
|
function clearRequestId() {
|
|
@@ -4,10 +4,10 @@ import { display } from '../helper/display'
|
|
|
4
4
|
import {
|
|
5
5
|
assign,
|
|
6
6
|
isPercentage,
|
|
7
|
-
ONE_KIBI_BYTE,
|
|
8
7
|
ONE_SECOND,
|
|
9
8
|
isNullUndefinedDefaultValue
|
|
10
9
|
} from '../helper/tools'
|
|
10
|
+
import { ONE_KIBI_BYTE } from '../helper/byteUtils'
|
|
11
11
|
import { computeTransportConfiguration } from './transportConfiguration'
|
|
12
12
|
|
|
13
13
|
export var DefaultPrivacyLevel = {
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { computeStackTrace } from '../tracekit'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createHandlingStack,
|
|
4
|
+
formatErrorMessage,
|
|
5
|
+
toStackTraceString
|
|
6
|
+
} from '../helper/errorTools'
|
|
3
7
|
import { mergeObservables, Observable } from '../helper/observable'
|
|
4
|
-
import { find,
|
|
8
|
+
import { find, map } from '../helper/tools'
|
|
9
|
+
import { jsonStringify } from '../helper/serialisation/jsonStringify'
|
|
5
10
|
import { ConsoleApiName } from '../helper/display'
|
|
6
11
|
|
|
7
|
-
|
|
8
12
|
var consoleObservablesByApi = {}
|
|
9
13
|
|
|
10
14
|
export function initConsoleObservable(apis) {
|
|
11
|
-
var consoleObservables = map(apis, function(api) {
|
|
15
|
+
var consoleObservables = map(apis, function (api) {
|
|
12
16
|
if (!consoleObservablesByApi[api]) {
|
|
13
17
|
consoleObservablesByApi[api] = createConsoleObservable(api)
|
|
14
18
|
}
|
|
@@ -20,15 +24,15 @@ export function initConsoleObservable(apis) {
|
|
|
20
24
|
|
|
21
25
|
/* eslint-disable no-console */
|
|
22
26
|
function createConsoleObservable(api) {
|
|
23
|
-
var observable = new Observable(function(){
|
|
27
|
+
var observable = new Observable(function () {
|
|
24
28
|
var originalConsoleApi = console[api]
|
|
25
|
-
console[api] = function() {
|
|
29
|
+
console[api] = function () {
|
|
26
30
|
var params = [].slice.call(arguments)
|
|
27
31
|
originalConsoleApi.apply(console, arguments)
|
|
28
32
|
var handlingStack = createHandlingStack()
|
|
29
33
|
observable.notify(buildConsoleLog(params, api, handlingStack))
|
|
30
34
|
}
|
|
31
|
-
return function() {
|
|
35
|
+
return function () {
|
|
32
36
|
console[api] = originalConsoleApi
|
|
33
37
|
}
|
|
34
38
|
})
|
|
@@ -37,20 +41,26 @@ function createConsoleObservable(api) {
|
|
|
37
41
|
|
|
38
42
|
function buildConsoleLog(params, api, handlingStack) {
|
|
39
43
|
// Todo: remove console error prefix in the next major version
|
|
40
|
-
var message = map(params, function(param) {
|
|
44
|
+
var message = map(params, function (param) {
|
|
45
|
+
return formatConsoleParameters(param)
|
|
46
|
+
}).join(' ')
|
|
41
47
|
var stack
|
|
42
48
|
|
|
43
49
|
if (api === ConsoleApiName.error) {
|
|
44
|
-
var firstErrorParam = find(params, function(param) {
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
var firstErrorParam = find(params, function (param) {
|
|
51
|
+
return param instanceof Error
|
|
52
|
+
})
|
|
53
|
+
stack = firstErrorParam
|
|
54
|
+
? toStackTraceString(computeStackTrace(firstErrorParam))
|
|
55
|
+
: undefined
|
|
56
|
+
message = 'console error: ' + message
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
return {
|
|
50
60
|
api: api,
|
|
51
61
|
message: message,
|
|
52
62
|
stack: stack,
|
|
53
|
-
handlingStack: handlingStack
|
|
63
|
+
handlingStack: handlingStack
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
package/src/dataMap.js
CHANGED
|
@@ -47,6 +47,7 @@ export var dataMap = {
|
|
|
47
47
|
},
|
|
48
48
|
fields: {
|
|
49
49
|
session_replay_stats: '_dd.replay_stats',
|
|
50
|
+
session_is_active: 'session.is_active',
|
|
50
51
|
view_error_count: 'view.error.count',
|
|
51
52
|
view_resource_count: 'view.resource.count',
|
|
52
53
|
view_long_task_count: 'view.long_task.count',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export var ONE_KIBI_BYTE = 1024
|
|
2
|
+
export var ONE_MEBI_BYTE = 1024 * ONE_KIBI_BYTE
|
|
3
|
+
// eslint-disable-next-line no-control-regex
|
|
4
|
+
var HAS_MULTI_BYTES_CHARACTERS = /[^\u0000-\u007F]/
|
|
5
|
+
|
|
6
|
+
export function computeBytesCount(candidate) {
|
|
7
|
+
// Accurate bytes count computations can degrade performances when there is a lot of events to process
|
|
8
|
+
if (!HAS_MULTI_BYTES_CHARACTERS.test(candidate)) {
|
|
9
|
+
return candidate.length
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (window.TextEncoder !== undefined) {
|
|
13
|
+
return new TextEncoder().encode(candidate).length
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new Blob([candidate]).size
|
|
17
|
+
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { ErrorSource } from './errorTools'
|
|
2
2
|
import { clocksNow, ONE_MINUTE } from './tools'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
limit,
|
|
6
|
-
onLimitReached
|
|
7
|
-
) {
|
|
3
|
+
import { setTimeout } from './timer'
|
|
4
|
+
export function createEventRateLimiter(eventType, limit, onLimitReached) {
|
|
8
5
|
var eventCount = 0
|
|
9
6
|
var allowNextEvent = false
|
|
10
7
|
return {
|
|
11
|
-
isLimitReached: function() {
|
|
8
|
+
isLimitReached: function () {
|
|
12
9
|
if (eventCount === 0) {
|
|
13
|
-
setTimeout(function(){
|
|
10
|
+
setTimeout(function () {
|
|
14
11
|
eventCount = 0
|
|
15
12
|
}, ONE_MINUTE)
|
|
16
13
|
}
|
|
@@ -23,9 +20,10 @@ export function createEventRateLimiter(
|
|
|
23
20
|
allowNextEvent = true
|
|
24
21
|
try {
|
|
25
22
|
onLimitReached({
|
|
26
|
-
message:
|
|
23
|
+
message:
|
|
24
|
+
'Reached max number of ' + eventType + 's by minute: ' + limit,
|
|
27
25
|
source: ErrorSource.AGENT,
|
|
28
|
-
startClocks: clocksNow()
|
|
26
|
+
startClocks: clocksNow()
|
|
29
27
|
})
|
|
30
28
|
} finally {
|
|
31
29
|
allowNextEvent = false
|
package/src/helper/errorTools.js
CHANGED