noibu-react-native 0.0.1

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 (40) hide show
  1. package/README.md +155 -0
  2. package/dist/api/clientConfig.js +416 -0
  3. package/dist/api/helpCode.js +106 -0
  4. package/dist/api/inputManager.js +233 -0
  5. package/dist/api/metroplexSocket.js +882 -0
  6. package/dist/api/storedMetrics.js +201 -0
  7. package/dist/api/storedPageVisit.js +235 -0
  8. package/dist/const_matchers.js +260 -0
  9. package/dist/constants.d.ts +264 -0
  10. package/dist/constants.js +528 -0
  11. package/dist/entry/index.d.ts +8 -0
  12. package/dist/entry/index.js +15 -0
  13. package/dist/entry/init.js +91 -0
  14. package/dist/monitors/clickMonitor.js +284 -0
  15. package/dist/monitors/elementMonitor.js +174 -0
  16. package/dist/monitors/errorMonitor.js +295 -0
  17. package/dist/monitors/gqlErrorValidator.js +306 -0
  18. package/dist/monitors/httpDataBundler.js +665 -0
  19. package/dist/monitors/inputMonitor.js +130 -0
  20. package/dist/monitors/keyboardInputMonitor.js +67 -0
  21. package/dist/monitors/locationChangeMonitor.js +30 -0
  22. package/dist/monitors/pageMonitor.js +119 -0
  23. package/dist/monitors/requestMonitor.js +679 -0
  24. package/dist/pageVisit/pageVisit.js +172 -0
  25. package/dist/pageVisit/pageVisitEventError/pageVisitEventError.js +313 -0
  26. package/dist/pageVisit/pageVisitEventHTTP/pageVisitEventHTTP.js +115 -0
  27. package/dist/pageVisit/userStep/userStep.js +20 -0
  28. package/dist/react/ErrorBoundary.d.ts +72 -0
  29. package/dist/react/ErrorBoundary.js +102 -0
  30. package/dist/storage/localStorageProvider.js +23 -0
  31. package/dist/storage/rnStorageProvider.js +62 -0
  32. package/dist/storage/sessionStorageProvider.js +23 -0
  33. package/dist/storage/storage.js +119 -0
  34. package/dist/storage/storageProvider.js +83 -0
  35. package/dist/utils/date.js +62 -0
  36. package/dist/utils/eventlistener.js +67 -0
  37. package/dist/utils/function.js +398 -0
  38. package/dist/utils/object.js +144 -0
  39. package/dist/utils/performance.js +21 -0
  40. package/package.json +57 -0
