noibu-react-native 0.0.8 → 0.1.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 CHANGED
@@ -164,10 +164,6 @@ import { NoibuJS } from 'noibu-react-native';
164
164
  NoibuJS.addJsSdkError(new Error('My Error'), 'myModule.js');
165
165
  ```
166
166
 
167
- ## Contributing
168
-
169
- You can contribute by checking out the project page and open issues. https://linear.app/noibu/project/react-native-sdk-5ccd19a3343a
170
-
171
167
  ## License
172
168
 
173
169
  Copyright 2023 Noibu.com
@@ -11,11 +11,12 @@ export default class ClientConfig {
11
11
  private browserId;
12
12
  private pageVisitSeq;
13
13
  private lastActiveTime;
14
- private noibuErrorURL;
14
+ private readonly noibuErrorURL;
15
15
  private cltErrorPostCounter;
16
16
  private readonly maxSocketInactiveTime;
17
+ private locationBreadcrumbs;
17
18
  private static instance;
18
- private static noibuErrorURL;
19
+ static noibuErrorURL: string;
19
20
  customerDomain: string;
20
21
  isClientDisabled: boolean;
21
22
  readonly listOfUrlsToCollectHttpDataFrom: CustomerConfig['listOfUrlsToCollectHttpDataFrom'];
@@ -88,6 +89,10 @@ export default class ClientConfig {
88
89
  * @param {} data the data to be stored
89
90
  */
90
91
  _storeBrowserData(data: StoredConfig): Promise<StoredConfig>;
92
+ /** sets current breadcrumbs to be reconstructed into global url */
93
+ set currentLocationBreadcrumbs(newValue: typeof this.locationBreadcrumbs);
94
+ /** gets current global url */
95
+ get globalUrl(): string;
91
96
  /**
92
97
  * postNoibuErrorAndOptionallyDisableClient will post errors that were thrown by collect
93
98
  * and disable the client if required
@@ -1,8 +1,7 @@
1
1
  import uuid from 'react-native-uuid';
2
2
  import { MAX_METROPLEX_SOCKET_INNACTIVE_TIME, SEVERITY, NOIBU_BROWSER_ID_KYWRD, PV_SEQ_NUM_RESET_TIME_MINUTES, JS_ENV, MAX_PAGEVISIT_VISITED, CLIENT_LOCK_TIME_MINUTES, GET_SCRIPT_ID, MAX_COLLECT_ERROR_LOG } from '../constants.js';
3
- import { stringifyJSON, getUserAgent, asString, makeRequest } from '../utils/function.js';
3
+ import { stringifyJSON, getMaxSubstringAllowed, getUserAgent, makeRequest } from '../utils/function.js';
4
4
  import Storage from '../storage/storage.js';
5
- import { AppNavigationMonitor } from '../monitors/AppNavigationMonitor.js';
6
5
 
7
6
  /** @module ClientConfig */
8
7
  /**
@@ -19,6 +18,7 @@ class ClientConfig {
19
18
  noibuErrorURL;
20
19
  cltErrorPostCounter;
21
20
  maxSocketInactiveTime;
21
+ locationBreadcrumbs = [];
22
22
  static instance;
23
23
  static noibuErrorURL;
24
24
  customerDomain;
@@ -61,9 +61,8 @@ class ClientConfig {
61
61
  // Set this.noibuErrorURL preemptively in case ClientConfig isn't able to be
62
62
  // configured properly and throws an error.
63
63
  // This will ensure we get the expected error POST request at the correct URL.
64
- this.noibuErrorURL = noibuErrorURL;
64
+ ClientConfig.noibuErrorURL = noibuErrorURL;
65
65
  this.instance = new ClientConfig(noibuErrorURL, customerConfig);
66
- this.instance.noibuErrorURL = noibuErrorURL;
67
66
  }
68
67
  }
69
68
  /**
@@ -271,6 +270,22 @@ class ClientConfig {
271
270
  return this._generateNewBrowserData();
272
271
  }
273
272
  }
273
+ /** sets current breadcrumbs to be reconstructed into global url */
274
+ set currentLocationBreadcrumbs(newValue) {
275
+ if (!Array.isArray(newValue)) {
276
+ return;
277
+ }
278
+ this.locationBreadcrumbs = newValue.slice();
279
+ }
280
+ /** gets current global url */
281
+ get globalUrl() {
282
+ const globalUrl = new URL('https://localhost');
283
+ globalUrl.hostname = ClientConfig.getInstance().customerDomain;
284
+ if (this.locationBreadcrumbs.length) {
285
+ globalUrl.pathname = this.locationBreadcrumbs.join('/');
286
+ }
287
+ return getMaxSubstringAllowed(globalUrl.toString());
288
+ }
274
289
  /**
275
290
  * postNoibuErrorAndOptionallyDisableClient will post errors that were thrown by collect
276
291
  * and disable the client if required
@@ -287,7 +302,13 @@ class ClientConfig {
287
302
  // don't log warning messages by default, as a cost savings
288
303
  return;
289
304
  }
290
- let errMsg = `Noibu Browser ID(${this.browserId ? this.browserId : ''}), PV ID ${this.pageVisitId}, Script ID ${GET_SCRIPT_ID()}, and User Agent ${await getUserAgent()} error: ${asString(errorMsg)}`;
305
+ const collectError = {
306
+ browserId: this.browserId || '',
307
+ pageVisitId: this.pageVisitId,
308
+ scriptId: GET_SCRIPT_ID(),
309
+ ua: await getUserAgent(),
310
+ error: errorMsg,
311
+ };
291
312
  // if the page visits sends more errors than the
292
313
  // allowed threshold we disable the client
293
314
  if (this.cltErrorPostCounter >= MAX_COLLECT_ERROR_LOG) {
@@ -302,20 +323,22 @@ class ClientConfig {
302
323
  this.isClientDisabled = true;
303
324
  // end of lock
304
325
  // overriding the message to be an alert that we are shutting collect off.
305
- errMsg =
306
- 'Shutting collect off, we reached the ' +
307
- 'maximum limit of collect errors sent.';
326
+ collectError.error = `Shutting collect off, we reached the maximum limit of collect errors sent.`;
308
327
  }
309
328
  const errorContent = {
310
- url: AppNavigationMonitor.getInstance().globalUrl,
311
- err_msg: errMsg,
329
+ url: this.globalUrl,
330
+ err_msg: collectError,
312
331
  sev: severity,
313
332
  };
314
333
  const headers = {
315
334
  'content-type': 'application/json',
316
- 'User-Agent': await getUserAgent(),
317
335
  };
318
- if (keepAlive) {
336
+ if (!keepAlive) {
337
+ makeRequest('POST', this.noibuErrorURL, errorContent, headers, 2000, false).catch(() => {
338
+ // we do nothing and let this error silently fail
339
+ });
340
+ }
341
+ else {
319
342
  fetch(this.noibuErrorURL, {
320
343
  method: 'POST',
321
344
  headers,
@@ -324,11 +347,6 @@ class ClientConfig {
324
347
  keepalive: true,
325
348
  });
326
349
  }
327
- else {
328
- makeRequest('POST', this.noibuErrorURL, errorContent, headers, 2000, false).catch(() => {
329
- // we do nothing and let this error silently fail
330
- });
331
- }
332
350
  // only increment if this was an actual error, not a warning or otherwise
333
351
  if (severity === SEVERITY.error) {
334
352
  this.cltErrorPostCounter += 1;
@@ -1,5 +1,5 @@
1
1
  import MetroplexSocket from './metroplexSocket.js';
2
- import { SEVERITY, WORK_REQUEST_ATT_NAME, HELP_CODE_ATT_NAME } from '../constants.js';
2
+ import { SEVERITY } from '../constants.js';
3
3
  import ClientConfig from './clientConfig.js';
4
4
 
5
5
  /**
@@ -23,6 +23,7 @@ class HelpCode {
23
23
  */
24
24
  constructor() {
25
25
  this.requestContext = null;
26
+ this.receiveHelpCode = this.receiveHelpCode.bind(this);
26
27
  }
27
28
 
28
29
  /**
@@ -50,7 +51,9 @@ class HelpCode {
50
51
 
51
52
  this.requestContext = context;
52
53
 
53
- const result = await this._sendRequest();
54
+ const result = await MetroplexSocket.getInstance().requestHelpCode(
55
+ this.receiveHelpCode,
56
+ );
54
57
 
55
58
  if (result === false) {
56
59
  this.requestContext = null;
@@ -92,15 +95,6 @@ class HelpCode {
92
95
  context.reject(new Error(data));
93
96
  }
94
97
  }
95
-
96
- /**
97
- * Sends metroplex request
98
- */
99
- _sendRequest() {
100
- return MetroplexSocket.getInstance().sendMessage(WORK_REQUEST_ATT_NAME, {
101
- [WORK_REQUEST_ATT_NAME]: HELP_CODE_ATT_NAME,
102
- });
103
- }
104
98
  }
105
99
 
106
100
  export { HelpCode as default };
@@ -1,14 +1,12 @@
1
1
  import uuid from 'react-native-uuid';
2
2
  import { getUserAgent, stringifyJSON } from '../utils/function.js';
3
3
  import { addSafeEventListener } from '../utils/eventlistener.js';
4
- import { GET_METROPLEX_BASE_SOCKET_URL, METROPLEX_FRAG_ROUTE, GET_METROPLEX_POST_URL, METROPLEX_RETRY_FREQUENCY, META_DATA_METROPLEX_TYPE, PAGE_VISIT_META_DATA_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME, VIDEO_METROPLEX_TYPE, PAGE_VISIT_VID_FRAG_ATT_NAME, PV_METROPLEX_TYPE, PAGE_VISIT_PART_ATT_NAME, SEQ_NUM_ATT_NAME, WORK_REQUEST_ATT_NAME, PV_EVENTS_ATT_NAME, TYPE_ATT_NAME, USERSTEP_EVENT_TYPE, GET_MAX_METROPLEX_RECONNECTION_NUMBER, MAX_METROPLEX_CONNECTION_COUNT, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, END_AT_ATT_NAME, SEVERITY, OK_SOCKET_MESSAGE, CLOSE_CONNECTION_FORCEFULLY, BLOCK_SOCKET_MESSAGE, STOP_STORING_PV_SOCKET_MESSAGE, STOP_STORING_VID_SOCKET_MESSAGE, MAX_BEACON_PAYLOAD_SIZE, PAGE_VISIT_INFORMATION_ATT_NAME, VIDEO_PART_COUNT_ATT_NAME, IS_LAST_ATT_NAME, BROWSER_ID_ATT_NAME, PV_ID_ATT_NAME, VER_ATT_NAME, CURRENT_PV_VERSION, PV_SEQ_ATT_NAME, ON_URL_ATT_NAME, REF_URL_ATT_NAME, STARTED_AT_ATT_NAME, CONN_COUNT_ATT_NAME, COLLECT_VER_ATT_NAME, CURRENT_NOIBUJS_VERSION, SCRIPT_ID_ATT_NAME, GET_SCRIPT_ID, SCRIPT_INSTANCE_ID_ATT_NAME, METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME, SOCKET_INSTANCE_ID_ATT_NAME, HELP_CODE_ATT_NAME } from '../constants.js';
4
+ import { GET_METROPLEX_BASE_SOCKET_URL, METROPLEX_FRAG_ROUTE, GET_METROPLEX_POST_URL, METROPLEX_RETRY_FREQUENCY, META_DATA_METROPLEX_TYPE, PAGE_VISIT_META_DATA_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME, VIDEO_METROPLEX_TYPE, PAGE_VISIT_VID_FRAG_ATT_NAME, PV_METROPLEX_TYPE, PAGE_VISIT_PART_ATT_NAME, SEQ_NUM_ATT_NAME, WORK_REQUEST_ATT_NAME, HELP_CODE_ATT_NAME, PV_EVENTS_ATT_NAME, TYPE_ATT_NAME, USERSTEP_EVENT_TYPE, GET_MAX_METROPLEX_RECONNECTION_NUMBER, MAX_METROPLEX_CONNECTION_COUNT, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, END_AT_ATT_NAME, SEVERITY, OK_SOCKET_MESSAGE, CLOSE_CONNECTION_FORCEFULLY, BLOCK_SOCKET_MESSAGE, STOP_STORING_PV_SOCKET_MESSAGE, STOP_STORING_VID_SOCKET_MESSAGE, MAX_BEACON_PAYLOAD_SIZE, PAGE_VISIT_INFORMATION_ATT_NAME, VIDEO_PART_COUNT_ATT_NAME, IS_LAST_ATT_NAME, BROWSER_ID_ATT_NAME, PV_ID_ATT_NAME, VER_ATT_NAME, CURRENT_PV_VERSION, PV_SEQ_ATT_NAME, ON_URL_ATT_NAME, REF_URL_ATT_NAME, STARTED_AT_ATT_NAME, CONN_COUNT_ATT_NAME, COLLECT_VER_ATT_NAME, CURRENT_NOIBUJS_VERSION, SCRIPT_ID_ATT_NAME, GET_SCRIPT_ID, SCRIPT_INSTANCE_ID_ATT_NAME, METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME, SOCKET_INSTANCE_ID_ATT_NAME } from '../constants.js';
5
5
  import ClientConfig from './clientConfig.js';
6
6
  import StoredMetrics from './storedMetrics.js';
7
7
  import StoredPageVisit from './storedPageVisit.js';
8
8
  import { safePerformanceNow } from '../utils/performance.js';
9
9
  import { isDateOverwritten } from '../utils/date.js';
10
- import HelpCode from './helpCode.js';
11
- import { AppNavigationMonitor } from '../monitors/AppNavigationMonitor.js';
12
10
 
13
11
  /** @module MetroplexSocket */
14
12
 
@@ -62,7 +60,7 @@ class MetroplexSocket {
62
60
  // setting initial URL at the start in order to guarentee that the
63
61
  // current page visit has the real initial onURL. Fragments and SPA's
64
62
  // can change the URL without reloading the page.
65
- this._initialURL = AppNavigationMonitor.getInstance().globalUrl;
63
+ this._initialURL = ClientConfig.getInstance().globalUrl;
66
64
  this.initialReferingURL = '';
67
65
  this.sessionTimestamp = new Date();
68
66
 
@@ -160,6 +158,14 @@ class MetroplexSocket {
160
158
  this.messageSequenceNum += 1;
161
159
  }
162
160
 
161
+ /** requests help code and saves a callback to be called on response */
162
+ requestHelpCode(cb) {
163
+ this.helpCodeCb = cb;
164
+ return MetroplexSocket.getInstance().sendMessage(WORK_REQUEST_ATT_NAME, {
165
+ [WORK_REQUEST_ATT_NAME]: HELP_CODE_ATT_NAME,
166
+ });
167
+ }
168
+
163
169
  /**
164
170
  * Immediately sends a message to Metroplex over the web socket
165
171
  * Queues the message if the connection isn't open yet.
@@ -831,7 +837,7 @@ class MetroplexSocket {
831
837
  const data = response.substring(prefix.length);
832
838
  const success = /^\d{6}$/.test(data);
833
839
 
834
- HelpCode.getInstance().receiveHelpCode({ detail: { success, data } });
840
+ this.helpCodeCb({ detail: { success, data } });
835
841
 
836
842
  return true;
837
843
  }
@@ -2,7 +2,6 @@ import { BROWSER_ID_ATT_NAME, PV_ID_ATT_NAME, COLLECT_VER_ATT_NAME, CURRENT_NOIB
2
2
  import ClientConfig from './clientConfig.js';
3
3
  import { getUserAgent, stringifyJSON } from '../utils/function.js';
4
4
  import { addSafeEventListener } from '../utils/eventlistener.js';
5
- import { AppNavigationMonitor } from '../monitors/AppNavigationMonitor.js';
6
5
 
7
6
  /** @module StoredMetrics */
8
7
 
@@ -180,7 +179,7 @@ class StoredMetrics {
180
179
  [DID_START_VID_ATT_NAME]: this.didStartVideo,
181
180
  [HTTP_COUNT_EXPECTED_ATT_NAME]: this.httpCount,
182
181
  [ERR_COUNT_EXPECTED_ATT_NAME]: this.errCount,
183
- [ON_URL_ATT_NAME]: AppNavigationMonitor.getInstance().globalUrl,
182
+ [ON_URL_ATT_NAME]: ClientConfig.getInstance().globalUrl,
184
183
  };
185
184
  global.fetch(GET_METROPLEX_METRICS_URL(), {
186
185
  method: 'POST',
@@ -1,4 +1,4 @@
1
- import { PV_METROPLEX_TYPE, PAGE_VISIT_PART_ATT_NAME, PV_EVENTS_ATT_NAME, TYPE_ATT_NAME, USERSTEP_EVENT_TYPE, ERROR_EVENT_TYPE, LOCATION_EVENT_TYPE, NOIBU_STORED_PAGE_VISIT, SEVERITY, MAX_METROPLEX_SOCKET_INNACTIVE_TIME, PAGE_VISIT_INFORMATION_ATT_NAME, PAGE_VISIT_VID_FRAG_ATT_NAME, IS_LAST_ATT_NAME, GET_METROPLEX_POST_URL } from '../constants.js';
1
+ import { PV_METROPLEX_TYPE, PAGE_VISIT_PART_ATT_NAME, PV_EVENTS_ATT_NAME, TYPE_ATT_NAME, USERSTEP_EVENT_TYPE, ERROR_EVENT_TYPE, NOIBU_STORED_PAGE_VISIT, SEVERITY, MAX_METROPLEX_SOCKET_INNACTIVE_TIME, PAGE_VISIT_INFORMATION_ATT_NAME, PAGE_VISIT_VID_FRAG_ATT_NAME, IS_LAST_ATT_NAME, GET_METROPLEX_POST_URL } from '../constants.js';
2
2
  import ClientConfig from './clientConfig.js';
3
3
  import { stringifyJSON, makeRequest } from '../utils/function.js';
4
4
  import Storage from '../storage/storage.js';
@@ -57,8 +57,7 @@ class StoredPageVisit {
57
57
  // Any userstep is used rather than just mouse clicks because we shouldn't
58
58
  // assume mouse clicks are the only way for any activity to happen
59
59
  event[TYPE_ATT_NAME] === USERSTEP_EVENT_TYPE ||
60
- event[TYPE_ATT_NAME] === ERROR_EVENT_TYPE ||
61
- event[TYPE_ATT_NAME] === LOCATION_EVENT_TYPE,
60
+ event[TYPE_ATT_NAME] === ERROR_EVENT_TYPE,
62
61
  );
63
62
 
64
63
  // Only write the retry queue if it contains a user step or location change
@@ -125,13 +125,11 @@ export declare const HTTP_EVENT_TYPE: "http";
125
125
  export declare const JS_EVENT_TYPE: "js";
126
126
  export declare const GQL_EVENT_TYPE: "gql";
127
127
  export declare const USERSTEP_EVENT_TYPE: "userstep";
128
- export declare const WEBVITAL_EVENT_TYPE: "wv";
129
128
  export declare const CLICK_EVENT_TYPE: "click";
130
129
  export declare const KEYBOARD_EVENT_TYPE: "kbd";
131
- export declare const NAVIGATION_EVENT_TYPE: "navigation";
132
- export declare const LOCATION_EVENT_TYPE: "loc";
133
130
  export declare const ERROR_EVENT_TYPE: "err";
134
131
  export declare const PAGE_EVENT_TYPE: "page";
132
+ export declare const APP_NAVIGATION_EVENT_TYPE = "app_nav";
135
133
  export declare const PAGE_VISIT_INFORMATION_ATT_NAME: "pvi";
136
134
  export declare const PAGE_VISIT_PART_ATT_NAME: "pvp";
137
135
  export declare const PAGE_VISIT_VID_FRAG_ATT_NAME: "pvvf";
@@ -228,4 +226,4 @@ export declare function GET_METROPLEX_METRICS_URL(): string;
228
226
  */
229
227
  export declare function JS_ENV(): string;
230
228
  export declare const METROPLEX_RETRY_FREQUENCY: 30000;
231
- export declare const CSS_URL_REGEX_STR: RegExp;
229
+ export declare const STACK_TRACE_SANITIZE_REGEXP: RegExp;
package/dist/constants.js CHANGED
@@ -223,9 +223,9 @@ const GQL_EVENT_TYPE = 'gql';
223
223
  const USERSTEP_EVENT_TYPE = 'userstep';
224
224
  const CLICK_EVENT_TYPE = 'click';
225
225
  const KEYBOARD_EVENT_TYPE = 'kbd';
226
- const LOCATION_EVENT_TYPE = 'loc';
227
226
  const ERROR_EVENT_TYPE = 'err';
228
227
  const PAGE_EVENT_TYPE = 'page';
228
+ const APP_NAVIGATION_EVENT_TYPE = 'app_nav';
229
229
  // complete page visit attributes
230
230
  const PAGE_VISIT_INFORMATION_ATT_NAME = 'pvi';
231
231
  const PAGE_VISIT_PART_ATT_NAME = 'pvp';
@@ -287,12 +287,10 @@ const PAGE_EVENTS_WINDOW = [
287
287
  'pageshow',
288
288
  'focus',
289
289
  'blur',
290
- // 'storage', // Disabled until compression is supported NOI-3998
291
290
  'popstate',
292
291
  'online',
293
292
  'offline',
294
293
  'messageerror',
295
- // 'message', // Disabled until compression is supported NOI-3998
296
294
  'languagechange',
297
295
  'hashchange',
298
296
  'beforeprint',
@@ -317,7 +315,7 @@ const CONTENT_LENGTH = 'content-length';
317
315
  * Gets the script id from the cookie object, returns default if cannot be found
318
316
  */
319
317
  function GET_SCRIPT_ID() {
320
- return "1.0.104" ;
318
+ return "1.0.104-rn-sdk-0.1.1" ;
321
319
  }
322
320
  /**
323
321
  *
@@ -425,5 +423,6 @@ function JS_ENV() {
425
423
  }
426
424
  // gets the frequency at which the client resends the message that were not confirmed by metroplex
427
425
  const METROPLEX_RETRY_FREQUENCY = 30000;
426
+ const STACK_TRACE_SANITIZE_REGEXP = /(nbuGlobalPromiseRejectWrapper|(hermes.*InternalBytecode\/InternalBytecode))/gi;
428
427
 
429
- export { BLOCKED_HTTP_HEADER_KEYS, BLOCK_SOCKET_MESSAGE, BROWSER_ID_ATT_NAME, CLICK_EVENT_TYPE, CLIENT_LOCK_TIME_MINUTES, CLOSE_CONNECTION_FORCEFULLY, COLLECT_VER_ATT_NAME, CONN_COUNT_ATT_NAME, CONSOLE_FUNCTION_OVERRIDES, CONTENT_LENGTH, CONTENT_TYPE, CSS_CLASS_ATT_NAME, CURRENT_METRICS_VERSION, CURRENT_NOIBUJS_VERSION, CURRENT_PV_VERSION, CUSTOM_ERROR_EVENT_TYPE, CUSTOM_ID_NAME_TYPE, CUSTOM_ID_VALUE_TYPE, DEFAULT_STACK_FRAME_FIELD_VALUE, DEFAULT_WEBSITE_SUBDOMAIN_PATTERN, DID_CUT_PV_ATT_NAME, DID_CUT_VID_ATT_NAME, DID_START_VID_ATT_NAME, END_AT_ATT_NAME, ERROR_EVENT_ERROR_TYPE, ERROR_EVENT_TYPE, ERROR_EVENT_UNHANDLED_REJECTION_TYPE, ERROR_LOG_EVENT_ERROR_TYPE, ERROR_SOURCE_ATT_NAME, ERR_COUNT_EXPECTED_ATT_NAME, EVENT_ERROR_TYPE, EXP_VIDEO_LENGTH_ATT_NAME, FETCH_EXCEPTION_ERROR_TYPE, GET_MAX_METROPLEX_RECONNECTION_NUMBER, GET_METROPLEX_BASE_HTTP_URL, GET_METROPLEX_BASE_SOCKET_URL, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, GET_METROPLEX_METRICS_URL, GET_METROPLEX_POST_URL, GET_SCRIPT_ID, GQL_ERROR_ATT_NAME, GQL_ERROR_TYPE, GQL_EVENT_TYPE, HELP_CODE_ATT_NAME, HTMLID_ATT_NAME, HTTP_BODY_DROPPED_LENGTH_MSG, HTTP_BODY_DROPPED_TYPE_MSG, HTTP_BODY_NULL_STRING, HTTP_CODE_ATT_NAME, HTTP_COUNT_EXPECTED_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, HTTP_DATA_PAYLOAD_ATT_NAME, HTTP_DATA_REQ_HEADERS_ATT_NAME, HTTP_DATA_RESP_HEADERS_ATT_NAME, HTTP_DATA_RESP_PAYLOAD_ATT_NAME, HTTP_EVENT_TYPE, HTTP_METHOD_ATT_NAME, HTTP_PII_BLOCKING_PATTERNS, HTTP_RESP_CODE_ATT_NAME, HTTP_RESP_LENGTH_ATT_NAME, HTTP_RESP_TIME_ATT_NAME, HUMAN_READABLE_CONTENT_TYPE_REGEX, IS_LAST_ATT_NAME, IS_NJS_VERSION_BETA, JS_ENV, JS_ERROR_ATT_NAME, JS_EVENT_TYPE, JS_STACK_FILE_ATT_NAME, JS_STACK_FRAMES_ATT_NAME, JS_STACK_MESSAGE_ATT_NAME, JS_STACK_METHOD_ATT_NAME, KEYBOARD_EVENT_TYPE, LOCATION_EVENT_TYPE, MAX_BEACON_PAYLOAD_SIZE, MAX_COLLECT_ERROR_LOG, MAX_CUSTOM_ERRORS_PER_PAGEVISIT, MAX_CUSTOM_IDS_PER_PAGEVISIT, MAX_FRAMES_IN_ARRAY, MAX_HTTP_DATA_EVENT_COUNT, MAX_HTTP_DATA_PAYLOAD_LENGTH, MAX_METROPLEX_CONNECTION_COUNT, MAX_METROPLEX_SOCKET_INNACTIVE_TIME, MAX_PAGEVISIT_EVENTS, MAX_PAGEVISIT_PARTS, MAX_PAGEVISIT_VISITED, MAX_STRING_LENGTH, MAX_TIME_FOR_UNSENT_DATA_MILLIS, META_DATA_METROPLEX_TYPE, METROPLEX_ERROR_ROUTE, METROPLEX_FRAG_ROUTE, METROPLEX_FULL_PV_ROUTE, METROPLEX_METRICS_ROUTE, METROPLEX_RETRY_FREQUENCY, METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME, NOIBUJS_SDK_ADD_ERROR_FROM_JS_FMW_FUNCTION, NOIBUJS_SDK_ADD_ERROR_FUNCTION, NOIBUJS_SDK_ADD_ID_FUNCTION, NOIBUJS_SDK_REQUEST_HELP_CODE, NOIBU_BROWSER_ID_KYWRD, NOIBU_INPUT_URLS, NOIBU_LOCAL_STORAGE_TEST_KEY, NOIBU_STORED_PAGE_VISIT, OCCURRED_AT_ATT_NAME, OK_SOCKET_MESSAGE, ON_URL_ATT_NAME, PAGE_EVENTS_DOCUMENT, PAGE_EVENTS_WINDOW, PAGE_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME, PAGE_VISIT_INFORMATION_ATT_NAME, PAGE_VISIT_META_DATA_ATT_NAME, PAGE_VISIT_PART_ATT_NAME, PAGE_VISIT_VID_FRAG_ATT_NAME, PII_DIGIT_PATTERN, PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING, PV_CLICKS_ATT_NAME, PV_EVENTS_ATT_NAME, PV_EXP_HTTP_DATA_SEQ_ATT_NAME, PV_EXP_PART_COUNTER_ATT_NAME, PV_EXP_VF_SEQ_ATT_NAME, PV_HTTP_PAYLOADS_COLLECTED_ATT_NAME, PV_HTTP_PAYLOADS_DROPPED_OVERSIZE_ATT_NAME, PV_HTTP_PAYLOADS_DROPPED_TYPE_ATT_NAME, PV_HTTP_REQUESTS_DROPPED_OVER_LIMIT, PV_ID_ATT_NAME, PV_METROPLEX_TYPE, PV_PART_COUNTER_ATT_NAME, PV_SEQ_ATT_NAME, PV_SEQ_NUM_RESET_TIME_MINUTES, REF_URL_ATT_NAME, REQUIRED_DATA_PROCESSING_URLS, RESPONSE_ERROR_TYPE, SCRIPT_ID_ATT_NAME, SCRIPT_INSTANCE_ID_ATT_NAME, SEQ_NUM_ATT_NAME, SEVERITY, SOCKET_INSTANCE_ID_ATT_NAME, SOURCE_ATT_NAME, STARTED_AT_ATT_NAME, STOP_STORING_PV_SOCKET_MESSAGE, STOP_STORING_VID_SOCKET_MESSAGE, TAGNAME_ATT_NAME, TEXT_ATT_NAME, TYPE_ATT_NAME, URL_ATT_NAME, USERSTEP_EVENT_TYPE, VER_ATT_NAME, VIDEO_CLICKS_ATT_NAME, VIDEO_METROPLEX_TYPE, VIDEO_PART_COUNT_ATT_NAME, WHITELIST_HTML_ID_TEXT_REGEX, WORK_REQUEST_ATT_NAME, WRAPPED_EXCEPTION_ERROR_TYPE, XML_HTTP_REQUEST_ERROR_TYPE };
428
+ export { APP_NAVIGATION_EVENT_TYPE, BLOCKED_HTTP_HEADER_KEYS, BLOCK_SOCKET_MESSAGE, BROWSER_ID_ATT_NAME, CLICK_EVENT_TYPE, CLIENT_LOCK_TIME_MINUTES, CLOSE_CONNECTION_FORCEFULLY, COLLECT_VER_ATT_NAME, CONN_COUNT_ATT_NAME, CONSOLE_FUNCTION_OVERRIDES, CONTENT_LENGTH, CONTENT_TYPE, CSS_CLASS_ATT_NAME, CURRENT_METRICS_VERSION, CURRENT_NOIBUJS_VERSION, CURRENT_PV_VERSION, CUSTOM_ERROR_EVENT_TYPE, CUSTOM_ID_NAME_TYPE, CUSTOM_ID_VALUE_TYPE, DEFAULT_STACK_FRAME_FIELD_VALUE, DEFAULT_WEBSITE_SUBDOMAIN_PATTERN, DID_CUT_PV_ATT_NAME, DID_CUT_VID_ATT_NAME, DID_START_VID_ATT_NAME, END_AT_ATT_NAME, ERROR_EVENT_ERROR_TYPE, ERROR_EVENT_TYPE, ERROR_EVENT_UNHANDLED_REJECTION_TYPE, ERROR_LOG_EVENT_ERROR_TYPE, ERROR_SOURCE_ATT_NAME, ERR_COUNT_EXPECTED_ATT_NAME, EVENT_ERROR_TYPE, EXP_VIDEO_LENGTH_ATT_NAME, FETCH_EXCEPTION_ERROR_TYPE, GET_MAX_METROPLEX_RECONNECTION_NUMBER, GET_METROPLEX_BASE_HTTP_URL, GET_METROPLEX_BASE_SOCKET_URL, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, GET_METROPLEX_METRICS_URL, GET_METROPLEX_POST_URL, GET_SCRIPT_ID, GQL_ERROR_ATT_NAME, GQL_ERROR_TYPE, GQL_EVENT_TYPE, HELP_CODE_ATT_NAME, HTMLID_ATT_NAME, HTTP_BODY_DROPPED_LENGTH_MSG, HTTP_BODY_DROPPED_TYPE_MSG, HTTP_BODY_NULL_STRING, HTTP_CODE_ATT_NAME, HTTP_COUNT_EXPECTED_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, HTTP_DATA_PAYLOAD_ATT_NAME, HTTP_DATA_REQ_HEADERS_ATT_NAME, HTTP_DATA_RESP_HEADERS_ATT_NAME, HTTP_DATA_RESP_PAYLOAD_ATT_NAME, HTTP_EVENT_TYPE, HTTP_METHOD_ATT_NAME, HTTP_PII_BLOCKING_PATTERNS, HTTP_RESP_CODE_ATT_NAME, HTTP_RESP_LENGTH_ATT_NAME, HTTP_RESP_TIME_ATT_NAME, HUMAN_READABLE_CONTENT_TYPE_REGEX, IS_LAST_ATT_NAME, IS_NJS_VERSION_BETA, JS_ENV, JS_ERROR_ATT_NAME, JS_EVENT_TYPE, JS_STACK_FILE_ATT_NAME, JS_STACK_FRAMES_ATT_NAME, JS_STACK_MESSAGE_ATT_NAME, JS_STACK_METHOD_ATT_NAME, KEYBOARD_EVENT_TYPE, MAX_BEACON_PAYLOAD_SIZE, MAX_COLLECT_ERROR_LOG, MAX_CUSTOM_ERRORS_PER_PAGEVISIT, MAX_CUSTOM_IDS_PER_PAGEVISIT, MAX_FRAMES_IN_ARRAY, MAX_HTTP_DATA_EVENT_COUNT, MAX_HTTP_DATA_PAYLOAD_LENGTH, MAX_METROPLEX_CONNECTION_COUNT, MAX_METROPLEX_SOCKET_INNACTIVE_TIME, MAX_PAGEVISIT_EVENTS, MAX_PAGEVISIT_PARTS, MAX_PAGEVISIT_VISITED, MAX_STRING_LENGTH, MAX_TIME_FOR_UNSENT_DATA_MILLIS, META_DATA_METROPLEX_TYPE, METROPLEX_ERROR_ROUTE, METROPLEX_FRAG_ROUTE, METROPLEX_FULL_PV_ROUTE, METROPLEX_METRICS_ROUTE, METROPLEX_RETRY_FREQUENCY, METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME, NOIBUJS_SDK_ADD_ERROR_FROM_JS_FMW_FUNCTION, NOIBUJS_SDK_ADD_ERROR_FUNCTION, NOIBUJS_SDK_ADD_ID_FUNCTION, NOIBUJS_SDK_REQUEST_HELP_CODE, NOIBU_BROWSER_ID_KYWRD, NOIBU_INPUT_URLS, NOIBU_LOCAL_STORAGE_TEST_KEY, NOIBU_STORED_PAGE_VISIT, OCCURRED_AT_ATT_NAME, OK_SOCKET_MESSAGE, ON_URL_ATT_NAME, PAGE_EVENTS_DOCUMENT, PAGE_EVENTS_WINDOW, PAGE_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME, PAGE_VISIT_INFORMATION_ATT_NAME, PAGE_VISIT_META_DATA_ATT_NAME, PAGE_VISIT_PART_ATT_NAME, PAGE_VISIT_VID_FRAG_ATT_NAME, PII_DIGIT_PATTERN, PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING, PV_CLICKS_ATT_NAME, PV_EVENTS_ATT_NAME, PV_EXP_HTTP_DATA_SEQ_ATT_NAME, PV_EXP_PART_COUNTER_ATT_NAME, PV_EXP_VF_SEQ_ATT_NAME, PV_HTTP_PAYLOADS_COLLECTED_ATT_NAME, PV_HTTP_PAYLOADS_DROPPED_OVERSIZE_ATT_NAME, PV_HTTP_PAYLOADS_DROPPED_TYPE_ATT_NAME, PV_HTTP_REQUESTS_DROPPED_OVER_LIMIT, PV_ID_ATT_NAME, PV_METROPLEX_TYPE, PV_PART_COUNTER_ATT_NAME, PV_SEQ_ATT_NAME, PV_SEQ_NUM_RESET_TIME_MINUTES, REF_URL_ATT_NAME, REQUIRED_DATA_PROCESSING_URLS, RESPONSE_ERROR_TYPE, SCRIPT_ID_ATT_NAME, SCRIPT_INSTANCE_ID_ATT_NAME, SEQ_NUM_ATT_NAME, SEVERITY, SOCKET_INSTANCE_ID_ATT_NAME, SOURCE_ATT_NAME, STACK_TRACE_SANITIZE_REGEXP, STARTED_AT_ATT_NAME, STOP_STORING_PV_SOCKET_MESSAGE, STOP_STORING_VID_SOCKET_MESSAGE, TAGNAME_ATT_NAME, TEXT_ATT_NAME, TYPE_ATT_NAME, URL_ATT_NAME, USERSTEP_EVENT_TYPE, VER_ATT_NAME, VIDEO_CLICKS_ATT_NAME, VIDEO_METROPLEX_TYPE, VIDEO_PART_COUNT_ATT_NAME, WHITELIST_HTML_ID_TEXT_REGEX, WORK_REQUEST_ATT_NAME, WRAPPED_EXCEPTION_ERROR_TYPE, XML_HTTP_REQUEST_ERROR_TYPE };
@@ -12,7 +12,7 @@ import { PageVisit } from '../pageVisit/pageVisit.js';
12
12
  import MetroplexSocket from '../api/metroplexSocket.js';
13
13
  import StoredPageVisit from '../api/storedPageVisit.js';
14
14
  import HelpCode from '../api/helpCode.js';
15
- import { AppNavigationMonitor } from '../monitors/AppNavigationMonitor.js';
15
+ import { AppNavigationMonitor } from '../monitors/appNavigationMonitor.js';
16
16
 
17
17
  /** @module Init */
18
18
  // these are set via rollup
@@ -33,12 +33,12 @@ function globalInit(customerConfig) {
33
33
  return;
34
34
  }
35
35
  const noibuErrorURL = `${urlConfig.metroplexHTTPBase}/${METROPLEX_ERROR_ROUTE}`;
36
- ClientConfig.configureInstance({
37
- noibuErrorURL,
38
- customerConfig,
39
- });
40
36
  // catch any errors that happened during initialization and send collect error
41
37
  try {
38
+ ClientConfig.configureInstance({
39
+ noibuErrorURL,
40
+ customerConfig,
41
+ });
42
42
  // create an instance ID for this script
43
43
  const instanceId = uuid.v4();
44
44
  // verifying if collect was disabled by other microservices
@@ -3,19 +3,18 @@
3
3
  */
4
4
  export declare class AppNavigationMonitor {
5
5
  private static instance;
6
- private breadcrumbs;
7
6
  /**
8
7
  * guesses which navigation is used in app, and registers a listener if found
9
8
  */
10
9
  constructor();
11
10
  /**
12
- * Gets the singleton instance
11
+ * handler for updating navigation breadcrumbs and notifying metro of location change
13
12
  */
14
- static getInstance(): AppNavigationMonitor;
13
+ onNavigation(breadcrumbs: string[]): void;
15
14
  /**
16
- * gets current global url
15
+ * Gets the singleton instance
17
16
  */
18
- get globalUrl(): string;
17
+ static getInstance(): AppNavigationMonitor;
19
18
  /**
20
19
  * Called when the event needs to be emitted
21
20
  */
@@ -1,5 +1,4 @@
1
- import { SEVERITY, URL_ATT_NAME, LOCATION_EVENT_TYPE } from '../constants.js';
2
- import { getMaxSubstringAllowed } from '../utils/function.js';
1
+ import { SEVERITY, APP_NAVIGATION_EVENT_TYPE } from '../constants.js';
3
2
  import { InputMonitor } from './inputMonitor.js';
4
3
  import { ReactNativeNavigationIntegration } from './integrations/react-native-navigation-integration.js';
5
4
  import ClientConfig from '../api/clientConfig.js';
@@ -9,25 +8,29 @@ import ClientConfig from '../api/clientConfig.js';
9
8
  */
10
9
  class AppNavigationMonitor {
11
10
  static instance;
12
- breadcrumbs = [];
13
11
  /**
14
12
  * guesses which navigation is used in app, and registers a listener if found
15
13
  */
16
14
  constructor() {
15
+ this.onNavigation = this.onNavigation.bind(this);
17
16
  try {
18
17
  // eslint-disable-next-line global-require,@typescript-eslint/no-var-requires,import/no-extraneous-dependencies
19
18
  const rnNavigation = require('react-native-navigation')?.Navigation;
20
19
  if (rnNavigation) {
21
- new ReactNativeNavigationIntegration().register(rnNavigation, breadcrumbs => {
22
- this.breadcrumbs = breadcrumbs;
23
- this.reportLocationChange();
24
- });
20
+ new ReactNativeNavigationIntegration().register(rnNavigation, this.onNavigation);
25
21
  }
26
22
  }
27
23
  catch (e) {
28
24
  ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`AppNavigationMonitor: ${e}`, false, SEVERITY.error);
29
25
  }
30
26
  }
27
+ /**
28
+ * handler for updating navigation breadcrumbs and notifying metro of location change
29
+ */
30
+ onNavigation(breadcrumbs) {
31
+ ClientConfig.getInstance().currentLocationBreadcrumbs = breadcrumbs;
32
+ this.reportLocationChange(breadcrumbs.pop());
33
+ }
31
34
  /**
32
35
  * Gets the singleton instance
33
36
  */
@@ -37,26 +40,12 @@ class AppNavigationMonitor {
37
40
  }
38
41
  return AppNavigationMonitor.instance;
39
42
  }
40
- /**
41
- * gets current global url
42
- */
43
- get globalUrl() {
44
- const globalUrl = new URL('https://localhost');
45
- globalUrl.hostname = ClientConfig.getInstance().customerDomain;
46
- if (this.breadcrumbs.length) {
47
- globalUrl.pathname = this.breadcrumbs.join('/');
48
- }
49
- return getMaxSubstringAllowed(globalUrl.toString());
50
- }
51
43
  /**
52
44
  * Called when the event needs to be emitted
53
45
  */
54
- reportLocationChange() {
55
- const payload = {
56
- [URL_ATT_NAME]: this.globalUrl,
57
- };
58
- // storing the location change in the page visit queue
59
- InputMonitor.getInstance().addEvent(payload, LOCATION_EVENT_TYPE);
46
+ reportLocationChange(currentLocation) {
47
+ const payload = { location: currentLocation || '' };
48
+ InputMonitor.getInstance().addEvent(payload, APP_NAVIGATION_EVENT_TYPE);
60
49
  }
61
50
  }
62
51
 
@@ -4,8 +4,9 @@ import { PageVisit } from '../pageVisit/pageVisit.js';
4
4
  import { updatePayload } from '../pageVisit/userStep/userStep.js';
5
5
  import StoredMetrics from '../api/storedMetrics.js';
6
6
  import { WHITELIST_TEXT_REGEX_STRING } from '../const_matchers.js';
7
- import { maskTextInput, getBlockedElements } from '../utils/function.js';
7
+ import { maskTextInput } from '../utils/function.js';
8
8
  import { timestampWrapper } from '../utils/date.js';
9
+ import ClientConfig from '../api/clientConfig.js';
9
10
 
10
11
  /** @module ClickMonitor */
11
12
 
@@ -190,7 +191,9 @@ class ClickMonitor {
190
191
 
191
192
  counter.value += 1;
192
193
 
193
- if (getBlockedElements().includes(element.memoizedProps.testID)) {
194
+ if (
195
+ ClickMonitor.getBlockedElements().includes(element.memoizedProps.testID)
196
+ ) {
194
197
  return `${text}${text ? ' ' : ''}*`;
195
198
  }
196
199
 
@@ -210,6 +213,18 @@ class ClickMonitor {
210
213
  return text;
211
214
  }
212
215
 
216
+ /**
217
+ * Gets selectors to prevent those elements from being recorded
218
+ */
219
+ static getBlockedElements() {
220
+ const selectors = ClientConfig.getInstance().blockedElements;
221
+ const blockedElements = ['noibu-blocked'];
222
+ if (selectors && Array.isArray(selectors)) {
223
+ blockedElements.push(...selectors);
224
+ }
225
+ return blockedElements;
226
+ }
227
+
213
228
  /**
214
229
  * normalize value and append to the resulting text if not empty
215
230
  * @param {String} text
@@ -15,9 +15,12 @@ export declare function wrap(functionToWrap: WrappedFunction): Function;
15
15
  export declare function processErrorLogArguments(argsFromErrorLog: any[]): void;
16
16
  /**
17
17
  * handler for promise rejection failures
18
- * @param {} event
18
+ * @param error
19
19
  */
20
- export declare function onPromiseRejectionHandler(event: PromiseRejectedResult): void;
20
+ export declare function onPromiseRejectionHandler(error: {
21
+ message: string;
22
+ stack?: string;
23
+ }): void;
21
24
  /**
22
25
  * Monitors the errors happening on the window
23
26
  */
@@ -1,6 +1,6 @@
1
1
  import { replace } from '../utils/object.js';
2
- import { isStackTrace } from '../utils/function.js';
3
- import { ERROR_EVENT_ERROR_TYPE, CONSOLE_FUNCTION_OVERRIDES, ERROR_LOG_EVENT_ERROR_TYPE } from '../constants.js';
2
+ import { asString, isStackTrace } from '../utils/function.js';
3
+ import { CONSOLE_FUNCTION_OVERRIDES, ERROR_EVENT_ERROR_TYPE, STACK_TRACE_SANITIZE_REGEXP, ERROR_EVENT_UNHANDLED_REJECTION_TYPE, ERROR_LOG_EVENT_ERROR_TYPE } from '../constants.js';
4
4
  import { saveErrorToPagevisit } from '../pageVisit/pageVisitEventError/pageVisitEventError.js';
5
5
 
6
6
  /* eslint-disable @typescript-eslint/ban-types,prefer-arrow-callback */
@@ -117,11 +117,49 @@ function configureEventListeners() {
117
117
  });
118
118
  }
119
119
  /**
120
- * Monitors the errors happening on the window
120
+ * handler for promise rejection failures
121
+ * @param error
121
122
  */
122
- function monitorErrors() {
123
- // wrapping all listeners as soon as possible after we set the above listener
124
- configureEventListeners();
123
+ function onPromiseRejectionHandler(error) {
124
+ if (!error || !error.message || !error.stack) {
125
+ return;
126
+ }
127
+ const sanitizedStack = error.stack
128
+ .split('\n')
129
+ .filter(line => !line.match(STACK_TRACE_SANITIZE_REGEXP))
130
+ .join('\n');
131
+ const payload = {
132
+ error: {
133
+ message: error.message,
134
+ stack: sanitizedStack,
135
+ },
136
+ };
137
+ saveErrorToPagevisit(ERROR_EVENT_UNHANDLED_REJECTION_TYPE, payload);
138
+ }
139
+ function configureHermesHooks() {
140
+ if (global.HermesInternal) {
141
+ global.HermesInternal.enablePromiseRejectionTracker?.({
142
+ allRejections: true,
143
+ });
144
+ /**
145
+ * This internal promise implementation method is populated only after enabling the promise tracker.
146
+ * It represents an improvement over the previous approach,
147
+ * which would lose stack context regarding the rejection because it ran asynchronously through setTimeout.
148
+ *
149
+ * This updated method ensures synchronous error capturing and retrieves the correct stack frames.
150
+ */
151
+ replace(Promise, '_m', (originalFunction) => function nbuGlobalPromiseRejectWrapper(promise, error) {
152
+ if (error.message && error.stack) {
153
+ onPromiseRejectionHandler(error);
154
+ }
155
+ else {
156
+ onPromiseRejectionHandler(new Error(asString(error)));
157
+ }
158
+ return originalFunction?.(promise, error);
159
+ });
160
+ }
161
+ }
162
+ function configureErrorUtilsHandler() {
125
163
  const existingHandler = global.ErrorUtils.getGlobalHandler() || (() => { });
126
164
  global.ErrorUtils.setGlobalHandler((error, ...rest) => {
127
165
  saveErrorToPagevisit(ERROR_EVENT_ERROR_TYPE, {
@@ -129,16 +167,14 @@ function monitorErrors() {
129
167
  });
130
168
  return existingHandler(error, ...rest);
131
169
  });
132
- if (global.HermesInternal) {
133
- global.HermesInternal.enablePromiseRejectionTracker({
134
- allRejections: true,
135
- onUnhandled: (id, rejection = {}) => {
136
- saveErrorToPagevisit(ERROR_EVENT_ERROR_TYPE, {
137
- error: rejection,
138
- });
139
- },
140
- });
141
- }
170
+ }
171
+ /**
172
+ * Monitors the errors happening on the window
173
+ */
174
+ function monitorErrors() {
175
+ configureEventListeners();
176
+ configureHermesHooks();
177
+ configureErrorUtilsHandler();
142
178
  }
143
179
 
144
- export { monitorErrors, processErrorLogArguments };
180
+ export { monitorErrors, onPromiseRejectionHandler, processErrorLogArguments };
@@ -2,8 +2,7 @@ import { HUMAN_READABLE_CONTENT_TYPE_REGEX, DEFAULT_WEBSITE_SUBDOMAIN_PATTERN, S
2
2
  import ClientConfig from '../api/clientConfig.js';
3
3
  import StoredMetrics from '../api/storedMetrics.js';
4
4
  import { safeFromEntries, iterateObjectRecursively } from '../utils/object.js';
5
- import { checkHttpDataCollectionEnabled, getHttpPayloadAllowedURLs } from '../utils/function.js';
6
- import { AppNavigationMonitor } from './AppNavigationMonitor.js';
5
+ import 'react-native-device-info';
7
6
 
8
7
  /** @module HTTPDataBundler */
9
8
 
@@ -20,7 +19,7 @@ class HTTPDataBundler {
20
19
  );
21
20
 
22
21
  // pull out the domain hostname
23
- const initialURL = AppNavigationMonitor.getInstance().globalUrl;
22
+ const initialURL = ClientConfig.getInstance().globalUrl;
24
23
  this.initialURLPartsReversed = [];
25
24
  if (initialURL && initialURL.length > 0) {
26
25
  try {
@@ -45,10 +44,11 @@ class HTTPDataBundler {
45
44
  }
46
45
  }
47
46
 
48
- this.httpDataCollectionEnabled = checkHttpDataCollectionEnabled();
47
+ this.httpDataCollectionEnabled =
48
+ !!ClientConfig.getInstance().enableHttpDataCollection;
49
49
 
50
50
  // compile the relative and full HTTP URL regexes
51
- const allowedURLs = getHttpPayloadAllowedURLs();
51
+ const allowedURLs = HTTPDataBundler.getHttpPayloadAllowedURLs();
52
52
  this.httpDataAllowedAbsoluteRegex = HTTPDataBundler.buildAllowedRegex(
53
53
  allowedURLs,
54
54
  'absolute',
@@ -86,6 +86,19 @@ class HTTPDataBundler {
86
86
  ];
87
87
  }
88
88
 
89
+ /** gets http data payload allowed URLs */
90
+ static getHttpPayloadAllowedURLs() {
91
+ const noibuConfig = ClientConfig.getInstance();
92
+ // return the allowed list or an empty list
93
+ if (
94
+ noibuConfig.listOfUrlsToCollectHttpDataFrom &&
95
+ Array.isArray(noibuConfig.listOfUrlsToCollectHttpDataFrom)
96
+ ) {
97
+ return noibuConfig.listOfUrlsToCollectHttpDataFrom;
98
+ }
99
+ return [];
100
+ }
101
+
89
102
  /**
90
103
  * gets the singleton instance
91
104
  * @returns {HTTPDataBundler}
@@ -1,5 +1,5 @@
1
1
  import { PageVisit } from '../pageVisit/pageVisit.js';
2
- import { LOCATION_EVENT_TYPE, PAGE_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS, ERROR_EVENT_TYPE, HTTP_EVENT_TYPE, KEYBOARD_EVENT_TYPE, USERSTEP_EVENT_TYPE } from '../constants.js';
2
+ import { APP_NAVIGATION_EVENT_TYPE, PAGE_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS, ERROR_EVENT_TYPE, HTTP_EVENT_TYPE, KEYBOARD_EVENT_TYPE, USERSTEP_EVENT_TYPE } from '../constants.js';
3
3
  import { timestampWrapper } from '../utils/date.js';
4
4
  import { addSafeEventListener } from '../utils/eventlistener.js';
5
5
 
@@ -29,7 +29,7 @@ class InputMonitor {
29
29
  // setting up debouncers for all events
30
30
  // we send clicks directly to the socket so they aren't registered here
31
31
  // we dont wait to send location changes, they happen at most once per second.
32
- this.registerInputType(LOCATION_EVENT_TYPE, 0);
32
+ this.registerInputType(APP_NAVIGATION_EVENT_TYPE, 0);
33
33
  this.registerInputType(PAGE_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
34
34
  this.registerInputType(ERROR_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
35
35
  this.registerInputType(HTTP_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
@@ -11,7 +11,8 @@ export declare class ReactNativeNavigationIntegration implements NavigationInteg
11
11
  */
12
12
  register(navigation: NavigationDelegate, onNavigation: (breadcrumbs: string[]) => void): void;
13
13
  /**
14
- * Listens to ComponentWillAppear events
14
+ * Listens to ComponentWillAppear events, keeps track of visited screens and
15
+ * pops them if the same page is visited to prevent cycles
15
16
  * @param onNavigation
16
17
  * @private
17
18
  */
@@ -13,7 +13,8 @@ class ReactNativeNavigationIntegration {
13
13
  .registerComponentWillAppearListener(this.getListener(onNavigation));
14
14
  }
15
15
  /**
16
- * Listens to ComponentWillAppear events
16
+ * Listens to ComponentWillAppear events, keeps track of visited screens and
17
+ * pops them if the same page is visited to prevent cycles
17
18
  * @param onNavigation
18
19
  * @private
19
20
  */
@@ -30,7 +31,8 @@ class ReactNativeNavigationIntegration {
30
31
  });
31
32
  this.stack.length = this.stackPointers[event.componentName];
32
33
  }
33
- onNavigation(this.stack.slice(1)); // slice 1 to skip root component
34
+ // shallow copy is returned
35
+ onNavigation(this.stack.slice());
34
36
  };
35
37
  }
36
38
  }
@@ -4,11 +4,9 @@ import { PageVisitEventHTTP, isHttpCodeFailure } from '../pageVisit/pageVisitEve
4
4
  import { propWriteableOrMadeWriteable, replace } from '../utils/object.js';
5
5
  import 'react-native-device-info';
6
6
  import { PV_SEQ_ATT_NAME, XML_HTTP_REQUEST_ERROR_TYPE, GQL_ERROR_TYPE, SEVERITY, RESPONSE_ERROR_TYPE, HTTP_METHOD_ATT_NAME, HTTP_RESP_CODE_ATT_NAME, URL_ATT_NAME, HTTP_RESP_TIME_ATT_NAME, HTTP_RESP_LENGTH_ATT_NAME, FETCH_EXCEPTION_ERROR_TYPE } from '../constants.js';
7
- import ClientConfig from '../api/clientConfig.js';
8
- import 'react-native-uuid';
9
7
  import { addSafeEventListener } from '../utils/eventlistener.js';
10
- import '@react-native-async-storage/async-storage';
11
8
  import { HTTPDataBundler } from './httpDataBundler.js';
9
+ import ClientConfig from '../api/clientConfig.js';
12
10
  import GqlErrorValidator from './gqlErrorValidator.js';
13
11
 
14
12
  /** @module RequestMonitor */
@@ -1,10 +1,9 @@
1
- import { isValidURL, getOnURL, getJSStack, stringifyJSON, getMaxSubstringAllowed } from '../../utils/function.js';
1
+ import { isValidURL, asString, getMaxSubstringAllowed, getJSStack, stringifyJSON } 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, 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
3
  import blacklisedDomains from './blacklistedDomains.js';
4
4
  import ClientConfig from '../../api/clientConfig.js';
5
5
  import { InputMonitor } from '../../monitors/inputMonitor.js';
6
6
  import StoredMetrics from '../../api/storedMetrics.js';
7
- import { AppNavigationMonitor } from '../../monitors/AppNavigationMonitor.js';
8
7
 
9
8
  /** @module PageVisitEventError */
10
9
 
@@ -26,6 +25,17 @@ function getPVErrorFromResponse(errPayload, httpDataSeqNum) {
26
25
  return errorObject;
27
26
  }
28
27
 
28
+ /**
29
+ * gets the onURL of a string, defaulting to the location of the webpage
30
+ */
31
+ function getOnURL(realOnURL) {
32
+ if (realOnURL && realOnURL.trim() !== '' && realOnURL !== 'undefined') {
33
+ return asString(getMaxSubstringAllowed(realOnURL));
34
+ }
35
+
36
+ return ClientConfig.getInstance().globalUrl;
37
+ }
38
+
29
39
  /**
30
40
  * getPVErrorFromGqlError will create the error information from an
31
41
  * GraphQL error payload.
@@ -71,7 +81,7 @@ function getPVErrorFromXMLHttpRequest(errPayload, httpDataSeqNum) {
71
81
  function getPVErrorFromErrorEvent(errPayload) {
72
82
  return {
73
83
  [URL_ATT_NAME]: getOnURL(
74
- errPayload.filename || AppNavigationMonitor.getInstance().globalUrl,
84
+ errPayload.filename || ClientConfig.getInstance().globalUrl,
75
85
  ),
76
86
  [TYPE_ATT_NAME]: JS_EVENT_TYPE,
77
87
  [JS_ERROR_ATT_NAME]: getJSStack(errPayload.error),
@@ -83,7 +93,7 @@ function getPVErrorFromErrorEvent(errPayload) {
83
93
  */
84
94
  function getPVErrorFromErrorLog(errPayload) {
85
95
  return {
86
- [URL_ATT_NAME]: getOnURL(AppNavigationMonitor.getInstance().globalUrl),
96
+ [URL_ATT_NAME]: getOnURL(ClientConfig.getInstance().globalUrl),
87
97
  [TYPE_ATT_NAME]: JS_EVENT_TYPE,
88
98
  [JS_ERROR_ATT_NAME]: getJSStack(errPayload),
89
99
  };
@@ -200,7 +210,7 @@ function createPageVisitEventError(type, errPayload, httpDataSeqNum) {
200
210
  * determines if an error is a collect error
201
211
  * @param {} pvError
202
212
  */
203
- function isCollectError(pvError) {
213
+ function isErrorCollectedByNoibu(pvError) {
204
214
  // checking if this error originated from the Noibu script
205
215
  if (pvError[TYPE_ATT_NAME] === JS_EVENT_TYPE) {
206
216
  if (pvError[JS_ERROR_ATT_NAME]) {
@@ -212,6 +222,7 @@ function isCollectError(pvError) {
212
222
 
213
223
  if (
214
224
  lowerCapFile.includes('noibu') &&
225
+ !lowerCapFile.includes('examplenoibureactnativeapp') &&
215
226
  !lowerCapMethod.includes('nbuwrapper')
216
227
  ) {
217
228
  // if the first file in the stack is Noibu then we do not
@@ -297,7 +308,7 @@ function saveErrorToPagevisit(type, payload, httpDataSeqNum) {
297
308
  }
298
309
  }
299
310
 
300
- if (isCollectError(pvError)) {
311
+ if (isErrorCollectedByNoibu(pvError)) {
301
312
  return;
302
313
  }
303
314
 
@@ -307,4 +318,4 @@ function saveErrorToPagevisit(type, payload, httpDataSeqNum) {
307
318
  InputMonitor.getInstance().addEvent(pvError, ERROR_EVENT_TYPE);
308
319
  }
309
320
 
310
- export { isCollectError, saveErrorToPagevisit };
321
+ export { getOnURL, isErrorCollectedByNoibu, saveErrorToPagevisit };
@@ -79,7 +79,7 @@ class ErrorBoundary extends React.Component {
79
79
  element = fallback({
80
80
  error,
81
81
  componentStack,
82
- resetError: this.resetErrorBoundary,
82
+ resetError: this.resetErrorBoundary.bind(this),
83
83
  eventId,
84
84
  });
85
85
  }
@@ -17,14 +17,23 @@ declare global {
17
17
  type Window = {
18
18
  noibuJSLoaded?: boolean;
19
19
  };
20
+ type PromiseRejectionTrackerOptions = Partial<{
21
+ allRejections: boolean;
22
+ whitelist: (typeof Error)[];
23
+ onUnhandled: (id: number, reason: any) => void;
24
+ onHandled: (id: number, reason: any) => void;
25
+ }>;
20
26
  type global = {
21
27
  window: Window;
22
28
  console: Console;
23
29
  noibuJSLoaded?: boolean;
24
30
  ErrorUtils: any;
25
31
  HermesInternal: null | {
26
- enablePromiseRejectionTracker?: any;
27
- setPromiseRejectionTrackingHook?: any;
32
+ enablePromiseRejectionTracker?: (options: PromiseRejectionTrackerOptions) => void;
33
+ setPromiseRejectionTrackingHook?: (enable: (options: PromiseRejectionTrackerOptions) => void) => void;
34
+ };
35
+ Promise: {
36
+ _m: () => void;
28
37
  };
29
38
  };
30
39
  const global: global;
@@ -43,14 +43,6 @@ export declare function stringifyJSON(jsonObject: unknown): string;
43
43
  * @param sendAndForget
44
44
  */
45
45
  export declare function makeRequest(method: string, url: string, data: unknown, headers: Record<string, string>, timeout: number, sendAndForget: boolean): Promise<unknown>;
46
- /** checks if http data collection is enabled */
47
- export declare function checkHttpDataCollectionEnabled(): boolean;
48
- /** gets http data payload allowed URLs */
49
- export declare function getHttpPayloadAllowedURLs(): string[];
50
- /**
51
- * Gets selectors to prevent those elements from being recorded
52
- */
53
- export declare function getBlockedElements(): string[];
54
46
  /**
55
47
  * makes sure the url sent is a valid URL
56
48
  */
@@ -79,10 +71,6 @@ export declare function asString(obj: unknown): string;
79
71
  * masks textual content if it ressembles something sensitive
80
72
  */
81
73
  export declare function maskTextInput(text: string): string;
82
- /**
83
- * gets the onURL of a string, defaulting to the location of the webpage
84
- */
85
- export declare function getOnURL(realOnURL: string): string;
86
74
  /**
87
75
  * Checks if the provided object is an instance of the specified type.
88
76
  * It's safer than `instanceof` operator as it handles cases
@@ -1,8 +1,6 @@
1
1
  import DeviceInfo from 'react-native-device-info';
2
2
  import { parseStack } from './stacktrace-parser.js';
3
3
  import { MAX_STRING_LENGTH, MAX_BEACON_PAYLOAD_SIZE, REQUIRED_DATA_PROCESSING_URLS, PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING, PII_DIGIT_PATTERN, DEFAULT_STACK_FRAME_FIELD_VALUE } from '../constants.js';
4
- import ClientConfig from '../api/clientConfig.js';
5
- import { AppNavigationMonitor } from '../monitors/AppNavigationMonitor.js';
6
4
 
7
5
  /** @module Functions */
8
6
  /**
@@ -43,6 +41,10 @@ function processFrames(rawFrames) {
43
41
  }
44
42
  if (frame.file && frame.file !== '<unknown>') {
45
43
  processedFrame.file = String(frame.file);
44
+ if (isValidURL(processedFrame.file)) {
45
+ // todo remove comment if assumption that react native only has local urls in filenames is correct
46
+ processedFrame.file = new URL(processedFrame.file).pathname;
47
+ }
46
48
  }
47
49
  if (frame.column) {
48
50
  if (Number.isInteger(frame.column)) {
@@ -66,7 +68,7 @@ function getJSStack(errObj) {
66
68
  ];
67
69
  // if the errObj type is not an object or null
68
70
  // return a default frame
69
- if (typeof errObj !== 'object' || !errObj) {
71
+ if (typeof errObj !== 'object' || !errObj || !errObj.stack) {
70
72
  return {
71
73
  frames,
72
74
  msg: '',
@@ -171,33 +173,6 @@ async function makeRequest(method, url, data, headers, timeout, sendAndForget) {
171
173
  }
172
174
  });
173
175
  }
174
- /** checks if http data collection is enabled */
175
- function checkHttpDataCollectionEnabled() {
176
- const noibuConfig = ClientConfig.getInstance();
177
- // Just a boolean, so safe to return truthiness. If undefined, will return false.
178
- return !!noibuConfig.enableHttpDataCollection;
179
- }
180
- /** gets http data payload allowed URLs */
181
- function getHttpPayloadAllowedURLs() {
182
- const noibuConfig = ClientConfig.getInstance();
183
- // return the allowed list or an empty list
184
- if (noibuConfig.listOfUrlsToCollectHttpDataFrom &&
185
- Array.isArray(noibuConfig.listOfUrlsToCollectHttpDataFrom)) {
186
- return noibuConfig.listOfUrlsToCollectHttpDataFrom;
187
- }
188
- return [];
189
- }
190
- /**
191
- * Gets selectors to prevent those elements from being recorded
192
- */
193
- function getBlockedElements() {
194
- const selectors = ClientConfig.getInstance().blockedElements;
195
- const blockedElements = ['noibu-blocked'];
196
- if (selectors && Array.isArray(selectors)) {
197
- blockedElements.push(...selectors);
198
- }
199
- return blockedElements;
200
- }
201
176
  /**
202
177
  * makes sure the url sent is a valid URL
203
178
  */
@@ -276,15 +251,6 @@ function maskTextInput(text) {
276
251
  .replace(PII_EMAIL_PATTERN, PII_REDACTION_REPLACEMENT_STRING)
277
252
  .replace(PII_DIGIT_PATTERN, '*');
278
253
  }
279
- /**
280
- * gets the onURL of a string, defaulting to the location of the webpage
281
- */
282
- function getOnURL(realOnURL) {
283
- if (realOnURL && realOnURL.trim() !== '' && realOnURL !== 'undefined') {
284
- return asString(getMaxSubstringAllowed(realOnURL));
285
- }
286
- return AppNavigationMonitor.getInstance().globalUrl;
287
- }
288
254
  /**
289
255
  * Checks if the provided object is an instance of the specified type.
290
256
  * It's safer than `instanceof` operator as it handles cases
@@ -299,4 +265,4 @@ function isInstanceOf(instance, type) {
299
265
  }
300
266
  }
301
267
 
302
- export { asString, checkHttpDataCollectionEnabled, getBlockedElements, getHttpPayloadAllowedURLs, getJSStack, getMaxSubstringAllowed, getOnURL, getUserAgent, isInstanceOf, isInvalidURLConfig, isNoibuJSAlreadyLoaded, isStackTrace, isValidURL, makeRequest, maskTextInput, processFrames, stringifyJSON };
268
+ export { asString, getJSStack, getMaxSubstringAllowed, getUserAgent, isInstanceOf, isInvalidURLConfig, isNoibuJSAlreadyLoaded, isStackTrace, isValidURL, makeRequest, maskTextInput, processFrames, stringifyJSON };
@@ -88,50 +88,6 @@ const chrome = (line) => {
88
88
  }
89
89
  return undefined;
90
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
91
  function parseStack(stackString) {
136
92
  const lines = stackString.split('\n');
137
93
  if (lines.length > MAX_FRAMES_IN_ARRAY) {
@@ -141,11 +97,7 @@ function parseStack(stackString) {
141
97
  if (line.length > MAX_STRING_LENGTH) {
142
98
  return stack;
143
99
  }
144
- const parseResult = gecko(line) ||
145
- chrome(line) ||
146
- winjs(line) ||
147
- opera11(line) ||
148
- opera10(line);
100
+ const parseResult = chrome(line);
149
101
  if (parseResult) {
150
102
  stack.push(parseResult);
151
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noibu-react-native",
3
- "version": "0.0.8",
3
+ "version": "0.1.1",
4
4
  "targetNjsVersion": "1.0.104",
5
5
  "description": "React-Native SDK for NoibuJS to collect errors in React-Native applications",
6
6
  "main": "dist/entry/index.js",
@@ -25,9 +25,9 @@
25
25
  "react-native-navigation": "7"
26
26
  },
27
27
  "dependencies": {
28
+ "@react-native-async-storage/async-storage": "^1.19.0",
28
29
  "react": ">=16.11.0",
29
30
  "react-native": ">=0.63.0",
30
- "@react-native-async-storage/async-storage": "^1.19.0",
31
31
  "react-native-device-info": "^10.6.0",
32
32
  "react-native-url-polyfill": "^1.3.0",
33
33
  "react-native-uuid": "^2.0.1"