noibu-react-native 0.2.6 → 0.2.8
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/{src/api/clientConfig.d.ts → api/ClientConfig.d.ts} +19 -20
- package/dist/api/{clientConfig.js → ClientConfig.js} +82 -63
- package/dist/{src/api/helpCode.d.ts → api/HelpCode.d.ts} +3 -10
- package/dist/api/{helpCode.js → HelpCode.js} +8 -14
- package/dist/api/InputManager.d.ts +39 -0
- package/dist/api/InputManager.js +156 -0
- package/dist/{src/api/metroplexSocket.d.ts → api/MetroplexSocket.d.ts} +33 -38
- package/dist/api/{metroplexSocket.js → MetroplexSocket.js} +190 -178
- package/dist/{src/api/storedMetrics.d.ts → api/StoredMetrics.d.ts} +10 -20
- package/dist/api/StoredMetrics.js +158 -0
- package/dist/{src/api/storedPageVisit.d.ts → api/StoredPageVisit.d.ts} +11 -8
- package/dist/api/{storedPageVisit.js → StoredPageVisit.js} +62 -48
- package/dist/const_matchers.js +1 -5
- package/dist/constants.d.ts +48 -0
- package/dist/constants.js +15 -397
- package/dist/{src/entry → entry}/index.d.ts +5 -6
- package/dist/entry/index.js +3 -4
- package/dist/entry/init.d.ts +8 -0
- package/dist/entry/init.js +34 -19
- package/dist/monitors/AppNavigationMonitor.d.ts +10 -0
- package/dist/monitors/AppNavigationMonitor.js +19 -19
- package/dist/monitors/AppNavigationMonitor.test.d.ts +1 -0
- package/dist/{src/monitors → monitors}/BaseMonitor.d.ts +5 -5
- package/dist/monitors/BaseMonitor.js +9 -4
- package/dist/monitors/BaseMonitor.test.d.ts +1 -0
- package/dist/{src/monitors → monitors}/ClickMonitor.d.ts +10 -13
- package/dist/monitors/ClickMonitor.js +72 -76
- package/dist/monitors/ClickMonitor.test.d.ts +1 -0
- package/dist/{src/monitors → monitors}/ErrorMonitor.d.ts +4 -28
- package/dist/monitors/ErrorMonitor.js +45 -55
- package/dist/{src/monitors → monitors}/KeyboardInputMonitor.d.ts +1 -3
- package/dist/monitors/KeyboardInputMonitor.js +13 -11
- package/dist/{src/monitors → monitors}/PageMonitor.d.ts +1 -1
- package/dist/monitors/PageMonitor.js +25 -2
- package/dist/{src/monitors → monitors}/RequestMonitor.d.ts +9 -29
- package/dist/monitors/RequestMonitor.js +46 -57
- package/dist/monitors/http-tools/GqlErrorValidator.d.ts +35 -0
- package/dist/monitors/http-tools/GqlErrorValidator.js +42 -70
- package/dist/{src/monitors → monitors}/http-tools/HTTPDataBundler.d.ts +9 -15
- package/dist/monitors/http-tools/HTTPDataBundler.js +74 -67
- package/dist/monitors/integrations/ReactNativeNavigationIntegration.d.ts +17 -0
- package/dist/monitors/integrations/{react-native-navigation-integration.js → ReactNativeNavigationIntegration.js} +15 -12
- package/dist/{src/pageVisit → pageVisit}/EventDebouncer.d.ts +9 -10
- package/dist/pageVisit/EventDebouncer.js +43 -74
- package/dist/pageVisit/HttpEventManager.d.ts +14 -0
- package/dist/pageVisit/HttpEventManager.js +88 -0
- package/dist/pageVisit/PageVisitManager.d.ts +31 -0
- package/dist/pageVisit/PageVisitManager.js +99 -0
- package/dist/pageVisit/pageVisitEventError.d.ts +12 -0
- package/dist/pageVisit/pageVisitEventError.js +170 -280
- package/dist/{src/react → react}/ErrorBoundary.d.ts +4 -9
- package/dist/react/ErrorBoundary.js +3 -6
- package/dist/{src/sessionRecorder/sessionRecorder.d.ts → sessionRecorder/SessionRecorder.d.ts} +7 -17
- package/dist/sessionRecorder/{sessionRecorder.js → SessionRecorder.js} +60 -71
- package/dist/{src/sessionRecorder → sessionRecorder}/nativeSessionRecorderSubscription.d.ts +4 -6
- package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +3 -5
- package/dist/{src/storage/rnStorageProvider.d.ts → storage/RNStorageProvider.d.ts} +4 -8
- package/dist/storage/{rnStorageProvider.js → RNStorageProvider.js} +3 -7
- package/dist/{src/storage/storage.d.ts → storage/Storage.d.ts} +8 -18
- package/dist/storage/{storage.js → Storage.js} +17 -30
- package/dist/{src/storage/storageProvider.d.ts → storage/StorageProvider.d.ts} +5 -8
- package/dist/storage/{storageProvider.js → StorageProvider.js} +7 -8
- package/dist/types/NavigationIntegration.d.ts +1 -1
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.js +41 -51
- package/dist/utils/eventlistener.js +6 -14
- package/dist/{src/utils → utils}/function.d.ts +13 -43
- package/dist/utils/function.js +42 -113
- package/dist/utils/log.d.ts +4 -0
- package/dist/utils/log.js +2 -4
- package/dist/{src/utils → utils}/object.d.ts +10 -8
- package/dist/utils/object.js +12 -12
- package/dist/{src/utils → utils}/performance.d.ts +1 -1
- package/dist/utils/piiRedactor.js +31 -3
- package/dist/utils/stacktrace-parser.d.ts +8 -0
- package/dist/utils/stacktrace-parser.js +29 -21
- package/dist/utils/stacktrace-parser.test.d.ts +1 -0
- 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/inputManager.d.ts +0 -87
- package/dist/src/constants.d.ts +0 -290
- package/dist/src/entry/init.d.ts +0 -5
- package/dist/src/monitors/AppNavigationMonitor.d.ts +0 -18
- package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +0 -59
- package/dist/src/monitors/integrations/react-native-navigation-integration.d.ts +0 -20
- 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/utils/date.d.ts +0 -6
- package/dist/src/utils/log.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/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
- /package/dist/{src/api/clientConfig.test.d.ts → api/ClientConfig.test.d.ts} +0 -0
- /package/dist/{src/monitors/BaseMonitor.test.d.ts → api/MetroplexSocket.test.d.ts} +0 -0
- /package/dist/{src/const_matchers.d.ts → const_matchers.d.ts} +0 -0
- /package/dist/{src/sessionRecorder → sessionRecorder}/types.d.ts +0 -0
- /package/dist/{src/utils → utils}/eventlistener.d.ts +0 -0
- /package/dist/{src/utils → utils}/piiRedactor.d.ts +0 -0
- /package/dist/{src/utils → utils}/polyfills.d.ts +0 -0
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import { isInstanceOf, getMaxSubstringAllowed } from '../../utils/function.js';
|
|
3
|
-
import { CONTENT_TYPE
|
|
4
|
-
import ClientConfig from '../../api/
|
|
3
|
+
import { CONTENT_TYPE } from '../../constants.js';
|
|
4
|
+
import ClientConfig from '../../api/ClientConfig.js';
|
|
5
|
+
import '../../node_modules/@noibu/metroplex-ts-bindings/dist/index.js';
|
|
6
|
+
import { Severity } from '../../node_modules/@noibu/metroplex-ts-bindings/dist/Severity.js';
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
8
|
+
const GQL_EXTENSIONS_ATT_NAME = 'extensions';
|
|
9
|
+
const GQL_LOCATIONS_ATT_NAME = 'locations';
|
|
10
|
+
const GQL_PATH_ATT_NAME = 'path';
|
|
11
|
+
const GQL_LINE_ATT_NAME = 'line';
|
|
12
|
+
const GQL_COLUMN_ATT_NAME = 'column';
|
|
13
|
+
const GQL_MESSAGE_ATT_NAME = 'message';
|
|
12
14
|
const MESSAGE_MAX_LENGTH = 1000;
|
|
13
15
|
/* eslint-disable no-restricted-syntax */
|
|
14
16
|
/* eslint-disable no-param-reassign */
|
|
15
|
-
/**
|
|
16
|
-
* Try detecting GraphQL errors from http response
|
|
17
|
-
*/
|
|
17
|
+
/** Try detecting GraphQL errors from http response */
|
|
18
18
|
class GqlErrorValidator {
|
|
19
|
-
/**
|
|
20
|
-
* Retrieves GQL error object based on fetch request/response
|
|
21
|
-
*/
|
|
19
|
+
/** Retrieves GQL error object based on fetch request/response */
|
|
22
20
|
static fromFetch(url, options, request, response) {
|
|
23
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
22
|
try {
|
|
@@ -42,15 +40,11 @@ class GqlErrorValidator {
|
|
|
42
40
|
return null;
|
|
43
41
|
});
|
|
44
42
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Retrieves GQL error object based on XHR object
|
|
47
|
-
*/
|
|
43
|
+
/** Retrieves GQL error object based on XHR object */
|
|
48
44
|
static fromXhr(url, xhr) {
|
|
49
45
|
return __awaiter(this, void 0, void 0, function* () {
|
|
50
46
|
try {
|
|
51
|
-
const isResponseValid = isInstanceOf(xhr, XMLHttpRequest) &&
|
|
52
|
-
xhr.status >= 200 &&
|
|
53
|
-
xhr.status <= 299;
|
|
47
|
+
const isResponseValid = isInstanceOf(xhr, XMLHttpRequest) && xhr.status >= 200 && xhr.status <= 299;
|
|
54
48
|
if (!isResponseValid) {
|
|
55
49
|
return null;
|
|
56
50
|
}
|
|
@@ -84,9 +78,7 @@ class GqlErrorValidator {
|
|
|
84
78
|
return null;
|
|
85
79
|
});
|
|
86
80
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Try safely parse a string and return null if fails
|
|
89
|
-
*/
|
|
81
|
+
/** Try safely parse a string and return null if fails */
|
|
90
82
|
static _parseJsonSafely(content) {
|
|
91
83
|
try {
|
|
92
84
|
return JSON.parse(content);
|
|
@@ -95,9 +87,7 @@ class GqlErrorValidator {
|
|
|
95
87
|
return null;
|
|
96
88
|
}
|
|
97
89
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Try to get content type for fetch arguments
|
|
100
|
-
*/
|
|
90
|
+
/** Try to get content type for fetch arguments */
|
|
101
91
|
static _getContentTypeFromFetchArguments(options, request) {
|
|
102
92
|
let headers = null;
|
|
103
93
|
if (isInstanceOf(request, Request)) {
|
|
@@ -125,9 +115,7 @@ class GqlErrorValidator {
|
|
|
125
115
|
}
|
|
126
116
|
return false;
|
|
127
117
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Determines if request should be processed
|
|
130
|
-
*/
|
|
118
|
+
/** Determines if request should be processed */
|
|
131
119
|
static _shouldHandleRequest(url, contentType) {
|
|
132
120
|
if (contentType) {
|
|
133
121
|
contentType = contentType.toLowerCase();
|
|
@@ -139,36 +127,31 @@ class GqlErrorValidator {
|
|
|
139
127
|
}
|
|
140
128
|
isGqlUrl = url.toLowerCase().includes('graphql');
|
|
141
129
|
}
|
|
142
|
-
return (
|
|
143
|
-
contentType === 'application/graphql');
|
|
130
|
+
return (contentType === 'application/json' && isGqlUrl) || contentType === 'application/graphql';
|
|
144
131
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Sanitizes payload object
|
|
147
|
-
*/
|
|
132
|
+
/** Sanitizes payload object */
|
|
148
133
|
static _validate(data, validationIssues) {
|
|
149
|
-
if (!(typeof data === 'object' &&
|
|
150
|
-
data &&
|
|
151
|
-
Array.isArray(data.errors))) {
|
|
134
|
+
if (!(typeof data === 'object' && data && Array.isArray(data.errors))) {
|
|
152
135
|
return null;
|
|
153
136
|
}
|
|
154
137
|
const errors = data.errors;
|
|
155
|
-
errors.forEach(error => {
|
|
138
|
+
errors.forEach((error) => {
|
|
156
139
|
if (typeof error !== 'object' || !error) {
|
|
157
140
|
return;
|
|
158
141
|
}
|
|
159
142
|
const properties = Object.keys(error);
|
|
160
143
|
for (const property of properties) {
|
|
161
144
|
switch (property) {
|
|
162
|
-
case
|
|
145
|
+
case GQL_MESSAGE_ATT_NAME:
|
|
163
146
|
this._validateMessage(error);
|
|
164
147
|
break;
|
|
165
|
-
case
|
|
148
|
+
case GQL_LOCATIONS_ATT_NAME:
|
|
166
149
|
this._validateLocations(error, validationIssues);
|
|
167
150
|
break;
|
|
168
|
-
case
|
|
151
|
+
case GQL_PATH_ATT_NAME:
|
|
169
152
|
this._validatePath(error, validationIssues);
|
|
170
153
|
break;
|
|
171
|
-
case
|
|
154
|
+
case GQL_EXTENSIONS_ATT_NAME:
|
|
172
155
|
this._validateExtensions(error);
|
|
173
156
|
break;
|
|
174
157
|
default:
|
|
@@ -183,32 +166,27 @@ class GqlErrorValidator {
|
|
|
183
166
|
}
|
|
184
167
|
return errors;
|
|
185
168
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Sanitizes message object
|
|
188
|
-
*/
|
|
169
|
+
/** Sanitizes message object */
|
|
189
170
|
static _validateMessage(error) {
|
|
190
|
-
error[
|
|
171
|
+
error[GQL_MESSAGE_ATT_NAME] = getMaxSubstringAllowed(error[GQL_MESSAGE_ATT_NAME], MESSAGE_MAX_LENGTH);
|
|
191
172
|
}
|
|
192
173
|
/**
|
|
193
174
|
* Sanitizes extensions object
|
|
194
|
-
* @param {any} error
|
|
195
175
|
*/
|
|
196
176
|
static _validateExtensions(error) {
|
|
197
|
-
const json = JSON.stringify(error[
|
|
198
|
-
error[
|
|
177
|
+
const json = JSON.stringify(error[GQL_EXTENSIONS_ATT_NAME]);
|
|
178
|
+
error[GQL_EXTENSIONS_ATT_NAME] = getMaxSubstringAllowed(json, MESSAGE_MAX_LENGTH);
|
|
199
179
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Sanitizes locations object
|
|
202
|
-
*/
|
|
180
|
+
/** Sanitizes locations object */
|
|
203
181
|
static _validateLocations(error, validationIssues) {
|
|
204
|
-
const locations = error[
|
|
182
|
+
const locations = error[GQL_LOCATIONS_ATT_NAME];
|
|
205
183
|
if (Array.isArray(locations)) {
|
|
206
184
|
for (const location of locations) {
|
|
207
185
|
const properties = Object.keys(location);
|
|
208
186
|
for (const property of properties) {
|
|
209
187
|
switch (property) {
|
|
210
|
-
case
|
|
211
|
-
case
|
|
188
|
+
case GQL_LINE_ATT_NAME:
|
|
189
|
+
case GQL_COLUMN_ATT_NAME:
|
|
212
190
|
if (!Number.isSafeInteger(location[property])) {
|
|
213
191
|
const value = location[property];
|
|
214
192
|
location[property] = 0;
|
|
@@ -224,35 +202,29 @@ class GqlErrorValidator {
|
|
|
224
202
|
}
|
|
225
203
|
}
|
|
226
204
|
else {
|
|
227
|
-
delete error[
|
|
205
|
+
delete error[GQL_LOCATIONS_ATT_NAME];
|
|
228
206
|
validationIssues.push(`unexpected error.locations`);
|
|
229
207
|
}
|
|
230
208
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Sanitizes path object
|
|
233
|
-
*/
|
|
209
|
+
/** Sanitizes path object */
|
|
234
210
|
static _validatePath(error, validationIssues) {
|
|
235
|
-
const path = error[
|
|
211
|
+
const path = error[GQL_PATH_ATT_NAME];
|
|
236
212
|
if (Array.isArray(path)) {
|
|
237
|
-
error[
|
|
213
|
+
error[GQL_PATH_ATT_NAME] = error[GQL_PATH_ATT_NAME].map(x => x.toString());
|
|
238
214
|
}
|
|
239
215
|
else {
|
|
240
|
-
delete error[
|
|
216
|
+
delete error[GQL_PATH_ATT_NAME];
|
|
241
217
|
validationIssues.push(`unexpected error.path`);
|
|
242
218
|
}
|
|
243
219
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Posts error
|
|
246
|
-
*/
|
|
220
|
+
/** Posts error */
|
|
247
221
|
static _postError(message) {
|
|
248
|
-
ClientConfig.getInstance().
|
|
222
|
+
ClientConfig.getInstance().postInternalError({ msg: `GQL parse error: ${message}` }, false, Severity.ERROR);
|
|
249
223
|
}
|
|
250
|
-
/**
|
|
251
|
-
* Posts issue found during object sanitization
|
|
252
|
-
*/
|
|
224
|
+
/** Posts issue found during object sanitization */
|
|
253
225
|
static _postValidationIssues(validationIssues) {
|
|
254
226
|
const message = validationIssues.join(',');
|
|
255
|
-
ClientConfig.getInstance().
|
|
227
|
+
ClientConfig.getInstance().postInternalError({ msg: `GQL error validation warning: ${message}` }, false, Severity.ERROR);
|
|
256
228
|
}
|
|
257
229
|
}
|
|
258
230
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { WrappedXMLHttpRequest } from '../../../types/WrappedObjects';
|
|
2
|
-
import { HTTPDataBundle } from '../../../types/PageVisitEvents.types';
|
|
3
1
|
import { Singleton } from '../BaseMonitor';
|
|
2
|
+
import { NoSeq } from '../../types/Metroplex';
|
|
3
|
+
import { RawHttpData } from '@noibu/metroplex-ts-bindings';
|
|
4
4
|
/** Bundles HTTP payloads and headers */
|
|
5
5
|
export declare class HTTPDataBundler extends Singleton {
|
|
6
6
|
contentTypeReadableRegex: RegExp;
|
|
@@ -9,13 +9,11 @@ export declare class HTTPDataBundler extends Singleton {
|
|
|
9
9
|
httpDataCollectionEnabled: boolean;
|
|
10
10
|
httpDataAllowedRelativeRegex: RegExp | null;
|
|
11
11
|
httpDataAllowedAbsoluteRegex: RegExp | null;
|
|
12
|
-
/**
|
|
13
|
-
* Creates an instance of the ClickMonitor instance
|
|
14
|
-
*/
|
|
12
|
+
/** Creates an instance of the ClickMonitor instance */
|
|
15
13
|
constructor();
|
|
16
14
|
/**
|
|
17
15
|
* Builds the HTTP payload allowed regexes for full and relative URLs
|
|
18
|
-
* @param allowedURLs
|
|
16
|
+
* @param allowedURLs Target list of allowed URLs
|
|
19
17
|
* @param absolute Use only absolute URLs if true, use only relative URL if false
|
|
20
18
|
* @returns a regex of allowed URLs
|
|
21
19
|
*/
|
|
@@ -41,15 +39,13 @@ export declare class HTTPDataBundler extends Singleton {
|
|
|
41
39
|
* @returns a string representation of the response, or null if this fails.
|
|
42
40
|
*/
|
|
43
41
|
static getResponseStringFromXHR(xhr: WrappedXMLHttpRequest): Promise<any>;
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
*/
|
|
47
|
-
bundleHTTPData(url: string, requestHeaders: Map<string, string> | undefined, rawRequestPayload: any, responseHeaders: Map<string, string>, rawResponsePayload: any, method: string, isError: boolean): HTTPDataBundle | null;
|
|
42
|
+
/** Builds an HTTP Data bundle */
|
|
43
|
+
bundleHTTPData(url: string, rawRequestHeaders: Map<string, string> | undefined, rawRequestPayload: any, rawResponseHeaders: Map<string, string>, rawResponsePayload: any, method: string, isError: boolean): NoSeq<RawHttpData> | null;
|
|
48
44
|
/**
|
|
49
45
|
* Validates a request based on the URL and method. When enabled, will handle
|
|
50
46
|
* de-duping the requests
|
|
51
47
|
*/
|
|
52
|
-
isValidRequest(method: unknown):
|
|
48
|
+
isValidRequest(method: unknown): unknown;
|
|
53
49
|
/**
|
|
54
50
|
* Checks two things: that the URL is either on the same domain (or an address relative to the
|
|
55
51
|
* current domain), and also checks that the config http_data_collection flag is enabled.
|
|
@@ -67,18 +63,16 @@ export declare class HTTPDataBundler extends Singleton {
|
|
|
67
63
|
* Checks whether HTTP payloads can be collected on this URL
|
|
68
64
|
* returns boolean indicating whether HTTP payloads can be collected on this URL
|
|
69
65
|
*/
|
|
70
|
-
shouldCollectPayloadForURL(url: any): boolean;
|
|
66
|
+
shouldCollectPayloadForURL(url: any): boolean | undefined;
|
|
71
67
|
/**
|
|
72
68
|
* Double checks content length if we couldn't read the headers, and redacts PII
|
|
73
69
|
* returns the restricted payload
|
|
74
70
|
*/
|
|
75
|
-
restrictPayload(payload:
|
|
71
|
+
restrictPayload(payload: unknown, url: string, isError: boolean): string;
|
|
76
72
|
/**
|
|
77
73
|
* Returns true if the content-length header is of acceptable size.
|
|
78
74
|
* Too big gets rejected
|
|
79
75
|
* If the headers are not found, check actual content for length
|
|
80
|
-
* @param {Headers} headers
|
|
81
|
-
* @returns boolean true if acceptable to collect
|
|
82
76
|
*/
|
|
83
77
|
contentLengthAcceptable(headers: Map<string, string>, isError: boolean): boolean;
|
|
84
78
|
/**
|
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
|
-
import {
|
|
3
|
-
import ClientConfig from '../../api/
|
|
4
|
-
import StoredMetrics from '../../api/
|
|
2
|
+
import { 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
|
+
import '../../node_modules/@noibu/metroplex-ts-bindings/dist/index.js';
|
|
10
|
+
import { Severity } from '../../node_modules/@noibu/metroplex-ts-bindings/dist/Severity.js';
|
|
9
11
|
|
|
12
|
+
const DEFAULT_WEBSITE_SUBDOMAIN_PATTERN = /^www\d{0,2}$/;
|
|
13
|
+
const CONTENT_LENGTH = 'content-length';
|
|
14
|
+
const HTTP_BODY_DROPPED_TYPE_MSG = 'Dropped due to unsupported type.';
|
|
15
|
+
const HTTP_BODY_DROPPED_LENGTH_MSG = 'Dropped due to length.';
|
|
16
|
+
const HTTP_BODY_NULL_STRING = 'null';
|
|
17
|
+
// the maximum size of http data payload that will be capture for success, otherwise it is dropped
|
|
18
|
+
// this is 64k
|
|
19
|
+
const MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH = 65536;
|
|
20
|
+
// the maximum size of http data payload that will be captured, otherwise it is dropped
|
|
21
|
+
const MAX_HTTP_DATA_PAYLOAD_LENGTH = 50000;
|
|
22
|
+
// regex of human readable content based on content-type header
|
|
23
|
+
const HUMAN_READABLE_CONTENT_TYPE_REGEX = 'text|json|xml|html|graphql|x-www-form-urlencoded|form-data';
|
|
24
|
+
// HTTP request/response header keys to be blocked - these must be lowercase.
|
|
25
|
+
const BLOCKED_HTTP_HEADER_KEYS = [
|
|
26
|
+
'authorization',
|
|
27
|
+
'from',
|
|
28
|
+
'proxy-authorization',
|
|
29
|
+
'content-md5',
|
|
30
|
+
'cookie',
|
|
31
|
+
'x-forwarded-for',
|
|
32
|
+
'x-real-ip',
|
|
33
|
+
'x-device-id',
|
|
34
|
+
'x-request-id',
|
|
35
|
+
'x-auth-token',
|
|
36
|
+
'x-user-id',
|
|
37
|
+
'x-forwarded-for',
|
|
38
|
+
'x-uidh',
|
|
39
|
+
'set-cookie',
|
|
40
|
+
'forwarded',
|
|
41
|
+
];
|
|
10
42
|
/** Bundles HTTP payloads and headers */
|
|
11
43
|
class HTTPDataBundler extends Singleton {
|
|
12
|
-
/**
|
|
13
|
-
* Creates an instance of the ClickMonitor instance
|
|
14
|
-
*/
|
|
44
|
+
/** Creates an instance of the ClickMonitor instance */
|
|
15
45
|
constructor() {
|
|
16
46
|
super();
|
|
17
47
|
// compile regex only once
|
|
@@ -32,11 +62,10 @@ class HTTPDataBundler extends Singleton {
|
|
|
32
62
|
this.initialURLPartsReversed.reverse();
|
|
33
63
|
}
|
|
34
64
|
catch (e) {
|
|
35
|
-
ClientConfig.getInstance().
|
|
65
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to determine hostname for initial URL`, error: e }, false, Severity.WARN);
|
|
36
66
|
}
|
|
37
67
|
}
|
|
38
|
-
this.httpDataCollectionEnabled =
|
|
39
|
-
!!ClientConfig.getInstance().enableHttpDataCollection;
|
|
68
|
+
this.httpDataCollectionEnabled = !!ClientConfig.getInstance().enableHttpDataCollection;
|
|
40
69
|
// compile the relative and full HTTP URL regexes
|
|
41
70
|
const allowedURLs = ClientConfig.getInstance().listOfUrlsToCollectHttpDataFrom;
|
|
42
71
|
this.httpDataAllowedAbsoluteRegex = HTTPDataBundler.buildAllowedRegex(allowedURLs, true);
|
|
@@ -47,7 +76,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
47
76
|
}
|
|
48
77
|
/**
|
|
49
78
|
* Builds the HTTP payload allowed regexes for full and relative URLs
|
|
50
|
-
* @param allowedURLs
|
|
79
|
+
* @param allowedURLs Target list of allowed URLs
|
|
51
80
|
* @param absolute Use only absolute URLs if true, use only relative URL if false
|
|
52
81
|
* @returns a regex of allowed URLs
|
|
53
82
|
*/
|
|
@@ -138,36 +167,34 @@ class HTTPDataBundler extends Singleton {
|
|
|
138
167
|
return text;
|
|
139
168
|
}
|
|
140
169
|
catch (e) {
|
|
141
|
-
ClientConfig.getInstance().
|
|
170
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to stringify JSON response`, error: e }, false, Severity.WARN);
|
|
142
171
|
return null;
|
|
143
172
|
}
|
|
144
173
|
}
|
|
145
174
|
return null;
|
|
146
175
|
});
|
|
147
176
|
}
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
*/
|
|
151
|
-
bundleHTTPData(url, requestHeaders, rawRequestPayload, responseHeaders, rawResponsePayload, method, isError) {
|
|
177
|
+
/** Builds an HTTP Data bundle */
|
|
178
|
+
bundleHTTPData(url, rawRequestHeaders, rawRequestPayload, rawResponseHeaders, rawResponsePayload, method, isError) {
|
|
152
179
|
if (!this.isValidRequest(method)) {
|
|
153
180
|
return null;
|
|
154
181
|
}
|
|
155
182
|
// stringify payload if correct type and not too large
|
|
156
|
-
let
|
|
157
|
-
let
|
|
183
|
+
let stringifiedRequestPayload = '';
|
|
184
|
+
let stringifiedResponsePayload = '';
|
|
158
185
|
if (this.shouldCollectPayloadForURL(url)) {
|
|
159
|
-
|
|
160
|
-
this.getReasonPayloadIsDropped(
|
|
161
|
-
this.stringFromRequestBody(rawRequestPayload,
|
|
162
|
-
|
|
163
|
-
this.getReasonPayloadIsDropped(
|
|
164
|
-
this.stringFromRequestBody(rawResponsePayload,
|
|
186
|
+
stringifiedRequestPayload =
|
|
187
|
+
this.getReasonPayloadIsDropped(rawRequestHeaders, isError) ||
|
|
188
|
+
this.stringFromRequestBody(rawRequestPayload, rawRequestHeaders);
|
|
189
|
+
stringifiedResponsePayload =
|
|
190
|
+
this.getReasonPayloadIsDropped(rawResponseHeaders, isError) ||
|
|
191
|
+
this.stringFromRequestBody(rawResponsePayload, rawResponseHeaders);
|
|
165
192
|
}
|
|
166
193
|
// don't bundle if there is no data
|
|
167
|
-
const safeRequestHeaders =
|
|
168
|
-
const safeRequestPayload =
|
|
169
|
-
const safeResponseHeaders =
|
|
170
|
-
const safeResponsePayload =
|
|
194
|
+
const safeRequestHeaders = rawRequestHeaders || new Map();
|
|
195
|
+
const safeRequestPayload = stringifiedRequestPayload || '';
|
|
196
|
+
const safeResponseHeaders = rawResponseHeaders || new Map();
|
|
197
|
+
const safeResponsePayload = stringifiedResponsePayload || '';
|
|
171
198
|
if (safeRequestHeaders.size === 0 &&
|
|
172
199
|
!safeRequestPayload &&
|
|
173
200
|
safeResponseHeaders.size === 0 &&
|
|
@@ -175,17 +202,17 @@ class HTTPDataBundler extends Singleton {
|
|
|
175
202
|
return null;
|
|
176
203
|
}
|
|
177
204
|
// Ensure payloads do not exceed the maximum size and redact PII
|
|
178
|
-
const
|
|
205
|
+
const requestPayload = this.restrictPayload(safeRequestPayload, url, isError);
|
|
179
206
|
// Ensure payloads do not exceed the maximum size and redact PII
|
|
180
|
-
const
|
|
207
|
+
const responsePayload = this.restrictPayload(safeResponsePayload, url, isError);
|
|
181
208
|
// Redact PII.
|
|
182
|
-
const
|
|
183
|
-
const
|
|
209
|
+
const requestHeaders = safeFromEntries(this.removePIIHeaders(rawRequestHeaders));
|
|
210
|
+
const responseHeaders = safeFromEntries(this.removePIIHeaders(rawResponseHeaders));
|
|
184
211
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
rqh: requestHeaders,
|
|
213
|
+
rqp: requestPayload,
|
|
214
|
+
rsh: responseHeaders,
|
|
215
|
+
rsp: responsePayload,
|
|
189
216
|
};
|
|
190
217
|
}
|
|
191
218
|
/**
|
|
@@ -193,7 +220,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
193
220
|
* de-duping the requests
|
|
194
221
|
*/
|
|
195
222
|
isValidRequest(method) {
|
|
196
|
-
return
|
|
223
|
+
return this.httpDataCollectionEnabled && method && typeof method === 'string';
|
|
197
224
|
}
|
|
198
225
|
/**
|
|
199
226
|
* Checks two things: that the URL is either on the same domain (or an address relative to the
|
|
@@ -256,28 +283,20 @@ class HTTPDataBundler extends Singleton {
|
|
|
256
283
|
return HTTP_BODY_NULL_STRING;
|
|
257
284
|
}
|
|
258
285
|
if (typeof payload !== 'string') {
|
|
259
|
-
ClientConfig.getInstance().
|
|
286
|
+
ClientConfig.getInstance().postInternalError({
|
|
260
287
|
msg: `restrictPayload received non string payload`,
|
|
261
288
|
payloadType: typeof payload,
|
|
262
|
-
}, false,
|
|
289
|
+
}, false, Severity.ERROR);
|
|
263
290
|
return HTTP_BODY_NULL_STRING;
|
|
264
291
|
}
|
|
265
292
|
if (payload === HTTP_BODY_NULL_STRING ||
|
|
266
293
|
(!!payload.startsWith &&
|
|
267
|
-
(payload.startsWith(HTTP_BODY_DROPPED_LENGTH_MSG) ||
|
|
268
|
-
payload.startsWith(HTTP_BODY_DROPPED_TYPE_MSG))) ||
|
|
294
|
+
(payload.startsWith(HTTP_BODY_DROPPED_LENGTH_MSG) || payload.startsWith(HTTP_BODY_DROPPED_TYPE_MSG))) ||
|
|
269
295
|
(!!payload.indexOf &&
|
|
270
|
-
(payload.indexOf(HTTP_BODY_DROPPED_LENGTH_MSG) === 0 ||
|
|
271
|
-
payload.indexOf(HTTP_BODY_DROPPED_TYPE_MSG) === 0))) {
|
|
296
|
+
(payload.indexOf(HTTP_BODY_DROPPED_LENGTH_MSG) === 0 || payload.indexOf(HTTP_BODY_DROPPED_TYPE_MSG) === 0))) {
|
|
272
297
|
return payload;
|
|
273
298
|
}
|
|
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
|
-
}
|
|
299
|
+
const restrictSize = isError ? MAX_HTTP_DATA_PAYLOAD_LENGTH : MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
281
300
|
if (payload.length > restrictSize) {
|
|
282
301
|
StoredMetrics.getInstance().addHttpDataDropByLength();
|
|
283
302
|
return `${HTTP_BODY_DROPPED_LENGTH_MSG} Payload length: ${payload.length}`;
|
|
@@ -290,19 +309,11 @@ class HTTPDataBundler extends Singleton {
|
|
|
290
309
|
* Returns true if the content-length header is of acceptable size.
|
|
291
310
|
* Too big gets rejected
|
|
292
311
|
* If the headers are not found, check actual content for length
|
|
293
|
-
* @param {Headers} headers
|
|
294
|
-
* @returns boolean true if acceptable to collect
|
|
295
312
|
*/
|
|
296
313
|
contentLengthAcceptable(headers, isError) {
|
|
297
314
|
// TODO This entire check should move into the parent's parent so there is a single place
|
|
298
315
|
// 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
|
-
}
|
|
316
|
+
const restrictSize = isError ? MAX_HTTP_DATA_PAYLOAD_LENGTH : MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH;
|
|
306
317
|
return this.contentLength(headers) <= restrictSize;
|
|
307
318
|
}
|
|
308
319
|
/**
|
|
@@ -314,8 +325,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
314
325
|
contentTypeAcceptable(headersMap) {
|
|
315
326
|
// check content type
|
|
316
327
|
const contentType = headersMap.get(CONTENT_TYPE);
|
|
317
|
-
return !(contentType &&
|
|
318
|
-
!this.contentTypeReadableRegex.test(contentType.toLowerCase()));
|
|
328
|
+
return !(contentType && !this.contentTypeReadableRegex.test(contentType.toLowerCase()));
|
|
319
329
|
}
|
|
320
330
|
/**
|
|
321
331
|
* Returns a descriptive string if we have to drop payload based on the length
|
|
@@ -375,12 +385,9 @@ class HTTPDataBundler extends Singleton {
|
|
|
375
385
|
if (value == null)
|
|
376
386
|
return null;
|
|
377
387
|
try {
|
|
378
|
-
if (
|
|
388
|
+
if ((typeof value === 'string' || value instanceof String) && requestHeaders instanceof Map) {
|
|
379
389
|
const contentType = requestHeaders.get(CONTENT_TYPE);
|
|
380
|
-
if (contentType &&
|
|
381
|
-
contentType
|
|
382
|
-
.toLowerCase()
|
|
383
|
-
.includes('application/x-www-form-urlencoded')) {
|
|
390
|
+
if (contentType && contentType.toLowerCase().includes('application/x-www-form-urlencoded')) {
|
|
384
391
|
value = new URLSearchParams(value.toString());
|
|
385
392
|
}
|
|
386
393
|
}
|
|
@@ -423,7 +430,7 @@ class HTTPDataBundler extends Singleton {
|
|
|
423
430
|
return stringifyJSON(value);
|
|
424
431
|
}
|
|
425
432
|
catch (e) {
|
|
426
|
-
ClientConfig.getInstance().
|
|
433
|
+
ClientConfig.getInstance().postInternalError({ msg: `Unable to stringify request body`, error: e }, false, Severity.WARN);
|
|
427
434
|
}
|
|
428
435
|
return null;
|
|
429
436
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NavigationDelegate } from 'react-native-navigation/lib/dist/src/NavigationDelegate';
|
|
2
|
+
import { Singleton } from '../BaseMonitor';
|
|
3
|
+
/** react-native-navigation adapter */
|
|
4
|
+
export declare class ReactNativeNavigationIntegration extends Singleton implements NavigationIntegration {
|
|
5
|
+
private stack;
|
|
6
|
+
private stackPointers;
|
|
7
|
+
private registration;
|
|
8
|
+
/** Attaches provided listeners to the integration */
|
|
9
|
+
register(navigation: NavigationDelegate, onNavigation: (breadcrumbs: string[]) => void): void;
|
|
10
|
+
/**
|
|
11
|
+
* Listens to ComponentWillAppear events, keeps track of visited screens and
|
|
12
|
+
* pops them if the same page is visited to prevent cycles
|
|
13
|
+
*/
|
|
14
|
+
private getListener;
|
|
15
|
+
/** Destructor */
|
|
16
|
+
protected destroy(): void;
|
|
17
|
+
}
|
|
@@ -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,24 +1,23 @@
|
|
|
1
1
|
import { Singleton } from '../monitors/BaseMonitor';
|
|
2
|
+
import { EventType, PageVisitEvent, UserStepType } from '@noibu/metroplex-ts-bindings';
|
|
2
3
|
/**
|
|
3
4
|
* Singleton class responsible for debouncing all events
|
|
4
5
|
* that are registered
|
|
5
6
|
*/
|
|
6
7
|
export declare class EventDebouncer extends Singleton {
|
|
7
|
-
private readonly
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
private readonly debouncePeriods;
|
|
9
|
+
private readonly timeouts;
|
|
10
|
+
private readonly events;
|
|
11
|
+
/** Creates an instance of EventDebouncer */
|
|
11
12
|
constructor();
|
|
12
13
|
/**
|
|
13
14
|
* Creates an event object with the event and the time it was added then pushes
|
|
14
15
|
* that event object to the queue of events waiting to be debounced.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
* to send the events if no more are received without the timeout
|
|
20
|
-
*/
|
|
21
|
-
_debouncePvEvents(type: string): void;
|
|
17
|
+
debounce(event: Omit<PageVisitEvent, 'occ_at'>): void;
|
|
18
|
+
/** Debounce function to be executed once the debounce period is completed */
|
|
19
|
+
sendEvents: (type: EventType | UserStepType) => void;
|
|
22
20
|
/** Sets up the page hide handler to try to push remaining events in the queues */
|
|
23
21
|
_setupUnloadHandler(): void;
|
|
22
|
+
protected destroy(): void;
|
|
24
23
|
}
|