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.
- package/README.md +155 -0
- package/dist/api/clientConfig.js +416 -0
- package/dist/api/helpCode.js +106 -0
- package/dist/api/inputManager.js +233 -0
- package/dist/api/metroplexSocket.js +882 -0
- package/dist/api/storedMetrics.js +201 -0
- package/dist/api/storedPageVisit.js +235 -0
- package/dist/const_matchers.js +260 -0
- package/dist/constants.d.ts +264 -0
- package/dist/constants.js +528 -0
- package/dist/entry/index.d.ts +8 -0
- package/dist/entry/index.js +15 -0
- package/dist/entry/init.js +91 -0
- package/dist/monitors/clickMonitor.js +284 -0
- package/dist/monitors/elementMonitor.js +174 -0
- package/dist/monitors/errorMonitor.js +295 -0
- package/dist/monitors/gqlErrorValidator.js +306 -0
- package/dist/monitors/httpDataBundler.js +665 -0
- package/dist/monitors/inputMonitor.js +130 -0
- package/dist/monitors/keyboardInputMonitor.js +67 -0
- package/dist/monitors/locationChangeMonitor.js +30 -0
- package/dist/monitors/pageMonitor.js +119 -0
- package/dist/monitors/requestMonitor.js +679 -0
- package/dist/pageVisit/pageVisit.js +172 -0
- package/dist/pageVisit/pageVisitEventError/pageVisitEventError.js +313 -0
- package/dist/pageVisit/pageVisitEventHTTP/pageVisitEventHTTP.js +115 -0
- package/dist/pageVisit/userStep/userStep.js +20 -0
- package/dist/react/ErrorBoundary.d.ts +72 -0
- package/dist/react/ErrorBoundary.js +102 -0
- package/dist/storage/localStorageProvider.js +23 -0
- package/dist/storage/rnStorageProvider.js +62 -0
- package/dist/storage/sessionStorageProvider.js +23 -0
- package/dist/storage/storage.js +119 -0
- package/dist/storage/storageProvider.js +83 -0
- package/dist/utils/date.js +62 -0
- package/dist/utils/eventlistener.js +67 -0
- package/dist/utils/function.js +398 -0
- package/dist/utils/object.js +144 -0
- package/dist/utils/performance.js +21 -0
- 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 };
|