noibu-react-native 0.0.1 → 0.0.2

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.
@@ -36,7 +36,9 @@ class PageVisit {
36
36
  }
37
37
  }
38
38
 
39
- /** gets the singleton instance */
39
+ /** gets the singleton instance
40
+ * @returns {PageVisit}
41
+ */
40
42
  static getInstance() {
41
43
  if (!this.instance) {
42
44
  throw new Error('Pagevisit was never configured');
@@ -0,0 +1,9 @@
1
+ var blacklisedDomains = {
2
+ 'input.noibu.com': true,
3
+ 'input.staging.noibu.com': true,
4
+ 'vf.noibu.com': true,
5
+ 'vf.staging.noibu.com': true,
6
+ 'cdn.noibu.com': true,
7
+ };
8
+
9
+ export { blacklisedDomains as default };
@@ -1,5 +1,6 @@
1
- import { isValidURL, getOnURL, getJSStack, stringifyJSON, getMaxSubstringAllowed } from '../../utils/function.js';
1
+ import { isValidURL, getOnURL, getProperGlobalUrl, getJSStack, stringifyJSON, getMaxSubstringAllowed } from '../../utils/function.js';
2
2
  import { EVENT_ERROR_TYPE, URL_ATT_NAME, ERROR_EVENT_TYPE, ERROR_EVENT_ERROR_TYPE, CUSTOM_ERROR_EVENT_TYPE, ERROR_EVENT_UNHANDLED_REJECTION_TYPE, ERROR_LOG_EVENT_ERROR_TYPE, FETCH_EXCEPTION_ERROR_TYPE, WRAPPED_EXCEPTION_ERROR_TYPE, GQL_ERROR_TYPE, RESPONSE_ERROR_TYPE, XML_HTTP_REQUEST_ERROR_TYPE, ERROR_SOURCE_ATT_NAME, TYPE_ATT_NAME, JS_EVENT_TYPE, JS_ERROR_ATT_NAME, JS_STACK_FRAMES_ATT_NAME, JS_STACK_FILE_ATT_NAME, JS_STACK_METHOD_ATT_NAME, SEVERITY_ERROR, JS_STACK_MESSAGE_ATT_NAME, HTTP_EVENT_TYPE, NOIBU_INPUT_URLS, HTTP_CODE_ATT_NAME, PV_SEQ_ATT_NAME, GQL_EVENT_TYPE, GQL_ERROR_ATT_NAME } from '../../constants.js';
3
+ import blacklisedDomains from './blacklistedDomains.js';
3
4
  import ClientConfig from '../../api/clientConfig.js';
4
5
  import { InputMonitor } from '../../monitors/inputMonitor.js';
5
6
  import StoredMetrics from '../../api/storedMetrics.js';
@@ -68,7 +69,7 @@ function getPVErrorFromXMLHttpRequest(errPayload, httpDataSeqNum) {
68
69
  */
69
70
  function getPVErrorFromErrorEvent(errPayload) {
70
71
  return {
71
- [URL_ATT_NAME]: getOnURL(errPayload.filename || 'http://localhost'),
72
+ [URL_ATT_NAME]: getOnURL(errPayload.filename || getProperGlobalUrl()), // todo implement navigation
72
73
  [TYPE_ATT_NAME]: JS_EVENT_TYPE,
73
74
  [JS_ERROR_ATT_NAME]: getJSStack(errPayload.error),
74
75
  };
@@ -80,9 +81,7 @@ function getPVErrorFromErrorEvent(errPayload) {
80
81
  function getPVErrorFromErrorLog(errPayload) {
81
82
  return {
82
83
  // default to window url
83
- [URL_ATT_NAME]: getOnURL(
84
- (window.location && window.location.href) || 'http://localhost',
85
- ),
84
+ [URL_ATT_NAME]: getOnURL(getProperGlobalUrl()), // todo should be current navigation
86
85
  [TYPE_ATT_NAME]: JS_EVENT_TYPE,
87
86
  [JS_ERROR_ATT_NAME]: getJSStack(errPayload),
88
87
  };
@@ -270,7 +269,7 @@ function isCollectError(pvError) {
270
269
  /** Saves the error to the ErrorQueue
271
270
  * @param {} type
272
271
  * @param {} payload
273
- * @param {} httpDataSeqNum
272
+ * @param {} [httpDataSeqNum]
274
273
  */
275
274
  function saveErrorToPagevisit(type, payload, httpDataSeqNum) {
276
275
  // We do not want to process errors events as errors (yet)
@@ -288,16 +287,12 @@ function saveErrorToPagevisit(type, payload, httpDataSeqNum) {
288
287
 
289
288
  // We need valid URLs in order to check their blacklist status
290
289
  if (isValidURL(currErrURL)) {
291
- new URL(currErrURL);
290
+ const urlOb = new URL(currErrURL);
292
291
 
293
- // if the domain is blacklisted or the protocol is not http(s)
294
- // we drop the error
295
- /* if ( // todo we don't support hostname on react native yet
296
- urlOb.hostname in blacklisedDomains ||
297
- !urlOb.protocol.startsWith('http')
298
- ) {
292
+ // if the domain is blacklisted we drop the error
293
+ if (urlOb.hostname in blacklisedDomains) {
299
294
  return;
300
- } */
295
+ }
301
296
  }
302
297
 
303
298
  if (isCollectError(pvError)) {
@@ -1,11 +1,10 @@
1
- import { getMaxSubstringAllowed, asString, getProperGlobalUrl } from '../../utils/function.js';
1
+ import { getMaxSubstringAllowed, asString } from '../../utils/function.js';
2
2
  import { timestampWrapper } from '../../utils/date.js';
3
3
  import { InputMonitor } from '../../monitors/inputMonitor.js';
4
- import { HTTP_METHOD_ATT_NAME, MAX_HTTP_DATA_EVENT_COUNT, PV_SEQ_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, NOIBUJS_CONFIG, HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME, SEVERITY_ERROR, HTTP_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME } from '../../constants.js';
4
+ import { HTTP_METHOD_ATT_NAME, MAX_HTTP_DATA_EVENT_COUNT, PV_SEQ_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, HTTP_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME } from '../../constants.js';
5
5
  import { PageVisit } from '../pageVisit.js';
6
6
  import StoredMetrics from '../../api/storedMetrics.js';
7
7
  import MetroplexSocket from '../../api/metroplexSocket.js';
8
- import ClientConfig from '../../api/clientConfig.js';
9
8
 
10
9
  /** @module PageVisitEventHTTP */
11
10
 
@@ -79,16 +78,6 @@ class PageVisitEventHTTP {
79
78
  HTTP_DATA_METROPLEX_TYPE,
80
79
  metroplexMsg,
81
80
  );
82
-
83
- if (getProperGlobalUrl().includes('checkout.mec.ca')) {
84
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
85
- `Sending PVH to Metro: ${JSON.stringify(this.httpData)}
86
-
87
- Whitelisted URLs: ${NOIBUJS_CONFIG()[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME]}`,
88
- false,
89
- SEVERITY_ERROR,
90
- );
91
- }
92
81
  } else {
93
82
  // have collected more than the max number of http requests for this
94
83
  // page visit, so increment the over request limit count
@@ -96,7 +85,7 @@ Whitelisted URLs: ${NOIBUJS_CONFIG()[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME]}`,
96
85
  }
97
86
  }
98
87
  // if this was an error, send immediately so we don't lose it
99
- if (isHttpCodeFailure(this.status)) {
88
+ if (isHttpCodeFailure(this.httpEvent.code)) {
100
89
  PageVisit.getInstance().addPageVisitEvent(
101
90
  {
102
91
  event: this.httpEvent,
@@ -29,7 +29,6 @@ class Storage {
29
29
  this._provider = new SessionStorageProvider();
30
30
  this._type = 'SessionStorage';
31
31
  } else if (this._isRNStorageAvailable) {
32
- console.log('_isRNStorageAvailable');
33
32
  this._provider = new RNStorageProvider();
34
33
  this._type = 'RNStorage';
35
34
  }
@@ -24,7 +24,6 @@ class StorageProvider {
24
24
  // access to localStorage/sessionStorage may throw an error
25
25
  // so use function to resolve it wrapped with try/catch
26
26
  const provider = resolver();
27
- console.log({ provider, providerset: provider.setItem });
28
27
  provider.setItem(NOIBU_LOCAL_STORAGE_TEST_KEY, 0);
29
28
  provider.removeItem(NOIBU_LOCAL_STORAGE_TEST_KEY);
30
29
  } catch (e) {
@@ -1,9 +1,20 @@
1
- import * as stackTraceParser from 'stacktrace-parser';
2
1
  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';
2
+ import { getLocales } from 'react-native-localize';
3
+ import { parseStack } from './stacktrace-parser.js';
4
+ import { MAX_BEACON_PAYLOAD_SIZE, MAX_STRING_LENGTH, 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, 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, NOIBUJS_CONFIG, JS_STACK_METHOD_ATT_NAME, DEFAULT_STACK_FRAME_FIELD_VALUE } from '../constants.js';
5
+ import ClientConfig from '../api/clientConfig.js';
4
6
 
5
7
  /** @module Functions */
6
8
 
9
+ /**
10
+ * Returns a stack trace frame with default filed values
11
+ */
12
+ const getDefaultFrame = () => ({
13
+ [JS_STACK_LINE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
14
+ [JS_STACK_METHOD_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
15
+ [JS_STACK_FILE_ATT_NAME]: DEFAULT_STACK_FRAME_FIELD_VALUE,
16
+ });
17
+
7
18
  /**
8
19
  *
9
20
  * returns a string that satisfies a max length
@@ -25,61 +36,54 @@ function getMaxSubstringAllowed(
25
36
 
26
37
  /**
27
38
  *
39
+ * todo implement navigation
28
40
  * getProperGlobalUrl gets the proper global url from the window
41
+ * @returns {string}
29
42
  */
30
43
  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);
44
+ const globalUrl = new URL('https://localhost');
45
+ globalUrl.hostname = ClientConfig.getInstance().customerDomain;
46
+ return getMaxSubstringAllowed(globalUrl.toString());
54
47
  }
55
48
 
56
49
  /**
57
50
  * Processes the raw stack frames and creates a readable stack in a safe manner
58
- * @param rawFrames
51
+ * @param {StackFrame[]} rawFrames
59
52
  */
60
53
  function processFrames(rawFrames) {
61
54
  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
- };
55
+ const processedFrame = getDefaultFrame();
67
56
 
68
- // stringification
69
- if (frame.lineNumber && frame.lineNumber !== '<unknown>') {
70
- processedFrame[JS_STACK_LINE_ATT_NAME] = String(frame.lineNumber);
57
+ if (frame[JS_STACK_LINE_ATT_NAME]) {
58
+ if (Number.isInteger(frame[JS_STACK_LINE_ATT_NAME])) {
59
+ processedFrame[JS_STACK_LINE_ATT_NAME] = String(
60
+ frame[JS_STACK_LINE_ATT_NAME],
61
+ );
62
+ }
71
63
  }
72
64
 
73
- if (frame.methodName && frame.methodName !== '<unknown>') {
74
- processedFrame[JS_STACK_METHOD_ATT_NAME] = String(frame.methodName);
65
+ if (
66
+ frame[JS_STACK_METHOD_ATT_NAME] &&
67
+ frame[JS_STACK_METHOD_ATT_NAME] !== '<unknown>'
68
+ ) {
69
+ processedFrame[JS_STACK_METHOD_ATT_NAME] = String(
70
+ frame[JS_STACK_METHOD_ATT_NAME],
71
+ );
75
72
  }
76
73
 
77
- if (frame.file && frame.file !== '<unknown>') {
78
- processedFrame[JS_STACK_FILE_ATT_NAME] = String(frame.file);
74
+ if (
75
+ frame[JS_STACK_FILE_ATT_NAME] &&
76
+ frame[JS_STACK_FILE_ATT_NAME] !== '<unknown>'
77
+ ) {
78
+ processedFrame[JS_STACK_FILE_ATT_NAME] = String(
79
+ frame[JS_STACK_FILE_ATT_NAME],
80
+ );
79
81
  }
80
82
 
81
- if (frame.column && typeof frame.column === 'number') {
82
- processedFrame[JS_STACK_COL_ATT_NAME] = frame.column;
83
+ if (frame[JS_STACK_COL_ATT_NAME]) {
84
+ if (Number.isInteger(frame[JS_STACK_COL_ATT_NAME])) {
85
+ processedFrame[JS_STACK_COL_ATT_NAME] = frame[JS_STACK_COL_ATT_NAME];
86
+ }
83
87
  }
84
88
 
85
89
  return processedFrame;
@@ -120,7 +124,7 @@ function getJSStack(errObj) {
120
124
  frames[0][JS_STACK_COL_ATT_NAME] = errObj.columnNumber;
121
125
  }
122
126
  } else {
123
- frames = processFrames(stackTraceParser.parse(errObj.stack));
127
+ frames = processFrames(parseStack(errObj.stack));
124
128
  }
125
129
 
126
130
  if (frames.length >= MAX_FRAMES_IN_ARRAY) {
@@ -134,6 +138,18 @@ function getJSStack(errObj) {
134
138
  };
135
139
  }
136
140
 
141
+ /**
142
+ * @param {string} possiblyStacktrace
143
+ * @returns {boolean}
144
+ */
145
+ function isStackTrace(possiblyStacktrace) {
146
+ try {
147
+ return parseStack(possiblyStacktrace).length > 0;
148
+ } catch (e) {
149
+ return false;
150
+ }
151
+ }
152
+
137
153
  /**
138
154
  * safely stringifies an object
139
155
  * @param jsonObject
@@ -166,7 +182,7 @@ function stringifyJSON(jsonObject) {
166
182
  * @param timeout
167
183
  * @param sendAndForget
168
184
  */
169
- function makeRequest(
185
+ async function makeRequest(
170
186
  method,
171
187
  url,
172
188
  data,
@@ -174,12 +190,20 @@ function makeRequest(
174
190
  timeout,
175
191
  sendAndForget,
176
192
  ) {
177
- // a send and forget request is made by using the beacon API (fetch + keepalive)
193
+ const ua = Object.keys(headers).findLast(
194
+ k => k.toLowerCase() === 'user-agent',
195
+ );
196
+ const headersWithUa = { ...headers };
197
+ if (!headers[ua]) {
198
+ headersWithUa['User-Agent'] = await getUserAgent();
199
+ }
200
+
201
+ // a send-and-forget request is made by using the beacon API (fetch + keepalive)
178
202
  if (sendAndForget) {
179
203
  const stringData = stringifyJSON(data);
180
204
  const currentPayloadSize = new Blob([stringData]).size;
181
205
  // 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) {
206
+ if (!global.fetch || currentPayloadSize > MAX_BEACON_PAYLOAD_SIZE) {
183
207
  return new Promise(resolve => {
184
208
  resolve();
185
209
  });
@@ -187,7 +211,7 @@ function makeRequest(
187
211
 
188
212
  return fetch(url, {
189
213
  method: 'POST',
190
- headers,
214
+ headers: headersWithUa,
191
215
  body: stringifyJSON(data),
192
216
  // keep alive outlives the current page, its the same as beacon
193
217
  keepalive: true,
@@ -195,10 +219,10 @@ function makeRequest(
195
219
  }
196
220
 
197
221
  return new Promise((resolve, reject) => {
198
- const xhr = new XMLHttpRequest();
222
+ const xhr = new global.XMLHttpRequest();
199
223
  xhr.open(method, url);
200
224
  xhr.timeout = timeout;
201
- Object.keys(headers).forEach(header => {
225
+ Object.keys(headersWithUa).forEach(header => {
202
226
  xhr.setRequestHeader(header, headers[header]);
203
227
  });
204
228
 
@@ -302,7 +326,7 @@ async function getUserAgent() {
302
326
  /**
303
327
  * isInvalidURLConfig will verify that Collect is being initializes with
304
328
  * the correct env vars.
305
- * @param {} urlConfig
329
+ * @param {Noibu.UrlConfig} urlConfig
306
330
  */
307
331
  function isInvalidURLConfig(urlConfig) {
308
332
  for (let i = 0; i < REQUIRED_DATA_PROCESSING_URLS.length; i += 1) {
@@ -359,9 +383,8 @@ function maskTextInput(text) {
359
383
  * @param {} realOnURL
360
384
  */
361
385
  function getOnURL(realOnURL) {
362
- let onURL = getMaxSubstringAllowed(
363
- (window.location && window.location.href) || 'http://localhost',
364
- );
386
+ let onURL = getProperGlobalUrl();
387
+
365
388
  if (realOnURL && realOnURL.trim() !== '' && realOnURL !== 'undefined') {
366
389
  onURL = asString(getMaxSubstringAllowed(realOnURL));
367
390
  }
@@ -371,7 +394,11 @@ function getOnURL(realOnURL) {
371
394
 
372
395
  /** gets the user language from the browser */
373
396
  function getUserLanguage() {
374
- const lang = window.navigator.userLanguage || window.navigator.language;
397
+ const locales = getLocales();
398
+ if (!locales.length) {
399
+ return null;
400
+ }
401
+ const lang = locales[0].languageCode;
375
402
 
376
403
  if (lang === '' || !lang) {
377
404
  return null;
@@ -395,4 +422,4 @@ function isInstanceOf(instance, type) {
395
422
  }
396
423
  }
397
424
 
398
- export { asString, checkHttpDataCollectionEnabled, getBlockedCSSForCurrentDomain, getHttpPayloadAllowedURLs, getJSStack, getMaxSubstringAllowed, getOnURL, getProperGlobalUrl, getUserAgent, getUserLanguage, isInstanceOf, isInvalidURLConfig, isNoibuJSAlreadyLoaded, isValidURL, makeRequest, maskTextInput, processFrames, stringifyJSON };
425
+ export { asString, checkHttpDataCollectionEnabled, getBlockedCSSForCurrentDomain, getHttpPayloadAllowedURLs, getJSStack, getMaxSubstringAllowed, getOnURL, getProperGlobalUrl, getUserAgent, getUserLanguage, isInstanceOf, isInvalidURLConfig, isNoibuJSAlreadyLoaded, isStackTrace, isValidURL, makeRequest, maskTextInput, processFrames, stringifyJSON };
@@ -8,13 +8,6 @@ import { timestampWrapper } from './date.js';
8
8
  * we return Date.now() instead.
9
9
  */
10
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
11
  return timestampWrapper(Date.now());
19
12
  }
20
13
 
@@ -0,0 +1,9 @@
1
+ import { JS_STACK_COL_ATT_NAME, JS_STACK_FILE_ATT_NAME, JS_STACK_LINE_ATT_NAME, JS_STACK_METHOD_ATT_NAME } from '../constants';
2
+ declare interface StackFrame {
3
+ [JS_STACK_FILE_ATT_NAME]: string;
4
+ [JS_STACK_LINE_ATT_NAME]?: number;
5
+ [JS_STACK_COL_ATT_NAME]?: number;
6
+ [JS_STACK_METHOD_ATT_NAME]: string;
7
+ }
8
+ export declare function parseStack(stackString: string): StackFrame[];
9
+ export {};
@@ -0,0 +1,156 @@
1
+ import { MAX_FRAMES_IN_ARRAY, MAX_STRING_LENGTH, JS_STACK_LINE_ATT_NAME, JS_STACK_COL_ATT_NAME, JS_STACK_FILE_ATT_NAME, JS_STACK_METHOD_ATT_NAME } from '../constants.js';
2
+
3
+ /* eslint-disable require-jsdoc,prefer-destructuring,camelcase */
4
+ // This is a loose copy of ravenjs code
5
+ // Copyright (c) 2013 Onur Can Cakmak onur.cakmak@gmail.com and all TraceKit contributors.
6
+ //
7
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this
8
+ // software and associated documentation files(the 'Software'), to deal in the Software
9
+ // without restriction, including without limitation the rights to use, copy, modify,
10
+ // merge, publish, distribute, sublicense, and / or sell copies of the Software, and to
11
+ // permit persons to whom the Software is furnished to do so, subject to the following
12
+ // conditions:
13
+ //
14
+ // The above copyright notice and this permission notice shall be included in all copies
15
+ // or substantial portions of the Software.
16
+ //
17
+ // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18
+ // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19
+ // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21
+ // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22
+ // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ // global reference to slice
24
+ const UNKNOWN_FUNCTION = '<unknown>';
25
+ /**
26
+ * Safari web extensions, starting version unknown, can produce "frames-only" stacktraces.
27
+ * What it means, is that instead of format like:
28
+ *
29
+ * Error: wat
30
+ * at function@url:row:col
31
+ * at function@url:row:col
32
+ * at function@url:row:col
33
+ *
34
+ * it produces something like:
35
+ *
36
+ * function@url:row:col
37
+ * function@url:row:col
38
+ * function@url:row:col
39
+ *
40
+ * Because of that, it won't be captured by `chrome` RegExp and will fall into `Gecko` branch.
41
+ * This function is extracted so that we can use it in both places without duplicating the logic.
42
+ * Unfortunately "just" changing RegExp is too complicated now and making it pass all tests
43
+ * and fix this case seems like an impossible, or at least way too time-consuming task.
44
+ */
45
+ const extractSafariExtensionDetails = (func, filename) => {
46
+ const isSafariExtension = func.indexOf('safari-extension') !== -1;
47
+ const isSafariWebExtension = func.indexOf('safari-web-extension') !== -1;
48
+ return isSafariExtension || isSafariWebExtension
49
+ ? [
50
+ func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION,
51
+ isSafariExtension
52
+ ? `safari-extension:${filename}`
53
+ : `safari-web-extension:${filename}`,
54
+ ]
55
+ : [func, filename];
56
+ };
57
+ function createFrame(filename, func, lineno, colno) {
58
+ const frame = {
59
+ [JS_STACK_FILE_ATT_NAME]: filename,
60
+ [JS_STACK_METHOD_ATT_NAME]: func,
61
+ };
62
+ if (lineno !== undefined) {
63
+ frame[JS_STACK_LINE_ATT_NAME] = lineno;
64
+ }
65
+ if (colno !== undefined) {
66
+ frame[JS_STACK_COL_ATT_NAME] = colno;
67
+ }
68
+ return frame;
69
+ }
70
+ // Chromium based browsers: Chrome, Brave, new Opera, new Edge
71
+ const chromeRegex = /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
72
+ const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
73
+ const chrome = (line) => {
74
+ const parts = chromeRegex.exec(line);
75
+ if (parts) {
76
+ const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line
77
+ if (isEval) {
78
+ const subMatch = chromeEvalRegex.exec(parts[2]);
79
+ if (subMatch) {
80
+ // throw out eval line/column and use top-most line/column number
81
+ parts[2] = subMatch[1]; // url
82
+ parts[3] = subMatch[2]; // line
83
+ parts[4] = subMatch[3]; // column
84
+ }
85
+ }
86
+ const [func, filename] = extractSafariExtensionDetails(parts[1] || UNKNOWN_FUNCTION, parts[2]);
87
+ return createFrame(filename, func, parts[3] ? +parts[3] : undefined, parts[4] ? +parts[4] : undefined);
88
+ }
89
+ return undefined;
90
+ };
91
+ const geckoREgex = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
92
+ const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
93
+ const gecko = (line) => {
94
+ const parts = geckoREgex.exec(line);
95
+ if (parts) {
96
+ const isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
97
+ if (isEval) {
98
+ const subMatch = geckoEvalRegex.exec(parts[3]);
99
+ if (subMatch) {
100
+ // throw out eval line/column and use top-most line number
101
+ parts[1] = parts[1] || 'eval';
102
+ parts[3] = subMatch[1];
103
+ parts[4] = subMatch[2];
104
+ parts[5] = ''; // no column when eval
105
+ }
106
+ }
107
+ let filename = parts[3];
108
+ let func = parts[1] || UNKNOWN_FUNCTION;
109
+ [func, filename] = extractSafariExtensionDetails(func, filename);
110
+ return createFrame(filename, func, parts[4] ? +parts[4] : undefined, parts[5] ? +parts[5] : undefined);
111
+ }
112
+ return undefined;
113
+ };
114
+ const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:[-a-z]+):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
115
+ const winjs = (line) => {
116
+ const parts = winjsRegex.exec(line);
117
+ return parts
118
+ ? createFrame(parts[2], parts[1] || UNKNOWN_FUNCTION, +parts[3], parts[4] ? +parts[4] : undefined)
119
+ : undefined;
120
+ };
121
+ const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i;
122
+ const opera10 = (line) => {
123
+ const parts = opera10Regex.exec(line);
124
+ return parts
125
+ ? createFrame(parts[2], parts[3] || UNKNOWN_FUNCTION, +parts[1])
126
+ : undefined;
127
+ };
128
+ const opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^)]+))\(.*\))? in (.*):\s*$/i;
129
+ const opera11 = (line) => {
130
+ const parts = opera11Regex.exec(line);
131
+ return parts
132
+ ? createFrame(parts[5], parts[3] || parts[4] || UNKNOWN_FUNCTION, +parts[1], +parts[2])
133
+ : undefined;
134
+ };
135
+ function parseStack(stackString) {
136
+ const lines = stackString.split('\n');
137
+ if (lines.length > MAX_FRAMES_IN_ARRAY) {
138
+ return [];
139
+ }
140
+ return lines.reduce((stack, line) => {
141
+ if (line.length > MAX_STRING_LENGTH) {
142
+ return stack;
143
+ }
144
+ const parseResult = gecko(line) ||
145
+ chrome(line) ||
146
+ winjs(line) ||
147
+ opera11(line) ||
148
+ opera10(line);
149
+ if (parseResult) {
150
+ stack.push(parseResult);
151
+ }
152
+ return stack;
153
+ }, []);
154
+ }
155
+
156
+ export { parseStack };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noibu-react-native",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "React-Native SDK for NoibuJS to collect errors in React-Native applications",
5
5
  "main": "dist/entry/index.js",
6
6
  "types": "dist/entry/index.d.ts",
@@ -10,7 +10,6 @@
10
10
  ],
11
11
  "author": "Noibu Inc",
12
12
  "license": "ISC",
13
-
14
13
  "scripts": {
15
14
  "clean": "rimraf ./dist/*",
16
15
  "build": "node ./build.js",
@@ -21,13 +20,13 @@
21
20
  "lint_output": "eslint src -c .eslintrc.json --ext js,ts,jsx,tsx -f json > eslint_report.json",
22
21
  "codecov": "codecov"
23
22
  },
24
- "peerDependencies": {
23
+ "dependencies": {
25
24
  "react": ">=16.11.0",
26
25
  "react-native": ">=0.63.0",
27
26
  "react-native-device-info": "^10.6.0",
28
27
  "react-native-url-polyfill": "^1.3.0",
29
28
  "react-native-uuid": "^2.0.1",
30
- "stacktrace-parser": "^0.1.10"
29
+ "react-native-localize": "^3.0.1"
31
30
  },
32
31
  "devDependencies": {
33
32
  "@rollup/plugin-commonjs": "^25.0.0",