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.
Files changed (114) hide show
  1. package/README.md +15 -15
  2. package/android/build.gradle +1 -1
  3. package/dist/{src/api/clientConfig.d.ts → api/ClientConfig.d.ts} +19 -20
  4. package/dist/api/{clientConfig.js → ClientConfig.js} +82 -63
  5. package/dist/{src/api/helpCode.d.ts → api/HelpCode.d.ts} +3 -10
  6. package/dist/api/{helpCode.js → HelpCode.js} +8 -14
  7. package/dist/api/InputManager.d.ts +39 -0
  8. package/dist/api/InputManager.js +156 -0
  9. package/dist/{src/api/metroplexSocket.d.ts → api/MetroplexSocket.d.ts} +33 -38
  10. package/dist/api/{metroplexSocket.js → MetroplexSocket.js} +190 -178
  11. package/dist/{src/api/storedMetrics.d.ts → api/StoredMetrics.d.ts} +10 -20
  12. package/dist/api/StoredMetrics.js +158 -0
  13. package/dist/{src/api/storedPageVisit.d.ts → api/StoredPageVisit.d.ts} +11 -8
  14. package/dist/api/{storedPageVisit.js → StoredPageVisit.js} +62 -48
  15. package/dist/const_matchers.js +1 -5
  16. package/dist/constants.d.ts +48 -0
  17. package/dist/constants.js +15 -397
  18. package/dist/{src/entry → entry}/index.d.ts +5 -6
  19. package/dist/entry/index.js +3 -4
  20. package/dist/entry/init.d.ts +8 -0
  21. package/dist/entry/init.js +34 -19
  22. package/dist/monitors/AppNavigationMonitor.d.ts +10 -0
  23. package/dist/monitors/AppNavigationMonitor.js +19 -19
  24. package/dist/monitors/AppNavigationMonitor.test.d.ts +1 -0
  25. package/dist/{src/monitors → monitors}/BaseMonitor.d.ts +5 -5
  26. package/dist/monitors/BaseMonitor.js +9 -4
  27. package/dist/monitors/BaseMonitor.test.d.ts +1 -0
  28. package/dist/{src/monitors → monitors}/ClickMonitor.d.ts +10 -13
  29. package/dist/monitors/ClickMonitor.js +72 -76
  30. package/dist/monitors/ClickMonitor.test.d.ts +1 -0
  31. package/dist/{src/monitors → monitors}/ErrorMonitor.d.ts +4 -28
  32. package/dist/monitors/ErrorMonitor.js +45 -55
  33. package/dist/{src/monitors → monitors}/KeyboardInputMonitor.d.ts +1 -3
  34. package/dist/monitors/KeyboardInputMonitor.js +13 -11
  35. package/dist/{src/monitors → monitors}/PageMonitor.d.ts +1 -1
  36. package/dist/monitors/PageMonitor.js +25 -2
  37. package/dist/{src/monitors → monitors}/RequestMonitor.d.ts +9 -29
  38. package/dist/monitors/RequestMonitor.js +46 -57
  39. package/dist/monitors/http-tools/GqlErrorValidator.d.ts +35 -0
  40. package/dist/monitors/http-tools/GqlErrorValidator.js +42 -70
  41. package/dist/{src/monitors → monitors}/http-tools/HTTPDataBundler.d.ts +9 -15
  42. package/dist/monitors/http-tools/HTTPDataBundler.js +74 -67
  43. package/dist/monitors/integrations/ReactNativeNavigationIntegration.d.ts +17 -0
  44. package/dist/monitors/integrations/{react-native-navigation-integration.js → ReactNativeNavigationIntegration.js} +15 -12
  45. package/dist/{src/pageVisit → pageVisit}/EventDebouncer.d.ts +9 -10
  46. package/dist/pageVisit/EventDebouncer.js +43 -74
  47. package/dist/pageVisit/HttpEventManager.d.ts +14 -0
  48. package/dist/pageVisit/HttpEventManager.js +88 -0
  49. package/dist/pageVisit/PageVisitManager.d.ts +31 -0
  50. package/dist/pageVisit/PageVisitManager.js +99 -0
  51. package/dist/pageVisit/pageVisitEventError.d.ts +12 -0
  52. package/dist/pageVisit/pageVisitEventError.js +170 -280
  53. package/dist/{src/react → react}/ErrorBoundary.d.ts +4 -9
  54. package/dist/react/ErrorBoundary.js +3 -6
  55. package/dist/{src/sessionRecorder/sessionRecorder.d.ts → sessionRecorder/SessionRecorder.d.ts} +7 -17
  56. package/dist/sessionRecorder/{sessionRecorder.js → SessionRecorder.js} +60 -71
  57. package/dist/{src/sessionRecorder → sessionRecorder}/nativeSessionRecorderSubscription.d.ts +4 -6
  58. package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +3 -5
  59. package/dist/{src/storage/rnStorageProvider.d.ts → storage/RNStorageProvider.d.ts} +4 -8
  60. package/dist/storage/{rnStorageProvider.js → RNStorageProvider.js} +3 -7
  61. package/dist/{src/storage/storage.d.ts → storage/Storage.d.ts} +8 -18
  62. package/dist/storage/{storage.js → Storage.js} +17 -30
  63. package/dist/{src/storage/storageProvider.d.ts → storage/StorageProvider.d.ts} +5 -8
  64. package/dist/storage/{storageProvider.js → StorageProvider.js} +7 -8
  65. package/dist/types/NavigationIntegration.d.ts +1 -1
  66. package/dist/utils/date.d.ts +7 -0
  67. package/dist/utils/date.js +41 -51
  68. package/dist/utils/eventlistener.js +6 -14
  69. package/dist/{src/utils → utils}/function.d.ts +13 -43
  70. package/dist/utils/function.js +42 -113
  71. package/dist/utils/log.d.ts +4 -0
  72. package/dist/utils/log.js +2 -4
  73. package/dist/{src/utils → utils}/object.d.ts +10 -8
  74. package/dist/utils/object.js +12 -12
  75. package/dist/{src/utils → utils}/performance.d.ts +1 -1
  76. package/dist/utils/piiRedactor.js +31 -3
  77. package/dist/utils/stacktrace-parser.d.ts +8 -0
  78. package/dist/utils/stacktrace-parser.js +29 -21
  79. package/dist/utils/stacktrace-parser.test.d.ts +1 -0
  80. package/package.json +14 -14
  81. package/dist/api/inputManager.js +0 -227
  82. package/dist/api/storedMetrics.js +0 -198
  83. package/dist/pageVisit/pageVisit.js +0 -181
  84. package/dist/pageVisit/pageVisitEventHTTP.js +0 -98
  85. package/dist/pageVisit/userStep.js +0 -20
  86. package/dist/src/api/inputManager.d.ts +0 -87
  87. package/dist/src/constants.d.ts +0 -290
  88. package/dist/src/entry/init.d.ts +0 -5
  89. package/dist/src/monitors/AppNavigationMonitor.d.ts +0 -18
  90. package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +0 -59
  91. package/dist/src/monitors/integrations/react-native-navigation-integration.d.ts +0 -20
  92. package/dist/src/pageVisit/pageVisit.d.ts +0 -52
  93. package/dist/src/pageVisit/pageVisitEventError.d.ts +0 -15
  94. package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +0 -25
  95. package/dist/src/pageVisit/userStep.d.ts +0 -5
  96. package/dist/src/utils/date.d.ts +0 -6
  97. package/dist/src/utils/log.d.ts +0 -4
  98. package/dist/src/utils/stacktrace-parser.d.ts +0 -7
  99. package/dist/types/Config.d.ts +0 -31
  100. package/dist/types/Metroplex.types.d.ts +0 -73
  101. package/dist/types/PageVisit.types.d.ts +0 -8
  102. package/dist/types/PageVisitErrors.types.d.ts +0 -114
  103. package/dist/types/PageVisitEvents.types.d.ts +0 -91
  104. package/dist/types/PageVisitMetrics.types.d.ts +0 -27
  105. package/dist/types/Storage.d.ts +0 -14
  106. package/dist/types/StoredPageVisit.types.d.ts +0 -11
  107. package/dist/types/WrappedObjects.d.ts +0 -6
  108. /package/dist/{src/api/clientConfig.test.d.ts → api/ClientConfig.test.d.ts} +0 -0
  109. /package/dist/{src/monitors/BaseMonitor.test.d.ts → api/MetroplexSocket.test.d.ts} +0 -0
  110. /package/dist/{src/const_matchers.d.ts → const_matchers.d.ts} +0 -0
  111. /package/dist/{src/sessionRecorder → sessionRecorder}/types.d.ts +0 -0
  112. /package/dist/{src/utils → utils}/eventlistener.d.ts +0 -0
  113. /package/dist/{src/utils → utils}/piiRedactor.d.ts +0 -0
  114. /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, SEVERITY } from '../../constants.js';
