@cloudcare/browser-core 1.1.2 → 1.2.0
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/{cookie.js → browser/cookie.js} +6 -37
- package/cjs/browser/fetchObservable.js +91 -0
- package/cjs/browser/xhrObservable.js +118 -0
- package/cjs/configuration/configuration.js +70 -0
- package/cjs/configuration/transportConfiguration.js +64 -0
- package/cjs/console/consoleObservable.js +84 -0
- package/cjs/dataMap.js +26 -21
- package/cjs/error/trackRuntimeError.js +27 -0
- package/cjs/helper/catchUserErrors.js +20 -0
- package/cjs/helper/contextHistory.js +134 -0
- package/cjs/helper/createEventRateLimiter.js +47 -0
- package/cjs/helper/deviceInfo.js +8 -25
- package/cjs/helper/display.js +31 -0
- package/cjs/helper/enums.js +28 -2
- package/cjs/helper/errorTools.js +63 -2
- package/cjs/helper/instrumentMethod.js +93 -0
- package/cjs/helper/lifeCycle.js +5 -3
- package/cjs/helper/observable.js +40 -2
- package/cjs/helper/tools.js +217 -11
- package/cjs/index.js +153 -23
- package/cjs/init.js +17 -27
- package/cjs/report/reportObservable.js +112 -0
- package/cjs/session/sessionConstants.js +13 -0
- package/cjs/session/sessionCookieStore.js +188 -0
- package/cjs/session/sessionManagement.js +96 -0
- package/cjs/session/sessionStore.js +150 -0
- package/cjs/synthetics/syntheticsWorkerValues.js +32 -0
- package/cjs/synthetics/usr.js +32 -0
- package/cjs/tracekit/computeStackTrace.js +73 -563
- package/cjs/tracekit/index.js +3 -15
- package/cjs/tracekit/tracekit.js +113 -0
- package/cjs/transport/batch.js +332 -0
- package/cjs/transport/httpRequest.js +112 -0
- package/cjs/transport/index.js +29 -0
- package/cjs/transport/sendWithRetryStrategy.js +177 -0
- package/cjs/transport/startBatchWithReplica.js +24 -0
- package/esm/{cookie.js → browser/cookie.js} +4 -35
- package/esm/browser/fetchObservable.js +77 -0
- package/esm/browser/xhrObservable.js +106 -0
- package/esm/configuration/configuration.js +55 -0
- package/esm/configuration/transportConfiguration.js +54 -0
- package/esm/console/consoleObservable.js +70 -0
- package/esm/dataMap.js +26 -21
- package/esm/error/trackRuntimeError.js +16 -0
- package/esm/helper/catchUserErrors.js +12 -0
- package/esm/helper/contextHistory.js +106 -0
- package/esm/helper/createEventRateLimiter.js +38 -0
- package/esm/helper/deviceInfo.js +6 -25
- package/esm/helper/display.js +21 -0
- package/esm/helper/enums.js +25 -1
- package/esm/helper/errorTools.js +58 -3
- package/esm/helper/instrumentMethod.js +81 -0
- package/esm/helper/lifeCycle.js +5 -3
- package/esm/helper/observable.js +39 -3
- package/esm/helper/tools.js +173 -3
- package/esm/index.js +17 -7
- package/esm/init.js +17 -22
- package/esm/report/reportObservable.js +98 -0
- package/esm/session/sessionConstants.js +3 -0
- package/esm/session/sessionCookieStore.js +168 -0
- package/esm/session/sessionManagement.js +78 -0
- package/esm/session/sessionStore.js +139 -0
- package/esm/synthetics/syntheticsWorkerValues.js +15 -0
- package/esm/synthetics/usr.js +18 -0
- package/esm/tracekit/computeStackTrace.js +73 -557
- package/esm/tracekit/index.js +1 -1
- package/esm/tracekit/tracekit.js +103 -0
- package/esm/transport/batch.js +316 -0
- package/esm/transport/httpRequest.js +104 -0
- package/esm/transport/index.js +3 -0
- package/esm/transport/sendWithRetryStrategy.js +165 -0
- package/esm/transport/startBatchWithReplica.js +15 -0
- package/package.json +2 -2
- package/src/.DS_Store +0 -0
- package/src/{cookie.js → browser/cookie.js} +6 -34
- package/src/browser/fetchObservable.js +86 -0
- package/src/browser/xhrObservable.js +102 -0
- package/src/configuration/configuration.js +61 -0
- package/src/configuration/transportConfiguration.js +42 -0
- package/src/console/consoleObservable.js +65 -0
- package/src/dataMap.js +26 -21
- package/src/error/trackRuntimeError.js +19 -0
- package/src/helper/catchUserErrors.js +12 -0
- package/src/helper/contextHistory.js +85 -0
- package/src/helper/createEventRateLimiter.js +37 -0
- package/src/helper/deviceInfo.js +5 -51
- package/src/helper/display.js +21 -0
- package/src/helper/enums.js +38 -14
- package/src/helper/errorTools.js +76 -12
- package/src/helper/instrumentMethod.js +97 -0
- package/src/helper/lifeCycle.js +5 -3
- package/src/helper/observable.js +33 -2
- package/src/helper/tools.js +159 -5
- package/src/index.js +18 -7
- package/src/init.js +17 -18
- package/src/report/reportObservable.js +113 -0
- package/src/session/sessionConstants.js +3 -0
- package/src/session/sessionCookieStore.js +159 -0
- package/src/session/sessionManagement.js +89 -0
- package/src/session/sessionStore.js +139 -0
- package/src/synthetics/syntheticsWorkerValues.js +22 -0
- package/src/synthetics/usr.js +18 -0
- package/src/tracekit/computeStackTrace.js +75 -551
- package/src/tracekit/index.js +1 -1
- package/src/tracekit/tracekit.js +100 -0
- package/src/transport/batch.js +289 -0
- package/src/transport/httpRequest.js +109 -0
- package/src/transport/index.js +3 -0
- package/src/transport/sendWithRetryStrategy.js +186 -0
- package/src/transport/startBatchWithReplica.js +26 -0
- package/cjs/configuration.js +0 -184
- package/cjs/errorCollection.js +0 -167
- package/cjs/fetchProxy.js +0 -162
- package/cjs/helper/errorFilter.js +0 -47
- package/cjs/sessionManagement.js +0 -221
- package/cjs/tracekit/report.js +0 -289
- package/cjs/transport.js +0 -373
- package/cjs/xhrProxy.js +0 -122
- package/esm/configuration.js +0 -167
- package/esm/errorCollection.js +0 -141
- package/esm/fetchProxy.js +0 -116
- package/esm/helper/errorFilter.js +0 -38
- package/esm/sessionManagement.js +0 -195
- package/esm/tracekit/report.js +0 -273
- package/esm/transport.js +0 -357
- package/esm/xhrProxy.js +0 -110
- package/src/configuration.js +0 -156
- package/src/errorCollection.js +0 -170
- package/src/fetchProxy.js +0 -109
- package/src/helper/errorFilter.js +0 -38
- package/src/sessionManagement.js +0 -224
- package/src/tracekit/report.js +0 -284
- package/src/transport.js +0 -380
- package/src/xhrProxy.js +0 -106
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { instrumentMethodAndCallOriginal } from '../helper/instrumentMethod';
|
|
2
|
+
import { computeStackTrace } from './computeStackTrace'; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
|
|
3
|
+
|
|
4
|
+
var ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
|
|
5
|
+
/**
|
|
6
|
+
* Cross-browser collection of unhandled errors
|
|
7
|
+
*
|
|
8
|
+
* Supports:
|
|
9
|
+
* - Firefox: full stack trace with line numbers, plus column number
|
|
10
|
+
* on top frame; column number is not guaranteed
|
|
11
|
+
* - Opera: full stack trace with line and column numbers
|
|
12
|
+
* - Chrome: full stack trace with line and column numbers
|
|
13
|
+
* - Safari: line and column number for the top frame only; some frames
|
|
14
|
+
* may be missing, and column number is not guaranteed
|
|
15
|
+
* - IE: line and column number for the top frame only; some frames
|
|
16
|
+
* may be missing, and column number is not guaranteed
|
|
17
|
+
*
|
|
18
|
+
* In theory, TraceKit should work on all of the following versions:
|
|
19
|
+
* - IE5.5+ (only 8.0 tested)
|
|
20
|
+
* - Firefox 0.9+ (only 3.5+ tested)
|
|
21
|
+
* - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
|
|
22
|
+
* Exceptions Have Stacktrace to be enabled in opera:config)
|
|
23
|
+
* - Safari 3+ (only 4+ tested)
|
|
24
|
+
* - Chrome 1+ (only 5+ tested)
|
|
25
|
+
* - Konqueror 3.5+ (untested)
|
|
26
|
+
*
|
|
27
|
+
* Tries to catch all unhandled errors and report them to the
|
|
28
|
+
* callback.
|
|
29
|
+
*
|
|
30
|
+
* Callbacks receive a StackTrace object as described in the
|
|
31
|
+
* computeStackTrace docs.
|
|
32
|
+
*
|
|
33
|
+
* @memberof TraceKit
|
|
34
|
+
* @namespace
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export function startUnhandledErrorCollection(callback) {
|
|
38
|
+
var _instrumentOnError = instrumentOnError(callback);
|
|
39
|
+
|
|
40
|
+
var _instrumentUnhandledRejection = instrumentUnhandledRejection(callback);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
stop: function stop() {
|
|
44
|
+
_instrumentOnError.stop();
|
|
45
|
+
|
|
46
|
+
_instrumentUnhandledRejection.stop();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Install a global onerror handler
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
function instrumentOnError(callback) {
|
|
55
|
+
return instrumentMethodAndCallOriginal(window, 'onerror', {
|
|
56
|
+
before: function before(message, url, lineNo, columnNo, errorObj) {
|
|
57
|
+
var stack;
|
|
58
|
+
|
|
59
|
+
if (errorObj) {
|
|
60
|
+
stack = computeStackTrace(errorObj);
|
|
61
|
+
callback(stack, errorObj);
|
|
62
|
+
} else {
|
|
63
|
+
var location = {
|
|
64
|
+
url: url,
|
|
65
|
+
column: columnNo,
|
|
66
|
+
line: lineNo
|
|
67
|
+
};
|
|
68
|
+
var name;
|
|
69
|
+
var msg = message;
|
|
70
|
+
|
|
71
|
+
if ({}.toString.call(message) === '[object String]') {
|
|
72
|
+
var groups = ERROR_TYPES_RE.exec(msg);
|
|
73
|
+
|
|
74
|
+
if (groups) {
|
|
75
|
+
name = groups[1];
|
|
76
|
+
msg = groups[2];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
stack = {
|
|
81
|
+
name: name,
|
|
82
|
+
message: typeof msg === 'string' ? msg : undefined,
|
|
83
|
+
stack: [location]
|
|
84
|
+
};
|
|
85
|
+
callback(stack, message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Install a global onunhandledrejection handler
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
function instrumentUnhandledRejection(callback) {
|
|
96
|
+
return instrumentMethodAndCallOriginal(window, 'onunhandledrejection', {
|
|
97
|
+
before: function before(e) {
|
|
98
|
+
var reason = e.reason || 'Empty reason';
|
|
99
|
+
var stack = computeStackTrace(reason);
|
|
100
|
+
callback(stack, reason);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { display } from '../helper/display';
|
|
2
|
+
import { addEventListener, noop, values, findByPath, escapeRowData, each, isNumber, isArray, extend, isString, toServerDuration, isBoolean, isEmptyObject, isObject, escapeJsonValue, escapeRowField } from '../helper/tools';
|
|
3
|
+
import { commonTags, dataMap } from '../dataMap';
|
|
4
|
+
import { DOM_EVENT, RumEventType } from '../helper/enums'; // https://en.wikipedia.org/wiki/UTF-8
|
|
5
|
+
// eslint-disable-next-line no-control-regex
|
|
6
|
+
|
|
7
|
+
var HAS_MULTI_BYTES_CHARACTERS = /[^\u0000-\u007F]/;
|
|
8
|
+
var CUSTOM_KEYS = 'custom_keys';
|
|
9
|
+
export var processedMessageByDataMap = function processedMessageByDataMap(message) {
|
|
10
|
+
if (!message || !message.type) return {
|
|
11
|
+
rowStr: '',
|
|
12
|
+
rowData: undefined
|
|
13
|
+
};
|
|
14
|
+
var rowData = {
|
|
15
|
+
tags: {},
|
|
16
|
+
fields: {}
|
|
17
|
+
};
|
|
18
|
+
var hasFileds = false;
|
|
19
|
+
var rowStr = '';
|
|
20
|
+
each(dataMap, function (value, key) {
|
|
21
|
+
if (value.type === message.type) {
|
|
22
|
+
rowStr += key + ',';
|
|
23
|
+
rowData.measurement = key;
|
|
24
|
+
var tagsStr = [];
|
|
25
|
+
var tags = extend({}, commonTags, value.tags);
|
|
26
|
+
var filterFileds = ['date', 'type', CUSTOM_KEYS]; // 已经在datamap中定义过的fields和tags
|
|
27
|
+
|
|
28
|
+
each(tags, function (value_path, _key) {
|
|
29
|
+
var _value = findByPath(message, value_path);
|
|
30
|
+
|
|
31
|
+
filterFileds.push(_key);
|
|
32
|
+
|
|
33
|
+
if (_value || isNumber(_value)) {
|
|
34
|
+
rowData.tags[_key] = escapeJsonValue(_value);
|
|
35
|
+
tagsStr.push(escapeRowData(_key) + '=' + escapeRowData(_value));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
var fieldsStr = [];
|
|
39
|
+
each(value.fields, function (_value, _key) {
|
|
40
|
+
if (isArray(_value) && _value.length === 2) {
|
|
41
|
+
var type = _value[0],
|
|
42
|
+
value_path = _value[1];
|
|
43
|
+
|
|
44
|
+
var _valueData = findByPath(message, value_path);
|
|
45
|
+
|
|
46
|
+
filterFileds.push(_key);
|
|
47
|
+
|
|
48
|
+
if (_valueData || isNumber(_valueData)) {
|
|
49
|
+
rowData.fields[_key] = _valueData; // 这里不需要转译
|
|
50
|
+
// _valueData =
|
|
51
|
+
// type === 'string'
|
|
52
|
+
// ? '"' +
|
|
53
|
+
// _valueData.replace(/[\\]*"/g, '"').replace(/"/g, '\\"') +
|
|
54
|
+
// '"'
|
|
55
|
+
// : escapeRowData(_valueData)
|
|
56
|
+
|
|
57
|
+
fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_valueData));
|
|
58
|
+
}
|
|
59
|
+
} else if (isString(_value)) {
|
|
60
|
+
var _valueData = findByPath(message, _value);
|
|
61
|
+
|
|
62
|
+
filterFileds.push(_key);
|
|
63
|
+
|
|
64
|
+
if (_valueData || isNumber(_valueData)) {
|
|
65
|
+
rowData.fields[_key] = _valueData; // 这里不需要转译
|
|
66
|
+
|
|
67
|
+
fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_valueData));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (message.tags && isObject(message.tags) && !isEmptyObject(message.tags)) {
|
|
73
|
+
// 自定义tag, 存储成field
|
|
74
|
+
var _tagKeys = [];
|
|
75
|
+
each(message.tags, function (_value, _key) {
|
|
76
|
+
// 如果和之前tag重名,则舍弃
|
|
77
|
+
if (filterFileds.indexOf(_key) > -1) return;
|
|
78
|
+
filterFileds.push(_key);
|
|
79
|
+
|
|
80
|
+
if (_value || isNumber(_value)) {
|
|
81
|
+
_tagKeys.push(_key);
|
|
82
|
+
|
|
83
|
+
rowData.fields[_key] = _value; // 这里不需要转译
|
|
84
|
+
|
|
85
|
+
fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_value));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (_tagKeys.length) {
|
|
90
|
+
rowData.fields[CUSTOM_KEYS] = escapeRowField(_tagKeys);
|
|
91
|
+
fieldsStr.push(escapeRowData(CUSTOM_KEYS) + '=' + escapeRowField(_tagKeys));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (message.type === RumEventType.LOGGER) {
|
|
96
|
+
// 这里处理日志类型数据自定义字段
|
|
97
|
+
each(message, function (value, key) {
|
|
98
|
+
if (filterFileds.indexOf(key) === -1 && (isNumber(value) || isString(value) || isBoolean(value))) {
|
|
99
|
+
rowData.fields[key] = value; // 这里不需要转译
|
|
100
|
+
|
|
101
|
+
fieldsStr.push(escapeRowData(key) + '=' + escapeRowField(value));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (tagsStr.length) {
|
|
107
|
+
rowStr += tagsStr.join(',');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (fieldsStr.length) {
|
|
111
|
+
rowStr += ' ';
|
|
112
|
+
rowStr += fieldsStr.join(',');
|
|
113
|
+
hasFileds = true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
rowStr = rowStr + ' ' + message.date;
|
|
117
|
+
rowData.time = toServerDuration(message.date); // 这里不需要转译
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
rowStr: hasFileds ? rowStr : '',
|
|
122
|
+
rowData: hasFileds ? rowData : undefined
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
var batch = function batch(request, batchMessagesLimit, batchBytesLimit, messageBytesLimit, flushTimeout, beforeUnloadCallback) {
|
|
127
|
+
this.pushOnlyBuffer = [];
|
|
128
|
+
this.upsertBuffer = {};
|
|
129
|
+
this.bufferBytesCount = 0;
|
|
130
|
+
this.bufferMessagesCount = 0;
|
|
131
|
+
this.request = request;
|
|
132
|
+
this.batchMessagesLimit = batchMessagesLimit;
|
|
133
|
+
this.batchBytesLimit = batchBytesLimit;
|
|
134
|
+
this.messageBytesLimit = messageBytesLimit;
|
|
135
|
+
this.flushTimeout = flushTimeout;
|
|
136
|
+
|
|
137
|
+
if (typeof beforeUnloadCallback === 'function') {
|
|
138
|
+
this.beforeUnloadCallback = beforeUnloadCallback;
|
|
139
|
+
} else {
|
|
140
|
+
this.beforeUnloadCallback = noop;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.setupFlushOnExit();
|
|
144
|
+
this.flushPeriodically();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
batch.prototype.add = function (message) {
|
|
148
|
+
this.addOrUpdate(message);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
batch.prototype.upsert = function (message, key) {
|
|
152
|
+
this.addOrUpdate(message, key);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
batch.prototype.flush = function (sendFn) {
|
|
156
|
+
if (typeof sendFn !== 'function') {
|
|
157
|
+
sendFn = this.request.send;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.bufferMessagesCount !== 0) {
|
|
161
|
+
var messages = this.pushOnlyBuffer.concat(values(this.upsertBuffer));
|
|
162
|
+
var bytesCount = this.bufferBytesCount;
|
|
163
|
+
this.pushOnlyBuffer = [];
|
|
164
|
+
this.upsertBuffer = {};
|
|
165
|
+
this.bufferBytesCount = 0;
|
|
166
|
+
this.bufferMessagesCount = 0;
|
|
167
|
+
|
|
168
|
+
if (messages.length > 0) {
|
|
169
|
+
sendFn({
|
|
170
|
+
data: messages.join('\n'),
|
|
171
|
+
bytesCount: bytesCount
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
batch.prototype.flushOnExit = function () {
|
|
178
|
+
this.flush(this.request.sendOnExit);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
batch.prototype.computeBytesCount = function (candidate) {
|
|
182
|
+
// Accurate bytes count computations can degrade performances when there is a lot of events to process
|
|
183
|
+
if (!HAS_MULTI_BYTES_CHARACTERS.test(candidate)) {
|
|
184
|
+
return candidate.length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (window.TextEncoder !== undefined) {
|
|
188
|
+
return new TextEncoder().encode(candidate).length;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return new Blob([candidate]).size;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
batch.prototype.addOrUpdate = function (message, key) {
|
|
195
|
+
var _process = this.process(message);
|
|
196
|
+
|
|
197
|
+
var processedMessage = _process.processedMessage;
|
|
198
|
+
var messageBytesCount = _process.messageBytesCount;
|
|
199
|
+
|
|
200
|
+
if (messageBytesCount >= this.messageBytesLimit) {
|
|
201
|
+
display.warn('Discarded a message whose size was bigger than the maximum allowed size ' + this.messageBytesLimit + 'KB.');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (this.hasMessageFor(key)) {
|
|
206
|
+
this.remove(key);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (this.willReachedBytesLimitWith(messageBytesCount)) {
|
|
210
|
+
this.flush();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.push(processedMessage, messageBytesCount, key);
|
|
214
|
+
|
|
215
|
+
if (this.isFull()) {
|
|
216
|
+
this.flush();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
batch.prototype.process = function (message) {
|
|
221
|
+
var processedMessage = processedMessageByDataMap(message).rowStr;
|
|
222
|
+
var messageBytesCount = this.computeBytesCount(processedMessage);
|
|
223
|
+
return {
|
|
224
|
+
processedMessage: processedMessage,
|
|
225
|
+
messageBytesCount: messageBytesCount
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
batch.prototype.push = function (processedMessage, messageBytesCount, key) {
|
|
230
|
+
if (this.bufferMessagesCount > 0) {
|
|
231
|
+
// \n separator at serialization
|
|
232
|
+
this.bufferBytesCount += 1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (key !== undefined) {
|
|
236
|
+
this.upsertBuffer[key] = processedMessage;
|
|
237
|
+
} else {
|
|
238
|
+
this.pushOnlyBuffer.push(processedMessage);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.bufferBytesCount += messageBytesCount;
|
|
242
|
+
this.bufferMessagesCount += 1;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
batch.prototype.remove = function (key) {
|
|
246
|
+
var removedMessage = this.upsertBuffer[key];
|
|
247
|
+
delete this.upsertBuffer[key];
|
|
248
|
+
var messageBytesCount = this.computeBytesCount(removedMessage);
|
|
249
|
+
this.bufferBytesCount -= messageBytesCount;
|
|
250
|
+
this.bufferMessagesCount -= 1;
|
|
251
|
+
|
|
252
|
+
if (this.bufferMessagesCount > 0) {
|
|
253
|
+
this.bufferBytesCount -= 1;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
batch.prototype.hasMessageFor = function (key) {
|
|
258
|
+
return key !== undefined && this.upsertBuffer[key] !== undefined;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
batch.prototype.willReachedBytesLimitWith = function (messageBytesCount) {
|
|
262
|
+
// byte of the separator at the end of the message
|
|
263
|
+
return this.bufferBytesCount + messageBytesCount + 1 >= this.batchBytesLimit;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
batch.prototype.isFull = function () {
|
|
267
|
+
return this.bufferMessagesCount === this.batchMessagesLimit || this.bufferBytesCount >= this.batchBytesLimit;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
batch.prototype.flushPeriodically = function () {
|
|
271
|
+
var _this = this;
|
|
272
|
+
|
|
273
|
+
setTimeout(function () {
|
|
274
|
+
_this.flush();
|
|
275
|
+
|
|
276
|
+
_this.flushPeriodically();
|
|
277
|
+
}, this.flushTimeout);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
batch.prototype.setupFlushOnExit = function () {
|
|
281
|
+
/**
|
|
282
|
+
* With sendBeacon, requests are guaranteed to be successfully sent during document unload
|
|
283
|
+
*/
|
|
284
|
+
// @ts-ignore this function is not always defined
|
|
285
|
+
var _this = this;
|
|
286
|
+
|
|
287
|
+
if (navigator.sendBeacon) {
|
|
288
|
+
/**
|
|
289
|
+
* beforeunload is called before visibilitychange
|
|
290
|
+
* register first to be sure to be called before flush on beforeunload
|
|
291
|
+
* caveat: unload can still be canceled by another listener
|
|
292
|
+
*/
|
|
293
|
+
addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, this.beforeUnloadCallback);
|
|
294
|
+
/**
|
|
295
|
+
* Only event that guarantee to fire on mobile devices when the page transitions to background state
|
|
296
|
+
* (e.g. when user switches to a different application, goes to homescreen, etc), or is being unloaded.
|
|
297
|
+
*/
|
|
298
|
+
|
|
299
|
+
addEventListener(document, DOM_EVENT.VISIBILITY_CHANGE, function () {
|
|
300
|
+
if (document.visibilityState === 'hidden') {
|
|
301
|
+
_this.flushOnExit();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
/**
|
|
305
|
+
* Safari does not support yet to send a request during:
|
|
306
|
+
* - a visibility change during doc unload (cf: https://bugs.webkit.org/show_bug.cgi?id=194897)
|
|
307
|
+
* - a page hide transition (cf: https://bugs.webkit.org/show_bug.cgi?id=188329)
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, function () {
|
|
311
|
+
return _this.flushOnExit();
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export var Batch = batch;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { newRetryState, sendWithRetryStrategy } from './sendWithRetryStrategy';
|
|
2
|
+
/**
|
|
3
|
+
* Use POST request without content type to:
|
|
4
|
+
* - avoid CORS preflight requests
|
|
5
|
+
* - allow usage of sendBeacon
|
|
6
|
+
*
|
|
7
|
+
* multiple elements are sent separated by \n in order
|
|
8
|
+
* to be parsed correctly without content type header
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function addBatchPrecision(url) {
|
|
12
|
+
if (!url) return url;
|
|
13
|
+
return url + (url.indexOf('?') === -1 ? '?' : '&') + 'precision=ms';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createHttpRequest(endpointUrl, bytesLimit, reportError) {
|
|
17
|
+
var retryState = newRetryState();
|
|
18
|
+
|
|
19
|
+
var sendStrategyForRetry = function sendStrategyForRetry(payload, onResponse) {
|
|
20
|
+
return fetchKeepAliveStrategy(endpointUrl, bytesLimit, payload, onResponse);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
send: function send(payload) {
|
|
25
|
+
sendWithRetryStrategy(payload, retryState, sendStrategyForRetry, reportError);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Since fetch keepalive behaves like regular fetch on Firefox,
|
|
30
|
+
* keep using sendBeaconStrategy on exit
|
|
31
|
+
*/
|
|
32
|
+
sendOnExit: function sendOnExit(payload) {
|
|
33
|
+
sendBeaconStrategy(endpointUrl, bytesLimit, payload);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sendBeaconStrategy(endpointUrl, bytesLimit, payload) {
|
|
39
|
+
var data = payload.data;
|
|
40
|
+
var bytesCount = payload.bytesCount;
|
|
41
|
+
var url = addBatchPrecision(endpointUrl);
|
|
42
|
+
var canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit;
|
|
43
|
+
|
|
44
|
+
if (canUseBeacon) {
|
|
45
|
+
try {
|
|
46
|
+
var isQueued = navigator.sendBeacon(url, data);
|
|
47
|
+
|
|
48
|
+
if (isQueued) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {// reportBeaconError(e)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sendXHR(url, data);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function fetchKeepAliveStrategy(endpointUrl, bytesLimit, payload, onResponse) {
|
|
59
|
+
var data = payload.data;
|
|
60
|
+
var bytesCount = payload.bytesCount;
|
|
61
|
+
var url = addBatchPrecision(endpointUrl);
|
|
62
|
+
var canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit;
|
|
63
|
+
|
|
64
|
+
if (canUseKeepAlive) {
|
|
65
|
+
fetch(url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: data,
|
|
68
|
+
keepalive: true
|
|
69
|
+
}).then(function (response) {
|
|
70
|
+
if (typeof onResponse === 'function') {
|
|
71
|
+
onResponse({
|
|
72
|
+
status: response.status
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, function () {
|
|
76
|
+
// failed to queue the request
|
|
77
|
+
sendXHR(url, data, onResponse);
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
sendXHR(url, data, onResponse);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isKeepAliveSupported() {
|
|
85
|
+
// Request can throw, cf https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#errors
|
|
86
|
+
try {
|
|
87
|
+
return window.Request && 'keepalive' in new Request('http://a');
|
|
88
|
+
} catch (_unused) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sendXHR(url, data, onResponse) {
|
|
94
|
+
var request = new XMLHttpRequest();
|
|
95
|
+
request.open('POST', url, true);
|
|
96
|
+
request.send(data);
|
|
97
|
+
request.addEventListener('loadend', function () {
|
|
98
|
+
if (typeof onResponse === 'function') {
|
|
99
|
+
onResponse({
|
|
100
|
+
status: request.status
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { clocksNow, ONE_KIBI_BYTE, ONE_MEBI_BYTE, ONE_SECOND } from '../helper/tools';
|
|
2
|
+
import { ErrorSource } from '../helper/errorTools';
|
|
3
|
+
export var MAX_ONGOING_BYTES_COUNT = 80 * ONE_KIBI_BYTE;
|
|
4
|
+
export var MAX_ONGOING_REQUESTS = 32;
|
|
5
|
+
export var MAX_QUEUE_BYTES_COUNT = 3 * ONE_MEBI_BYTE;
|
|
6
|
+
export var MAX_BACKOFF_TIME = 256 * ONE_SECOND;
|
|
7
|
+
export var INITIAL_BACKOFF_TIME = ONE_SECOND;
|
|
8
|
+
var TransportStatus = {
|
|
9
|
+
UP: 'UP',
|
|
10
|
+
FAILURE_DETECTED: 'FAILURE_DETECTED',
|
|
11
|
+
DOWN: 'DOWN'
|
|
12
|
+
};
|
|
13
|
+
var RetryReason = {
|
|
14
|
+
AFTER_SUCCESS: 'AFTER_SUCCESS',
|
|
15
|
+
AFTER_RESUME: 'AFTER_RESUME'
|
|
16
|
+
};
|
|
17
|
+
export function sendWithRetryStrategy(payload, state, sendStrategy, reportError) {
|
|
18
|
+
if (state.transportStatus === TransportStatus.UP && state.queuedPayloads.size() === 0 && state.bandwidthMonitor.canHandle(payload)) {
|
|
19
|
+
send(payload, state, sendStrategy, {
|
|
20
|
+
onSuccess: function onSuccess() {
|
|
21
|
+
return retryQueuedPayloads(RetryReason.AFTER_SUCCESS, state, sendStrategy, reportError);
|
|
22
|
+
},
|
|
23
|
+
onFailure: function onFailure() {
|
|
24
|
+
state.queuedPayloads.enqueue(payload);
|
|
25
|
+
scheduleRetry(state, sendStrategy, reportError);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
state.queuedPayloads.enqueue(payload);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scheduleRetry(state, sendStrategy, reportError) {
|
|
34
|
+
if (state.transportStatus !== TransportStatus.DOWN) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setTimeout(function () {
|
|
39
|
+
var payload = state.queuedPayloads.first();
|
|
40
|
+
send(payload, state, sendStrategy, {
|
|
41
|
+
onSuccess: function onSuccess() {
|
|
42
|
+
state.queuedPayloads.dequeue(); // if (state.lastFailureStatus !== 0) {
|
|
43
|
+
// addTelemetryDebug('resuming after transport down', {
|
|
44
|
+
// failureStatus: state.lastFailureStatus,
|
|
45
|
+
// })
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
state.currentBackoffTime = INITIAL_BACKOFF_TIME;
|
|
49
|
+
retryQueuedPayloads(RetryReason.AFTER_RESUME, state, sendStrategy, reportError);
|
|
50
|
+
},
|
|
51
|
+
onFailure: function onFailure() {
|
|
52
|
+
state.currentBackoffTime = Math.min(MAX_BACKOFF_TIME, state.currentBackoffTime * 2);
|
|
53
|
+
scheduleRetry(state, sendStrategy, reportError);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}, state.currentBackoffTime);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function send(payload, state, sendStrategy, responseData) {
|
|
60
|
+
var onSuccess = responseData.onSuccess;
|
|
61
|
+
var onFailure = responseData.onFailure;
|
|
62
|
+
state.bandwidthMonitor.add(payload);
|
|
63
|
+
sendStrategy(payload, function (response) {
|
|
64
|
+
state.bandwidthMonitor.remove(payload);
|
|
65
|
+
|
|
66
|
+
if (wasRequestSuccessful(response)) {
|
|
67
|
+
state.transportStatus = TransportStatus.UP;
|
|
68
|
+
onSuccess();
|
|
69
|
+
} else {
|
|
70
|
+
// do not consider transport down if another ongoing request could succeed
|
|
71
|
+
state.transportStatus = state.bandwidthMonitor.ongoingRequestCount > 0 ? TransportStatus.FAILURE_DETECTED : TransportStatus.DOWN;
|
|
72
|
+
state.lastFailureStatus = response.status;
|
|
73
|
+
onFailure();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function retryQueuedPayloads(reason, state, sendStrategy, reportError) {
|
|
79
|
+
if (reason === RetryReason.AFTER_SUCCESS && state.queuedPayloads.isFull() && !state.queueFullReported) {
|
|
80
|
+
reportError({
|
|
81
|
+
message: 'Reached max events size queued for upload: ' + MAX_QUEUE_BYTES_COUNT / ONE_MEBI_BYTE + 'MiB',
|
|
82
|
+
source: ErrorSource.AGENT,
|
|
83
|
+
startClocks: clocksNow()
|
|
84
|
+
});
|
|
85
|
+
state.queueFullReported = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var previousQueue = state.queuedPayloads;
|
|
89
|
+
state.queuedPayloads = newPayloadQueue();
|
|
90
|
+
|
|
91
|
+
while (previousQueue.size() > 0) {
|
|
92
|
+
sendWithRetryStrategy(previousQueue.dequeue(), state, sendStrategy, reportError);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function wasRequestSuccessful(response) {
|
|
97
|
+
return response.status !== 0 && response.status < 500;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function newRetryState() {
|
|
101
|
+
return {
|
|
102
|
+
transportStatus: TransportStatus.UP,
|
|
103
|
+
lastFailureStatus: 0,
|
|
104
|
+
currentBackoffTime: INITIAL_BACKOFF_TIME,
|
|
105
|
+
bandwidthMonitor: newBandwidthMonitor(),
|
|
106
|
+
queuedPayloads: newPayloadQueue(),
|
|
107
|
+
queueFullReported: false
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function newPayloadQueue() {
|
|
112
|
+
var queue = [];
|
|
113
|
+
return {
|
|
114
|
+
bytesCount: 0,
|
|
115
|
+
enqueue: function enqueue(payload) {
|
|
116
|
+
if (this.isFull()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
queue.push(payload);
|
|
121
|
+
this.bytesCount += payload.bytesCount;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
first() {
|
|
125
|
+
return queue[0];
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
dequeue() {
|
|
129
|
+
var payload = queue.shift();
|
|
130
|
+
|
|
131
|
+
if (payload) {
|
|
132
|
+
this.bytesCount -= payload.bytesCount;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return payload;
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
size() {
|
|
139
|
+
return queue.length;
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
isFull() {
|
|
143
|
+
return this.bytesCount >= MAX_QUEUE_BYTES_COUNT;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function newBandwidthMonitor() {
|
|
150
|
+
return {
|
|
151
|
+
ongoingRequestCount: 0,
|
|
152
|
+
ongoingByteCount: 0,
|
|
153
|
+
canHandle: function canHandle(payload) {
|
|
154
|
+
return this.ongoingRequestCount === 0 || this.ongoingByteCount + payload.bytesCount <= MAX_ONGOING_BYTES_COUNT && this.ongoingRequestCount < MAX_ONGOING_REQUESTS;
|
|
155
|
+
},
|
|
156
|
+
add: function add(payload) {
|
|
157
|
+
this.ongoingRequestCount += 1;
|
|
158
|
+
this.ongoingByteCount += payload.bytesCount;
|
|
159
|
+
},
|
|
160
|
+
remove: function remove(payload) {
|
|
161
|
+
this.ongoingRequestCount -= 1;
|
|
162
|
+
this.ongoingByteCount -= payload.bytesCount;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Batch } from './batch';
|
|
2
|
+
import { createHttpRequest } from './httpRequest';
|
|
3
|
+
export function startBatchWithReplica(configuration, endpointUrl, reportError) {
|
|
4
|
+
var primaryBatch = createBatch(endpointUrl);
|
|
5
|
+
|
|
6
|
+
function createBatch(endpointUrl) {
|
|
7
|
+
return new Batch(createHttpRequest(endpointUrl, configuration.batchBytesLimit, reportError), configuration.batchMessagesLimit, configuration.batchBytesLimit, configuration.messageBytesLimit, configuration.flushTimeout);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
add: function add(message) {
|
|
12
|
+
primaryBatch.add(message);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|