noibu-react-native 0.2.3 → 0.2.4

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.
Files changed (66) hide show
  1. package/dist/api/helpCode.js +61 -87
  2. package/dist/api/metroplexSocket.js +72 -65
  3. package/dist/api/storedPageVisit.js +150 -208
  4. package/dist/constants.js +3 -7
  5. package/dist/entry/init.js +11 -11
  6. package/dist/monitors/{appNavigationMonitor.js → AppNavigationMonitor.js} +10 -19
  7. package/dist/monitors/ClickMonitor.js +198 -0
  8. package/dist/monitors/ErrorMonitor.js +206 -0
  9. package/dist/monitors/KeyboardInputMonitor.js +60 -0
  10. package/dist/monitors/PageMonitor.js +98 -0
  11. package/dist/monitors/RequestMonitor.js +390 -0
  12. package/dist/monitors/http-tools/GqlErrorValidator.js +259 -0
  13. package/dist/monitors/{httpDataBundler.js → http-tools/HTTPDataBundler.js} +23 -102
  14. package/dist/pageVisit/{eventDebouncer.js → EventDebouncer.js} +36 -47
  15. package/dist/pageVisit/pageVisitEventError.js +3 -3
  16. package/dist/pageVisit/pageVisitEventHTTP.js +5 -4
  17. package/dist/sessionRecorder/sessionRecorder.js +1 -1
  18. package/dist/src/api/clientConfig.d.ts +1 -1
  19. package/dist/src/api/helpCode.d.ts +10 -16
  20. package/dist/src/api/metroplexSocket.d.ts +52 -71
  21. package/dist/src/api/storedPageVisit.d.ts +12 -21
  22. package/dist/src/constants.d.ts +1 -0
  23. package/dist/src/monitors/AppNavigationMonitor.d.ts +18 -0
  24. package/dist/src/monitors/ClickMonitor.d.ts +31 -0
  25. package/dist/src/monitors/ErrorMonitor.d.ts +63 -0
  26. package/dist/src/monitors/{keyboardInputMonitor.d.ts → KeyboardInputMonitor.d.ts} +7 -4
  27. package/dist/src/monitors/{pageMonitor.d.ts → PageMonitor.d.ts} +6 -8
  28. package/dist/src/monitors/RequestMonitor.d.ts +94 -0
  29. package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +59 -0
  30. package/dist/src/monitors/{httpDataBundler.d.ts → http-tools/HTTPDataBundler.d.ts} +13 -28
  31. package/dist/src/monitors/integrations/react-native-navigation-integration.d.ts +3 -2
  32. package/dist/src/pageVisit/{eventDebouncer.d.ts → EventDebouncer.d.ts} +3 -10
  33. package/dist/src/pageVisit/pageVisit.d.ts +1 -1
  34. package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +3 -3
  35. package/dist/src/storage/rnStorageProvider.d.ts +1 -1
  36. package/dist/src/storage/storage.d.ts +1 -1
  37. package/dist/src/storage/storageProvider.d.ts +2 -2
  38. package/dist/src/utils/function.d.ts +4 -5
  39. package/dist/src/utils/object.d.ts +3 -5
  40. package/dist/src/utils/polyfills.d.ts +1 -4
  41. package/dist/types/Metroplex.types.d.ts +73 -0
  42. package/dist/types/Monitor.d.ts +11 -0
  43. package/dist/types/Monitor.js +19 -0
  44. package/dist/types/PageVisit.types.d.ts +2 -145
  45. package/dist/types/PageVisitErrors.types.d.ts +114 -0
  46. package/dist/types/PageVisitEvents.types.d.ts +91 -0
  47. package/dist/types/Storage.d.ts +1 -1
  48. package/dist/types/StoredPageVisit.types.d.ts +4 -45
  49. package/dist/utils/function.js +0 -1
  50. package/dist/utils/object.js +1 -0
  51. package/package.json +4 -3
  52. package/dist/monitors/clickMonitor.js +0 -258
  53. package/dist/monitors/errorMonitor.js +0 -202
  54. package/dist/monitors/gqlErrorValidator.js +0 -306
  55. package/dist/monitors/inputMonitor.js +0 -138
  56. package/dist/monitors/keyboardInputMonitor.js +0 -66
  57. package/dist/monitors/pageMonitor.js +0 -122
  58. package/dist/monitors/requestMonitor.js +0 -386
  59. package/dist/src/monitors/appNavigationMonitor.d.ts +0 -22
  60. package/dist/src/monitors/clickMonitor.d.ts +0 -44
  61. package/dist/src/monitors/errorMonitor.d.ts +0 -28
  62. package/dist/src/monitors/gqlErrorValidator.d.ts +0 -82
  63. package/dist/src/monitors/inputMonitor.d.ts +0 -34
  64. package/dist/src/monitors/requestMonitor.d.ts +0 -10
  65. package/dist/types/RRWeb.d.ts +0 -48
  66. package/dist/types/ReactNative.d.ts +0 -4