@@ -0,0 +1,172 @@
1
+ import uuid from 'react-native-uuid';
2
+ import { TYPE_ATT_NAME, OCCURRED_AT_ATT_NAME, MAX_PAGEVISIT_EVENTS, MAX_PAGEVISIT_PARTS, PV_METROPLEX_TYPE, PV_EVENTS_ATT_NAME, PV_PART_COUNTER_ATT_NAME, PAGE_VISIT_PART_ATT_NAME } from '../constants.js';
3
+ import ClientConfig from '../api/clientConfig.js';
4
+ import MetroplexSocket from '../api/metroplexSocket.js';
5
+ import StoredMetrics from '../api/storedMetrics.js';
6
+
7
+ /** @module Pagevisit */
8
+
9
+ /**
10
+ * Singleton class to hold all the information
11
+ * about the gathered errors throught the session
12
+ */
13
+ class PageVisit {
14
+ /**
15
+ * Creates an instance of a PageVisit
16
+ */
17
+ constructor() {
18
+ this.partCounter = 0;
19
+ // TODO: Remove pvMap and related variables since we only send a single event at a time
20
+ this.pvMap = {};
21
+ this.partCounter = 0;
22
+ this.pvEventLength = 0;
23
+ // variables used for monitoring our posting frequency
24
+ this.visibilityChangedCounter = 0;
25
+ this.totalPvEventLength = 0;
26
+ this.inDebounceHandle = 0;
27
+ this.isInAcceleratedPvPostMode = false;
28
+ }
29
+
30
+ /**
31
+ * creates a PV object
32
+ */
33
+ static configureInstance() {
34
+ if (!this.instance) {
35
+ this.instance = new PageVisit();
36
+ }
37
+ }
38
+
39
+ /** gets the singleton instance */
40
+ static getInstance() {
41
+ if (!this.instance) {
42
+ throw new Error('Pagevisit was never configured');
43
+ }
44
+ return this.instance;
45
+ }
46
+
47
+ /** adds page visit events into the current page visit map and then sends a page visit message
48
+ * @param {} eventObjects
49
+ * @param {} type
50
+ */
51
+ addPageVisitEvents(eventObjects, type) {
52
+ eventObjects.forEach(eventObj => {
53
+ this._addPageVisitEvent(eventObj, type);
54
+ });
55
+ this._sendPageVisitMessage();
56
+ }
57
+
58
+ /**
59
+ * adds the page visit event into the current page visit map and then sends a page visit message
60
+ * returns the the key to access this event in the map
61
+ * @param {} eventObj
62
+ * @param {} type
63
+ */
64
+ addPageVisitEvent(eventObj, type) {
65
+ const id = this._addPageVisitEvent(eventObj, type);
66
+ this._sendPageVisitMessage();
67
+ return id;
68
+ }
69
+
70
+ /**
71
+ * adds a new page visit event into the current page visit map and returns the
72
+ * the key to access this event in the map
73
+ * @param {} eventObj
74
+ * @param {} type
75
+ */
76
+ _addPageVisitEvent(eventObj, type) {
77
+ if (!('occurredAt' in eventObj && 'event' in eventObj)) {
78
+ throw new Error('missing attributes in the eventObj');
79
+ }
80
+
81
+ // creating the pvEvent from the passed pv
82
+ const pvEvent = {
83
+ [TYPE_ATT_NAME]: type,
84
+ [OCCURRED_AT_ATT_NAME]: eventObj.occurredAt,
85
+ [type]: eventObj.event,
86
+ };
87
+
88
+ // if we are over the limit set by the Beacon API limit, we need to
89
+ // send what we currently have in the buffer to metroplex
90
+ if (this.pvEventLength >= MAX_PAGEVISIT_EVENTS) {
91
+ this._sendPageVisitMessage();
92
+ }
93
+
94
+ const pvEventId = uuid.v4();
95
+ // updating sizes
96
+ this.pvMap[pvEventId] = pvEvent;
97
+ this.pvEventLength += 1;
98
+ this.totalPvEventLength += 1;
99
+
100
+ return pvEventId;
101
+ }
102
+
103
+ /**
104
+ * Creates a page visit frag from an events array and part counter. Appends
105
+ * the end time field and returns the frag
106
+ * @param {} pvEvents
107
+ * @param {} partCounter
108
+ */
109
+ static makePageVisitFrag(pvEvents, partCounter) {
110
+ const pagevistFrag = {};
111
+ pagevistFrag[PV_EVENTS_ATT_NAME] = pvEvents;
112
+ pagevistFrag[PV_PART_COUNTER_ATT_NAME] = partCounter;
113
+ MetroplexSocket.getInstance().addEndTimeToPayload(pagevistFrag, true);
114
+ return pagevistFrag;
115
+ }
116
+
117
+ /**
118
+ * _sendPageVisitMessage will reset the buffer and post the current
119
+ * content to metroplex
120
+ */
121
+ _sendPageVisitMessage() {
122
+ // not using Object.values since we want to support as many browsers
123
+ // as possible
124
+ const pvEvents = Object.keys(this.pvMap).map(id => this.pvMap[id]);
125
+
126
+ if (pvEvents.length === 0) {
127
+ // don't send to metroplex if the event buffer is empty.
128
+ // A previous call to this function from visibilityChange
129
+ // would have sent it as a final post. Another reason it would be empty is
130
+ // if the user has not done anything on the page and switched tabs,
131
+ // or closed the browser etc
132
+ return;
133
+ }
134
+
135
+ if (this.partCounter >= MAX_PAGEVISIT_PARTS) {
136
+ // if we are attempting to send over the MAX_PAGEVISIT_PARTS
137
+ // number of parts then we block any subsequent part post to not
138
+ // inondate our back end. We lock the client for 10 minute, something
139
+ // must be going bad.
140
+ ClientConfig.getInstance().lockClientUntilNextPage(
141
+ `NoibuJS will stop processing parts because we ` +
142
+ `reached max parts: ${MAX_PAGEVISIT_PARTS}. Variables: ` +
143
+ `
144
+ total Pv Event Length: ${this.totalPvEventLength}
145
+ visibility Changed Counter: ${this.visibilityChangedCounter}
146
+ `,
147
+ );
148
+ return;
149
+ }
150
+
151
+ const pagevistFrag = PageVisit.makePageVisitFrag(
152
+ pvEvents,
153
+ this.partCounter,
154
+ );
155
+
156
+ StoredMetrics.getInstance().setPvPart(this.partCounter);
157
+
158
+ const metroplexMsg = {
159
+ [PAGE_VISIT_PART_ATT_NAME]: pagevistFrag,
160
+ };
161
+
162
+ MetroplexSocket.getInstance().sendMessage(PV_METROPLEX_TYPE, metroplexMsg);
163
+
164
+ // since we sent the content of the buffer to metroplex, we reset
165
+ // all variables that contained information about the past buffer.
166
+ this.pvMap = {};
167
+ this.pvEventLength = 0;
168
+ this.partCounter += 1;
169
+ }
170
+ }
171
+
172
+ export { PageVisit };
@@ -0,0 +1,313 @@
1
+ import { isValidURL, getOnURL, getJSStack, stringifyJSON, getMaxSubstringAllowed } from '../../utils/function.js';
2
+ import { EVENT_ERROR_TYPE, URL_ATT_NAME, ERROR_EVENT_TYPE, ERROR_EVENT_ERROR_TYPE, CUSTOM_ERROR_EVENT_TYPE, ERROR_EVENT_UNHANDLED_REJECTION_TYPE, ERROR_LOG_EVENT_ERROR_TYPE, FETCH_EXCEPTION_ERROR_TYPE, WRAPPED_EXCEPTION_ERROR_TYPE, GQL_ERROR_TYPE, RESPONSE_ERROR_TYPE, XML_HTTP_REQUEST_ERROR_TYPE, ERROR_SOURCE_ATT_NAME, TYPE_ATT_NAME, JS_EVENT_TYPE, JS_ERROR_ATT_NAME, JS_STACK_FRAMES_ATT_NAME, JS_STACK_FILE_ATT_NAME, JS_STACK_METHOD_ATT_NAME, SEVERITY_ERROR, JS_STACK_MESSAGE_ATT_NAME, HTTP_EVENT_TYPE, NOIBU_INPUT_URLS, HTTP_CODE_ATT_NAME, PV_SEQ_ATT_NAME, GQL_EVENT_TYPE, GQL_ERROR_ATT_NAME } from '../../constants.js';
3
+ import ClientConfig from '../../api/clientConfig.js';
4
+ import { InputMonitor } from '../../monitors/inputMonitor.js';
5
+ import StoredMetrics from '../../api/storedMetrics.js';
6
+
7
+ /** @module PageVisitEventError */
8
+
9
+ /**
10
+ * getPVErrorFromResponse will create the error information from an
11
+ * http response error type.
12
+ * @param {} errPayload
13
+ * @param {} httpDataSeqNum
14
+ */
15
+ function getPVErrorFromResponse(errPayload, httpDataSeqNum) {
16
+ const errorObject = {
17
+ [URL_ATT_NAME]: getOnURL(errPayload.url),
18
+ [TYPE_ATT_NAME]: HTTP_EVENT_TYPE,
19
+ [HTTP_CODE_ATT_NAME]: errPayload.status,
20
+ };
21
+ if (httpDataSeqNum || httpDataSeqNum === 0) {
22
+ errorObject[PV_SEQ_ATT_NAME] = httpDataSeqNum;
23
+ }
24
+ return errorObject;
25
+ }
26
+
27
+ /**
28
+ * getPVErrorFromGqlError will create the error information from an
29
+ * GraphQL error payload.
30
+ * @param {} errPayload
31
+ * @param {} httpDataSeqNum
32
+ */
33
+ function getPVErrorFromGqlError(payload, httpDataSeqNum) {
34
+ const result = {
35
+ [URL_ATT_NAME]: getOnURL(payload.url),
36
+ [TYPE_ATT_NAME]: GQL_EVENT_TYPE,
37
+ [GQL_ERROR_ATT_NAME]: payload,
38
+ };
39
+
40
+ if (httpDataSeqNum || httpDataSeqNum === 0) {
41
+ result[PV_SEQ_ATT_NAME] = httpDataSeqNum;
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * getPVErrorFromXMLHttpRequest will create the error information from
49
+ * an HttpRequest error type
50
+ * @param {} errPayload
51
+ * @param {} httpDataSeqNum
52
+ */
53
+ function getPVErrorFromXMLHttpRequest(errPayload, httpDataSeqNum) {
54
+ const errorObject = {
55
+ [URL_ATT_NAME]: getOnURL(errPayload.responseURL),
56
+ [TYPE_ATT_NAME]: HTTP_EVENT_TYPE,
57
+ [HTTP_CODE_ATT_NAME]: errPayload.status,
58
+ };
59
+ if (httpDataSeqNum || httpDataSeqNum === 0) {
60
+ errorObject[PV_SEQ_ATT_NAME] = httpDataSeqNum;
61
+ }
62
+ return errorObject;
63
+ }
64
+ /**
65
+ * getPVErrorFromErrorEvent will create the error information from an
66
+ * error event type
67
+ * @param {} errPayload
68
+ */
69
+ function getPVErrorFromErrorEvent(errPayload) {
70
+ return {
71
+ [URL_ATT_NAME]: getOnURL(errPayload.filename || 'http://localhost'),
72
+ [TYPE_ATT_NAME]: JS_EVENT_TYPE,
73
+ [JS_ERROR_ATT_NAME]: getJSStack(errPayload.error),
74
+ };
75
+ }
76
+
77
+ /** getPVErrorFromErrorLog extracts an error from an error log
78
+ * @param {} errPayload
79
+ */
80
+ function getPVErrorFromErrorLog(errPayload) {
81
+ return {
82
+ // default to window url
83
+ [URL_ATT_NAME]: getOnURL(
84
+ (window.location && window.location.href) || 'http://localhost',
85
+ ),
86
+ [TYPE_ATT_NAME]: JS_EVENT_TYPE,
87
+ [JS_ERROR_ATT_NAME]: getJSStack(errPayload),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Sets an error message to pvError by a given error event.
93
+ * @param {string} type
94
+ * @param {} pvError
95
+ * @param {} originalError
96
+ */
97
+ function setErrorMessageForUnhandledEvent(type, pvError, originalError) {
98
+ let detail = null;
99
+ if (originalError.detail) {
100
+ try {
101
+ detail = stringifyJSON(originalError.detail);
102
+ } catch (e) {
103
+ detail = 'non-serializable';
104
+ }
105
+ }
106
+ let message = `Fire error event of type ${type}.`;
107
+ if (detail != null) {
108
+ message += ` Detail: ${detail}.`;
109
+ }
110
+ pvError.j_err.msg = getMaxSubstringAllowed(message); // eslint-disable-line no-param-reassign
111
+ }
112
+
113
+ /**
114
+ * createPageVisitEventError returns creates an error information
115
+ * object based on the type of error
116
+ * @param {} type
117
+ * @param {} errPayload
118
+ * @param {} httpDataSeqNum
119
+ */
120
+ function createPageVisitEventError(type, errPayload, httpDataSeqNum) {
121
+ // We have seen that errors from requireJS contain the originalError
122
+ // attribute which is the real error. If we do not extract the originalError we are
123
+ // not looking at the full error available.
124
+ // https://github.com/requirejs/requirejs/blob/master/require.js#L172
125
+ let originalError = errPayload;
126
+ if (errPayload.error && errPayload.error.originalError) {
127
+ originalError = errPayload.error.originalError;
128
+ }
129
+
130
+ let pvError = {};
131
+ switch (type) {
132
+ case XML_HTTP_REQUEST_ERROR_TYPE: {
133
+ pvError = getPVErrorFromXMLHttpRequest(originalError, httpDataSeqNum);
134
+ break;
135
+ }
136
+ case ERROR_EVENT_ERROR_TYPE: {
137
+ pvError = getPVErrorFromErrorEvent(originalError);
138
+ break;
139
+ }
140
+ case RESPONSE_ERROR_TYPE: {
141
+ pvError = getPVErrorFromResponse(originalError, httpDataSeqNum);
142
+ break;
143
+ }
144
+ case GQL_ERROR_TYPE: {
145
+ // The error is retrieved from the response of the server
146
+ // eslint-disable-next-line no-param-reassign
147
+ type = RESPONSE_ERROR_TYPE;
148
+ pvError = getPVErrorFromGqlError(originalError, httpDataSeqNum);
149
+ break;
150
+ }
151
+ case WRAPPED_EXCEPTION_ERROR_TYPE: {
152
+ pvError = getPVErrorFromErrorEvent(originalError);
153
+ break;
154
+ }
155
+ // the fetch handler makes sure that we create the appropriate
156
+ // error type before sending it for processing thus we do not
157
+ // need a specific error handler for fetch errors.
158
+ case FETCH_EXCEPTION_ERROR_TYPE: {
159
+ pvError = getPVErrorFromErrorEvent(originalError);
160
+ break;
161
+ }
162
+ case ERROR_LOG_EVENT_ERROR_TYPE: {
163
+ pvError = getPVErrorFromErrorLog(originalError);
164
+ break;
165
+ }
166
+ case ERROR_EVENT_UNHANDLED_REJECTION_TYPE: {
167
+ pvError = getPVErrorFromErrorEvent(originalError);
168
+ break;
169
+ }
170
+ case CUSTOM_ERROR_EVENT_TYPE: {
171
+ pvError = getPVErrorFromErrorEvent(originalError);
172
+ break;
173
+ }
174
+ default: {
175
+ // TODO: report to us that we sent an invalid type
176
+ // users have their own type that they will be passing, does not warrant
177
+ // a failure. We try to process it as an error.
178
+ try {
179
+ pvError = getPVErrorFromErrorEvent(originalError);
180
+ if (window.Event && originalError instanceof window.Event) {
181
+ // generate error message
182
+ setErrorMessageForUnhandledEvent(type, pvError, originalError);
183
+ // override type so metroplex can accept it
184
+ type = ERROR_EVENT_ERROR_TYPE; // eslint-disable-line no-param-reassign
185
+ }
186
+ } catch (e) {
187
+ // TODO: report to us that this failed
188
+ return null;
189
+ }
190
+ }
191
+ }
192
+
193
+ // setting the source of this error to be the type
194
+ pvError[ERROR_SOURCE_ATT_NAME] = type;
195
+ return pvError;
196
+ }
197
+
198
+ /**
199
+ * determines if an error is a collect error
200
+ * @param {} pvError
201
+ */
202
+ function isCollectError(pvError) {
203
+ // checking if this error originated from the Noibu script
204
+ if (pvError[TYPE_ATT_NAME] === JS_EVENT_TYPE) {
205
+ if (pvError[JS_ERROR_ATT_NAME]) {
206
+ const frames = pvError[JS_ERROR_ATT_NAME][JS_STACK_FRAMES_ATT_NAME];
207
+ if (frames && frames.length > 0) {
208
+ const lowerCapFile = frames[0][JS_STACK_FILE_ATT_NAME].toLowerCase();
209
+ const lowerCapMethod =
210
+ frames[0][JS_STACK_METHOD_ATT_NAME].toLowerCase();
211
+
212
+ if (
213
+ lowerCapFile.includes('noibu') &&
214
+ !lowerCapMethod.includes('nbuwrapper')
215
+ ) {
216
+ // if the first file in the stack is Noibu then we do not
217
+ // queue this error for analytics, we send it to another
218
+ // endpoint to alert us
219
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
220
+ pvError,
221
+ false,
222
+ SEVERITY_ERROR,
223
+ );
224
+ return true;
225
+ }
226
+ }
227
+ }
228
+ // checking if the message contains noibu.
229
+ if (pvError[JS_STACK_MESSAGE_ATT_NAME]) {
230
+ const lowerCapMessage = pvError[JS_STACK_MESSAGE_ATT_NAME].toLowerCase();
231
+ // we do not want all errors that contain noibu in their messages to be ignored
232
+ // only the errors that we are confident are not impact the user.
233
+ if (
234
+ lowerCapMessage.includes('input.noibu') ||
235
+ lowerCapMessage.includes('input.b.noibu')
236
+ ) {
237
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
238
+ pvError,
239
+ false,
240
+ SEVERITY_ERROR,
241
+ );
242
+ return true;
243
+ }
244
+ }
245
+ } else if (
246
+ pvError[TYPE_ATT_NAME] === HTTP_EVENT_TYPE &&
247
+ pvError[URL_ATT_NAME]
248
+ ) {
249
+ const currURL = pvError[URL_ATT_NAME];
250
+ if (typeof currURL === 'string') {
251
+ // this means that our endpoing has failed and we need to
252
+ // send an alert to stack driver
253
+ for (let i = 0; i < NOIBU_INPUT_URLS.length; i += 1) {
254
+ const inputURL = NOIBU_INPUT_URLS[i];
255
+ if (currURL.includes(inputURL)) {
256
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
257
+ pvError,
258
+ false,
259
+ SEVERITY_ERROR,
260
+ );
261
+ return true;
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ return false;
268
+ }
269
+
270
+ /** Saves the error to the ErrorQueue
271
+ * @param {} type
272
+ * @param {} payload
273
+ * @param {} httpDataSeqNum
274
+ */
275
+ function saveErrorToPagevisit(type, payload, httpDataSeqNum) {
276
+ // We do not want to process errors events as errors (yet)
277
+ if (type === EVENT_ERROR_TYPE) {
278
+ return;
279
+ }
280
+ const pvError = createPageVisitEventError(type, payload, httpDataSeqNum);
281
+
282
+ // making sure we have the error object defined
283
+ if (!pvError || !pvError[URL_ATT_NAME]) {
284
+ return;
285
+ }
286
+
287
+ const currErrURL = pvError[URL_ATT_NAME];
288
+
289
+ // We need valid URLs in order to check their blacklist status
290
+ if (isValidURL(currErrURL)) {
291
+ new URL(currErrURL);
292
+
293
+ // if the domain is blacklisted or the protocol is not http(s)
294
+ // we drop the error
295
+ /* if ( // todo we don't support hostname on react native yet
296
+ urlOb.hostname in blacklisedDomains ||
297
+ !urlOb.protocol.startsWith('http')
298
+ ) {
299
+ return;
300
+ } */
301
+ }
302
+
303
+ if (isCollectError(pvError)) {
304
+ return;
305
+ }
306
+
307
+ // we register an error event
308
+ StoredMetrics.getInstance().addError();
309
+ // debounce event
310
+ InputMonitor.getInstance().addEvent(pvError, ERROR_EVENT_TYPE);
311
+ }
312
+
313
+ export { isCollectError, saveErrorToPagevisit };
@@ -0,0 +1,115 @@
1
+ import { getMaxSubstringAllowed, asString, getProperGlobalUrl } from '../../utils/function.js';
2
+ import { timestampWrapper } from '../../utils/date.js';
3
+ import { InputMonitor } from '../../monitors/inputMonitor.js';
4
+ import { HTTP_METHOD_ATT_NAME, MAX_HTTP_DATA_EVENT_COUNT, PV_SEQ_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, NOIBUJS_CONFIG, HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME, SEVERITY_ERROR, HTTP_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME } from '../../constants.js';
5
+ import { PageVisit } from '../pageVisit.js';
6
+ import StoredMetrics from '../../api/storedMetrics.js';
7
+ import MetroplexSocket from '../../api/metroplexSocket.js';
8
+ import ClientConfig from '../../api/clientConfig.js';
9
+
10
+ /** @module PageVisitEventHTTP */
11
+
12
+ /**
13
+ * Determines if a response is a failure
14
+ * @param {number} code
15
+ */
16
+ function isHttpCodeFailure(code) {
17
+ if (typeof code !== 'number') {
18
+ return true;
19
+ }
20
+
21
+ return code >= 400 || code <= 0;
22
+ }
23
+
24
+ /** Class representing a PageVisitEventHTTP */
25
+ class PageVisitEventHTTP {
26
+ /**
27
+ * Creates an instance of the http event for the pv
28
+ * @param {} httpEvent
29
+ * @param {} httpData
30
+ */
31
+ constructor(httpEvent, httpData) {
32
+ const mutatedHttpEvent = httpEvent;
33
+ // TODO: implement attribute checking in this function
34
+ // we force the response time to at least be 0 if it is negative or if its not present
35
+ if (!mutatedHttpEvent.resp_time || mutatedHttpEvent.resp_time < 0) {
36
+ mutatedHttpEvent.resp_time = 0;
37
+ }
38
+
39
+ // setting the method to be upper case
40
+ mutatedHttpEvent[HTTP_METHOD_ATT_NAME] =
41
+ httpEvent[HTTP_METHOD_ATT_NAME].toUpperCase();
42
+
43
+ mutatedHttpEvent.url = getMaxSubstringAllowed(
44
+ asString(mutatedHttpEvent.url),
45
+ );
46
+
47
+ this.httpEvent = mutatedHttpEvent;
48
+ this.httpData = httpData;
49
+ }
50
+
51
+ /** Saves the HTTP event to the pageVisit Queue */
52
+ saveHTTPEvent() {
53
+ // we do not store http events that have empty urls
54
+ if (
55
+ !this.httpEvent ||
56
+ !this.httpEvent.url ||
57
+ this.httpEvent.url.trim() === ''
58
+ ) {
59
+ return;
60
+ }
61
+ // we register an http event
62
+ StoredMetrics.getInstance().addHttpEvent();
63
+
64
+ // send http data down to metroplex
65
+ if (this.httpData) {
66
+ // add the sequence number to both events
67
+ const sequenceNumber = StoredMetrics.getInstance().httpSequenceNumber;
68
+ // restrict total number of events collected per page visit to ensure we don't
69
+ // blow up memory and storage usage
70
+ if (sequenceNumber < MAX_HTTP_DATA_EVENT_COUNT) {
71
+ this.httpData[PV_SEQ_ATT_NAME] = sequenceNumber;
72
+ this.httpEvent[PV_SEQ_ATT_NAME] = sequenceNumber;
73
+ // increment the count
74
+ StoredMetrics.getInstance().addHttpData();
75
+
76
+ const metroplexMsg = {};
77
+ metroplexMsg[PAGE_VISIT_HTTP_DATA_ATT_NAME] = this.httpData;
78
+ MetroplexSocket.getInstance().sendMessage(
79
+ HTTP_DATA_METROPLEX_TYPE,
80
+ metroplexMsg,
81
+ );
82
+
83
+ if (getProperGlobalUrl().includes('checkout.mec.ca')) {
84
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
85
+ `Sending PVH to Metro: ${JSON.stringify(this.httpData)}
86
+
87
+ Whitelisted URLs: ${NOIBUJS_CONFIG()[HTTP_DATA_PAYLOAD_URL_REGEXES_FLAG_NAME]}`,
88
+ false,
89
+ SEVERITY_ERROR,
90
+ );
91
+ }
92
+ } else {
93
+ // have collected more than the max number of http requests for this
94
+ // page visit, so increment the over request limit count
95
+ StoredMetrics.getInstance().addHttpDataOverLimit();
96
+ }
97
+ }
98
+ // if this was an error, send immediately so we don't lose it
99
+ if (isHttpCodeFailure(this.status)) {
100
+ PageVisit.getInstance().addPageVisitEvent(
101
+ {
102
+ event: this.httpEvent,
103
+ occurredAt: new Date(timestampWrapper(Date.now())).toISOString(),
104
+ },
105
+ HTTP_EVENT_TYPE,
106
+ );
107
+ return;
108
+ }
109
+
110
+ // debounce event
111
+ InputMonitor.getInstance().addEvent(this.httpEvent, HTTP_EVENT_TYPE);
112
+ }
113
+ }
114
+
115
+ export { PageVisitEventHTTP, isHttpCodeFailure };
@@ -0,0 +1,20 @@
1
+ import { asString } from '../../utils/function.js';
2
+ import { CSS_CLASS_ATT_NAME } from '../../constants.js';
3
+
4
+ /** @module UserStep */
5
+ /**
6
+ * updates the payload of a user step in order to stringify the css class
7
+ * @param {} payload
8
+ */
9
+ function updatePayload(payload) {
10
+ // In certain cases the class returned by the attribute will be an
11
+ // object. This will break the metroplex validation and thus needs to be
12
+ // stringified
13
+ const updatedPayload = payload;
14
+ updatedPayload[CSS_CLASS_ATT_NAME] = asString(
15
+ updatedPayload[CSS_CLASS_ATT_NAME],
16
+ );
17
+ return updatedPayload;
18
+ }
19
+
20
+ export { updatePayload };
@@ -0,0 +1,72 @@
1
+ import * as React from 'react';
2
+ export declare const UNKNOWN_COMPONENT = "unknown";
3
+ export type FallbackRender = (errorData: {
4
+ error: Error;
5
+ componentStack: string | null;
6
+ eventId: string | null;
7
+ resetError(): void;
8
+ }) => React.ReactElement;
9
+ export type ErrorBoundaryProps = {
10
+ children?: React.ReactNode | (() => React.ReactNode);
11
+ /**
12
+ * A fallback component that gets rendered when the error boundary encounters an error.
13
+ *
14
+ * Can either provide a React Component, or a function that returns React Component as
15
+ * a valid fallback prop. If a function is provided, the function will be called with
16
+ * the error, the component stack, and a function that resets the error boundary on error.
17
+ *
18
+ */
19
+ fallback?: React.ReactElement | FallbackRender;
20
+ /** Called when the error boundary encounters an error */
21
+ onError?(error: Error, componentStack: string, eventId: string): void;
22
+ /** Called on componentDidMount() */
23
+ onMount?(): void;
24
+ /** Called if resetError() is called from the fallback render props function */
25
+ onReset?(error: Error | null, componentStack: string | null, eventId: string | null): void;
26
+ /** Called on componentWillUnmount() */
27
+ onUnmount?(error: Error | null, componentStack: string | null, eventId: string | null): void;
28
+ };
29
+ export type ErrorBoundaryState = {
30
+ componentStack: React.ErrorInfo['componentStack'] | null;
31
+ error: Error | null;
32
+ eventId: string | null;
33
+ };
34
+ /**
35
+ * @description A ErrorBoundary component that logs errors to Noibu. Requires React >= 16.
36
+ * @extends {Component<ErrorBoundaryProps, ErrorBoundaryState>}
37
+ */
38
+ declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
39
+ state: ErrorBoundaryState;
40
+ /**
41
+ * Lifecycle hook on mount
42
+ * @returns void
43
+ */
44
+ componentDidMount(): void;
45
+ /**
46
+ *
47
+ * Handler for all errors that happen in a wrapped component
48
+ * @param {Error&{cause?:Error}} error
49
+ * @param {React.ErrorInfo} {componentStack}
50
+ * @returns void
51
+ */
52
+ componentDidCatch(error: Error & {
53
+ cause?: Error;
54
+ }, { componentStack }: React.ErrorInfo): void;
55
+ /**
56
+ * * Lifecycle hook on unmount
57
+ * @returns void
58
+ */
59
+ componentWillUnmount(): void;
60
+ /**
61
+ * Callback from fallback to reset the error boundary
62
+ * @param {} =>void=(
63
+ */
64
+ resetErrorBoundary: () => void;
65
+ /**
66
+ *
67
+ * Renders the fallback ui
68
+ * @returns {React.ReactNode}
69
+ */
70
+ render(): React.ReactNode;
71
+ }
72
+ export { ErrorBoundary };