noibu-react-native 0.2.3 → 0.2.5

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