@@ -1,258 +0,0 @@
1
- import Pressability from 'react-native/Libraries/Pressability/Pressability';
2
- import { WHITELIST_HTML_ID_TEXT_REGEX, USERSTEP_EVENT_TYPE, SOURCE_ATT_NAME, TEXT_ATT_NAME, TAGNAME_ATT_NAME, HTMLID_ATT_NAME, TYPE_ATT_NAME, CLICK_EVENT_TYPE, CSS_CLASS_ATT_NAME } from '../constants.js';
3
- import { PageVisit } from '../pageVisit/pageVisit.js';
4
- import { updatePayload } from '../pageVisit/userStep.js';
5
- import StoredMetrics from '../api/storedMetrics.js';
6
- import { WHITELIST_TEXT_REGEX_STRING } from '../const_matchers.js';
7
- import { maskTextInput } from '../utils/function.js';
8
- import { timestampWrapper } from '../utils/date.js';
9
- import ClientConfig from '../api/clientConfig.js';
10
-
11
- /** @module ClickMonitor */
12
-
13
-
14
- /** Monitors the clicks which we capture and later process */
15
- class ClickMonitor {
16
- /**
17
- * Creates an instance of the ClickMonitor instance
18
- */
19
- constructor() {
20
- // compile white list regex only once
21
- this.textCapturedWhiteListRegex = new RegExp(
22
- WHITELIST_TEXT_REGEX_STRING(),
23
- 'i',
24
- );
25
-
26
- this.htmlIDAllowListRegex = new RegExp(WHITELIST_HTML_ID_TEXT_REGEX, 'i');
27
- }
28
-
29
- /** gets the singleton instance
30
- * @returns {ClickMonitor}
31
- */
32
- static getInstance() {
33
- if (!this.instance) {
34
- this.instance = new ClickMonitor();
35
- }
36
-
37
- return this.instance;
38
- }
39
-
40
- /** Starts monitoring clicks on every Press-able component */
41
- monitorClicks() {
42
- const onClickHandler = this._onClickHandle.bind(this);
43
-
44
- if (!Pressability.prototype.originalCreateEventHandlers) {
45
- Pressability.prototype.originalCreateEventHandlers =
46
- Pressability.prototype.getEventHandlers;
47
-
48
- Pressability.prototype.getEventHandlers = function () {
49
- const ehs =
50
- Pressability.prototype.originalCreateEventHandlers.call(this);
51
-
52
- return Object.fromEntries(
53
- Object.entries(ehs).map(([key, handler]) => [
54
- key,
55
- (event, ...args) => {
56
- if (key === 'onResponderRelease') {
57
- onClickHandler(event);
58
- }
59
- return handler(event, ...args);
60
- },
61
- ]),
62
- );
63
- };
64
- }
65
- }
66
-
67
- /**
68
- * Handles a single click event
69
- * @param {{ _targetInst: RNNode }} event
70
- */
71
- _onClickHandle(event) {
72
- if (event) {
73
- const { _targetInst: target } = event;
74
- const targetClassName = target.elementType;
75
-
76
- let text = '';
77
- // if the tag name of the src element is image, then we need
78
- // to process the image name, else we need to get the textual content
79
- // todo process images
80
- text = this._getTextualContentFromEl(target);
81
-
82
- let textFromElement = this._trimText(text);
83
-
84
- let tagName = '';
85
- if (targetClassName) {
86
- tagName = targetClassName.toLowerCase();
87
- }
88
-
89
- // id of element
90
- let hid = target.memoizedProps.testID || '';
91
- // in some bizarre cases, the html id of an element gets overriden
92
- // to contain jquery objects. If the hid is an object, it's of no
93
- // use to us.
94
- if (typeof hid !== 'string') {
95
- hid = '';
96
- }
97
- // if we find that the text matches analytic data used
98
- // to find checkout starts, add to cart clicks, etc.
99
- // we do not mask it.
100
- if (
101
- !this.textCapturedWhiteListRegex.test(textFromElement) &&
102
- !this.htmlIDAllowListRegex.test(hid)
103
- ) {
104
- if (tagName === 'input') {
105
- if (
106
- event.type &&
107
- (event.type === 'button' || event.type === 'submit')
108
- ) ; else {
109
- textFromElement = '*';
110
- }
111
- } else if (tagName === 'textarea') {
112
- textFromElement = '*';
113
- }
114
- }
115
-
116
- textFromElement = maskTextInput(textFromElement);
117
- const tPayload = {
118
- [SOURCE_ATT_NAME]: '',
119
- [TEXT_ATT_NAME]: textFromElement,
120
- [TAGNAME_ATT_NAME]: tagName,
121
- [HTMLID_ATT_NAME]: hid,
122
- [TYPE_ATT_NAME]: CLICK_EVENT_TYPE,
123
- [CSS_CLASS_ATT_NAME]: '',
124
- };
125
-
126
- StoredMetrics.getInstance().addPvClick();
127
-
128
- PageVisit.getInstance().addPageVisitEvents(
129
- [
130
- {
131
- event: updatePayload(tPayload),
132
- occurredAt: new Date(timestampWrapper(Date.now())).toISOString(),
133
- },
134
- ],
135
- USERSTEP_EVENT_TYPE,
136
- );
137
- }
138
- }
139
-
140
- /** Gets the textual content from an element, if any
141
- * @param {} element
142
- */
143
- _getTextualContentFromEl(element) {
144
- return this._parseInnerContent(element, '', 100, { value: 0, limit: 100 });
145
- }
146
-
147
- /** Parse and trim text
148
- * @param {} text
149
- */
150
- _trimText(text) {
151
- // regex to replace all whitespace with a single space and trim text
152
- let parsedText = text.trim().replace(/\s+/g, ' ');
153
-
154
- // if there is text that is longer than average sentence length,
155
- // chances are that it is not a valid text so we need to further process it
156
- if (parsedText.length > 100) {
157
- const index = parsedText.lastIndexOf(' ', 97);
158
- // We can not chop the text at exactly 97 characters since chopping in the
159
- // middle of an encoded value may cause deserialization issues. Chop at the
160
- // last ' ' character before the 97th character.
161
- if (index > 0) {
162
- parsedText = `${parsedText.substring(0, index)}...`;
163
- } else {
164
- // If there are no ' ' characters then the text is likely not valid so just
165
- // return '...'.
166
- parsedText = '...';
167
- }
168
- }
169
- return parsedText;
170
- }
171
-
172
- /**
173
- * Recursively parses element's inner content and masks blocked css classes
174
- * @param {NReactNative.Node} element
175
- * @param {String} text
176
- * @param {Number} textLimit
177
- * @param {Object} counter
178
- */
179
- _parseInnerContent(element, text, textLimit, counter) {
180
- /* eslint-disable no-restricted-syntax */
181
- /* eslint-disable no-param-reassign */
182
-
183
- if (text.length >= textLimit) {
184
- return text;
185
- }
186
-
187
- if (counter.value >= counter.limit) {
188
- return text;
189
- }
190
-
191
- counter.value += 1;
192
-
193
- if (
194
- ClickMonitor.getBlockedElements().includes(element.memoizedProps.testID)
195
- ) {
196
- return `${text}${text ? ' ' : ''}*`;
197
- }
198
-
199
- // eslint-disable-next-line require-jsdoc
200
- const walk = node => {
201
- // Check if the node is a Text element
202
- if (
203
- node.elementType &&
204
- node.elementType.displayName === 'Text' &&
205
- node.memoizedProps &&
206
- typeof node.memoizedProps.children === 'string'
207
- ) {
208
- text = this._parseAndAppendText(text, [node.memoizedProps.children]);
209
- if (text.length >= textLimit) return;
210
- }
211
-
212
- // If the node has children, traverse them
213
- if (node.child) walk(node.child);
214
-
215
- // After traversing children, traverse siblings
216
- if (node.sibling) walk(node.sibling);
217
- };
218
- walk(element);
219
-
220
- return text;
221
- }
222
-
223
- /**
224
- * Gets selectors to prevent those elements from being recorded
225
- */
226
- static getBlockedElements() {
227
- const selectors = ClientConfig.getInstance().blockedElements;
228
- const blockedElements = ['noibu-blocked'];
229
- if (selectors && Array.isArray(selectors)) {
230
- blockedElements.push(...selectors);
231
- }
232
- return blockedElements;
233
- }
234
-
235
- /**
236
- * normalize value and append to the resulting text if not empty
237
- * @param {String} text
238
- * @param {Array.<any>} values
239
- */
240
- _parseAndAppendText(text, values) {
241
- const goodValues = [];
242
- for (const v of values) {
243
- if (Number.isFinite(v) || typeof v === 'string') {
244
- goodValues.push(v);
245
- }
246
- }
247
-
248
- for (let value of goodValues) {
249
- value = `${value}`.trim().replace(/\s+/g, ' ');
250
- if (value.length > 0) {
251
- return text + (text ? ' ' : '') + value;
252
- }
253
- }
254
- return text;
255
- }
256
- }
257
-
258
- export { ClickMonitor };
@@ -1,202 +0,0 @@
1
- import { asString, isStackTrace } from '../utils/function.js';
2
- 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';
3
- import { saveErrorToPagevisit } from '../pageVisit/pageVisitEventError.js';
4
- import { replace } from '../utils/object.js';
5
-
6
- /* eslint-disable @typescript-eslint/ban-types,prefer-arrow-callback */
7
- /** @module ErrorMonitor */
8
- let ignoreError = 0;
9
- /**
10
- * returns boolean that indicates wether we should
11
- * ignore the next error event caught by the error event
12
- * listener.
13
- */
14
- function shouldIgnoreError() {
15
- return ignoreError > 0;
16
- }
17
- /**
18
- * transform a log into an error since React hides component errors
19
- * tps://reactjs.org/docs/error-boundaries.html
20
- * @param {Error|any} errorLog
21
- * @returns {boolean} true on success
22
- */
23
- function processErrorLog(errorLog) {
24
- // handle empty arguments
25
- if (!errorLog) {
26
- return false;
27
- }
28
- const { message, stack } = errorLog;
29
- // for now, we only process error logs if they are a way to describe an error
30
- if (!stack || !message) {
31
- return false;
32
- }
33
- saveErrorToPagevisit(ERROR_LOG_EVENT_ERROR_TYPE, { message, stack });
34
- return true;
35
- }
36
- /**
37
- * Constructs error objects based on console args.
38
- * Relies on isStackTrace() to determine if arg is a stack trace, otherwise the arg must be a message.
39
- *
40
- * If there are multiple stack traces or the number of error messages is not equal to one,
41
- * it maps the stack traces to objects with their respective first lines as messages.
42
- * Otherwise, if there is exactly one stack trace and one error message,
43
- * it constructs an array with a single object containing the first stack trace and the first error message.
44
- * @param {Array<string>} args
45
- */
46
- function constructErrors(args) {
47
- if (args.length === 0) {
48
- return [];
49
- }
50
- if (args.length === 2) {
51
- if (isStackTrace(args[0])) {
52
- return [{ stack: args[0], message: args[1] }];
53
- }
54
- if (isStackTrace(args[1])) {
55
- return [{ stack: args[1], message: args[0] }];
56
- }
57
- return [];
58
- }
59
- const stacks = [];
60
- const messages = [];
61
- args.forEach(sm => {
62
- if (isStackTrace()) {
63
- stacks.push(sm);
64
- const lines = sm.split('\n');
65
- if (isStackTrace(lines[0])) {
66
- messages.push('_');
67
- }
68
- else {
69
- // we can use 1st line of stack trace as a message
70
- messages.push(lines[0]);
71
- }
72
- }
73
- });
74
- return stacks.map((stack, i) => ({ stack, message: messages[i] }));
75
- }
76
- /** iterates arguments to try to extract errors from them
77
- * @param {Array<string | Error>} argsFromErrorLog
78
- */
79
- function processErrorLogArguments(argsFromErrorLog) {
80
- // tracks if arguments were processed at least once successfully, and if not, tries to reconstruct an Error
81
- const collectedArgs = [];
82
- // we may receive multiple arguments
83
- argsFromErrorLog.forEach(arg => {
84
- // ensure arg exists before attempting processing
85
- if (!arg) {
86
- return;
87
- }
88
- // if we pass an array of elements in the console, we want to go through that
89
- // away and make sure we are extracting the error object. We don't do nested arrays.
90
- if (Array.isArray(arg)) {
91
- collectedArgs.push(...arg);
92
- return;
93
- }
94
- collectedArgs.push(arg);
95
- });
96
- const argsToBeReconstructedIntoErrors = collectedArgs.filter(arg => !processErrorLog(arg));
97
- // if previous process calls return nothing,
98
- // try to reconstruct error from the args
99
- constructErrors(argsToBeReconstructedIntoErrors).forEach(processErrorLog);
100
- }
101
- /**
102
- * wraps and replaces the addEventListener property
103
- * of event targets to try and catch errors
104
- * eventTargetString: event target name
105
- */
106
- function configureEventListeners() {
107
- // we wrap all logging to catch any potential errors passed in logs
108
- CONSOLE_FUNCTION_OVERRIDES.forEach(consoleFunction => {
109
- if (!window.console || !window.console[consoleFunction]) {
110
- return;
111
- }
112
- replace(window.console, consoleFunction, function (originalFunction) {
113
- // We set nbuWrapper to the handler function so if this returns an error
114
- // the trace message will start with nbuWrapper which would allow us to
115
- // detect that this is a wrapped function and not a NoibuJS error
116
- return function nbuWrapper() {
117
- // ignoring this linting error since we don't want
118
- // potentially overide the actual functioning of console.error
119
- // eslint-disable-next-line prefer-rest-params
120
- originalFunction.call(window.console, ...arguments);
121
- // converting the arguments to an array so that we do not nest argument objects
122
- // eslint-disable-next-line prefer-rest-params
123
- processErrorLogArguments(Array.from(arguments));
124
- };
125
- });
126
- });
127
- }
128
- /**
129
- * handler for promise rejection failures
130
- * @param error
131
- */
132
- function onPromiseRejectionHandler(error) {
133
- if (!error || !error.message || !error.stack) {
134
- return;
135
- }
136
- const sanitizedStack = error.stack
137
- .split('\n')
138
- .filter(line => !line.match(STACK_TRACE_SANITIZE_REGEXP))
139
- .join('\n');
140
- const payload = {
141
- error: {
142
- message: error.message,
143
- stack: sanitizedStack,
144
- },
145
- };
146
- saveErrorToPagevisit(ERROR_EVENT_UNHANDLED_REJECTION_TYPE, payload);
147
- }
148
- function configureHermesHooks() {
149
- var _a;
150
- if (typeof HermesInternal !== 'undefined' && HermesInternal !== null) {
151
- (_a = HermesInternal.enablePromiseRejectionTracker) === null || _a === void 0 ? void 0 : _a.call(HermesInternal, {
152
- allRejections: true,
153
- });
154
- /**
155
- * This internal promise implementation method is populated only after enabling the promise tracker.
156
- * It represents an improvement over the previous approach,
157
- * which would lose stack context regarding the rejection because it ran asynchronously through setTimeout.
158
- *
159
- * This updated method ensures synchronous error capturing and retrieves the correct stack frames.
160
- */
161
- replace(Promise, '_m', (originalFunction) => function nbuGlobalPromiseRejectWrapper(promise, error) {
162
- if (error.message && error.stack) {
163
- onPromiseRejectionHandler(error);
164
- }
165
- else {
166
- onPromiseRejectionHandler(new Error(asString(error)));
167
- }
168
- return originalFunction === null || originalFunction === void 0 ? void 0 : originalFunction(promise, error);
169
- });
170
- }
171
- }
172
- /**
173
- * Handles a single error event
174
- */
175
- function onErrorHandler(error) {
176
- if (!error || shouldIgnoreError()) {
177
- return;
178
- }
179
- saveErrorToPagevisit(ERROR_EVENT_ERROR_TYPE, {
180
- error,
181
- });
182
- }
183
- function configureErrorUtilsHandler() {
184
- if (typeof ErrorUtils === 'undefined') {
185
- return;
186
- }
187
- const existingHandler = ErrorUtils.getGlobalHandler() || (() => { });
188
- ErrorUtils.setGlobalHandler((error, ...rest) => {
189
- onErrorHandler(error);
190
- return existingHandler(error, ...rest);
191
- });
192
- }
193
- /**
194
- * Monitors the errors happening on the window
195
- */
196
- function monitorErrors() {
197
- configureEventListeners();
198
- configureHermesHooks();
199
- configureErrorUtilsHandler();
200
- }
201
-
202
- export { monitorErrors, onPromiseRejectionHandler, processErrorLogArguments };