@cloudcare/browser-core 2.0.14 → 2.0.16
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/dataMap.js +9 -9
- package/cjs/helper/enums.js +1 -0
- package/cjs/helper/limitModification.js +39 -18
- package/cjs/helper/sanitize.js +239 -0
- package/cjs/helper/tools.js +6 -0
- package/cjs/index.js +13 -0
- package/esm/dataMap.js +9 -9
- package/esm/helper/enums.js +1 -0
- package/esm/helper/limitModification.js +20 -15
- package/esm/helper/sanitize.js +226 -0
- package/esm/helper/tools.js +4 -0
- package/esm/index.js +1 -0
- package/package.json +2 -2
- package/src/dataMap.js +9 -9
- package/src/helper/enums.js +1 -0
- package/src/helper/limitModification.js +25 -15
- package/src/helper/sanitize.js +260 -0
- package/src/helper/tools.js +6 -1
- package/src/index.js +1 -0
package/cjs/dataMap.js
CHANGED
|
@@ -8,8 +8,8 @@ exports.dataMap = exports.commonTags = exports.commonFields = void 0;
|
|
|
8
8
|
var _enums = require("./helper/enums");
|
|
9
9
|
|
|
10
10
|
var commonTags = {
|
|
11
|
-
sdk_name: '
|
|
12
|
-
sdk_version: '
|
|
11
|
+
sdk_name: '_gc.sdk_name',
|
|
12
|
+
sdk_version: '_gc.sdk_version',
|
|
13
13
|
app_id: 'application.id',
|
|
14
14
|
env: 'env',
|
|
15
15
|
service: 'service',
|
|
@@ -57,7 +57,7 @@ var dataMap = {
|
|
|
57
57
|
},
|
|
58
58
|
fields: {
|
|
59
59
|
is_active: 'view.is_active',
|
|
60
|
-
session_replay_stats: '
|
|
60
|
+
session_replay_stats: '_gc.replay_stats',
|
|
61
61
|
session_is_active: 'session.is_active',
|
|
62
62
|
view_error_count: 'view.error.count',
|
|
63
63
|
view_resource_count: 'view.resource.count',
|
|
@@ -86,8 +86,8 @@ var dataMap = {
|
|
|
86
86
|
resource: {
|
|
87
87
|
type: _enums.RumEventType.RESOURCE,
|
|
88
88
|
tags: {
|
|
89
|
-
trace_id: '
|
|
90
|
-
span_id: '
|
|
89
|
+
trace_id: '_gc.trace_id',
|
|
90
|
+
span_id: '_gc.span_id',
|
|
91
91
|
resource_url: 'resource.url',
|
|
92
92
|
resource_url_host: 'resource.url_host',
|
|
93
93
|
resource_url_path: 'resource.url_path',
|
|
@@ -120,8 +120,8 @@ var dataMap = {
|
|
|
120
120
|
type: _enums.RumEventType.ERROR,
|
|
121
121
|
tags: {
|
|
122
122
|
error_id: 'error.id',
|
|
123
|
-
trace_id: '
|
|
124
|
-
span_id: '
|
|
123
|
+
trace_id: '_gc.trace_id',
|
|
124
|
+
span_id: '_gc.span_id',
|
|
125
125
|
error_source: 'error.source',
|
|
126
126
|
error_type: 'error.type',
|
|
127
127
|
error_handling: 'error.handling' // resource_url: 'error.resource.url',
|
|
@@ -160,8 +160,8 @@ var dataMap = {
|
|
|
160
160
|
action_resource_count: 'action.resource.count',
|
|
161
161
|
action_frustration_types: 'action.frustration.type',
|
|
162
162
|
action_long_task_count: 'action.long_task.count',
|
|
163
|
-
action_target: '
|
|
164
|
-
action_position: '
|
|
163
|
+
action_target: '_gc.action.target',
|
|
164
|
+
action_position: '_gc.action.position'
|
|
165
165
|
}
|
|
166
166
|
},
|
|
167
167
|
browser_log: {
|
package/cjs/helper/enums.js
CHANGED
|
@@ -7,22 +7,31 @@ exports.limitModification = limitModification;
|
|
|
7
7
|
|
|
8
8
|
var _tools = require("./tools");
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
var _sanitize = require("./sanitize");
|
|
11
|
+
|
|
12
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
|
|
13
|
+
|
|
14
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
15
|
+
|
|
16
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
|
-
* Current
|
|
19
|
+
* Current limitation:
|
|
14
20
|
* - field path do not support array, 'a.b.c' only
|
|
15
|
-
* - modifiable fields type must be string
|
|
16
21
|
*/
|
|
17
22
|
function limitModification(object, modifiableFieldPaths, modifier) {
|
|
18
|
-
var clone = (0, _tools.
|
|
23
|
+
var clone = (0, _tools.deepClone)(object);
|
|
19
24
|
var result = modifier(clone);
|
|
20
|
-
(0, _tools.each)(modifiableFieldPaths, function (
|
|
21
|
-
var
|
|
22
|
-
var
|
|
25
|
+
(0, _tools.each)((0, _tools.objectEntries)(modifiableFieldPaths), function (filedPaths) {
|
|
26
|
+
var fieldPath = filedPaths[0];
|
|
27
|
+
var fieldType = filedPaths[1];
|
|
28
|
+
var newValue = get(clone, fieldPath);
|
|
29
|
+
var newType = (0, _tools.getType)(newValue);
|
|
23
30
|
|
|
24
|
-
if (
|
|
25
|
-
set(object,
|
|
31
|
+
if (newType === fieldType) {
|
|
32
|
+
set(object, fieldPath, (0, _sanitize.sanitize)(newValue));
|
|
33
|
+
} else if (fieldType === 'object' && (newType === 'undefined' || newType === 'null')) {
|
|
34
|
+
set(object, fieldPath, {});
|
|
26
35
|
}
|
|
27
36
|
});
|
|
28
37
|
return result;
|
|
@@ -30,16 +39,24 @@ function limitModification(object, modifiableFieldPaths, modifier) {
|
|
|
30
39
|
|
|
31
40
|
function get(object, path) {
|
|
32
41
|
var current = object;
|
|
33
|
-
var fields = path.split('.');
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
var _iterator = _createForOfIteratorHelper(path.split('.')),
|
|
44
|
+
_step;
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
try {
|
|
47
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
48
|
+
var field = _step.value;
|
|
49
|
+
|
|
50
|
+
if (!isValidObjectContaining(current, field)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
current = current[field];
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
_iterator.e(err);
|
|
58
|
+
} finally {
|
|
59
|
+
_iterator.f();
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
return current;
|
|
@@ -52,7 +69,7 @@ function set(object, path, value) {
|
|
|
52
69
|
for (var i = 0; i < fields.length; i += 1) {
|
|
53
70
|
var field = fields[i];
|
|
54
71
|
|
|
55
|
-
if (!
|
|
72
|
+
if (!isValidObject(current)) {
|
|
56
73
|
return;
|
|
57
74
|
}
|
|
58
75
|
|
|
@@ -64,6 +81,10 @@ function set(object, path, value) {
|
|
|
64
81
|
}
|
|
65
82
|
}
|
|
66
83
|
|
|
84
|
+
function isValidObject(object) {
|
|
85
|
+
return (0, _tools.getType)(object) === 'object';
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
function isValidObjectContaining(object, field) {
|
|
68
|
-
return
|
|
89
|
+
return isValidObject(object) && Object.prototype.hasOwnProperty.call(object, field);
|
|
69
90
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.sanitize = sanitize;
|
|
7
|
+
|
|
8
|
+
var _display = require("./display");
|
|
9
|
+
|
|
10
|
+
var _byteUtils = require("./byteUtils");
|
|
11
|
+
|
|
12
|
+
var _jsonStringify = require("./serialisation/jsonStringify");
|
|
13
|
+
|
|
14
|
+
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
|
15
|
+
|
|
16
|
+
// The maximum size of a single event is 256KiB. By default, we ensure that user-provided data
|
|
17
|
+
// going through sanitize fits inside our events, while leaving room for other contexts, metadata, ...
|
|
18
|
+
var SANITIZE_DEFAULT_MAX_CHARACTER_COUNT = 220 * _byteUtils.ONE_KIBI_BYTE; // Symbol for the root element of the JSONPath used for visited objects
|
|
19
|
+
|
|
20
|
+
var JSON_PATH_ROOT_ELEMENT = '$'; // When serializing (using JSON.stringify) a key of an object, { key: 42 } gets wrapped in quotes as "key".
|
|
21
|
+
// With the separator (:), we need to add 3 characters to the count.
|
|
22
|
+
|
|
23
|
+
var KEY_DECORATION_LENGTH = 3;
|
|
24
|
+
/**
|
|
25
|
+
* Ensures user-provided data is 'safe' for the SDK
|
|
26
|
+
* - Deep clones data
|
|
27
|
+
* - Removes cyclic references
|
|
28
|
+
* - Transforms unserializable types to a string representation
|
|
29
|
+
*
|
|
30
|
+
* LIMITATIONS:
|
|
31
|
+
* - Size is in characters, not byte count (may differ according to character encoding)
|
|
32
|
+
* - Size does not take into account indentation that can be applied to JSON.stringify
|
|
33
|
+
* - Non-numerical properties of Arrays are ignored. Same behavior as JSON.stringify
|
|
34
|
+
*
|
|
35
|
+
* @param source User-provided data meant to be serialized using JSON.stringify
|
|
36
|
+
* @param maxCharacterCount Maximum number of characters allowed in serialized form
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
function sanitize(source, maxCharacterCount) {
|
|
40
|
+
if (maxCharacterCount === undefined) {
|
|
41
|
+
maxCharacterCount = SANITIZE_DEFAULT_MAX_CHARACTER_COUNT;
|
|
42
|
+
} // Unbind any toJSON function we may have on [] or {} prototypes
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
var restoreObjectPrototypeToJson = (0, _jsonStringify.detachToJsonMethod)(Object.prototype);
|
|
46
|
+
var restoreArrayPrototypeToJson = (0, _jsonStringify.detachToJsonMethod)(Array.prototype); // Initial call to sanitizeProcessor - will populate containerQueue if source is an Array or a plain Object
|
|
47
|
+
|
|
48
|
+
var containerQueue = [];
|
|
49
|
+
var visitedObjectsWithPath = new WeakMap();
|
|
50
|
+
var sanitizedData = sanitizeProcessor(source, JSON_PATH_ROOT_ELEMENT, undefined, containerQueue, visitedObjectsWithPath);
|
|
51
|
+
var accumulatedCharacterCount = JSON.stringify(sanitizedData) && JSON.stringify(sanitizedData).length || 0;
|
|
52
|
+
|
|
53
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
54
|
+
warnOverCharacterLimit(maxCharacterCount, 'discarded', source);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
while (containerQueue.length > 0 && accumulatedCharacterCount < maxCharacterCount) {
|
|
59
|
+
var containerToProcess = containerQueue.shift();
|
|
60
|
+
var separatorLength = 0; // 0 for the first element, 1 for subsequent elements
|
|
61
|
+
// Arrays and Objects have to be handled distinctly to ensure
|
|
62
|
+
// we do not pick up non-numerical properties from Arrays
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(containerToProcess.source)) {
|
|
65
|
+
for (var key = 0; key < containerToProcess.source.length; key++) {
|
|
66
|
+
var targetData = sanitizeProcessor(containerToProcess.source[key], containerToProcess.path, key, containerQueue, visitedObjectsWithPath);
|
|
67
|
+
|
|
68
|
+
if (targetData !== undefined) {
|
|
69
|
+
accumulatedCharacterCount += JSON.stringify(targetData).length;
|
|
70
|
+
} else {
|
|
71
|
+
// When an element of an Array (targetData) is undefined, it is serialized as null:
|
|
72
|
+
// JSON.stringify([undefined]) => '[null]' - This accounts for 4 characters
|
|
73
|
+
accumulatedCharacterCount += 4;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
accumulatedCharacterCount += separatorLength;
|
|
77
|
+
separatorLength = 1;
|
|
78
|
+
|
|
79
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
80
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
containerToProcess.target[key] = targetData;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
for (var key in containerToProcess.source) {
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(containerToProcess.source, key)) {
|
|
89
|
+
var targetData = sanitizeProcessor(containerToProcess.source[key], containerToProcess.path, key, containerQueue, visitedObjectsWithPath); // When a property of an object has an undefined value, it will be dropped during serialization:
|
|
90
|
+
// JSON.stringify({a:undefined}) => '{}'
|
|
91
|
+
|
|
92
|
+
if (targetData !== undefined) {
|
|
93
|
+
accumulatedCharacterCount += JSON.stringify(targetData).length + separatorLength + key.length + KEY_DECORATION_LENGTH;
|
|
94
|
+
separatorLength = 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
98
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
containerToProcess.target[key] = targetData;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} // Rebind detached toJSON functions
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
restoreObjectPrototypeToJson();
|
|
110
|
+
restoreArrayPrototypeToJson();
|
|
111
|
+
return sanitizedData;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Internal function to factorize the process common to the
|
|
115
|
+
* initial call to sanitize, and iterations for Arrays and Objects
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
function sanitizeProcessor(source, parentPath, key, queue, visitedObjectsWithPath) {
|
|
121
|
+
// Start by handling toJSON, as we want to sanitize its output
|
|
122
|
+
var sourceToSanitize = tryToApplyToJSON(source);
|
|
123
|
+
|
|
124
|
+
if (!sourceToSanitize || _typeof(sourceToSanitize) !== 'object') {
|
|
125
|
+
return sanitizePrimitivesAndFunctions(sourceToSanitize);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var sanitizedSource = sanitizeObjects(sourceToSanitize);
|
|
129
|
+
|
|
130
|
+
if (sanitizedSource !== '[Object]' && sanitizedSource !== '[Array]' && sanitizedSource !== '[Error]') {
|
|
131
|
+
return sanitizedSource;
|
|
132
|
+
} // Handle potential cyclic references
|
|
133
|
+
// We need to use source as sourceToSanitize could be a reference to a new object
|
|
134
|
+
// At this stage, we know the source is an object type
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
var sourceAsObject = source;
|
|
138
|
+
|
|
139
|
+
if (visitedObjectsWithPath.has(sourceAsObject)) {
|
|
140
|
+
return '[Reference seen at ' + visitedObjectsWithPath.get(sourceAsObject) + ']';
|
|
141
|
+
} // Add processed source to queue
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
var currentPath = key !== undefined ? parentPath + '.' + key : parentPath;
|
|
145
|
+
var target = Array.isArray(sourceToSanitize) ? [] : {};
|
|
146
|
+
visitedObjectsWithPath.set(sourceAsObject, currentPath);
|
|
147
|
+
queue.push({
|
|
148
|
+
source: sourceToSanitize,
|
|
149
|
+
target: target,
|
|
150
|
+
path: currentPath
|
|
151
|
+
});
|
|
152
|
+
return target;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Handles sanitization of simple, non-object types
|
|
156
|
+
*
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
function sanitizePrimitivesAndFunctions(value) {
|
|
161
|
+
// BigInt cannot be serialized by JSON.stringify(), convert it to a string representation
|
|
162
|
+
if (typeof value === 'bigint') {
|
|
163
|
+
return '[BigInt] ' + value.toString();
|
|
164
|
+
} // Functions cannot be serialized by JSON.stringify(). Moreover, if a faulty toJSON is present, it needs to be converted
|
|
165
|
+
// so it won't prevent stringify from serializing later
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if (typeof value === 'function') {
|
|
169
|
+
return '[Function] ' + value.name || 'unknown';
|
|
170
|
+
} // JSON.stringify() does not serialize symbols.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if (_typeof(value) === 'symbol') {
|
|
174
|
+
// symbol.description is part of ES2019+
|
|
175
|
+
return '[Symbol] ' + value.description || value.toString();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Handles sanitization of object types
|
|
182
|
+
*
|
|
183
|
+
* LIMITATIONS
|
|
184
|
+
* - If a class defines a toStringTag Symbol, it will fall in the catch-all method and prevent enumeration of properties.
|
|
185
|
+
* To avoid this, a toJSON method can be defined.
|
|
186
|
+
* - IE11 does not return a distinct type for objects such as Map, WeakMap, ... These objects will pass through and their
|
|
187
|
+
* properties enumerated if any.
|
|
188
|
+
*
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
function sanitizeObjects(value) {
|
|
193
|
+
try {
|
|
194
|
+
// Handle events - Keep a simple implementation to avoid breaking changes
|
|
195
|
+
if (value instanceof Event) {
|
|
196
|
+
return {
|
|
197
|
+
isTrusted: value.isTrusted
|
|
198
|
+
};
|
|
199
|
+
} // Handle all remaining object types in a generic way
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
var result = Object.prototype.toString.call(value);
|
|
203
|
+
var match = result.match(/\[object (.*)\]/);
|
|
204
|
+
|
|
205
|
+
if (match && match[1]) {
|
|
206
|
+
return '[' + match[1] + ']';
|
|
207
|
+
}
|
|
208
|
+
} catch (_unused) {// If the previous serialization attempts failed, and we cannot convert using
|
|
209
|
+
// Object.prototype.toString, declare the value unserializable
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return '[Unserializable]';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Checks if a toJSON function exists and tries to execute it
|
|
216
|
+
*
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
function tryToApplyToJSON(value) {
|
|
221
|
+
var object = value;
|
|
222
|
+
|
|
223
|
+
if (object && typeof object.toJSON === 'function') {
|
|
224
|
+
try {
|
|
225
|
+
return object.toJSON();
|
|
226
|
+
} catch (_unused2) {// If toJSON fails, we continue by trying to serialize the value manually
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Helper function to display the warning when the accumulated character count is over the limit
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
function warnOverCharacterLimit(maxCharacterCount, changeType, source) {
|
|
238
|
+
_display.display.warn('The data provided has been ' + changeType + ' as it is over the limit of ' + maxCharacterCount + ' characters:', source);
|
|
239
|
+
}
|
package/cjs/helper/tools.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.isFunction = exports.isEmptyObject = exports.isElement = exports.isDate
|
|
|
48
48
|
exports.isHashAnAnchor = isHashAnAnchor;
|
|
49
49
|
exports.isIE = isIE;
|
|
50
50
|
exports.isJSONString = void 0;
|
|
51
|
+
exports.isMatchOption = isMatchOption;
|
|
51
52
|
exports.isNullUndefinedDefaultValue = isNullUndefinedDefaultValue;
|
|
52
53
|
exports.isObject = exports.isNumber = void 0;
|
|
53
54
|
exports.isPercentage = isPercentage;
|
|
@@ -1889,6 +1890,11 @@ function safeTruncate(candidate, length) {
|
|
|
1889
1890
|
return candidate.slice(0, length);
|
|
1890
1891
|
}
|
|
1891
1892
|
|
|
1893
|
+
function isMatchOption(item) {
|
|
1894
|
+
var itemType = getType(item);
|
|
1895
|
+
return itemType === 'string' || itemType === 'function' || item instanceof RegExp;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1892
1898
|
function includes(candidate, search) {
|
|
1893
1899
|
// tslint:disable-next-line: no-unsafe-any
|
|
1894
1900
|
return candidate.indexOf(search) !== -1;
|
package/cjs/index.js
CHANGED
|
@@ -147,6 +147,19 @@ Object.keys(_display).forEach(function (key) {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
var _sanitize = require("./helper/sanitize");
|
|
151
|
+
|
|
152
|
+
Object.keys(_sanitize).forEach(function (key) {
|
|
153
|
+
if (key === "default" || key === "__esModule") return;
|
|
154
|
+
if (key in exports && exports[key] === _sanitize[key]) return;
|
|
155
|
+
Object.defineProperty(exports, key, {
|
|
156
|
+
enumerable: true,
|
|
157
|
+
get: function get() {
|
|
158
|
+
return _sanitize[key];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
150
163
|
var _eventEmitter = require("./helper/eventEmitter");
|
|
151
164
|
|
|
152
165
|
Object.keys(_eventEmitter).forEach(function (key) {
|
package/esm/dataMap.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RumEventType } from './helper/enums';
|
|
2
2
|
export var commonTags = {
|
|
3
|
-
sdk_name: '
|
|
4
|
-
sdk_version: '
|
|
3
|
+
sdk_name: '_gc.sdk_name',
|
|
4
|
+
sdk_version: '_gc.sdk_version',
|
|
5
5
|
app_id: 'application.id',
|
|
6
6
|
env: 'env',
|
|
7
7
|
service: 'service',
|
|
@@ -47,7 +47,7 @@ export var dataMap = {
|
|
|
47
47
|
},
|
|
48
48
|
fields: {
|
|
49
49
|
is_active: 'view.is_active',
|
|
50
|
-
session_replay_stats: '
|
|
50
|
+
session_replay_stats: '_gc.replay_stats',
|
|
51
51
|
session_is_active: 'session.is_active',
|
|
52
52
|
view_error_count: 'view.error.count',
|
|
53
53
|
view_resource_count: 'view.resource.count',
|
|
@@ -76,8 +76,8 @@ export var dataMap = {
|
|
|
76
76
|
resource: {
|
|
77
77
|
type: RumEventType.RESOURCE,
|
|
78
78
|
tags: {
|
|
79
|
-
trace_id: '
|
|
80
|
-
span_id: '
|
|
79
|
+
trace_id: '_gc.trace_id',
|
|
80
|
+
span_id: '_gc.span_id',
|
|
81
81
|
resource_url: 'resource.url',
|
|
82
82
|
resource_url_host: 'resource.url_host',
|
|
83
83
|
resource_url_path: 'resource.url_path',
|
|
@@ -110,8 +110,8 @@ export var dataMap = {
|
|
|
110
110
|
type: RumEventType.ERROR,
|
|
111
111
|
tags: {
|
|
112
112
|
error_id: 'error.id',
|
|
113
|
-
trace_id: '
|
|
114
|
-
span_id: '
|
|
113
|
+
trace_id: '_gc.trace_id',
|
|
114
|
+
span_id: '_gc.span_id',
|
|
115
115
|
error_source: 'error.source',
|
|
116
116
|
error_type: 'error.type',
|
|
117
117
|
error_handling: 'error.handling' // resource_url: 'error.resource.url',
|
|
@@ -150,8 +150,8 @@ export var dataMap = {
|
|
|
150
150
|
action_resource_count: 'action.resource.count',
|
|
151
151
|
action_frustration_types: 'action.frustration.type',
|
|
152
152
|
action_long_task_count: 'action.long_task.count',
|
|
153
|
-
action_target: '
|
|
154
|
-
action_position: '
|
|
153
|
+
action_target: '_gc.action.target',
|
|
154
|
+
action_position: '_gc.action.position'
|
|
155
155
|
}
|
|
156
156
|
},
|
|
157
157
|
browser_log: {
|
package/esm/helper/enums.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import { extend2Lev, each } from './tools';
|
|
1
|
+
import { extend2Lev, each, objectEntries, getType, deepClone } from './tools';
|
|
2
|
+
import { sanitize } from './sanitize';
|
|
2
3
|
/**
|
|
3
|
-
* Current
|
|
4
|
+
* Current limitation:
|
|
4
5
|
* - field path do not support array, 'a.b.c' only
|
|
5
|
-
* - modifiable fields type must be string
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export function limitModification(object, modifiableFieldPaths, modifier) {
|
|
9
|
-
var clone =
|
|
9
|
+
var clone = deepClone(object);
|
|
10
10
|
var result = modifier(clone);
|
|
11
|
-
each(modifiableFieldPaths, function (
|
|
12
|
-
var
|
|
13
|
-
var
|
|
11
|
+
each(objectEntries(modifiableFieldPaths), function (filedPaths) {
|
|
12
|
+
var fieldPath = filedPaths[0];
|
|
13
|
+
var fieldType = filedPaths[1];
|
|
14
|
+
var newValue = get(clone, fieldPath);
|
|
15
|
+
var newType = getType(newValue);
|
|
14
16
|
|
|
15
|
-
if (
|
|
16
|
-
set(object,
|
|
17
|
+
if (newType === fieldType) {
|
|
18
|
+
set(object, fieldPath, sanitize(newValue));
|
|
19
|
+
} else if (fieldType === 'object' && (newType === 'undefined' || newType === 'null')) {
|
|
20
|
+
set(object, fieldPath, {});
|
|
17
21
|
}
|
|
18
22
|
});
|
|
19
23
|
return result;
|
|
@@ -21,11 +25,8 @@ export function limitModification(object, modifiableFieldPaths, modifier) {
|
|
|
21
25
|
|
|
22
26
|
function get(object, path) {
|
|
23
27
|
var current = object;
|
|
24
|
-
var fields = path.split('.');
|
|
25
|
-
|
|
26
|
-
for (var i = 0; i < fields.length; i++) {
|
|
27
|
-
var field = fields[i];
|
|
28
28
|
|
|
29
|
+
for (var field of path.split('.')) {
|
|
29
30
|
if (!isValidObjectContaining(current, field)) {
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
@@ -43,7 +44,7 @@ function set(object, path, value) {
|
|
|
43
44
|
for (var i = 0; i < fields.length; i += 1) {
|
|
44
45
|
var field = fields[i];
|
|
45
46
|
|
|
46
|
-
if (!
|
|
47
|
+
if (!isValidObject(current)) {
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -55,6 +56,10 @@ function set(object, path, value) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function isValidObject(object) {
|
|
60
|
+
return getType(object) === 'object';
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
function isValidObjectContaining(object, field) {
|
|
59
|
-
return
|
|
64
|
+
return isValidObject(object) && Object.prototype.hasOwnProperty.call(object, field);
|
|
60
65
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { display } from './display';
|
|
2
|
+
import { ONE_KIBI_BYTE } from './byteUtils';
|
|
3
|
+
import { detachToJsonMethod } from './serialisation/jsonStringify'; // The maximum size of a single event is 256KiB. By default, we ensure that user-provided data
|
|
4
|
+
// going through sanitize fits inside our events, while leaving room for other contexts, metadata, ...
|
|
5
|
+
|
|
6
|
+
var SANITIZE_DEFAULT_MAX_CHARACTER_COUNT = 220 * ONE_KIBI_BYTE; // Symbol for the root element of the JSONPath used for visited objects
|
|
7
|
+
|
|
8
|
+
var JSON_PATH_ROOT_ELEMENT = '$'; // When serializing (using JSON.stringify) a key of an object, { key: 42 } gets wrapped in quotes as "key".
|
|
9
|
+
// With the separator (:), we need to add 3 characters to the count.
|
|
10
|
+
|
|
11
|
+
var KEY_DECORATION_LENGTH = 3;
|
|
12
|
+
/**
|
|
13
|
+
* Ensures user-provided data is 'safe' for the SDK
|
|
14
|
+
* - Deep clones data
|
|
15
|
+
* - Removes cyclic references
|
|
16
|
+
* - Transforms unserializable types to a string representation
|
|
17
|
+
*
|
|
18
|
+
* LIMITATIONS:
|
|
19
|
+
* - Size is in characters, not byte count (may differ according to character encoding)
|
|
20
|
+
* - Size does not take into account indentation that can be applied to JSON.stringify
|
|
21
|
+
* - Non-numerical properties of Arrays are ignored. Same behavior as JSON.stringify
|
|
22
|
+
*
|
|
23
|
+
* @param source User-provided data meant to be serialized using JSON.stringify
|
|
24
|
+
* @param maxCharacterCount Maximum number of characters allowed in serialized form
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export function sanitize(source, maxCharacterCount) {
|
|
28
|
+
if (maxCharacterCount === undefined) {
|
|
29
|
+
maxCharacterCount = SANITIZE_DEFAULT_MAX_CHARACTER_COUNT;
|
|
30
|
+
} // Unbind any toJSON function we may have on [] or {} prototypes
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
var restoreObjectPrototypeToJson = detachToJsonMethod(Object.prototype);
|
|
34
|
+
var restoreArrayPrototypeToJson = detachToJsonMethod(Array.prototype); // Initial call to sanitizeProcessor - will populate containerQueue if source is an Array or a plain Object
|
|
35
|
+
|
|
36
|
+
var containerQueue = [];
|
|
37
|
+
var visitedObjectsWithPath = new WeakMap();
|
|
38
|
+
var sanitizedData = sanitizeProcessor(source, JSON_PATH_ROOT_ELEMENT, undefined, containerQueue, visitedObjectsWithPath);
|
|
39
|
+
var accumulatedCharacterCount = JSON.stringify(sanitizedData) && JSON.stringify(sanitizedData).length || 0;
|
|
40
|
+
|
|
41
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
42
|
+
warnOverCharacterLimit(maxCharacterCount, 'discarded', source);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
while (containerQueue.length > 0 && accumulatedCharacterCount < maxCharacterCount) {
|
|
47
|
+
var containerToProcess = containerQueue.shift();
|
|
48
|
+
var separatorLength = 0; // 0 for the first element, 1 for subsequent elements
|
|
49
|
+
// Arrays and Objects have to be handled distinctly to ensure
|
|
50
|
+
// we do not pick up non-numerical properties from Arrays
|
|
51
|
+
|
|
52
|
+
if (Array.isArray(containerToProcess.source)) {
|
|
53
|
+
for (var key = 0; key < containerToProcess.source.length; key++) {
|
|
54
|
+
var targetData = sanitizeProcessor(containerToProcess.source[key], containerToProcess.path, key, containerQueue, visitedObjectsWithPath);
|
|
55
|
+
|
|
56
|
+
if (targetData !== undefined) {
|
|
57
|
+
accumulatedCharacterCount += JSON.stringify(targetData).length;
|
|
58
|
+
} else {
|
|
59
|
+
// When an element of an Array (targetData) is undefined, it is serialized as null:
|
|
60
|
+
// JSON.stringify([undefined]) => '[null]' - This accounts for 4 characters
|
|
61
|
+
accumulatedCharacterCount += 4;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
accumulatedCharacterCount += separatorLength;
|
|
65
|
+
separatorLength = 1;
|
|
66
|
+
|
|
67
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
68
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
containerToProcess.target[key] = targetData;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
for (var key in containerToProcess.source) {
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(containerToProcess.source, key)) {
|
|
77
|
+
var targetData = sanitizeProcessor(containerToProcess.source[key], containerToProcess.path, key, containerQueue, visitedObjectsWithPath); // When a property of an object has an undefined value, it will be dropped during serialization:
|
|
78
|
+
// JSON.stringify({a:undefined}) => '{}'
|
|
79
|
+
|
|
80
|
+
if (targetData !== undefined) {
|
|
81
|
+
accumulatedCharacterCount += JSON.stringify(targetData).length + separatorLength + key.length + KEY_DECORATION_LENGTH;
|
|
82
|
+
separatorLength = 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
86
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
containerToProcess.target[key] = targetData;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} // Rebind detached toJSON functions
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
restoreObjectPrototypeToJson();
|
|
98
|
+
restoreArrayPrototypeToJson();
|
|
99
|
+
return sanitizedData;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Internal function to factorize the process common to the
|
|
103
|
+
* initial call to sanitize, and iterations for Arrays and Objects
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
function sanitizeProcessor(source, parentPath, key, queue, visitedObjectsWithPath) {
|
|
108
|
+
// Start by handling toJSON, as we want to sanitize its output
|
|
109
|
+
var sourceToSanitize = tryToApplyToJSON(source);
|
|
110
|
+
|
|
111
|
+
if (!sourceToSanitize || typeof sourceToSanitize !== 'object') {
|
|
112
|
+
return sanitizePrimitivesAndFunctions(sourceToSanitize);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var sanitizedSource = sanitizeObjects(sourceToSanitize);
|
|
116
|
+
|
|
117
|
+
if (sanitizedSource !== '[Object]' && sanitizedSource !== '[Array]' && sanitizedSource !== '[Error]') {
|
|
118
|
+
return sanitizedSource;
|
|
119
|
+
} // Handle potential cyclic references
|
|
120
|
+
// We need to use source as sourceToSanitize could be a reference to a new object
|
|
121
|
+
// At this stage, we know the source is an object type
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
var sourceAsObject = source;
|
|
125
|
+
|
|
126
|
+
if (visitedObjectsWithPath.has(sourceAsObject)) {
|
|
127
|
+
return '[Reference seen at ' + visitedObjectsWithPath.get(sourceAsObject) + ']';
|
|
128
|
+
} // Add processed source to queue
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
var currentPath = key !== undefined ? parentPath + '.' + key : parentPath;
|
|
132
|
+
var target = Array.isArray(sourceToSanitize) ? [] : {};
|
|
133
|
+
visitedObjectsWithPath.set(sourceAsObject, currentPath);
|
|
134
|
+
queue.push({
|
|
135
|
+
source: sourceToSanitize,
|
|
136
|
+
target,
|
|
137
|
+
path: currentPath
|
|
138
|
+
});
|
|
139
|
+
return target;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Handles sanitization of simple, non-object types
|
|
143
|
+
*
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
function sanitizePrimitivesAndFunctions(value) {
|
|
148
|
+
// BigInt cannot be serialized by JSON.stringify(), convert it to a string representation
|
|
149
|
+
if (typeof value === 'bigint') {
|
|
150
|
+
return '[BigInt] ' + value.toString();
|
|
151
|
+
} // Functions cannot be serialized by JSON.stringify(). Moreover, if a faulty toJSON is present, it needs to be converted
|
|
152
|
+
// so it won't prevent stringify from serializing later
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if (typeof value === 'function') {
|
|
156
|
+
return '[Function] ' + value.name || 'unknown';
|
|
157
|
+
} // JSON.stringify() does not serialize symbols.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if (typeof value === 'symbol') {
|
|
161
|
+
// symbol.description is part of ES2019+
|
|
162
|
+
return '[Symbol] ' + value.description || value.toString();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Handles sanitization of object types
|
|
169
|
+
*
|
|
170
|
+
* LIMITATIONS
|
|
171
|
+
* - If a class defines a toStringTag Symbol, it will fall in the catch-all method and prevent enumeration of properties.
|
|
172
|
+
* To avoid this, a toJSON method can be defined.
|
|
173
|
+
* - IE11 does not return a distinct type for objects such as Map, WeakMap, ... These objects will pass through and their
|
|
174
|
+
* properties enumerated if any.
|
|
175
|
+
*
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
function sanitizeObjects(value) {
|
|
180
|
+
try {
|
|
181
|
+
// Handle events - Keep a simple implementation to avoid breaking changes
|
|
182
|
+
if (value instanceof Event) {
|
|
183
|
+
return {
|
|
184
|
+
isTrusted: value.isTrusted
|
|
185
|
+
};
|
|
186
|
+
} // Handle all remaining object types in a generic way
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
var result = Object.prototype.toString.call(value);
|
|
190
|
+
var match = result.match(/\[object (.*)\]/);
|
|
191
|
+
|
|
192
|
+
if (match && match[1]) {
|
|
193
|
+
return '[' + match[1] + ']';
|
|
194
|
+
}
|
|
195
|
+
} catch (_unused) {// If the previous serialization attempts failed, and we cannot convert using
|
|
196
|
+
// Object.prototype.toString, declare the value unserializable
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return '[Unserializable]';
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Checks if a toJSON function exists and tries to execute it
|
|
203
|
+
*
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
function tryToApplyToJSON(value) {
|
|
208
|
+
var object = value;
|
|
209
|
+
|
|
210
|
+
if (object && typeof object.toJSON === 'function') {
|
|
211
|
+
try {
|
|
212
|
+
return object.toJSON();
|
|
213
|
+
} catch (_unused2) {// If toJSON fails, we continue by trying to serialize the value manually
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Helper function to display the warning when the accumulated character count is over the limit
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
function warnOverCharacterLimit(maxCharacterCount, changeType, source) {
|
|
225
|
+
display.warn('The data provided has been ' + changeType + ' as it is over the limit of ' + maxCharacterCount + ' characters:', source);
|
|
226
|
+
}
|
package/esm/helper/tools.js
CHANGED
|
@@ -1558,6 +1558,10 @@ export function safeTruncate(candidate, length) {
|
|
|
1558
1558
|
|
|
1559
1559
|
return candidate.slice(0, length);
|
|
1560
1560
|
}
|
|
1561
|
+
export function isMatchOption(item) {
|
|
1562
|
+
var itemType = getType(item);
|
|
1563
|
+
return itemType === 'string' || itemType === 'function' || item instanceof RegExp;
|
|
1564
|
+
}
|
|
1561
1565
|
export function includes(candidate, search) {
|
|
1562
1566
|
// tslint:disable-next-line: no-unsafe-any
|
|
1563
1567
|
return candidate.indexOf(search) !== -1;
|
package/esm/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './error/trackRuntimeError';
|
|
|
9
9
|
export * from './console/consoleObservable';
|
|
10
10
|
export * from './report/reportObservable';
|
|
11
11
|
export * from './helper/display';
|
|
12
|
+
export * from './helper/sanitize';
|
|
12
13
|
export * from './helper/eventEmitter';
|
|
13
14
|
export * from './helper/lifeCycle';
|
|
14
15
|
export * from './helper/limitModification';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcare/browser-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"main": "cjs/index.js",
|
|
5
5
|
"module": "esm/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"author": "dataflux",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"description": "DataFlux RUM Web 端数据指标监控",
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "d68cc2e25637dbe6a09461ca4186b882f878c169"
|
|
24
24
|
}
|
package/src/dataMap.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RumEventType } from './helper/enums'
|
|
2
2
|
export var commonTags = {
|
|
3
|
-
sdk_name: '
|
|
4
|
-
sdk_version: '
|
|
3
|
+
sdk_name: '_gc.sdk_name',
|
|
4
|
+
sdk_version: '_gc.sdk_version',
|
|
5
5
|
app_id: 'application.id',
|
|
6
6
|
env: 'env',
|
|
7
7
|
service: 'service',
|
|
@@ -46,7 +46,7 @@ export var dataMap = {
|
|
|
46
46
|
},
|
|
47
47
|
fields: {
|
|
48
48
|
is_active: 'view.is_active',
|
|
49
|
-
session_replay_stats: '
|
|
49
|
+
session_replay_stats: '_gc.replay_stats',
|
|
50
50
|
session_is_active: 'session.is_active',
|
|
51
51
|
view_error_count: 'view.error.count',
|
|
52
52
|
view_resource_count: 'view.resource.count',
|
|
@@ -75,8 +75,8 @@ export var dataMap = {
|
|
|
75
75
|
resource: {
|
|
76
76
|
type: RumEventType.RESOURCE,
|
|
77
77
|
tags: {
|
|
78
|
-
trace_id: '
|
|
79
|
-
span_id: '
|
|
78
|
+
trace_id: '_gc.trace_id',
|
|
79
|
+
span_id: '_gc.span_id',
|
|
80
80
|
resource_url: 'resource.url',
|
|
81
81
|
resource_url_host: 'resource.url_host',
|
|
82
82
|
resource_url_path: 'resource.url_path',
|
|
@@ -109,8 +109,8 @@ export var dataMap = {
|
|
|
109
109
|
type: RumEventType.ERROR,
|
|
110
110
|
tags: {
|
|
111
111
|
error_id: 'error.id',
|
|
112
|
-
trace_id: '
|
|
113
|
-
span_id: '
|
|
112
|
+
trace_id: '_gc.trace_id',
|
|
113
|
+
span_id: '_gc.span_id',
|
|
114
114
|
error_source: 'error.source',
|
|
115
115
|
error_type: 'error.type',
|
|
116
116
|
error_handling: 'error.handling'
|
|
@@ -149,8 +149,8 @@ export var dataMap = {
|
|
|
149
149
|
action_resource_count: 'action.resource.count',
|
|
150
150
|
action_frustration_types: 'action.frustration.type',
|
|
151
151
|
action_long_task_count: 'action.long_task.count',
|
|
152
|
-
action_target: '
|
|
153
|
-
action_position: '
|
|
152
|
+
action_target: '_gc.action.target',
|
|
153
|
+
action_position: '_gc.action.position'
|
|
154
154
|
}
|
|
155
155
|
},
|
|
156
156
|
browser_log: {
|
package/src/helper/enums.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import { extend2Lev, each } from './tools'
|
|
1
|
+
import { extend2Lev, each, objectEntries, getType, deepClone } from './tools'
|
|
2
|
+
import { sanitize } from './sanitize'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Current
|
|
5
|
+
* Current limitation:
|
|
5
6
|
* - field path do not support array, 'a.b.c' only
|
|
6
|
-
* - modifiable fields type must be string
|
|
7
7
|
*/
|
|
8
8
|
export function limitModification(object, modifiableFieldPaths, modifier) {
|
|
9
|
-
var clone =
|
|
9
|
+
var clone = deepClone(object)
|
|
10
10
|
var result = modifier(clone)
|
|
11
|
-
each(modifiableFieldPaths, function (
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
each(objectEntries(modifiableFieldPaths), function (filedPaths) {
|
|
12
|
+
var fieldPath = filedPaths[0]
|
|
13
|
+
var fieldType = filedPaths[1]
|
|
14
|
+
var newValue = get(clone, fieldPath)
|
|
15
|
+
var newType = getType(newValue)
|
|
16
|
+
if (newType === fieldType) {
|
|
17
|
+
set(object, fieldPath, sanitize(newValue))
|
|
18
|
+
} else if (
|
|
19
|
+
fieldType === 'object' &&
|
|
20
|
+
(newType === 'undefined' || newType === 'null')
|
|
21
|
+
) {
|
|
22
|
+
set(object, fieldPath, {})
|
|
16
23
|
}
|
|
17
24
|
})
|
|
18
25
|
|
|
@@ -21,15 +28,12 @@ export function limitModification(object, modifiableFieldPaths, modifier) {
|
|
|
21
28
|
|
|
22
29
|
function get(object, path) {
|
|
23
30
|
var current = object
|
|
24
|
-
var
|
|
25
|
-
for (var i = 0; i < fields.length; i++) {
|
|
26
|
-
var field = fields[i]
|
|
31
|
+
for (var field of path.split('.')) {
|
|
27
32
|
if (!isValidObjectContaining(current, field)) {
|
|
28
33
|
return
|
|
29
34
|
}
|
|
30
35
|
current = current[field]
|
|
31
36
|
}
|
|
32
|
-
|
|
33
37
|
return current
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -38,7 +42,7 @@ function set(object, path, value) {
|
|
|
38
42
|
var fields = path.split('.')
|
|
39
43
|
for (var i = 0; i < fields.length; i += 1) {
|
|
40
44
|
var field = fields[i]
|
|
41
|
-
if (!
|
|
45
|
+
if (!isValidObject(current)) {
|
|
42
46
|
return
|
|
43
47
|
}
|
|
44
48
|
if (i !== fields.length - 1) {
|
|
@@ -49,6 +53,12 @@ function set(object, path, value) {
|
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
function isValidObject(object) {
|
|
57
|
+
return getType(object) === 'object'
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
function isValidObjectContaining(object, field) {
|
|
53
|
-
return
|
|
61
|
+
return (
|
|
62
|
+
isValidObject(object) && Object.prototype.hasOwnProperty.call(object, field)
|
|
63
|
+
)
|
|
54
64
|
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { display } from './display'
|
|
2
|
+
import { ONE_KIBI_BYTE } from './byteUtils'
|
|
3
|
+
import { detachToJsonMethod } from './serialisation/jsonStringify'
|
|
4
|
+
|
|
5
|
+
// The maximum size of a single event is 256KiB. By default, we ensure that user-provided data
|
|
6
|
+
// going through sanitize fits inside our events, while leaving room for other contexts, metadata, ...
|
|
7
|
+
var SANITIZE_DEFAULT_MAX_CHARACTER_COUNT = 220 * ONE_KIBI_BYTE
|
|
8
|
+
|
|
9
|
+
// Symbol for the root element of the JSONPath used for visited objects
|
|
10
|
+
var JSON_PATH_ROOT_ELEMENT = '$'
|
|
11
|
+
|
|
12
|
+
// When serializing (using JSON.stringify) a key of an object, { key: 42 } gets wrapped in quotes as "key".
|
|
13
|
+
// With the separator (:), we need to add 3 characters to the count.
|
|
14
|
+
var KEY_DECORATION_LENGTH = 3
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ensures user-provided data is 'safe' for the SDK
|
|
18
|
+
* - Deep clones data
|
|
19
|
+
* - Removes cyclic references
|
|
20
|
+
* - Transforms unserializable types to a string representation
|
|
21
|
+
*
|
|
22
|
+
* LIMITATIONS:
|
|
23
|
+
* - Size is in characters, not byte count (may differ according to character encoding)
|
|
24
|
+
* - Size does not take into account indentation that can be applied to JSON.stringify
|
|
25
|
+
* - Non-numerical properties of Arrays are ignored. Same behavior as JSON.stringify
|
|
26
|
+
*
|
|
27
|
+
* @param source User-provided data meant to be serialized using JSON.stringify
|
|
28
|
+
* @param maxCharacterCount Maximum number of characters allowed in serialized form
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export function sanitize(source, maxCharacterCount) {
|
|
32
|
+
if (maxCharacterCount === undefined) {
|
|
33
|
+
maxCharacterCount = SANITIZE_DEFAULT_MAX_CHARACTER_COUNT
|
|
34
|
+
}
|
|
35
|
+
// Unbind any toJSON function we may have on [] or {} prototypes
|
|
36
|
+
var restoreObjectPrototypeToJson = detachToJsonMethod(Object.prototype)
|
|
37
|
+
var restoreArrayPrototypeToJson = detachToJsonMethod(Array.prototype)
|
|
38
|
+
|
|
39
|
+
// Initial call to sanitizeProcessor - will populate containerQueue if source is an Array or a plain Object
|
|
40
|
+
var containerQueue = []
|
|
41
|
+
var visitedObjectsWithPath = new WeakMap()
|
|
42
|
+
var sanitizedData = sanitizeProcessor(
|
|
43
|
+
source,
|
|
44
|
+
JSON_PATH_ROOT_ELEMENT,
|
|
45
|
+
undefined,
|
|
46
|
+
containerQueue,
|
|
47
|
+
visitedObjectsWithPath
|
|
48
|
+
)
|
|
49
|
+
var accumulatedCharacterCount =
|
|
50
|
+
(JSON.stringify(sanitizedData) && JSON.stringify(sanitizedData).length) || 0
|
|
51
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
52
|
+
warnOverCharacterLimit(maxCharacterCount, 'discarded', source)
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
while (
|
|
57
|
+
containerQueue.length > 0 &&
|
|
58
|
+
accumulatedCharacterCount < maxCharacterCount
|
|
59
|
+
) {
|
|
60
|
+
var containerToProcess = containerQueue.shift()
|
|
61
|
+
var separatorLength = 0 // 0 for the first element, 1 for subsequent elements
|
|
62
|
+
|
|
63
|
+
// Arrays and Objects have to be handled distinctly to ensure
|
|
64
|
+
// we do not pick up non-numerical properties from Arrays
|
|
65
|
+
if (Array.isArray(containerToProcess.source)) {
|
|
66
|
+
for (var key = 0; key < containerToProcess.source.length; key++) {
|
|
67
|
+
var targetData = sanitizeProcessor(
|
|
68
|
+
containerToProcess.source[key],
|
|
69
|
+
containerToProcess.path,
|
|
70
|
+
key,
|
|
71
|
+
containerQueue,
|
|
72
|
+
visitedObjectsWithPath
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (targetData !== undefined) {
|
|
76
|
+
accumulatedCharacterCount += JSON.stringify(targetData).length
|
|
77
|
+
} else {
|
|
78
|
+
// When an element of an Array (targetData) is undefined, it is serialized as null:
|
|
79
|
+
// JSON.stringify([undefined]) => '[null]' - This accounts for 4 characters
|
|
80
|
+
accumulatedCharacterCount += 4
|
|
81
|
+
}
|
|
82
|
+
accumulatedCharacterCount += separatorLength
|
|
83
|
+
separatorLength = 1
|
|
84
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
85
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source)
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
containerToProcess.target[key] = targetData
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
for (var key in containerToProcess.source) {
|
|
92
|
+
if (
|
|
93
|
+
Object.prototype.hasOwnProperty.call(containerToProcess.source, key)
|
|
94
|
+
) {
|
|
95
|
+
var targetData = sanitizeProcessor(
|
|
96
|
+
containerToProcess.source[key],
|
|
97
|
+
containerToProcess.path,
|
|
98
|
+
key,
|
|
99
|
+
containerQueue,
|
|
100
|
+
visitedObjectsWithPath
|
|
101
|
+
)
|
|
102
|
+
// When a property of an object has an undefined value, it will be dropped during serialization:
|
|
103
|
+
// JSON.stringify({a:undefined}) => '{}'
|
|
104
|
+
if (targetData !== undefined) {
|
|
105
|
+
accumulatedCharacterCount +=
|
|
106
|
+
JSON.stringify(targetData).length +
|
|
107
|
+
separatorLength +
|
|
108
|
+
key.length +
|
|
109
|
+
KEY_DECORATION_LENGTH
|
|
110
|
+
separatorLength = 1
|
|
111
|
+
}
|
|
112
|
+
if (accumulatedCharacterCount > maxCharacterCount) {
|
|
113
|
+
warnOverCharacterLimit(maxCharacterCount, 'truncated', source)
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
containerToProcess.target[key] = targetData
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Rebind detached toJSON functions
|
|
123
|
+
restoreObjectPrototypeToJson()
|
|
124
|
+
restoreArrayPrototypeToJson()
|
|
125
|
+
|
|
126
|
+
return sanitizedData
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Internal function to factorize the process common to the
|
|
131
|
+
* initial call to sanitize, and iterations for Arrays and Objects
|
|
132
|
+
*
|
|
133
|
+
*/
|
|
134
|
+
function sanitizeProcessor(
|
|
135
|
+
source,
|
|
136
|
+
parentPath,
|
|
137
|
+
key,
|
|
138
|
+
queue,
|
|
139
|
+
visitedObjectsWithPath
|
|
140
|
+
) {
|
|
141
|
+
// Start by handling toJSON, as we want to sanitize its output
|
|
142
|
+
var sourceToSanitize = tryToApplyToJSON(source)
|
|
143
|
+
|
|
144
|
+
if (!sourceToSanitize || typeof sourceToSanitize !== 'object') {
|
|
145
|
+
return sanitizePrimitivesAndFunctions(sourceToSanitize)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var sanitizedSource = sanitizeObjects(sourceToSanitize)
|
|
149
|
+
if (
|
|
150
|
+
sanitizedSource !== '[Object]' &&
|
|
151
|
+
sanitizedSource !== '[Array]' &&
|
|
152
|
+
sanitizedSource !== '[Error]'
|
|
153
|
+
) {
|
|
154
|
+
return sanitizedSource
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle potential cyclic references
|
|
158
|
+
// We need to use source as sourceToSanitize could be a reference to a new object
|
|
159
|
+
// At this stage, we know the source is an object type
|
|
160
|
+
var sourceAsObject = source
|
|
161
|
+
if (visitedObjectsWithPath.has(sourceAsObject)) {
|
|
162
|
+
return (
|
|
163
|
+
'[Reference seen at ' + visitedObjectsWithPath.get(sourceAsObject) + ']'
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add processed source to queue
|
|
168
|
+
var currentPath = key !== undefined ? parentPath + '.' + key : parentPath
|
|
169
|
+
var target = Array.isArray(sourceToSanitize) ? [] : {}
|
|
170
|
+
visitedObjectsWithPath.set(sourceAsObject, currentPath)
|
|
171
|
+
queue.push({ source: sourceToSanitize, target, path: currentPath })
|
|
172
|
+
|
|
173
|
+
return target
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Handles sanitization of simple, non-object types
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
function sanitizePrimitivesAndFunctions(value) {
|
|
181
|
+
// BigInt cannot be serialized by JSON.stringify(), convert it to a string representation
|
|
182
|
+
if (typeof value === 'bigint') {
|
|
183
|
+
return '[BigInt] ' + value.toString()
|
|
184
|
+
}
|
|
185
|
+
// Functions cannot be serialized by JSON.stringify(). Moreover, if a faulty toJSON is present, it needs to be converted
|
|
186
|
+
// so it won't prevent stringify from serializing later
|
|
187
|
+
if (typeof value === 'function') {
|
|
188
|
+
return '[Function] ' + value.name || 'unknown'
|
|
189
|
+
}
|
|
190
|
+
// JSON.stringify() does not serialize symbols.
|
|
191
|
+
if (typeof value === 'symbol') {
|
|
192
|
+
// symbol.description is part of ES2019+
|
|
193
|
+
return '[Symbol] ' + value.description || value.toString()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return value
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Handles sanitization of object types
|
|
201
|
+
*
|
|
202
|
+
* LIMITATIONS
|
|
203
|
+
* - If a class defines a toStringTag Symbol, it will fall in the catch-all method and prevent enumeration of properties.
|
|
204
|
+
* To avoid this, a toJSON method can be defined.
|
|
205
|
+
* - IE11 does not return a distinct type for objects such as Map, WeakMap, ... These objects will pass through and their
|
|
206
|
+
* properties enumerated if any.
|
|
207
|
+
*
|
|
208
|
+
*/
|
|
209
|
+
function sanitizeObjects(value) {
|
|
210
|
+
try {
|
|
211
|
+
// Handle events - Keep a simple implementation to avoid breaking changes
|
|
212
|
+
if (value instanceof Event) {
|
|
213
|
+
return {
|
|
214
|
+
isTrusted: value.isTrusted
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle all remaining object types in a generic way
|
|
219
|
+
var result = Object.prototype.toString.call(value)
|
|
220
|
+
var match = result.match(/\[object (.*)\]/)
|
|
221
|
+
if (match && match[1]) {
|
|
222
|
+
return '[' + match[1] + ']'
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// If the previous serialization attempts failed, and we cannot convert using
|
|
226
|
+
// Object.prototype.toString, declare the value unserializable
|
|
227
|
+
}
|
|
228
|
+
return '[Unserializable]'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Checks if a toJSON function exists and tries to execute it
|
|
233
|
+
*
|
|
234
|
+
*/
|
|
235
|
+
function tryToApplyToJSON(value) {
|
|
236
|
+
var object = value
|
|
237
|
+
if (object && typeof object.toJSON === 'function') {
|
|
238
|
+
try {
|
|
239
|
+
return object.toJSON()
|
|
240
|
+
} catch {
|
|
241
|
+
// If toJSON fails, we continue by trying to serialize the value manually
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return value
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Helper function to display the warning when the accumulated character count is over the limit
|
|
250
|
+
*/
|
|
251
|
+
function warnOverCharacterLimit(maxCharacterCount, changeType, source) {
|
|
252
|
+
display.warn(
|
|
253
|
+
'The data provided has been ' +
|
|
254
|
+
changeType +
|
|
255
|
+
' as it is over the limit of ' +
|
|
256
|
+
maxCharacterCount +
|
|
257
|
+
' characters:',
|
|
258
|
+
source
|
|
259
|
+
)
|
|
260
|
+
}
|
package/src/helper/tools.js
CHANGED
|
@@ -1542,7 +1542,12 @@ export function safeTruncate(candidate, length) {
|
|
|
1542
1542
|
}
|
|
1543
1543
|
return candidate.slice(0, length)
|
|
1544
1544
|
}
|
|
1545
|
-
|
|
1545
|
+
export function isMatchOption(item) {
|
|
1546
|
+
const itemType = getType(item)
|
|
1547
|
+
return (
|
|
1548
|
+
itemType === 'string' || itemType === 'function' || item instanceof RegExp
|
|
1549
|
+
)
|
|
1550
|
+
}
|
|
1546
1551
|
export function includes(candidate, search) {
|
|
1547
1552
|
// tslint:disable-next-line: no-unsafe-any
|
|
1548
1553
|
return candidate.indexOf(search) !== -1
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './error/trackRuntimeError'
|
|
|
9
9
|
export * from './console/consoleObservable'
|
|
10
10
|
export * from './report/reportObservable'
|
|
11
11
|
export * from './helper/display'
|
|
12
|
+
export * from './helper/sanitize'
|
|
12
13
|
export * from './helper/eventEmitter'
|
|
13
14
|
export * from './helper/lifeCycle'
|
|
14
15
|
export * from './helper/limitModification'
|