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.
- package/dist/api/clientConfig.js +20 -27
- package/dist/api/helpCode.js +61 -87
- package/dist/api/metroplexSocket.js +72 -65
- package/dist/api/storedPageVisit.js +150 -208
- package/dist/constants.js +3 -7
- package/dist/entry/init.js +13 -15
- package/dist/monitors/{appNavigationMonitor.js → AppNavigationMonitor.js} +10 -19
- package/dist/monitors/BaseMonitor.js +23 -0
- package/dist/monitors/ClickMonitor.js +198 -0
- package/dist/monitors/ErrorMonitor.js +206 -0
- package/dist/monitors/KeyboardInputMonitor.js +60 -0
- package/dist/monitors/PageMonitor.js +98 -0
- package/dist/monitors/RequestMonitor.js +390 -0
- package/dist/monitors/http-tools/GqlErrorValidator.js +259 -0
- package/dist/monitors/{httpDataBundler.js → http-tools/HTTPDataBundler.js} +23 -102
- package/dist/pageVisit/{eventDebouncer.js → EventDebouncer.js} +36 -47
- package/dist/pageVisit/pageVisitEventError.js +3 -3
- package/dist/pageVisit/pageVisitEventHTTP.js +5 -4
- package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +22 -5
- package/dist/sessionRecorder/sessionRecorder.js +5 -2
- package/dist/src/api/clientConfig.d.ts +8 -13
- package/dist/src/api/clientConfig.test.d.ts +1 -0
- package/dist/src/api/helpCode.d.ts +10 -16
- package/dist/src/api/metroplexSocket.d.ts +52 -71
- package/dist/src/api/storedPageVisit.d.ts +12 -21
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/monitors/AppNavigationMonitor.d.ts +18 -0
- package/dist/src/monitors/BaseMonitor.d.ts +13 -0
- package/dist/src/monitors/BaseMonitor.test.d.ts +1 -0
- package/dist/src/monitors/ClickMonitor.d.ts +31 -0
- package/dist/src/monitors/ErrorMonitor.d.ts +63 -0
- package/dist/src/monitors/{keyboardInputMonitor.d.ts → KeyboardInputMonitor.d.ts} +7 -4
- package/dist/src/monitors/{pageMonitor.d.ts → PageMonitor.d.ts} +6 -8
- package/dist/src/monitors/RequestMonitor.d.ts +94 -0
- package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +59 -0
- package/dist/src/monitors/{httpDataBundler.d.ts → http-tools/HTTPDataBundler.d.ts} +13 -28
- package/dist/src/monitors/integrations/react-native-navigation-integration.d.ts +3 -2
- package/dist/src/pageVisit/{eventDebouncer.d.ts → EventDebouncer.d.ts} +3 -10
- package/dist/src/pageVisit/pageVisit.d.ts +1 -1
- package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +3 -3
- package/dist/src/sessionRecorder/nativeSessionRecorderSubscription.d.ts +15 -0
- package/dist/src/storage/rnStorageProvider.d.ts +1 -1
- package/dist/src/storage/storage.d.ts +1 -1
- package/dist/src/storage/storageProvider.d.ts +2 -2
- package/dist/src/utils/function.d.ts +4 -5
- package/dist/src/utils/object.d.ts +3 -5
- package/dist/src/utils/polyfills.d.ts +1 -4
- package/dist/types/Metroplex.types.d.ts +73 -0
- package/dist/types/PageVisit.types.d.ts +2 -145
- package/dist/types/PageVisitErrors.types.d.ts +114 -0
- package/dist/types/PageVisitEvents.types.d.ts +91 -0
- package/dist/types/Storage.d.ts +1 -1
- package/dist/types/StoredPageVisit.types.d.ts +4 -45
- package/dist/utils/function.js +0 -1
- package/dist/utils/object.js +1 -0
- package/package.json +11 -7
- package/dist/monitors/clickMonitor.js +0 -258
- package/dist/monitors/errorMonitor.js +0 -202
- package/dist/monitors/gqlErrorValidator.js +0 -306
- package/dist/monitors/inputMonitor.js +0 -138
- package/dist/monitors/keyboardInputMonitor.js +0 -66
- package/dist/monitors/pageMonitor.js +0 -122
- package/dist/monitors/requestMonitor.js +0 -386
- package/dist/src/monitors/appNavigationMonitor.d.ts +0 -22
- package/dist/src/monitors/clickMonitor.d.ts +0 -44
- package/dist/src/monitors/errorMonitor.d.ts +0 -28
- package/dist/src/monitors/gqlErrorValidator.d.ts +0 -82
- package/dist/src/monitors/inputMonitor.d.ts +0 -34
- package/dist/src/monitors/requestMonitor.d.ts +0 -10
- package/dist/types/RRWeb.d.ts +0 -48
- package/dist/types/ReactNative.d.ts +0 -4
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import { CONTENT_TYPE, SEVERITY } from '../constants.js';
|
|
2
|
-
import { isInstanceOf, getMaxSubstringAllowed } from '../utils/function.js';
|
|
3
|
-
import ClientConfig from '../api/clientConfig.js';
|
|
4
|
-
|
|
5
|
-
const MESSAGE_ATT_NAME = 'message';
|
|
6
|
-
const EXTENSIONS_ATT_NAME = 'extensions';
|
|
7
|
-
const LOCATIONS_ATT_NAME = 'locations';
|
|
8
|
-
const PATH_ATT_NAME = 'path';
|
|
9
|
-
const LINE_ATT_NAME = 'line';
|
|
10
|
-
const COLUMN_ATT_NAME = 'column';
|
|
11
|
-
const MESSAGE_MAX_LENGTH = 1000;
|
|
12
|
-
|
|
13
|
-
/* eslint-disable no-restricted-syntax */
|
|
14
|
-
/* eslint-disable no-param-reassign */
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Try detecting GraphQL errors from http response
|
|
18
|
-
*/
|
|
19
|
-
class GqlErrorValidator {
|
|
20
|
-
/**
|
|
21
|
-
* Retrieves GQL error object based on fetch request/response
|
|
22
|
-
* @param {String} url
|
|
23
|
-
* @param {{}} [options]
|
|
24
|
-
* @param {Request} [request]
|
|
25
|
-
* @param {Response} [response] - cloned() from original response
|
|
26
|
-
*/
|
|
27
|
-
static async fromFetch(url, options, request, response) {
|
|
28
|
-
try {
|
|
29
|
-
const isResponseValid = isInstanceOf(response, Response) && response.ok;
|
|
30
|
-
if (!isResponseValid) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const contentType = this._getContentTypeFromFetchArguments(
|
|
34
|
-
options,
|
|
35
|
-
request,
|
|
36
|
-
);
|
|
37
|
-
if (this._shouldHandleRequest(url, contentType)) {
|
|
38
|
-
const data = await response.json();
|
|
39
|
-
return this._validate(data, []);
|
|
40
|
-
}
|
|
41
|
-
} catch (e) {
|
|
42
|
-
// can't read the response if request is aborted
|
|
43
|
-
// so just ignore it
|
|
44
|
-
if (!this._isRequestAborted(options, request)) {
|
|
45
|
-
this._postError(e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Retrieves GQL error object based on XHR object
|
|
54
|
-
* @param {String} url
|
|
55
|
-
* @param {XMLHttpRequest} xhr
|
|
56
|
-
*/
|
|
57
|
-
static async fromXhr(url, xhr) {
|
|
58
|
-
try {
|
|
59
|
-
const isResponseValid =
|
|
60
|
-
isInstanceOf(xhr, XMLHttpRequest) &&
|
|
61
|
-
xhr.status >= 200 &&
|
|
62
|
-
xhr.status <= 299;
|
|
63
|
-
if (!isResponseValid) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
let contentType = null;
|
|
67
|
-
if (xhr.noibuRequestHeaders) {
|
|
68
|
-
contentType = xhr.noibuRequestHeaders.get(CONTENT_TYPE);
|
|
69
|
-
}
|
|
70
|
-
if (this._shouldHandleRequest(url, contentType)) {
|
|
71
|
-
let data = null;
|
|
72
|
-
if (xhr.responseType === 'blob') {
|
|
73
|
-
if (xhr.response.text) {
|
|
74
|
-
const content = await xhr.response.text();
|
|
75
|
-
data = this._parseJsonSafely(content);
|
|
76
|
-
}
|
|
77
|
-
} else if (xhr.responseType === 'json') {
|
|
78
|
-
data = xhr.response;
|
|
79
|
-
} else {
|
|
80
|
-
const content = xhr.responseText;
|
|
81
|
-
data = this._parseJsonSafely(content);
|
|
82
|
-
}
|
|
83
|
-
if (data) {
|
|
84
|
-
return this._validate(data, []);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} catch (e) {
|
|
88
|
-
this._postError(e);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Try safely parse a string and return null if fails
|
|
96
|
-
* @param {String} content
|
|
97
|
-
*/
|
|
98
|
-
static _parseJsonSafely(content) {
|
|
99
|
-
try {
|
|
100
|
-
return JSON.parse(content);
|
|
101
|
-
} catch (e) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Try to get content type for fetch arguments
|
|
108
|
-
* @param {{}} options
|
|
109
|
-
* @param {Request} [request]
|
|
110
|
-
*/
|
|
111
|
-
static _getContentTypeFromFetchArguments(options, request) {
|
|
112
|
-
let headers = null;
|
|
113
|
-
if (isInstanceOf(request, Request)) {
|
|
114
|
-
headers = request.headers;
|
|
115
|
-
} else if (options && options.headers) {
|
|
116
|
-
headers = new Headers(options.headers);
|
|
117
|
-
}
|
|
118
|
-
let contentType = null;
|
|
119
|
-
if (headers) {
|
|
120
|
-
contentType = headers.get(CONTENT_TYPE);
|
|
121
|
-
}
|
|
122
|
-
return contentType;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Checks if request is aborted
|
|
127
|
-
* If it has been aborder we are not able to consume the response
|
|
128
|
-
* @param {{}} options
|
|
129
|
-
* @param {Request} request
|
|
130
|
-
*/
|
|
131
|
-
static _isRequestAborted(options, request) {
|
|
132
|
-
if (isInstanceOf(request, Request)) {
|
|
133
|
-
return request.signal && request.signal.aborted;
|
|
134
|
-
}
|
|
135
|
-
if (options && isInstanceOf(options.signal, AbortSignal)) {
|
|
136
|
-
return options.signal.aborted;
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Determines if request should be processed
|
|
143
|
-
* @param {String|URL} url
|
|
144
|
-
* @param {String} contentType
|
|
145
|
-
*/
|
|
146
|
-
static _shouldHandleRequest(url, contentType) {
|
|
147
|
-
if (contentType) {
|
|
148
|
-
contentType = contentType.toLowerCase();
|
|
149
|
-
}
|
|
150
|
-
let isGqlUrl = false;
|
|
151
|
-
if (url) {
|
|
152
|
-
if (isInstanceOf(url, URL)) {
|
|
153
|
-
url = url.toString();
|
|
154
|
-
}
|
|
155
|
-
isGqlUrl = url.toLowerCase().includes('graphql');
|
|
156
|
-
}
|
|
157
|
-
return (
|
|
158
|
-
(contentType === 'application/json' && isGqlUrl) ||
|
|
159
|
-
contentType === 'application/graphql'
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Sanitizes payload object
|
|
165
|
-
* @param {any} data
|
|
166
|
-
* @param {Array<String>} validationIssues
|
|
167
|
-
*/
|
|
168
|
-
static _validate(data, validationIssues) {
|
|
169
|
-
let errors = null;
|
|
170
|
-
|
|
171
|
-
if (data && Array.isArray(data.errors)) {
|
|
172
|
-
errors = data.errors;
|
|
173
|
-
|
|
174
|
-
for (const error of errors) {
|
|
175
|
-
const properties = Object.keys(error);
|
|
176
|
-
for (const property of properties) {
|
|
177
|
-
switch (property) {
|
|
178
|
-
case MESSAGE_ATT_NAME:
|
|
179
|
-
this._validateMessage(error);
|
|
180
|
-
break;
|
|
181
|
-
case LOCATIONS_ATT_NAME:
|
|
182
|
-
this._validateLocations(error, validationIssues);
|
|
183
|
-
break;
|
|
184
|
-
case PATH_ATT_NAME:
|
|
185
|
-
this._validatePath(error, validationIssues);
|
|
186
|
-
break;
|
|
187
|
-
case EXTENSIONS_ATT_NAME:
|
|
188
|
-
this._validateExtensions(error);
|
|
189
|
-
break;
|
|
190
|
-
default:
|
|
191
|
-
delete error[property];
|
|
192
|
-
validationIssues.push(`unexpected error.${property}`);
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (validationIssues.length > 0) {
|
|
199
|
-
this._postValidationIssues(validationIssues);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return errors;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Sanitizes message object
|
|
208
|
-
* @param {any} error
|
|
209
|
-
*/
|
|
210
|
-
static _validateMessage(error) {
|
|
211
|
-
error[MESSAGE_ATT_NAME] = getMaxSubstringAllowed(
|
|
212
|
-
error[MESSAGE_ATT_NAME],
|
|
213
|
-
MESSAGE_MAX_LENGTH,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Sanitizes extensions object
|
|
219
|
-
* @param {any} error
|
|
220
|
-
* @param {Array<String>} validationIssues
|
|
221
|
-
*/
|
|
222
|
-
static _validateExtensions(error) {
|
|
223
|
-
const json = JSON.stringify(error[EXTENSIONS_ATT_NAME]);
|
|
224
|
-
error[EXTENSIONS_ATT_NAME] = getMaxSubstringAllowed(
|
|
225
|
-
json,
|
|
226
|
-
MESSAGE_MAX_LENGTH,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Sanitizes locations object
|
|
232
|
-
* @param {any} error
|
|
233
|
-
* @param {Array<String>} validationIssues
|
|
234
|
-
*/
|
|
235
|
-
static _validateLocations(error, validationIssues) {
|
|
236
|
-
const locations = error[LOCATIONS_ATT_NAME];
|
|
237
|
-
if (Array.isArray(locations)) {
|
|
238
|
-
for (const location of locations) {
|
|
239
|
-
const properties = Object.keys(location);
|
|
240
|
-
for (const property of properties) {
|
|
241
|
-
switch (property) {
|
|
242
|
-
case LINE_ATT_NAME:
|
|
243
|
-
case COLUMN_ATT_NAME:
|
|
244
|
-
if (!Number.isSafeInteger(location[property])) {
|
|
245
|
-
const value = location[property];
|
|
246
|
-
location[property] = 0;
|
|
247
|
-
validationIssues.push(
|
|
248
|
-
`unexpected ${property} value '${value}'`,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
break;
|
|
252
|
-
default:
|
|
253
|
-
delete location[property];
|
|
254
|
-
validationIssues.push(`unexpected error.location.${property}`);
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
delete error[LOCATIONS_ATT_NAME];
|
|
261
|
-
validationIssues.push(`unexpected error.locations`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Sanitizes path object
|
|
267
|
-
* @param {any} error
|
|
268
|
-
* @param {Array<String>} validationIssues
|
|
269
|
-
*/
|
|
270
|
-
static _validatePath(error, validationIssues) {
|
|
271
|
-
const path = error[PATH_ATT_NAME];
|
|
272
|
-
if (Array.isArray(path)) {
|
|
273
|
-
error[PATH_ATT_NAME] = error[PATH_ATT_NAME].map(x => x.toString());
|
|
274
|
-
} else {
|
|
275
|
-
delete error[PATH_ATT_NAME];
|
|
276
|
-
validationIssues.push(`unexpected error.path`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Posts error
|
|
282
|
-
* @param {String} message
|
|
283
|
-
*/
|
|
284
|
-
static _postError(message) {
|
|
285
|
-
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
286
|
-
`GQL parse error: ${message}`,
|
|
287
|
-
false,
|
|
288
|
-
SEVERITY.error,
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Posts issue found during object sanitization
|
|
294
|
-
* @param {Array<String>} validationIssues
|
|
295
|
-
*/
|
|
296
|
-
static _postValidationIssues(validationIssues) {
|
|
297
|
-
const message = validationIssues.join(',');
|
|
298
|
-
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
299
|
-
`GQL error validation warning: ${message}`,
|
|
300
|
-
false,
|
|
301
|
-
SEVERITY.error,
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export { GqlErrorValidator as default };
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { PageVisit } from '../pageVisit/pageVisit.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
|
-
import { timestampWrapper } from '../utils/date.js';
|
|
4
|
-
import { addSafeEventListener } from '../utils/eventlistener.js';
|
|
5
|
-
import { noibuLog } from '../utils/log.js';
|
|
6
|
-
|
|
7
|
-
/** @module InputMonitor */
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Singleton class to manage the client configuration
|
|
11
|
-
* this class will be responsible for debouncing all events
|
|
12
|
-
* that are registered
|
|
13
|
-
*/
|
|
14
|
-
class InputMonitor {
|
|
15
|
-
/**
|
|
16
|
-
* Creates an instance of InputMonitor
|
|
17
|
-
*/
|
|
18
|
-
constructor() {
|
|
19
|
-
// events are stored in queues that are debounced seperatly
|
|
20
|
-
// each object in this map is
|
|
21
|
-
// type: {
|
|
22
|
-
// timeout: timeout,
|
|
23
|
-
// events: [...],
|
|
24
|
-
// debouncePeriod: period at which we debounce events,
|
|
25
|
-
// eventName: name of the event, could be different than type
|
|
26
|
-
// (clicks and keyboards are usersteps)
|
|
27
|
-
// }
|
|
28
|
-
this.eventsToDebounce = {};
|
|
29
|
-
|
|
30
|
-
// setting up debouncers for all events
|
|
31
|
-
// we send clicks directly to the socket so they aren't registered here
|
|
32
|
-
// we dont wait to send location changes, they happen at most once per second.
|
|
33
|
-
this.registerInputType(APP_NAVIGATION_EVENT_TYPE, 0);
|
|
34
|
-
this.registerInputType(PAGE_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
|
|
35
|
-
this.registerInputType(ERROR_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
|
|
36
|
-
this.registerInputType(HTTP_EVENT_TYPE, MAX_TIME_FOR_UNSENT_DATA_MILLIS);
|
|
37
|
-
this.registerInputType(
|
|
38
|
-
KEYBOARD_EVENT_TYPE,
|
|
39
|
-
MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
40
|
-
USERSTEP_EVENT_TYPE,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
this._setupUnloadHandler();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* gets the instance of InputMonitor
|
|
48
|
-
* @returns {InputMonitor}
|
|
49
|
-
*/
|
|
50
|
-
static getInstance() {
|
|
51
|
-
if (!this.instance) {
|
|
52
|
-
this.instance = new InputMonitor();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return this.instance;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** will debounce all events that are of this type by the debounce period
|
|
59
|
-
* @param {} type
|
|
60
|
-
* @param {} debouncePeriod
|
|
61
|
-
* @param {} eventName=type
|
|
62
|
-
*/
|
|
63
|
-
registerInputType(type, debouncePeriod, eventName = type) {
|
|
64
|
-
if (type in this.eventsToDebounce) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// registering this event type as a debouncable
|
|
69
|
-
this.eventsToDebounce[type] = {
|
|
70
|
-
timeout: null,
|
|
71
|
-
events: [],
|
|
72
|
-
debouncePeriod,
|
|
73
|
-
eventName,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Creates an event object with the event and the time it was added then pushes
|
|
79
|
-
* that event object to the queue of events waiting to be debounced.
|
|
80
|
-
* @param {} event
|
|
81
|
-
* @param {} type
|
|
82
|
-
*/
|
|
83
|
-
addEvent(event, type) {
|
|
84
|
-
noibuLog('addEvent', {
|
|
85
|
-
event,
|
|
86
|
-
type,
|
|
87
|
-
});
|
|
88
|
-
if (!(type in this.eventsToDebounce)) {
|
|
89
|
-
throw new Error(`Type: ${type} is not in eventsToDebounce`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
this.eventsToDebounce[type].events.push({
|
|
93
|
-
event,
|
|
94
|
-
occurredAt: new Date(timestampWrapper(Date.now())).toISOString(),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
this._debouncePvEvents(type);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Adds the events from the object to the page visit and sets up a timer
|
|
102
|
-
* to send the events if no more are received without the timeout
|
|
103
|
-
* @param {} type
|
|
104
|
-
*/
|
|
105
|
-
_debouncePvEvents(type) {
|
|
106
|
-
/**
|
|
107
|
-
* Debounce function to be executed once the debounce period is completed
|
|
108
|
-
*/
|
|
109
|
-
const later = () => {
|
|
110
|
-
this.eventsToDebounce[type].timeout = null;
|
|
111
|
-
PageVisit.getInstance().addPageVisitEvents(
|
|
112
|
-
this.eventsToDebounce[type].events,
|
|
113
|
-
this.eventsToDebounce[type].eventName,
|
|
114
|
-
);
|
|
115
|
-
this.eventsToDebounce[type].events = [];
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
clearTimeout(this.eventsToDebounce[type].timeout);
|
|
119
|
-
this.eventsToDebounce[type].timeout = setTimeout(
|
|
120
|
-
later,
|
|
121
|
-
this.eventsToDebounce[type].debouncePeriod,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Sets up the page hide handler to try to push remaining events in the queues */
|
|
126
|
-
_setupUnloadHandler() {
|
|
127
|
-
addSafeEventListener(window, 'pagehide', () => {
|
|
128
|
-
Object.values(this.eventsToDebounce).forEach(eventObject => {
|
|
129
|
-
PageVisit.getInstance().addPageVisitEvents(
|
|
130
|
-
eventObject.events,
|
|
131
|
-
eventObject.eventName,
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export { InputMonitor };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { TextInput } from 'react-native';
|
|
2
|
-
import { updatePayload } from '../pageVisit/userStep.js';
|
|
3
|
-
import { InputMonitor } from './inputMonitor.js';
|
|
4
|
-
import { SOURCE_ATT_NAME, TEXT_ATT_NAME, TAGNAME_ATT_NAME, TYPE_ATT_NAME, KEYBOARD_EVENT_TYPE } from '../constants.js';
|
|
5
|
-
|
|
6
|
-
/** @module KeyboardInputMonitor */
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* KeyboardInputMonitor is a listener class that attaches a
|
|
10
|
-
* keyboard input listener of the document object.
|
|
11
|
-
*/
|
|
12
|
-
class KeyboardInputMonitor {
|
|
13
|
-
/**
|
|
14
|
-
* Begins the monitoring process
|
|
15
|
-
* we currently only monitor two input locations: textarea and input
|
|
16
|
-
*/
|
|
17
|
-
monitor() {
|
|
18
|
-
const handler = this._handle.bind(this);
|
|
19
|
-
|
|
20
|
-
if (!TextInput.originalRender) {
|
|
21
|
-
TextInput.originalRender = TextInput.render;
|
|
22
|
-
TextInput.render = function (props, ...args) {
|
|
23
|
-
const onChange = event => {
|
|
24
|
-
const currentText = event.nativeEvent.text;
|
|
25
|
-
handler(event.target.viewConfig.uiViewClassName, props);
|
|
26
|
-
if (props.onChange) props.onChange(event);
|
|
27
|
-
if (props.onChangeText) props.onChangeText(currentText);
|
|
28
|
-
};
|
|
29
|
-
const newProps = {
|
|
30
|
-
...props,
|
|
31
|
-
onChange,
|
|
32
|
-
};
|
|
33
|
-
return TextInput.originalRender(newProps, ...args);
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validates an event
|
|
40
|
-
* @param uiViewClassName
|
|
41
|
-
* @param props
|
|
42
|
-
*/
|
|
43
|
-
_handle(uiViewClassName, props) {
|
|
44
|
-
if (!/(text|input)/i.test(uiViewClassName)) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const name = props.placeholder || props.testID;
|
|
49
|
-
|
|
50
|
-
if (!name) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
InputMonitor.getInstance().addEvent(
|
|
55
|
-
updatePayload({
|
|
56
|
-
[SOURCE_ATT_NAME]: '',
|
|
57
|
-
[TEXT_ATT_NAME]: name,
|
|
58
|
-
[TAGNAME_ATT_NAME]: uiViewClassName.toLowerCase(),
|
|
59
|
-
[TYPE_ATT_NAME]: KEYBOARD_EVENT_TYPE,
|
|
60
|
-
}),
|
|
61
|
-
KEYBOARD_EVENT_TYPE,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { KeyboardInputMonitor };
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { PAGE_EVENTS_WINDOW, PAGE_EVENTS_DOCUMENT, PAGE_EVENT_TYPE } from '../constants.js';
|
|
2
|
-
import { InputMonitor } from './inputMonitor.js';
|
|
3
|
-
import { addSafeEventListener } from '../utils/eventlistener.js';
|
|
4
|
-
|
|
5
|
-
/** @module PageMonitor */
|
|
6
|
-
|
|
7
|
-
/** Monitors the page events which we capture and later process */
|
|
8
|
-
class PageMonitor {
|
|
9
|
-
/**
|
|
10
|
-
* gets the singleton instance
|
|
11
|
-
* @returns {PageMonitor}
|
|
12
|
-
*/
|
|
13
|
-
static getInstance() {
|
|
14
|
-
if (!this.instance) {
|
|
15
|
-
this.instance = new PageMonitor();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return this.instance;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Starts monitoring page events on the document */
|
|
22
|
-
monitor() {
|
|
23
|
-
PAGE_EVENTS_WINDOW.forEach(pageEvent => {
|
|
24
|
-
addSafeEventListener(
|
|
25
|
-
window,
|
|
26
|
-
pageEvent,
|
|
27
|
-
this._onPageEventHandle.bind(this),
|
|
28
|
-
true,
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
if (global.document) {
|
|
32
|
-
PAGE_EVENTS_DOCUMENT.forEach(pageEvent => {
|
|
33
|
-
addSafeEventListener(
|
|
34
|
-
document,
|
|
35
|
-
pageEvent,
|
|
36
|
-
this._onPageEventHandle.bind(this),
|
|
37
|
-
true,
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Handles a single page event
|
|
45
|
-
* @param {} event
|
|
46
|
-
*/
|
|
47
|
-
_onPageEventHandle(event) {
|
|
48
|
-
if (!event || !event.type) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const payload = {
|
|
53
|
-
type: event.type,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Add data if applicable for the event type
|
|
57
|
-
switch (event.type) {
|
|
58
|
-
case 'visibilitychange':
|
|
59
|
-
payload.data = `state: ${this.getDocumentState()}`;
|
|
60
|
-
break;
|
|
61
|
-
case 'readystatechange':
|
|
62
|
-
payload.data = `state: ${document.readyState}`;
|
|
63
|
-
break;
|
|
64
|
-
case 'pagehide':
|
|
65
|
-
case 'pageshow':
|
|
66
|
-
case 'load':
|
|
67
|
-
if (event.persisted) {
|
|
68
|
-
payload.data = `persisted: ${event.persisted}`;
|
|
69
|
-
}
|
|
70
|
-
break;
|
|
71
|
-
case 'storage':
|
|
72
|
-
if (event.key) {
|
|
73
|
-
payload.data = `key: ${event.key}`;
|
|
74
|
-
}
|
|
75
|
-
break;
|
|
76
|
-
case 'message':
|
|
77
|
-
case 'messageerror':
|
|
78
|
-
if (event.data && event.origin) {
|
|
79
|
-
payload.data = `origin: ${event.origin} size: ${this.getSizeInBytes(
|
|
80
|
-
event.data,
|
|
81
|
-
)}`;
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
case 'hashchange':
|
|
85
|
-
if (event.newURL) {
|
|
86
|
-
payload.data = `newURL: ${event.newURL}`;
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
// do nothing
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// storing the page event in the page visit queue
|
|
93
|
-
InputMonitor.getInstance().addEvent(payload, PAGE_EVENT_TYPE);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Returns document state */
|
|
97
|
-
getDocumentState() {
|
|
98
|
-
if (document.visibilityState === 'hidden') {
|
|
99
|
-
return 'hidden';
|
|
100
|
-
}
|
|
101
|
-
if (document.hasFocus()) {
|
|
102
|
-
return 'active';
|
|
103
|
-
}
|
|
104
|
-
return 'passive';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Returns object size in bytes
|
|
109
|
-
* @param {} obj
|
|
110
|
-
*/
|
|
111
|
-
getSizeInBytes(obj) {
|
|
112
|
-
let str = obj;
|
|
113
|
-
if (typeof obj !== 'string') {
|
|
114
|
-
// Else, make obj into a string
|
|
115
|
-
str = JSON.stringify(obj);
|
|
116
|
-
}
|
|
117
|
-
// 2B per character in the string
|
|
118
|
-
return str.length * 2;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export { PageMonitor };
|