noibu-react-native 0.0.1
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/README.md +155 -0
- package/dist/api/clientConfig.js +416 -0
- package/dist/api/helpCode.js +106 -0
- package/dist/api/inputManager.js +233 -0
- package/dist/api/metroplexSocket.js +882 -0
- package/dist/api/storedMetrics.js +201 -0
- package/dist/api/storedPageVisit.js +235 -0
- package/dist/const_matchers.js +260 -0
- package/dist/constants.d.ts +264 -0
- package/dist/constants.js +528 -0
- package/dist/entry/index.d.ts +8 -0
- package/dist/entry/index.js +15 -0
- package/dist/entry/init.js +91 -0
- package/dist/monitors/clickMonitor.js +284 -0
- package/dist/monitors/elementMonitor.js +174 -0
- package/dist/monitors/errorMonitor.js +295 -0
- package/dist/monitors/gqlErrorValidator.js +306 -0
- package/dist/monitors/httpDataBundler.js +665 -0
- package/dist/monitors/inputMonitor.js +130 -0
- package/dist/monitors/keyboardInputMonitor.js +67 -0
- package/dist/monitors/locationChangeMonitor.js +30 -0
- package/dist/monitors/pageMonitor.js +119 -0
- package/dist/monitors/requestMonitor.js +679 -0
- package/dist/pageVisit/pageVisit.js +172 -0
- package/dist/pageVisit/pageVisitEventError/pageVisitEventError.js +313 -0
- package/dist/pageVisit/pageVisitEventHTTP/pageVisitEventHTTP.js +115 -0
- package/dist/pageVisit/userStep/userStep.js +20 -0
- package/dist/react/ErrorBoundary.d.ts +72 -0
- package/dist/react/ErrorBoundary.js +102 -0
- package/dist/storage/localStorageProvider.js +23 -0
- package/dist/storage/rnStorageProvider.js +62 -0
- package/dist/storage/sessionStorageProvider.js +23 -0
- package/dist/storage/storage.js +119 -0
- package/dist/storage/storageProvider.js +83 -0
- package/dist/utils/date.js +62 -0
- package/dist/utils/eventlistener.js +67 -0
- package/dist/utils/function.js +398 -0
- package/dist/utils/object.js +144 -0
- package/dist/utils/performance.js +21 -0
- package/package.json +57 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import * as stackTraceParser from 'stacktrace-parser';
|
|
2
|
+
import DeviceInfo from 'react-native-device-info';
|
|
3
|
+
import { MAX_STRING_LENGTH, MAX_BEACON_PAYLOAD_SIZE, REQUIRED_DATA_PROCESSING_URLS, HTTP_DATA_COLLECTION_FLAG_NAME, HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME, WIN_BLOCKED_SELECTOR_ATT_NAME, PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING, PII_DIGIT_PATTERN, NOIBUJS_CONFIG, JS_STACK_FRAMES_ATT_NAME, JS_STACK_MESSAGE_ATT_NAME, JS_STACK_FILE_ATT_NAME, JS_STACK_LINE_ATT_NAME, JS_STACK_COL_ATT_NAME, MAX_FRAMES_IN_ARRAY, JS_STACK_METHOD_ATT_NAME, DEFAULT_STACK_FRAME_FIELD_VALUE } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
/** @module Functions */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* returns a string that satisfies a max length
|
|
10
|
+
* stringToVerify: string that needs to be verified
|
|
11
|
+
* length :optional, max length that stringToVerify can be
|
|
12
|
+
* @param stringToVerify
|
|
13
|
+
* @param length
|
|
14
|
+
*/
|
|
15
|
+
function getMaxSubstringAllowed(
|
|
16
|
+
stringToVerify,
|
|
17
|
+
length = MAX_STRING_LENGTH,
|
|
18
|
+
) {
|
|
19
|
+
if (!stringToVerify) {
|
|
20
|
+
return stringToVerify;
|
|
21
|
+
}
|
|
22
|
+
if (stringToVerify.length < length) return stringToVerify;
|
|
23
|
+
return stringToVerify.substring(0, length);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* getProperGlobalUrl gets the proper global url from the window
|
|
29
|
+
*/
|
|
30
|
+
function getProperGlobalUrl() {
|
|
31
|
+
let globalUrl =
|
|
32
|
+
(window.location && window.location.href) || 'http://localhost';
|
|
33
|
+
// first we try to get the location
|
|
34
|
+
// if it does not follow the http protocol,
|
|
35
|
+
// then we check the parent frame and if that
|
|
36
|
+
// fails then we return what we have without
|
|
37
|
+
// trying anything else
|
|
38
|
+
if (
|
|
39
|
+
(
|
|
40
|
+
(window.location && window.location.href) ||
|
|
41
|
+
'http://localhost'
|
|
42
|
+
).startsWith('http')
|
|
43
|
+
) {
|
|
44
|
+
globalUrl = (window.location && window.location.href) || 'http://localhost';
|
|
45
|
+
} else if (
|
|
46
|
+
window.parent &&
|
|
47
|
+
window.parent.location &&
|
|
48
|
+
window.parent.location.href.startsWith('http')
|
|
49
|
+
) {
|
|
50
|
+
globalUrl = window.parent.location.href;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return getMaxSubstringAllowed(globalUrl);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Processes the raw stack frames and creates a readable stack in a safe manner
|
|
58
|
+
* @param rawFrames
|
|
59
|
+
*/
|
|
60
|
+
function processFrames(rawFrames) {
|
|
61
|
+
return rawFrames.map(frame => {
|
|
62
|
+
const processedFrame = {
|
|
63
|
+
[JS_STACK_LINE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
64
|
+
[JS_STACK_METHOD_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
65
|
+
[JS_STACK_FILE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// stringification
|
|
69
|
+
if (frame.lineNumber && frame.lineNumber !== '<unknown>') {
|
|
70
|
+
processedFrame[JS_STACK_LINE_ATT_NAME] = String(frame.lineNumber);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (frame.methodName && frame.methodName !== '<unknown>') {
|
|
74
|
+
processedFrame[JS_STACK_METHOD_ATT_NAME] = String(frame.methodName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (frame.file && frame.file !== '<unknown>') {
|
|
78
|
+
processedFrame[JS_STACK_FILE_ATT_NAME] = String(frame.file);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (frame.column && typeof frame.column === 'number') {
|
|
82
|
+
processedFrame[JS_STACK_COL_ATT_NAME] = frame.column;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return processedFrame;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Retrieves the javascript stack and message from an error event object
|
|
91
|
+
* @param errObj error to extract stack from
|
|
92
|
+
*/
|
|
93
|
+
function getJSStack(errObj) {
|
|
94
|
+
let frames = [
|
|
95
|
+
{
|
|
96
|
+
[JS_STACK_LINE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
97
|
+
[JS_STACK_METHOD_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
98
|
+
[JS_STACK_FILE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// if the errObj type is not an object or null
|
|
103
|
+
// return a default frame
|
|
104
|
+
if (typeof errObj !== 'object' || !errObj) {
|
|
105
|
+
return {
|
|
106
|
+
[JS_STACK_FRAMES_ATT_NAME]: frames,
|
|
107
|
+
[JS_STACK_MESSAGE_ATT_NAME]: '',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// If there is no stack field present then try to read the single file/line/column values
|
|
112
|
+
if (!errObj.stack) {
|
|
113
|
+
if (errObj.fileName && typeof errObj.fileName === 'string') {
|
|
114
|
+
frames[0][JS_STACK_FILE_ATT_NAME] = errObj.fileName;
|
|
115
|
+
}
|
|
116
|
+
if (errObj.lineNumber) {
|
|
117
|
+
frames[0][JS_STACK_LINE_ATT_NAME] = String(errObj.lineNumber);
|
|
118
|
+
}
|
|
119
|
+
if (errObj.columnNumber && Number.isInteger(errObj.columnNumber)) {
|
|
120
|
+
frames[0][JS_STACK_COL_ATT_NAME] = errObj.columnNumber;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
frames = processFrames(stackTraceParser.parse(errObj.stack));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (frames.length >= MAX_FRAMES_IN_ARRAY) {
|
|
127
|
+
frames = frames.slice(0, MAX_FRAMES_IN_ARRAY);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const msg = errObj.message ? getMaxSubstringAllowed(errObj.message) : '';
|
|
131
|
+
return {
|
|
132
|
+
[JS_STACK_FRAMES_ATT_NAME]: frames,
|
|
133
|
+
[JS_STACK_MESSAGE_ATT_NAME]: msg,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* safely stringifies an object
|
|
139
|
+
* @param jsonObject
|
|
140
|
+
*/
|
|
141
|
+
function stringifyJSON(jsonObject) {
|
|
142
|
+
// prototype.js version < 1.7 defines a toJSON on the prototype of
|
|
143
|
+
// arrays. This is wrong and they have fixed it since, but some old websites
|
|
144
|
+
// still use that lib and it messes up our the expected serilization on metroplex's
|
|
145
|
+
// side.
|
|
146
|
+
if (Array.prototype.toJSON) {
|
|
147
|
+
// this piece of code is safe since js guarentees that only one thing is running
|
|
148
|
+
// at once, therefore the prototype will be changed only while this function is executing
|
|
149
|
+
const oldToJSON = Array.prototype.toJSON;
|
|
150
|
+
delete Array.prototype.toJSON;
|
|
151
|
+
const jsonString = JSON.stringify(jsonObject);
|
|
152
|
+
// we want to re-enable the existing prototype behaviour
|
|
153
|
+
// eslint-disable-next-line no-extend-native
|
|
154
|
+
Array.prototype.toJSON = oldToJSON;
|
|
155
|
+
return jsonString;
|
|
156
|
+
}
|
|
157
|
+
return JSON.stringify(jsonObject);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Wrapper for a request, since we have to do some special handling
|
|
162
|
+
* @param method
|
|
163
|
+
* @param url
|
|
164
|
+
* @param data
|
|
165
|
+
* @param headers
|
|
166
|
+
* @param timeout
|
|
167
|
+
* @param sendAndForget
|
|
168
|
+
*/
|
|
169
|
+
function makeRequest(
|
|
170
|
+
method,
|
|
171
|
+
url,
|
|
172
|
+
data,
|
|
173
|
+
headers,
|
|
174
|
+
timeout,
|
|
175
|
+
sendAndForget,
|
|
176
|
+
) {
|
|
177
|
+
// a send and forget request is made by using the beacon API (fetch + keepalive)
|
|
178
|
+
if (sendAndForget) {
|
|
179
|
+
const stringData = stringifyJSON(data);
|
|
180
|
+
const currentPayloadSize = new Blob([stringData]).size;
|
|
181
|
+
// if we have a large object or fetch is not available, we skip sending the message
|
|
182
|
+
if (!window.fetch || currentPayloadSize > MAX_BEACON_PAYLOAD_SIZE) {
|
|
183
|
+
return new Promise(resolve => {
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return fetch(url, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers,
|
|
191
|
+
body: stringifyJSON(data),
|
|
192
|
+
// keep alive outlives the current page, its the same as beacon
|
|
193
|
+
keepalive: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const xhr = new XMLHttpRequest();
|
|
199
|
+
xhr.open(method, url);
|
|
200
|
+
xhr.timeout = timeout;
|
|
201
|
+
Object.keys(headers).forEach(header => {
|
|
202
|
+
xhr.setRequestHeader(header, headers[header]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
xhr.onload = () => {
|
|
206
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
207
|
+
resolve(xhr.response);
|
|
208
|
+
} else {
|
|
209
|
+
reject(new Error(`Custom Request failed: ${xhr.statusText}`));
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
xhr.onerror = () => {
|
|
213
|
+
reject(new Error(`Custom Request failed: ${xhr.statusText}`));
|
|
214
|
+
};
|
|
215
|
+
if (data) {
|
|
216
|
+
xhr.send(stringifyJSON(data));
|
|
217
|
+
} else {
|
|
218
|
+
xhr.send();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** determines if we have received the block selector */
|
|
224
|
+
function checkBlockedSelectorExistence() {
|
|
225
|
+
const noibuConfig = NOIBUJS_CONFIG();
|
|
226
|
+
return (
|
|
227
|
+
noibuConfig[WIN_BLOCKED_SELECTOR_ATT_NAME] &&
|
|
228
|
+
Array.isArray(noibuConfig[WIN_BLOCKED_SELECTOR_ATT_NAME])
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** checks if http data collection is enabled */
|
|
233
|
+
function checkHttpDataCollectionEnabled() {
|
|
234
|
+
const noibuConfig = NOIBUJS_CONFIG();
|
|
235
|
+
// Just a boolean, so safe to return truthiness. If undefined, will return false.
|
|
236
|
+
return !!noibuConfig[HTTP_DATA_COLLECTION_FLAG_NAME];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** determines if we have received the http allowed urls */
|
|
240
|
+
function checkHttpPayloadAllowedURLsExistence() {
|
|
241
|
+
const noibuConfig = NOIBUJS_CONFIG();
|
|
242
|
+
return (
|
|
243
|
+
noibuConfig[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME] &&
|
|
244
|
+
Array.isArray(noibuConfig[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME])
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** gets http data payload allowed URLs */
|
|
249
|
+
function getHttpPayloadAllowedURLs() {
|
|
250
|
+
const noibuConfig = NOIBUJS_CONFIG();
|
|
251
|
+
// return the allowed list or an empty list
|
|
252
|
+
if (checkHttpPayloadAllowedURLsExistence()) {
|
|
253
|
+
return noibuConfig[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME];
|
|
254
|
+
}
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Gets all the blocked css classes to prevent those elements from being recorded
|
|
259
|
+
*/
|
|
260
|
+
function getBlockedCSSForCurrentDomain() {
|
|
261
|
+
const noibuConfig = NOIBUJS_CONFIG();
|
|
262
|
+
const blockedCSS = ['noibu-blocked'];
|
|
263
|
+
if (checkBlockedSelectorExistence()) {
|
|
264
|
+
noibuConfig[WIN_BLOCKED_SELECTOR_ATT_NAME].forEach(sel => {
|
|
265
|
+
// this means that we are looking at a class
|
|
266
|
+
if (sel.startsWith('.') && sel.length > 0) {
|
|
267
|
+
// selecting everything after and including the element at index 1
|
|
268
|
+
blockedCSS.push(sel.substring(1));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return blockedCSS;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* makes sure the url sent is a valid URL
|
|
276
|
+
* @param {} string url to be validated
|
|
277
|
+
*/
|
|
278
|
+
function isValidURL(string) {
|
|
279
|
+
try {
|
|
280
|
+
const _ = new URL(string);
|
|
281
|
+
return true;
|
|
282
|
+
} catch (_) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let userAgentCache = '';
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Because of the nature of user agent in react native, we have to make this async.
|
|
291
|
+
* But I promise, this is really fast, since we memoize the result for the whole session :)
|
|
292
|
+
* @returns {Promise<string>}
|
|
293
|
+
*/
|
|
294
|
+
async function getUserAgent() {
|
|
295
|
+
if (userAgentCache) {
|
|
296
|
+
return userAgentCache;
|
|
297
|
+
}
|
|
298
|
+
userAgentCache = await DeviceInfo.getUserAgent();
|
|
299
|
+
return userAgentCache;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* isInvalidURLConfig will verify that Collect is being initializes with
|
|
304
|
+
* the correct env vars.
|
|
305
|
+
* @param {} urlConfig
|
|
306
|
+
*/
|
|
307
|
+
function isInvalidURLConfig(urlConfig) {
|
|
308
|
+
for (let i = 0; i < REQUIRED_DATA_PROCESSING_URLS.length; i += 1) {
|
|
309
|
+
const u = REQUIRED_DATA_PROCESSING_URLS[i];
|
|
310
|
+
if (!urlConfig[u]) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* isNoibuJSAlreadyLoaded will verify if there are already other
|
|
320
|
+
* copies of NoibuJS runnung
|
|
321
|
+
*/
|
|
322
|
+
function isNoibuJSAlreadyLoaded() {
|
|
323
|
+
// check if the global variable exists and its value
|
|
324
|
+
const loaded = window.noibuJSLoaded !== undefined;
|
|
325
|
+
// set the variable so future copies of the script
|
|
326
|
+
// will know this instance is running
|
|
327
|
+
window.noibuJSLoaded = true;
|
|
328
|
+
return loaded;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** asString will create a string out of anything passed to it.
|
|
332
|
+
* @param {} obj
|
|
333
|
+
*/
|
|
334
|
+
function asString(obj) {
|
|
335
|
+
if (!obj) {
|
|
336
|
+
return '';
|
|
337
|
+
}
|
|
338
|
+
// we've seen the url be an object in some cases
|
|
339
|
+
// we would still like to send those to our backend in the case
|
|
340
|
+
// that this is an issue and needs to be queried
|
|
341
|
+
if (typeof obj === 'object') {
|
|
342
|
+
return stringifyJSON(obj);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return String(obj);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** masks textual content if it ressembles something sensitive
|
|
349
|
+
* @param {} text
|
|
350
|
+
*/
|
|
351
|
+
function maskTextInput(text) {
|
|
352
|
+
// if it has an email or digit(s), we mask the text
|
|
353
|
+
return text
|
|
354
|
+
.replace(PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING)
|
|
355
|
+
.replace(PII_DIGIT_PATTERN, '*');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** gets the onURL of a string, defaulting to the location of the webpage
|
|
359
|
+
* @param {} realOnURL
|
|
360
|
+
*/
|
|
361
|
+
function getOnURL(realOnURL) {
|
|
362
|
+
let onURL = getMaxSubstringAllowed(
|
|
363
|
+
(window.location && window.location.href) || 'http://localhost',
|
|
364
|
+
);
|
|
365
|
+
if (realOnURL && realOnURL.trim() !== '' && realOnURL !== 'undefined') {
|
|
366
|
+
onURL = asString(getMaxSubstringAllowed(realOnURL));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return onURL;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** gets the user language from the browser */
|
|
373
|
+
function getUserLanguage() {
|
|
374
|
+
const lang = window.navigator.userLanguage || window.navigator.language;
|
|
375
|
+
|
|
376
|
+
if (lang === '' || !lang) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return lang.toLowerCase();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Checks if the provided object is an instance of the specified type.
|
|
385
|
+
* It's safer than `instanceof` operator as it handles cases
|
|
386
|
+
* where type is not actually a type but an object.
|
|
387
|
+
* @param {any} instance
|
|
388
|
+
* @param {any} type
|
|
389
|
+
*/
|
|
390
|
+
function isInstanceOf(instance, type) {
|
|
391
|
+
try {
|
|
392
|
+
return typeof type === 'function' && instance instanceof type;
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export { asString, checkHttpDataCollectionEnabled, getBlockedCSSForCurrentDomain, getHttpPayloadAllowedURLs, getJSStack, getMaxSubstringAllowed, getOnURL, getProperGlobalUrl, getUserAgent, getUserLanguage, isInstanceOf, isInvalidURLConfig, isNoibuJSAlreadyLoaded, isValidURL, makeRequest, maskTextInput, processFrames, stringifyJSON };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** @module Object */
|
|
2
|
+
/**
|
|
3
|
+
* Replaces an attribute value found in an object with another value
|
|
4
|
+
* sourceObject: source object whose attribute will get replaced
|
|
5
|
+
* attributeName: the attribute key whose value will be replace
|
|
6
|
+
* processingFunction: function that accepts the original value
|
|
7
|
+
* and returns the newValue
|
|
8
|
+
* @param {} sourceObject
|
|
9
|
+
* @param {} attributeName
|
|
10
|
+
* @param {} processingFunction
|
|
11
|
+
*/
|
|
12
|
+
const replace = (sourceObject, attributeName, processingFunction) => {
|
|
13
|
+
if (!(attributeName in sourceObject)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const originalAttribute = sourceObject[attributeName];
|
|
18
|
+
const newValue = processingFunction(originalAttribute);
|
|
19
|
+
|
|
20
|
+
if (typeof newValue === 'function') {
|
|
21
|
+
try {
|
|
22
|
+
newValue.prototype = newValue.prototype || {};
|
|
23
|
+
Object.defineProperties(newValue, {
|
|
24
|
+
__noibu__: {
|
|
25
|
+
enumerable: false,
|
|
26
|
+
value: true,
|
|
27
|
+
},
|
|
28
|
+
__noibu_original__: {
|
|
29
|
+
enumerable: false,
|
|
30
|
+
value: originalAttribute,
|
|
31
|
+
},
|
|
32
|
+
__noibu_wrapped__: {
|
|
33
|
+
enumerable: false,
|
|
34
|
+
value: newValue,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// silent fail
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line no-param-reassign
|
|
43
|
+
sourceObject[attributeName] = newValue;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether the prototype's property is writeable. If it is not,
|
|
48
|
+
* checks whether the property can be made writeable. If it can, it is
|
|
49
|
+
* set to writeable.
|
|
50
|
+
* @param {object} proto
|
|
51
|
+
* @param {string} property
|
|
52
|
+
* @returns {boolean} Whether the property on the prototype is (or is now) writeable
|
|
53
|
+
*/
|
|
54
|
+
const propWriteableOrMadeWriteable = (proto, property) => {
|
|
55
|
+
if (
|
|
56
|
+
!proto ||
|
|
57
|
+
!proto.hasOwnProperty ||
|
|
58
|
+
!Object.prototype.hasOwnProperty.call(proto, property)
|
|
59
|
+
) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Getting the properties that this the prototype
|
|
64
|
+
// has under the open property
|
|
65
|
+
const propDescriptor = Object.getOwnPropertyDescriptor(proto, property);
|
|
66
|
+
|
|
67
|
+
// Checking if the open property is read-only
|
|
68
|
+
if (!propDescriptor.writable) {
|
|
69
|
+
// Checking if we can write to it
|
|
70
|
+
if (propDescriptor.configurable) {
|
|
71
|
+
// Making it writable to wrap it
|
|
72
|
+
Object.defineProperty(proto, property, {
|
|
73
|
+
writable: true,
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Replaces the behaviour of Object.fromEntries() as it is not supported on all browsers
|
|
84
|
+
* @param {Iterable} entries The iterable to parse into an object
|
|
85
|
+
* @returns An object containing the same key/values as the iterable passed
|
|
86
|
+
*/
|
|
87
|
+
const safeFromEntries = entries => {
|
|
88
|
+
if (!entries || !entries[Symbol.iterator]) {
|
|
89
|
+
throw new Error('Object.fromEntries() requires a single iterable argument');
|
|
90
|
+
}
|
|
91
|
+
const obj = {};
|
|
92
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
93
|
+
for (const [key, value] of entries) {
|
|
94
|
+
obj[key] = value;
|
|
95
|
+
}
|
|
96
|
+
return obj;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Iterates object recursively and calls visit function
|
|
101
|
+
* for each property allowing to override its value
|
|
102
|
+
* @param {Object} instance An object to iterate through
|
|
103
|
+
* @param {Function} visit A callback function that is called for each property
|
|
104
|
+
* There are 3 arguments: current object, current property and its value
|
|
105
|
+
* @param {{depth: number}} limit Use limit config object to set depth of the recursion
|
|
106
|
+
*/
|
|
107
|
+
const iterateObjectRecursively = (
|
|
108
|
+
instance,
|
|
109
|
+
visit,
|
|
110
|
+
limit = { depth: 5 },
|
|
111
|
+
) => {
|
|
112
|
+
/* eslint-disable no-shadow, no-restricted-syntax, no-param-reassign, guard-for-in */
|
|
113
|
+
/**
|
|
114
|
+
* internal recursive function
|
|
115
|
+
* @param {Object} instance Current object
|
|
116
|
+
* @param {Number} depth Current depth
|
|
117
|
+
*/
|
|
118
|
+
const iterate = (instance, depth) => {
|
|
119
|
+
// stop to go deeper if it is deep enough already
|
|
120
|
+
if (depth > limit.depth) return;
|
|
121
|
+
for (const property in instance) {
|
|
122
|
+
try {
|
|
123
|
+
// call visit function and assign its result if not undefined
|
|
124
|
+
const newValue = visit(instance, property, instance[property]);
|
|
125
|
+
if (newValue !== undefined) {
|
|
126
|
+
instance[property] = newValue;
|
|
127
|
+
}
|
|
128
|
+
// go one level deeper if it is an object
|
|
129
|
+
if (
|
|
130
|
+
instance[property] !== null &&
|
|
131
|
+
typeof instance[property] === 'object'
|
|
132
|
+
) {
|
|
133
|
+
iterate(instance[property], depth + 1);
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// just continue to the next property
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
iterate(instance, 1);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export { iterateObjectRecursively, propWriteableOrMadeWriteable, replace, safeFromEntries };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { timestampWrapper } from './date.js';
|
|
2
|
+
|
|
3
|
+
/** @module Performance */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper function for window.performance.now() to ensure
|
|
7
|
+
* it's available before calling it. If it's not available,
|
|
8
|
+
* we return Date.now() instead.
|
|
9
|
+
*/
|
|
10
|
+
function safePerformanceNow() {
|
|
11
|
+
if (window.performance && window.performance.now) {
|
|
12
|
+
// There is a 70% hit with using this
|
|
13
|
+
// https://jsperf.com/perf-vs-date/1
|
|
14
|
+
// performance.now is for relative time measurement
|
|
15
|
+
// cannot be used instead of Date.getTime() on it's own
|
|
16
|
+
return window.performance.now();
|
|
17
|
+
}
|
|
18
|
+
return timestampWrapper(Date.now());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { safePerformanceNow };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "noibu-react-native",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React-Native SDK for NoibuJS to collect errors in React-Native applications",
|
|
5
|
+
"main": "dist/entry/index.js",
|
|
6
|
+
"types": "dist/entry/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/*",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"author": "Noibu Inc",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
|
|
14
|
+
"scripts": {
|
|
15
|
+
"clean": "rimraf ./dist/*",
|
|
16
|
+
"build": "node ./build.js",
|
|
17
|
+
"build:dev": "node ./build.watch.js",
|
|
18
|
+
"prepare": "npm run clean; npm run build;",
|
|
19
|
+
"test": "jest --coverage --passWithNoTests",
|
|
20
|
+
"lint": "eslint src -c .eslintrc.json --ext js,ts,jsx,tsx",
|
|
21
|
+
"lint_output": "eslint src -c .eslintrc.json --ext js,ts,jsx,tsx -f json > eslint_report.json",
|
|
22
|
+
"codecov": "codecov"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=16.11.0",
|
|
26
|
+
"react-native": ">=0.63.0",
|
|
27
|
+
"react-native-device-info": "^10.6.0",
|
|
28
|
+
"react-native-url-polyfill": "^1.3.0",
|
|
29
|
+
"react-native-uuid": "^2.0.1",
|
|
30
|
+
"stacktrace-parser": "^0.1.10"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
34
|
+
"@rollup/plugin-json": "^6.0.0",
|
|
35
|
+
"@rollup/plugin-node-resolve": "^15.1.0",
|
|
36
|
+
"@rollup/plugin-replace": "^5.0.2",
|
|
37
|
+
"@rollup/plugin-typescript": "^11.1.1",
|
|
38
|
+
"@tsconfig/react-native": "^3.0.2",
|
|
39
|
+
"@types/jest": "^29.5.1",
|
|
40
|
+
"@types/node": "^20.2.3",
|
|
41
|
+
"@types/react": "^18.2.6",
|
|
42
|
+
"@types/react-test-renderer": "^18.0.0",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
|
44
|
+
"codecov": "^3.8.3",
|
|
45
|
+
"dotenv": "^16.1.3",
|
|
46
|
+
"eslint": "^8.41.0",
|
|
47
|
+
"eslint-config-airbnb": "^19.0.4",
|
|
48
|
+
"eslint-config-prettier": "^8.8.0",
|
|
49
|
+
"eslint-plugin-jsdoc": "^44.2.4",
|
|
50
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
51
|
+
"jest": "^29.5.0",
|
|
52
|
+
"prettier": "^2.8.8",
|
|
53
|
+
"rimraf": "^5.0.1",
|
|
54
|
+
"rollup": "^3.24.0",
|
|
55
|
+
"rollup-plugin-dotenv": "^0.5.0"
|
|
56
|
+
}
|
|
57
|
+
}
|