4
- import ClientConfig from '../../api/clientConfig.js';
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 MESSAGE_ATT_NAME = 'message';
7
- const EXTENSIONS_ATT_NAME = 'extensions';
8
- const LOCATIONS_ATT_NAME = 'locations';
9
- const PATH_ATT_NAME = 'path';
10
- const LINE_ATT_NAME = 'line';
11
- const COLUMN_ATT_NAME = 'column';
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 ((contentType === 'application/json' && isGqlUrl) ||
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 MESSAGE_ATT_NAME:
145
+ case GQL_MESSAGE_ATT_NAME:
163
146
  this._validateMessage(error);
164
147
  break;
165
- case LOCATIONS_ATT_NAME:
148
+ case GQL_LOCATIONS_ATT_NAME:
166
149
  this._validateLocations(error, validationIssues);
167
150
  break;
168
- case PATH_ATT_NAME:
151
+ case GQL_PATH_ATT_NAME:
169
152
  this._validatePath(error, validationIssues);
170
153
  break;
171
- case EXTENSIONS_ATT_NAME:
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[MESSAGE_ATT_NAME] = getMaxSubstringAllowed(error[MESSAGE_ATT_NAME], MESSAGE_MAX_LENGTH);
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[EXTENSIONS_ATT_NAME]);
198
- error[EXTENSIONS_ATT_NAME] = getMaxSubstringAllowed(json, MESSAGE_MAX_LENGTH);
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[LOCATIONS_ATT_NAME];
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 LINE_ATT_NAME:
211
- case COLUMN_ATT_NAME:
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[LOCATIONS_ATT_NAME];
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[PATH_ATT_NAME];
211
+ const path = error[GQL_PATH_ATT_NAME];
236
212
  if (Array.isArray(path)) {
237
- error[PATH_ATT_NAME] = error[PATH_ATT_NAME].map(x => x.toString());
213
+ error[GQL_PATH_ATT_NAME] = error[GQL_PATH_ATT_NAME].map(x => x.toString());
238
214
  }
