noibu-react-native 0.2.5 → 0.2.7
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 +15 -15
- package/android/build.gradle +1 -1
- package/dist/api/{clientConfig.js → ClientConfig.js} +69 -52
- package/dist/api/{helpCode.js → HelpCode.js} +6 -13
- package/dist/api/InputManager.js +156 -0
- package/dist/api/{metroplexSocket.js → MetroplexSocket.js} +189 -178
- package/dist/api/StoredMetrics.js +158 -0
- package/dist/api/{storedPageVisit.js → StoredPageVisit.js} +61 -48
- package/dist/const_matchers.js +1 -5
- package/dist/constants.js +15 -390
- package/dist/entry/index.js +3 -4
- package/dist/entry/init.js +33 -19
- package/dist/monitors/AppNavigationMonitor.js +19 -19
- package/dist/monitors/BaseMonitor.js +9 -4
- package/dist/monitors/ClickMonitor.js +72 -76
- package/dist/monitors/ErrorMonitor.js +45 -55
- package/dist/monitors/KeyboardInputMonitor.js +13 -11
- package/dist/monitors/PageMonitor.js +25 -2
- package/dist/monitors/RequestMonitor.js +46 -57
- package/dist/monitors/http-tools/GqlErrorValidator.js +39 -69
- package/dist/monitors/http-tools/HTTPDataBundler.js +71 -66
- package/dist/monitors/integrations/{react-native-navigation-integration.js → ReactNativeNavigationIntegration.js} +15 -12
- package/dist/pageVisit/EventDebouncer.js +43 -74
- package/dist/pageVisit/HttpEventManager.js +88 -0
- package/dist/pageVisit/PageVisitManager.js +99 -0
- package/dist/pageVisit/pageVisitEventError.js +170 -280
- package/dist/react/ErrorBoundary.js +3 -6
- package/dist/sessionRecorder/{sessionRecorder.js → SessionRecorder.js} +58 -70
- package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +3 -5
- package/dist/storage/{rnStorageProvider.js → RNStorageProvider.js} +3 -7
- package/dist/storage/{storage.js → Storage.js} +17 -30
- package/dist/storage/{storageProvider.js → StorageProvider.js} +7 -8
- package/dist/utils/date.js +39 -50
- package/dist/utils/eventlistener.js +5 -12
- package/dist/utils/function.js +42 -113
- package/dist/utils/log.js +5 -5
- package/dist/utils/object.js +12 -12
- package/dist/utils/piiRedactor.js +31 -3
- package/dist/utils/stacktrace-parser.js +29 -21
- package/package.json +14 -14
- package/dist/api/inputManager.js +0 -227
- package/dist/api/storedMetrics.js +0 -198
- package/dist/pageVisit/pageVisit.js +0 -181
- package/dist/pageVisit/pageVisitEventHTTP.js +0 -98
- package/dist/pageVisit/userStep.js +0 -20
- package/dist/src/api/clientConfig.d.ts +0 -100
- package/dist/src/api/clientConfig.test.d.ts +0 -1
- package/dist/src/api/helpCode.d.ts +0 -23
- package/dist/src/api/inputManager.d.ts +0 -87
- package/dist/src/api/metroplexSocket.d.ts +0 -137
- package/dist/src/api/storedMetrics.d.ts +0 -73
- package/dist/src/api/storedPageVisit.d.ts +0 -40
- package/dist/src/const_matchers.d.ts +0 -1
- package/dist/src/constants.d.ts +0 -290
- package/dist/src/entry/index.d.ts +0 -14
- package/dist/src/entry/init.d.ts +0 -5
- package/dist/src/monitors/AppNavigationMonitor.d.ts +0 -18
- package/dist/src/monitors/BaseMonitor.d.ts +0 -13
- package/dist/src/monitors/BaseMonitor.test.d.ts +0 -1
- package/dist/src/monitors/ClickMonitor.d.ts +0 -31
- package/dist/src/monitors/ErrorMonitor.d.ts +0 -63
- package/dist/src/monitors/KeyboardInputMonitor.d.ts +0 -20
- package/dist/src/monitors/PageMonitor.d.ts +0 -20
- package/dist/src/monitors/RequestMonitor.d.ts +0 -94
- package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +0 -59
- package/dist/src/monitors/http-tools/HTTPDataBundler.d.ts +0 -112
- package/dist/src/monitors/integrations/react-native-navigation-integration.d.ts +0 -20
- package/dist/src/pageVisit/EventDebouncer.d.ts +0 -24
- package/dist/src/pageVisit/pageVisit.d.ts +0 -52
- package/dist/src/pageVisit/pageVisitEventError.d.ts +0 -15
- package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +0 -25
- package/dist/src/pageVisit/userStep.d.ts +0 -5
- package/dist/src/react/ErrorBoundary.d.ts +0 -72
- package/dist/src/sessionRecorder/nativeSessionRecorderSubscription.d.ts +0 -79
- package/dist/src/sessionRecorder/sessionRecorder.d.ts +0 -60
- package/dist/src/sessionRecorder/types.d.ts +0 -91
- package/dist/src/storage/rnStorageProvider.d.ts +0 -23
- package/dist/src/storage/storage.d.ts +0 -39
- package/dist/src/storage/storageProvider.d.ts +0 -26
- package/dist/src/utils/date.d.ts +0 -6
- package/dist/src/utils/eventlistener.d.ts +0 -8
- package/dist/src/utils/function.d.ts +0 -102
- package/dist/src/utils/log.d.ts +0 -4
- package/dist/src/utils/object.d.ts +0 -44
- package/dist/src/utils/performance.d.ts +0 -6
- package/dist/src/utils/piiRedactor.d.ts +0 -11
- package/dist/src/utils/polyfills.d.ts +0 -4
- package/dist/src/utils/stacktrace-parser.d.ts +0 -7
- package/dist/types/Config.d.ts +0 -31
- package/dist/types/Metroplex.types.d.ts +0 -73
- package/dist/types/NavigationIntegration.d.ts +0 -6
- package/dist/types/PageVisit.types.d.ts +0 -8
- package/dist/types/PageVisitErrors.types.d.ts +0 -114
- package/dist/types/PageVisitEvents.types.d.ts +0 -91
- package/dist/types/PageVisitMetrics.types.d.ts +0 -27
- package/dist/types/Storage.d.ts +0 -14
- package/dist/types/StoredPageVisit.types.d.ts +0 -11
- package/dist/types/WrappedObjects.d.ts +0 -6
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
|
-
import {
|
|
3
|
-
import ClientConfig from '../../api/
|
|
4
|
-
import StoredMetrics from '../../api/
|
|
2
|
+
import { SEVERITY, CONTENT_TYPE, PII_REDACTION_REPLACEMENT_STRING } from '../../constants.js';
|
|
3
|
+
import ClientConfig from '../../api/ClientConfig.js';
|
|
4
|
+
import StoredMetrics from '../../api/StoredMetrics.js';
|
|
5
5
|
import { safeFromEntries, safeEntries } from '../../utils/object.js';
|
|
6
|
-
import { safeTrim, stringifyJSON
|
|
6
|
+
import { safeTrim, stringifyJSON } from '../../utils/function.js';
|
|
7
7
|
import { removePII } from '../../utils/piiRedactor.js';
|
|
8
8
|
import { Singleton } from '../BaseMonitor.js';
|
|
9
9
|
|
|
10
|
+
const DEFAULT_WEBSITE_SUBDOMAIN_PATTERN = /^www\d{0,2}$/;
|
|
11
|
+
const CONTENT_LENGTH = 'content-length';
|
|
12
|
+
const HTTP_BODY_DROPPED_TYPE_MSG = 'Dropped due to unsupported type.';
|
|
13
|
+
const HTTP_BODY_DROPPED_LENGTH_MSG = 'Dropped due to length.';
|
|
14
|
+
const HTTP_BODY_NULL_STRING = 'null';
|
|
15
|
+
// the maximum size of http data payload that will be capture for success, otherwise it is dropped
|
|
16
|
+
// this is 64k
|
|
17
|
+
const MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH = 65536;
|
|
18
|
+
// the maximum size of http data payload that will be captured, otherwise it is dropped
|
|
19
|
+
const MAX_HTTP_DATA_PAYLOAD_LENGTH = 50000;
|
|
20
|
+
// regex of human readable content based on content-type header
|
|
21
|
+
const HUMAN_READABLE_CONTENT_TYPE_REGEX = 'text|json|xml|html|graphql|x-www-form-urlencoded|form-data';
|
|
22
|
+
// HTTP request/response header keys to be blocked - these must be lowercase.
|
|
23
|
+
const BLOCKED_HTTP_HEADER_KEYS = [
|
|
24
|
+
'authorization',
|
|
25
|
+
'from',
|
|
26
|
+
'proxy-authorization',
|
|
27
|
+
'content-md5',
|
|
28
|
+
'cookie',
|
|
29
|
+
'x-forwarded-for',
|
|
30
|
+
'x-real-ip',
|
|
31
|
+
'x-device-id',
|
|
32
|
+
'x-request-id',
|
|
33
|
+
'x-auth-token',
|
|
34
|
+
'x-user-id',
|
|
35
|
+
'x-forwarded-for',
|
|
36
|
+
'x-uidh',
|
|
37
|
+
'set-cookie',
|
|
38
|
+
'forwarded',
|
|
39
|
+
];
|
|
10
40
|
/** Bundles HTTP payloads and headers */
|
|
11
41
|
class HTTPDataBundler extends Singleton {
|
|
12
|
-
/**
|
|
13
|
-
* Creates an instance of the ClickMonitor instance
|
|
14
|
-
*/
|
|
42
|
+
/** Creates an instance of the ClickMonitor instance */
|
|
15
43
|
constructor() {
|
|
16
44
|
super();
|
|
17
45
|
// compile regex only once
|
|
@@ -32,11 +60,10 @@ class HTTPDataBundler extends Singleton {
|
|
|
32
60
|
this.initialURLPartsReversed.reverse();
|
|
33
61
|
}
|
|
34
62
|
catch (e) {
|
|
35
|
-
ClientConfig.getInstance().
|
|
63
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to determine hostname for initial URL`, error: e }, false, SEVERITY.warn);
|
|
36
64
|
}
|
|
37
65
|
}
|
|
38
|
-
this.httpDataCollectionEnabled =
|
|
39
|
-
!!ClientConfig.getInstance().enableHttpDataCollection;
|
|
66
|
+
this.httpDataCollectionEnabled = !!ClientConfig.getInstance().enableHttpDataCollection;
|
|
40
67
|
// compile the relative and full HTTP URL regexes
|
|
41
68
|
const allowedURLs = ClientConfig.getInstance().listOfUrlsToCollectHttpDataFrom;
|
|
42
69
|
this.httpDataAllowedAbsoluteRegex = HTTPDataBundler.buildAllowedRegex(allowedURLs, true);
|
|
@@ -47,7 +74,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
47
74
|
}
|
|
48
75
|
/**
|
|
49
76
|
* Builds the HTTP payload allowed regexes for full and relative URLs
|
|
50
|
-
* @param allowedURLs
|
|
77
|
+
* @param allowedURLs Target list of allowed URLs
|
|
51
78
|
* @param absolute Use only absolute URLs if true, use only relative URL if false
|
|
52
79
|
* @returns a regex of allowed URLs
|
|
53
80
|
*/
|
|
@@ -138,36 +165,34 @@ class HTTPDataBundler extends Singleton {
|
|
|
138
165
|
return text;
|
|
139
166
|
}
|
|
140
167
|
catch (e) {
|
|
141
|
-
ClientConfig.getInstance().
|
|
168
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to stringify JSON response`, error: e }, false, SEVERITY.warn);
|
|
142
169
|
return null;
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
172
|
return null;
|
|
146
173
|
});
|
|
147
174
|
}
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
*/
|
|
151
|
-
bundleHTTPData(url, requestHeaders, rawRequestPayload, responseHeaders, rawResponsePayload, method, isError) {
|
|
175
|
+
/** Builds an HTTP Data bundle */
|
|
176
|
+
bundleHTTPData(url, rawRequestHeaders, rawRequestPayload, rawResponseHeaders, rawResponsePayload, method, isError) {
|
|
152
177
|
if (!this.isValidRequest(method)) {
|
|
153
178
|
return null;
|
|
154
179
|
}
|
|
155
180
|
// stringify payload if correct type and not too large
|
|
156
|
-
let
|
|
157
|
-
let
|
|
181
|
+
let stringifiedRequestPayload = '';
|
|
182
|
+
let stringifiedResponsePayload = '';
|
|
158
183
|
if (this.shouldCollectPayloadForURL(url)) {
|
|
159
|
-
|
|
160
|
-
this.getReasonPayloadIsDropped(
|
|
161
|
-
this.stringFromRequestBody(rawRequestPayload,
|
|
162
|
-
|
|
163
|
-
this.getReasonPayloadIsDropped(
|
|
164
|
-
this.stringFromRequestBody(rawResponsePayload,
|
|
184
|
+
stringifiedRequestPayload =
|
|
185
|
+
this.getReasonPayloadIsDropped(rawRequestHeaders, isError) ||
|
|
186
|
+
this.stringFromRequestBody(rawRequestPayload, rawRequestHeaders);
|
|
187
|
+
stringifiedResponsePayload =
|
|
188
|
+
this.getReasonPayloadIsDropped(rawResponseHeaders, isError) ||
|
|
189
|
+
this.stringFromRequestBody(rawResponsePayload, rawResponseHeaders);
|
|
165
190
|
}
|
|
166
191
|
// don't bundle if there is no data
|
|
167
|
-
const safeRequestHeaders =
|
|
168
|
-
const safeRequestPayload =
|
|
169
|
-
const safeResponseHeaders =
|
|
170
|
-
const safeResponsePayload =
|
|
192
|
+
const safeRequestHeaders = rawRequestHeaders || new Map();
|
|
193
|
+
const safeRequestPayload = stringifiedRequestPayload || '';
|
|
194
|
+
const safeResponseHeaders = rawResponseHeaders || new Map();
|
|
195
|
+
const safeResponsePayload = stringifiedResponsePayload || '';
|
|
171
196
|
if (safeRequestHeaders.size === 0 &&
|
|
172
197
|
!safeRequestPayload &&
|
|
173
198
|
safeResponseHeaders.size === 0 &&
|
|
@@ -175,17 +200,17 @@ class HTTPDataBundler extends Singleton {
|
|
|
175
200
|
return null;
|
|
176
201
|
}
|
|
177
202
|
// Ensure payloads do not exceed the maximum size and redact PII
|
|
178
|
-
const
|
|
203
|
+
const requestPayload = this.restrictPayload(safeRequestPayload, url, isError);
|
|
179
204
|
// Ensure payloads do not exceed the maximum size and redact PII
|
|
180
|
-
const
|
|
205
|
+
const responsePayload = this.restrictPayload(safeResponsePayload, url, isError);
|
|
181
206
|
// Redact PII.
|
|
182
|
-
const
|
|
183
|
-
const
|
|
207
|
+
const requestHeaders = safeFromEntries(this.removePIIHeaders(rawRequestHeaders));
|
|
208
|
+
const responseHeaders = safeFromEntries(this.removePIIHeaders(rawResponseHeaders));
|
|
184
209
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
rqh: requestHeaders,
|
|
211
|
+
rqp: requestPayload,
|
|
212
|
+
rsh: responseHeaders,
|
|
213
|
+
rsp: responsePayload,
|
|
189
214
|
};
|
|
190
215
|
}
|
|
191
216
|
/**
|
|
@@ -193,7 +218,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
193
218
|
* de-duping the requests
|
|
194
219
|
*/
|
|
195
220
|
isValidRequest(method) {
|
|
196
|
-
return
|
|
221
|
+
return this.httpDataCollectionEnabled && method && typeof method === 'string';
|
|
197
222
|
}
|
|
198
223
|
/**
|
|
199
224
|
* Checks two things: that the URL is either on the same domain (or an address relative to the
|
|
@@ -256,7 +281,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
256
281
|
return HTTP_BODY_NULL_STRING;
|
|
257
282
|
}
|
|
258
283
|
if (typeof payload !== 'string') {
|
|
259
|
-
ClientConfig.getInstance().
|
|
284
|
+
ClientConfig.getInstance().postInternalError({
|
|
260
285
|
msg: `restrictPayload received non string payload`,
|
|
261
286
|
payloadType: typeof payload,
|
|
262
287
|
}, false, SEVERITY.error);
|
|
@@ -264,20 +289,12 @@ class HTTPDataBundler extends Singleton {
|
|
|
264
289
|
}
|
|
265
290
|
if (payload === HTTP_BODY_NULL_STRING ||
|
|
266
291
|
(!!payload.startsWith &&
|
|
267
|
-
(payload.startsWith(HTTP_BODY_DROPPED_LENGTH_MSG) ||
|
|
268
|
-
payload.startsWith(HTTP_BODY_DROPPED_TYPE_MSG))) ||
|
|
292
|
+
(payload.startsWith(HTTP_BODY_DROPPED_LENGTH_MSG) || payload.startsWith(HTTP_BODY_DROPPED_TYPE_MSG))) ||
|
|
269
293
|
(!!payload.indexOf &&
|
|
270
|
-
(payload.indexOf(HTTP_BODY_DROPPED_LENGTH_MSG) === 0 ||
|
|
271
|
-
payload.indexOf(HTTP_BODY_DROPPED_TYPE_MSG) === 0))) {
|
|
294
|
+
(payload.indexOf(HTTP_BODY_DROPPED_LENGTH_MSG) === 0 || payload.indexOf(HTTP_BODY_DROPPED_TYPE_MSG) === 0))) {
|
|
272
295
|
return payload;
|
|
273
296
|
}
|
|
274
|
-
|
|
275
|
-
? MAX_HTTP_DATA_PAYLOAD_LENGTH
|
|
276
|
-
: MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
277
|
-
// TODO CHARLIE: Temporary hack for www.holtrenfrew.com to limit them to 1.5MB to help solve a bug
|
|
278
|
-
if (this.hostname === 'www.holtrenfrew.com') {
|
|
279
|
-
restrictSize = 1.5 * 1024 * 1024;
|
|
280
|
-
}
|
|
297
|
+
const restrictSize = isError ? MAX_HTTP_DATA_PAYLOAD_LENGTH : MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
281
298
|
if (payload.length > restrictSize) {
|
|
282
299
|
StoredMetrics.getInstance().addHttpDataDropByLength();
|
|
283
300
|
return `${HTTP_BODY_DROPPED_LENGTH_MSG} Payload length: ${payload.length}`;
|
|
@@ -290,19 +307,11 @@ class HTTPDataBundler extends Singleton {
|
|
|
290
307
|
* Returns true if the content-length header is of acceptable size.
|
|
291
308
|
* Too big gets rejected
|
|
292
309
|
* If the headers are not found, check actual content for length
|
|
293
|
-
* @param {Headers} headers
|
|
294
|
-
* @returns boolean true if acceptable to collect
|
|
295
310
|
*/
|
|
296
311
|
contentLengthAcceptable(headers, isError) {
|
|
297
312
|
// TODO This entire check should move into the parent's parent so there is a single place
|
|
298
313
|
// that restricts paylods and unterstands these 2 values
|
|
299
|
-
|
|
300
|
-
? MAX_HTTP_DATA_PAYLOAD_LENGTH
|
|
301
|
-
: MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
302
|
-
// TODO CHARLIE: Temporary hack for www.holtrenfrew.com to limit them to 1.5MB to help solve a bug
|
|
303
|
-
if (this.hostname === 'www.holtrenfrew.com') {
|
|
304
|
-
restrictSize = 1.5 * 1024 * 1024;
|
|
305
|
-
}
|
|
314
|
+
const restrictSize = isError ? MAX_HTTP_DATA_PAYLOAD_LENGTH : MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
306
315
|
return this.contentLength(headers) <= restrictSize;
|
|
307
316
|
}
|
|
308
317
|
/**
|
|
@@ -314,8 +323,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
314
323
|
contentTypeAcceptable(headersMap) {
|
|
315
324
|
// check content type
|
|
316
325
|
const contentType = headersMap.get(CONTENT_TYPE);
|
|
317
|
-
return !(contentType &&
|
|
318
|
-
!this.contentTypeReadableRegex.test(contentType.toLowerCase()));
|
|
326
|
+
return !(contentType && !this.contentTypeReadableRegex.test(contentType.toLowerCase()));
|
|
319
327
|
}
|
|
320
328
|
/**
|
|
321
329
|
* Returns a descriptive string if we have to drop payload based on the length
|
|
@@ -375,12 +383,9 @@ class HTTPDataBundler extends Singleton {
|
|
|
375
383
|
if (value == null)
|
|
376
384
|
return null;
|
|
377
385
|
try {
|
|
378
|
-
if (
|
|
386
|
+
if ((typeof value === 'string' || value instanceof String) && requestHeaders instanceof Map) {
|
|
379
387
|
const contentType = requestHeaders.get(CONTENT_TYPE);
|
|
380
|
-
if (contentType &&
|
|
381
|
-
contentType
|
|
382
|
-
.toLowerCase()
|
|
383
|
-
.includes('application/x-www-form-urlencoded')) {
|
|
388
|
+
if (contentType && contentType.toLowerCase().includes('application/x-www-form-urlencoded')) {
|
|
384
389
|
value = new URLSearchParams(value.toString());
|
|
385
390
|
}
|
|
386
391
|
}
|
|
@@ -423,7 +428,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
423
428
|
return stringifyJSON(value);
|
|
424
429
|
}
|
|
425
430
|
catch (e) {
|
|
426
|
-
ClientConfig.getInstance().
|
|
431
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to stringify request body`, error: e }, false, SEVERITY.warn);
|
|
427
432
|
}
|
|
428
433
|
return null;
|
|
429
434
|
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
class ReactNativeNavigationIntegration {
|
|
1
|
+
import { Singleton } from '../BaseMonitor.js';
|
|
2
|
+
|
|
3
|
+
/** react-native-navigation adapter */
|
|
4
|
+
class ReactNativeNavigationIntegration extends Singleton {
|
|
5
5
|
constructor() {
|
|
6
|
+
super(...arguments);
|
|
6
7
|
this.stack = [];
|
|
7
8
|
this.stackPointers = {};
|
|
8
9
|
}
|
|
9
|
-
/**
|
|
10
|
-
* attaches provided listeners to the integration
|
|
11
|
-
*/
|
|
10
|
+
/** Attaches provided listeners to the integration */
|
|
12
11
|
register(navigation, onNavigation) {
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
12
|
+
if (this.registration) {
|
|
13
|
+
this.registration.remove();
|
|
14
|
+
}
|
|
15
|
+
this.registration = navigation.events().registerComponentWillAppearListener(this.getListener(onNavigation));
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Listens to ComponentWillAppear events, keeps track of visited screens and
|
|
19
19
|
* pops them if the same page is visited to prevent cycles
|
|
20
|
-
* @param onNavigation
|
|
21
|
-
* @private
|
|
22
20
|
*/
|
|
23
21
|
getListener(onNavigation) {
|
|
24
22
|
return (event) => {
|
|
@@ -37,6 +35,11 @@ class ReactNativeNavigationIntegration {
|
|
|
37
35
|
onNavigation(this.stack.slice());
|
|
38
36
|
};
|
|
39
37
|
}
|
|
38
|
+
/** Destructor */
|
|
39
|
+
destroy() {
|
|
40
|
+
console.log('child destroy');
|
|
41
|
+
this.registration.remove();
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export { ReactNativeNavigationIntegration };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { PageVisitManager } from './PageVisitManager.js';
|
|
2
|
+
import { MAX_TIME_FOR_UNSENT_DATA_MILLIS } from '../constants.js';
|
|
3
|
+
import { getOccurredNow } from '../utils/date.js';
|
|
4
4
|
import { addSafeEventListener } from '../utils/eventlistener.js';
|
|
5
|
-
import ClientConfig from '../api/clientConfig.js';
|
|
6
5
|
import { Singleton } from '../monitors/BaseMonitor.js';
|
|
7
6
|
|
|
8
7
|
/** @module EventDebouncer */
|
|
@@ -11,42 +10,35 @@ import { Singleton } from '../monitors/BaseMonitor.js';
|
|
|
11
10
|
* that are registered
|
|
12
11
|
*/
|
|
13
12
|
class EventDebouncer extends Singleton {
|
|
14
|
-
/**
|
|
15
|
-
* Creates an instance of EventDebouncer
|
|
16
|
-
*/
|
|
13
|
+
/** Creates an instance of EventDebouncer */
|
|
17
14
|
constructor() {
|
|
18
15
|
super();
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
[
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[
|
|
45
|
-
timeout: null,
|
|
46
|
-
events: [],
|
|
47
|
-
debouncePeriod: MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
48
|
-
eventName: KEYBOARD_EVENT_TYPE,
|
|
49
|
-
},
|
|
16
|
+
this.debouncePeriods = {
|
|
17
|
+
app_nav: 0,
|
|
18
|
+
page: MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
19
|
+
err: MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
20
|
+
http: MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
21
|
+
userstep: MAX_TIME_FOR_UNSENT_DATA_MILLIS,
|
|
22
|
+
};
|
|
23
|
+
this.timeouts = {
|
|
24
|
+
app_nav: null,
|
|
25
|
+
page: null,
|
|
26
|
+
err: null,
|
|
27
|
+
http: null,
|
|
28
|
+
userstep: null,
|
|
29
|
+
};
|
|
30
|
+
this.events = {
|
|
31
|
+
app_nav: [],
|
|
32
|
+
page: [],
|
|
33
|
+
err: [],
|
|
34
|
+
http: [],
|
|
35
|
+
userstep: [],
|
|
36
|
+
};
|
|
37
|
+
/** Debounce function to be executed once the debounce period is completed */
|
|
38
|
+
this.sendEvents = (type) => {
|
|
39
|
+
this.timeouts[type] = null;
|
|
40
|
+
PageVisitManager.getInstance().addPageVisitEvents(this.events[type]);
|
|
41
|
+
this.events[type] = [];
|
|
50
42
|
};
|
|
51
43
|
this._setupUnloadHandler();
|
|
52
44
|
}
|
|
@@ -54,45 +46,22 @@ class EventDebouncer extends Singleton {
|
|
|
54
46
|
* Creates an event object with the event and the time it was added then pushes
|
|
55
47
|
* that event object to the queue of events waiting to be debounced.
|
|
56
48
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
this.eventsToDebounce[type].events.push({
|
|
66
|
-
event,
|
|
67
|
-
occurredAt: new Date(timestampWrapper(occurredAt)).toISOString(),
|
|
68
|
-
});
|
|
69
|
-
this._debouncePvEvents(type);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Adds the events from the object to the page visit and sets up a timer
|
|
73
|
-
* to send the events if no more are received without the timeout
|
|
74
|
-
*/
|
|
75
|
-
_debouncePvEvents(type) {
|
|
76
|
-
/**
|
|
77
|
-
* Debounce function to be executed once the debounce period is completed
|
|
78
|
-
*/
|
|
79
|
-
const later = () => {
|
|
80
|
-
this.eventsToDebounce[type].timeout = null;
|
|
81
|
-
PageVisit.getInstance().addPageVisitEvents(this.eventsToDebounce[type].events, this.eventsToDebounce[type].eventName);
|
|
82
|
-
this.eventsToDebounce[type].events = [];
|
|
83
|
-
};
|
|
84
|
-
if (this.eventsToDebounce[type].timeout !== null) {
|
|
85
|
-
clearTimeout(this.eventsToDebounce[type].timeout);
|
|
86
|
-
}
|
|
87
|
-
this.eventsToDebounce[type].timeout = setTimeout(later, this.eventsToDebounce[type].debouncePeriod);
|
|
49
|
+
debounce(event) {
|
|
50
|
+
const type = event.type;
|
|
51
|
+
const withTimestamp = Object.assign(Object.assign({}, event), { occ_at: getOccurredNow() });
|
|
52
|
+
const debouncePeriod = this.debouncePeriods[type];
|
|
53
|
+
this.events[type].push(withTimestamp);
|
|
54
|
+
clearTimeout(this.timeouts[type]);
|
|
55
|
+
this.timeouts[event.type] = setTimeout(() => this.sendEvents(type), debouncePeriod);
|
|
88
56
|
}
|
|
89
57
|
/** Sets up the page hide handler to try to push remaining events in the queues */
|
|
90
58
|
_setupUnloadHandler() {
|
|
91
|
-
addSafeEventListener(window, 'pagehide', () =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
59
|
+
addSafeEventListener(window, 'pagehide', () => Object.keys(this.debouncePeriods).forEach((key) => this.sendEvents(key)));
|
|
60
|
+
}
|
|
61
|
+
destroy() {
|
|
62
|
+
Object.values(this.timeouts)
|
|
63
|
+
.filter(t => !!t)
|
|
64
|
+
.forEach(clearTimeout);
|
|
96
65
|
}
|
|
97
66
|
}
|
|
98
67
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { PageVisitManager } from './PageVisitManager.js';
|
|
2
|
+
import StoredMetrics from '../api/StoredMetrics.js';
|
|
3
|
+
import MetroplexSocket from '../api/MetroplexSocket.js';
|
|
4
|
+
import { safeTrim, getMaxSubstringAllowed, asString } from '../utils/function.js';
|
|
5
|
+
import { EventDebouncer } from './EventDebouncer.js';
|
|
6
|
+
import '../node_modules/@noibu/metroplex-ts-bindings/dist/index.js';
|
|
7
|
+
import { WebsocketMessageType } from '../node_modules/@noibu/metroplex-ts-bindings/dist/WebsocketMessageType.js';
|
|
8
|
+
import { EventType } from '../node_modules/@noibu/metroplex-ts-bindings/dist/EventType.js';
|
|
9
|
+
|
|
10
|
+
/** @module PageVisitEventHTTP */
|
|
11
|
+
/** http event manager */
|
|
12
|
+
// maximum number of HTTP data events including errors to collect per page visit
|
|
13
|
+
const MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT = 120;
|
|
14
|
+
// maximum number of HTTP data events to collect per page visit
|
|
15
|
+
const MAX_HTTP_DATA_EVENT_COUNT = 100;
|
|
16
|
+
/** if no value or it's less than 0, fallback to 0 */
|
|
17
|
+
const validate = (value) => (!value || value < 0 ? 0 : value);
|
|
18
|
+
/** Saves the HTTP event to the pageVisit Queue */
|
|
19
|
+
function saveHTTPEvent(httpEvent, httpData, isGqlError = false) {
|
|
20
|
+
if (!httpEvent) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const url = safeTrim(getMaxSubstringAllowed(asString(httpEvent.url)));
|
|
24
|
+
// we do not store http events that have empty urls
|
|
25
|
+
if (!url) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const validatedEvent = {
|
|
29
|
+
code: validate(httpEvent.code),
|
|
30
|
+
r_time: validate(httpEvent.r_time),
|
|
31
|
+
mtd: (httpEvent.mtd || 'get').toUpperCase(),
|
|
32
|
+
url,
|
|
33
|
+
};
|
|
34
|
+
// we register an http event
|
|
35
|
+
StoredMetrics.getInstance().addHttpEvent();
|
|
36
|
+
// send http data down to metroplex
|
|
37
|
+
if (httpData) {
|
|
38
|
+
// add the sequence number to both events
|
|
39
|
+
const sequenceNumber = StoredMetrics.getInstance().httpSequenceNumber;
|
|
40
|
+
// restrict total number of events collected per page visit to ensure we don't
|
|
41
|
+
// blow up memory and storage usage
|
|
42
|
+
if (isSendAllowed(validatedEvent.code, sequenceNumber, isGqlError)) {
|
|
43
|
+
const httpDataWithSeq = Object.assign(Object.assign({}, httpData), { seq: sequenceNumber });
|
|
44
|
+
validatedEvent.seq = sequenceNumber;
|
|
45
|
+
// increment the count
|
|
46
|
+
StoredMetrics.getInstance().addHttpData();
|
|
47
|
+
MetroplexSocket.getInstance().sendMessage({ type: WebsocketMessageType.PageVisitHttp, payload: httpDataWithSeq });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// have collected more than the max number of http requests for this
|
|
51
|
+
// page visit, so increment the over request limit count
|
|
52
|
+
StoredMetrics.getInstance().addHttpDataOverLimit();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const pve = {
|
|
56
|
+
type: EventType.Http,
|
|
57
|
+
http: validatedEvent,
|
|
58
|
+
};
|
|
59
|
+
// if this was an error, send immediately, so we don't lose it, delay otherwise
|
|
60
|
+
if (isAnError(validatedEvent.code, isGqlError)) {
|
|
61
|
+
PageVisitManager.getInstance().addPageVisitEvent(pve);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
EventDebouncer.getInstance().debounce(pve);
|
|
65
|
+
}
|
|
66
|
+
return validatedEvent.seq;
|
|
67
|
+
}
|
|
68
|
+
/** utility function to determine if status/flags combo is an error */
|
|
69
|
+
function isAnError(status, isGqlError) {
|
|
70
|
+
return isHttpCodeFailure(status) || isGqlError;
|
|
71
|
+
}
|
|
72
|
+
/** Determines if a response is a failure */
|
|
73
|
+
function isHttpCodeFailure(code) {
|
|
74
|
+
return typeof code !== 'number' ? true : code >= 400 || code <= 0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Checks if sending data is allowed based on the HTTP status code and count.
|
|
78
|
+
* status - The HTTP status code to evaluate.
|
|
79
|
+
* count - The count of events to consider.
|
|
80
|
+
* isGqlError - Whether the context is considered as a GQL error.
|
|
81
|
+
* Returns `true` if sending data is allowed, `false` otherwise.
|
|
82
|
+
*/
|
|
83
|
+
function isSendAllowed(status, count, isGqlError = false) {
|
|
84
|
+
const threshold = isAnError(status, isGqlError) ? MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT : MAX_HTTP_DATA_EVENT_COUNT;
|
|
85
|
+
return count < threshold;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { isHttpCodeFailure, isSendAllowed, saveHTTPEvent };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import ClientConfig from '../api/ClientConfig.js';
|
|
2
|
+
import MetroplexSocket from '../api/MetroplexSocket.js';
|
|
3
|
+
import StoredMetrics from '../api/StoredMetrics.js';
|
|
4
|
+
import { noibuLog } from '../utils/log.js';
|
|
5
|
+
import { Singleton } from '../monitors/BaseMonitor.js';
|
|
6
|
+
import { getOccurredNow } from '../utils/date.js';
|
|
7
|
+
import '../node_modules/@noibu/metroplex-ts-bindings/dist/index.js';
|
|
8
|
+
import { WebsocketMessageType } from '../node_modules/@noibu/metroplex-ts-bindings/dist/WebsocketMessageType.js';
|
|
9
|
+
|
|
10
|
+
/** @module Pagevisit */
|
|
11
|
+
const MAX_PAGEVISIT_EVENTS = 200;
|
|
12
|
+
const MAX_PAGEVISIT_PARTS = 10000;
|
|
13
|
+
/**
|
|
14
|
+
* Singleton class to hold all the information
|
|
15
|
+
* about the gathered errors throught the session
|
|
16
|
+
*/
|
|
17
|
+
class PageVisitManager extends Singleton {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.partCounter = 0;
|
|
21
|
+
this.pvEvents = [];
|
|
22
|
+
this.pvEventLength = 0;
|
|
23
|
+
// variables used for monitoring our posting frequency
|
|
24
|
+
this.visibilityChangedCounter = 0;
|
|
25
|
+
this.totalPvEventLength = 0;
|
|
26
|
+
}
|
|
27
|
+
/** adds page visit events into the current page visit map and then sends a page visit message
|
|
28
|
+
*/
|
|
29
|
+
addPageVisitEvents(eventObjects) {
|
|
30
|
+
eventObjects.forEach(eventObj => this._addPageVisitEvent(eventObj));
|
|
31
|
+
this._sendPageVisitMessage();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* adds the page visit event into the current page visit map and then sends a page visit message
|
|
35
|
+
* returns the key to access this event in the map
|
|
36
|
+
*/
|
|
37
|
+
addPageVisitEvent(eventObj) {
|
|
38
|
+
const withTimestamp = Object.assign(Object.assign({}, eventObj), { occ_at: getOccurredNow() });
|
|
39
|
+
noibuLog('addPageVisitEvent', withTimestamp);
|
|
40
|
+
this._addPageVisitEvent(withTimestamp);
|
|
41
|
+
this._sendPageVisitMessage();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* adds a new page visit event into the current page visit map and returns the
|
|
45
|
+
* the key to access this event in the map
|
|
46
|
+
*/
|
|
47
|
+
_addPageVisitEvent(pvEvent) {
|
|
48
|
+
// if we are over the limit set by the Beacon API limit, we need to
|
|
49
|
+
// send what we currently have in the buffer to metroplex
|
|
50
|
+
if (this.pvEventLength >= MAX_PAGEVISIT_EVENTS) {
|
|
51
|
+
this._sendPageVisitMessage();
|
|
52
|
+
}
|
|
53
|
+
// updating sizes
|
|
54
|
+
this.pvEvents.push(pvEvent);
|
|
55
|
+
this.pvEventLength += 1;
|
|
56
|
+
this.totalPvEventLength += 1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* _sendPageVisitMessage will reset the buffer and post the current
|
|
60
|
+
* content to metroplex
|
|
61
|
+
*/
|
|
62
|
+
_sendPageVisitMessage() {
|
|
63
|
+
if (this.pvEvents.length === 0) {
|
|
64
|
+
/**
|
|
65
|
+
* don't send to metroplex if the event buffer is empty.
|
|
66
|
+
* Target previous call to this function from visibilityChange
|
|
67
|
+
* would have sent it as a final post. Another reason it would be empty is
|
|
68
|
+
* if the user has not done anything on the page and switched tabs,
|
|
69
|
+
* or closed the browser etc
|
|
70
|
+
*/
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (this.partCounter >= MAX_PAGEVISIT_PARTS) {
|
|
74
|
+
/**
|
|
75
|
+
* if we are attempting to send over the MAX_PAGEVISIT_PARTS
|
|
76
|
+
* number of parts then we block any subsequent part post to not
|
|
77
|
+
* overwhelm our back end. We lock the client for 10 minute, something
|
|
78
|
+
* must be going bad.
|
|
79
|
+
*/
|
|
80
|
+
ClientConfig.getInstance().lockClientUntilNextPage({
|
|
81
|
+
msg: 'NoibuJS will stop processing parts because we reached max parts',
|
|
82
|
+
MAX_PAGEVISIT_PARTS,
|
|
83
|
+
totalPvEventLength: this.totalPvEventLength,
|
|
84
|
+
visibilityChangedCounter: this.visibilityChangedCounter,
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const pvp = MetroplexSocket.getInstance().addEndTimeToPayload({ events: this.pvEvents, pc: this.partCounter }, true);
|
|
89
|
+
StoredMetrics.getInstance().setPvPart(this.partCounter);
|
|
90
|
+
MetroplexSocket.getInstance().sendMessage({ payload: pvp, type: WebsocketMessageType.PageVisitPart });
|
|
91
|
+
// since we sent the content of the buffer to metroplex, we reset
|
|
92
|
+
// all variables that contained information about the past buffer.
|
|
93
|
+
this.pvEvents = [];
|
|
94
|
+
this.pvEventLength = 0;
|
|
95
|
+
this.partCounter += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { PageVisitManager };
|