239
215
  else {
240
- delete error[PATH_ATT_NAME];
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().postNoibuErrorAndOptionallyDisableClient(`GQL parse error: ${message}`, false, SEVERITY.error);
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().postNoibuErrorAndOptionallyDisableClient(`GQL error validation warning: ${message}`, false, SEVERITY.error);
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 A list of allowed URLs
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
- * Builds an HTTP Data bundle
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): boolean;
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: string, url: string, isError: boolean): string;
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 { HUMAN_READABLE_CONTENT_TYPE_REGEX, DEFAULT_WEBSITE_SUBDOMAIN_PATTERN, SEVERITY, HTTP_BODY_NULL_STRING, HTTP_DATA_REQ_HEADERS_ATT_NAME, HTTP_DATA_PAYLOAD_ATT_NAME, HTTP_DATA_RESP_HEADERS_ATT_NAME, HTTP_DATA_RESP_PAYLOAD_ATT_NAME, HTTP_BODY_DROPPED_LENGTH_MSG, HTTP_BODY_DROPPED_TYPE_MSG, CONTENT_TYPE, CONTENT_LENGTH, BLOCKED_HTTP_HEADER_KEYS, PII_REDACTION_REPLACEMENT_STRING, MAX_HTTP_DATA_PAYLOAD_LENGTH, MAX_SUCCESS_HTTP_DATA_PAYLOAD_LENGTH } from '../../constants.js';
3
- import ClientConfig from '../../api/clientConfig.js';
4
- import StoredMetrics from '../../api/storedMetrics.js';
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, isString } from '../../utils/function.js';
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().postNoibuErrorAndOptionallyDisableClient(`Unable to determine hostname for initial URL: ${e}`, false, SEVERITY.warn);
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 A list of allowed URLs
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().postNoibuErrorAndOptionallyDisableClient(`Unable to stringify JSON response: ${e}`, false, SEVERITY.warn);
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
- * Builds an HTTP Data bundle
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 requestPayload = '';
157
- let responsePayload = '';
183
+ let stringifiedRequestPayload = '';
184
+ let stringifiedResponsePayload = '';
158
185
  if (this.shouldCollectPayloadForURL(url)) {
159
- requestPayload =
160
- this.getReasonPayloadIsDropped(requestHeaders, isError) ||
161
- this.stringFromRequestBody(rawRequestPayload, requestHeaders);
162
- responsePayload =
163
- this.getReasonPayloadIsDropped(responseHeaders, isError) ||
164
- this.stringFromRequestBody(rawResponsePayload, responseHeaders);
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 = requestHeaders || new Map();
168
- const safeRequestPayload = requestPayload || '';
169
- const safeResponseHeaders = responseHeaders || new Map();
170
- const safeResponsePayload = responsePayload || '';
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 requestPayloadUpdated = this.restrictPayload(safeRequestPayload, url, isError);
205
+ const requestPayload = this.restrictPayload(safeRequestPayload, url, isError);
179
206
  // Ensure payloads do not exceed the maximum size and redact PII
180
- const responsePayloadUpdated = this.restrictPayload(safeResponsePayload, url, isError);
207
+ const responsePayload = this.restrictPayload(safeResponsePayload, url, isError);
181
208
  // Redact PII.
182
- const cleanRequestHeaders = safeFromEntries(this.removePIIHeaders(requestHeaders));
183
- const cleanResponseHeaders = safeFromEntries(this.removePIIHeaders(responseHeaders));
209
+ const requestHeaders = safeFromEntries(this.removePIIHeaders(rawRequestHeaders));
210
+ const responseHeaders = safeFromEntries(this.removePIIHeaders(rawResponseHeaders));
184
211
  return {
185
- [HTTP_DATA_REQ_HEADERS_ATT_NAME]: cleanRequestHeaders,
186
- [HTTP_DATA_PAYLOAD_ATT_NAME]: requestPayloadUpdated,
187
- [HTTP_DATA_RESP_HEADERS_ATT_NAME]: cleanResponseHeaders,
188
- [HTTP_DATA_RESP_PAYLOAD_ATT_NAME]: responsePayloadUpdated,
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 (this.httpDataCollectionEnabled && method && typeof method === 'string');
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().postNoibuErrorAndOptionallyDisableClient({
286
+ ClientConfig.getInstance().postInternalError({
260
287
  msg: `restrictPayload received non string payload`,
261
288
  payloadType: typeof payload,
262
- }, false, SEVERITY.error);
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
- let restrictSize = isError
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
- let restrictSize = isError
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 (isString(value) && requestHeaders instanceof Map) {
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().postNoibuErrorAndOptionallyDisableClient(`Unable to stringify request body: ${e}`, false, SEVERITY.warn);
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
- * react-native-navigation adapter
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
- navigation
14
- .events()
15
- .registerComponentWillAppearListener(this.getListener(onNavigation));
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 eventsToDebounce;
8
- /**
9
- * Creates an instance of EventDebouncer
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
- addEvent(event: any, type: keyof typeof this.eventsToDebounce, occurredAt?: number): void;
17
- /**
18
- * Adds the events from the object to the page visit and sets up a timer
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
  }