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,295 @@
|
|
|
1
|
+
import { replace } from '../utils/object.js';
|
|
2
|
+
import 'stacktrace-parser';
|
|
3
|
+
import 'react-native-device-info';
|
|
4
|
+
import { ERROR_EVENT_ERROR_TYPE, EVENT_TARGETS, CONSOLE_FUNCTION_OVERRIDES, WRAPPED_EXCEPTION_ERROR_TYPE, ERROR_LOG_EVENT_ERROR_TYPE } from '../constants.js';
|
|
5
|
+
import { saveErrorToPagevisit } from '../pageVisit/pageVisitEventError/pageVisitEventError.js';
|
|
6
|
+
import 'react-native-uuid';
|
|
7
|
+
|
|
8
|
+
/** @module ErrorMonitor */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* will make sure that we ignore the next error caught by the error event listener
|
|
12
|
+
* ignoreError is used to not store errors that were generated by noibu wrapped
|
|
13
|
+
* code twice (once by wrapped code, once by error event listener).
|
|
14
|
+
*/
|
|
15
|
+
function ignoreNextError() {
|
|
16
|
+
|
|
17
|
+
// this works because setTimeout without a delay fires
|
|
18
|
+
// as the immidiate next event in the event loop. Which should
|
|
19
|
+
// be right after the error that we throw before callign this error
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* wraps a provided function into a function that try and catches
|
|
26
|
+
* the original function, doing so allows us to catch errors
|
|
27
|
+
* functionToWrap: function to be wrapped
|
|
28
|
+
* @param {} functionToWrap
|
|
29
|
+
*/
|
|
30
|
+
function wrap(functionToWrap) {
|
|
31
|
+
if (typeof functionToWrap !== 'function') {
|
|
32
|
+
return functionToWrap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// the function was wrapped already
|
|
37
|
+
if (functionToWrap.__noibu__) {
|
|
38
|
+
return functionToWrap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// trying to wrap a function that was already wrapped in the past,
|
|
42
|
+
// return the wrapped value
|
|
43
|
+
if (functionToWrap.__noibu_wrapped__) {
|
|
44
|
+
return functionToWrap.__noibu_wrapped__;
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// silently failling
|
|
48
|
+
return functionToWrap;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* wrapper for most prototypes we know can generate errors
|
|
52
|
+
* @param {} ...args
|
|
53
|
+
*/
|
|
54
|
+
const nbuWrapper = function (...args) {
|
|
55
|
+
// Trying to call the function in a wrapped try and catch
|
|
56
|
+
// this will generate an error in the console if there is one.
|
|
57
|
+
try {
|
|
58
|
+
// Recursively wrap arguments in case that some are
|
|
59
|
+
// functions
|
|
60
|
+
const wrappedArguments = Array.prototype.slice
|
|
61
|
+
.call(args)
|
|
62
|
+
.map(arg => wrap(arg));
|
|
63
|
+
|
|
64
|
+
// checking if the function is served through the handleEvent property
|
|
65
|
+
// for compatibility reasons
|
|
66
|
+
if (functionToWrap.handleEvent) {
|
|
67
|
+
return functionToWrap.handleEvent.apply(this, wrappedArguments);
|
|
68
|
+
}
|
|
69
|
+
return functionToWrap.apply(this, wrappedArguments);
|
|
70
|
+
// user land
|
|
71
|
+
} catch (exception) {
|
|
72
|
+
// we need to ignore this error during the onerror otherwise
|
|
73
|
+
// we catch it 2x
|
|
74
|
+
saveErrorToPagevisit(WRAPPED_EXCEPTION_ERROR_TYPE, {
|
|
75
|
+
error: exception,
|
|
76
|
+
});
|
|
77
|
+
ignoreNextError();
|
|
78
|
+
throw exception;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
84
|
+
for (const property in functionToWrap) {
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(functionToWrap, property)) {
|
|
86
|
+
nbuWrapper[property] = functionToWrap[property];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
// silently failling
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
nbuWrapper.prototype = functionToWrap.prototype || {};
|
|
94
|
+
|
|
95
|
+
Object.defineProperty(functionToWrap, '__noibu_wrapped__', {
|
|
96
|
+
enumerable: false,
|
|
97
|
+
value: nbuWrapper,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Signal that this function has been wrapped/filled already
|
|
101
|
+
// for both debugging and to prevent it to being wrapped/filled twice
|
|
102
|
+
Object.defineProperties(nbuWrapper, {
|
|
103
|
+
__noibu__: {
|
|
104
|
+
enumerable: false,
|
|
105
|
+
value: true,
|
|
106
|
+
},
|
|
107
|
+
__noibu_original__: {
|
|
108
|
+
enumerable: false,
|
|
109
|
+
value: functionToWrap,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Restore original function name (not all browsers allow that)
|
|
114
|
+
try {
|
|
115
|
+
const descriptor = Object.getOwnPropertyDescriptor(nbuWrapper, 'name');
|
|
116
|
+
if (descriptor.configurable) {
|
|
117
|
+
Object.defineProperty(nbuWrapper, 'name', {
|
|
118
|
+
// eslint-disable-next-line require-jsdoc
|
|
119
|
+
get() {
|
|
120
|
+
return functionToWrap.name;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return functionToWrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return nbuWrapper;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* transform a log into an error since React hides component errors
|
|
133
|
+
* tps://reactjs.org/docs/error-boundaries.html
|
|
134
|
+
* @param {} errorLog
|
|
135
|
+
*/
|
|
136
|
+
function processErrorLog(errorLog) {
|
|
137
|
+
// handle empty arguments
|
|
138
|
+
if (!errorLog) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const { message, stack } = errorLog;
|
|
142
|
+
// for now we only process error logs if they are a way to describe an error
|
|
143
|
+
if (!stack || !message) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
saveErrorToPagevisit(ERROR_LOG_EVENT_ERROR_TYPE, { message, stack });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** iterates arguments to try to extract errors from them
|
|
151
|
+
* @param {} argsFromErrorLog
|
|
152
|
+
*/
|
|
153
|
+
function processErrorLogArguments(argsFromErrorLog) {
|
|
154
|
+
// we may receive multiple arguments
|
|
155
|
+
argsFromErrorLog.forEach(arg => {
|
|
156
|
+
// ensure arg exists before attempting processing
|
|
157
|
+
if (!arg) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// if we pass an array of elements in the console, we want to go through that
|
|
161
|
+
// away and make sure we are extracting the error object. We dont do nested arrays.
|
|
162
|
+
if (Array.isArray(arg)) {
|
|
163
|
+
arg.forEach(nestedArg => {
|
|
164
|
+
processErrorLog(nestedArg);
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
processErrorLog(arg);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* wraps and replaces the addEventListener property
|
|
174
|
+
* of event targets to try and catch errors
|
|
175
|
+
* eventTargetString: event target name
|
|
176
|
+
*/
|
|
177
|
+
function configureEventListeners() {
|
|
178
|
+
EVENT_TARGETS.forEach(eventTargetString => {
|
|
179
|
+
const eventTarget = window[eventTargetString];
|
|
180
|
+
// The event target needs to have a prototype and have the
|
|
181
|
+
// addEventListener function
|
|
182
|
+
const proto = eventTarget && eventTarget.prototype;
|
|
183
|
+
if (
|
|
184
|
+
!proto ||
|
|
185
|
+
!proto.hasOwnProperty ||
|
|
186
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
187
|
+
!proto.hasOwnProperty('addEventListener')
|
|
188
|
+
) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
replace(
|
|
192
|
+
proto,
|
|
193
|
+
'addEventListener',
|
|
194
|
+
originalFunction =>
|
|
195
|
+
// We set nbuWrapper to the handler function so if this returns an error
|
|
196
|
+
// the trace message will start with nbuWrapper which would allow us to
|
|
197
|
+
// detect that this is a wrapped function and not a NoibuJS error
|
|
198
|
+
function nbuWrapper(eventName, handler, capture) {
|
|
199
|
+
// testPassive is done by passing a null handler, so we code
|
|
200
|
+
// against that
|
|
201
|
+
if (!handler) {
|
|
202
|
+
return originalFunction.call(this, eventName, handler, capture);
|
|
203
|
+
}
|
|
204
|
+
let wrappedHandler;
|
|
205
|
+
// Checking if handleEvent is explicitely
|
|
206
|
+
// passed for compatibility reason
|
|
207
|
+
if (handler.handleEvent) {
|
|
208
|
+
const wrappedHandleEvent = wrap(handler.handleEvent.bind(handler));
|
|
209
|
+
// in some case the handler is an object that contains much more than just the
|
|
210
|
+
// the single handle event property, in such cases we need to make sure we are not
|
|
211
|
+
// overwritting any other critical functionality by wrapping the event handler
|
|
212
|
+
wrappedHandler = handler;
|
|
213
|
+
wrappedHandler.handleEvent = wrappedHandleEvent;
|
|
214
|
+
} else {
|
|
215
|
+
wrappedHandler = wrap(handler);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return originalFunction.call(
|
|
219
|
+
this,
|
|
220
|
+
eventName,
|
|
221
|
+
wrappedHandler,
|
|
222
|
+
capture,
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// replacing the removeEventListener function as well to wrap the listener
|
|
228
|
+
replace(
|
|
229
|
+
proto,
|
|
230
|
+
'removeEventListener',
|
|
231
|
+
originalFunction =>
|
|
232
|
+
// We set nbuWrapper to the handler function so if this returns an error
|
|
233
|
+
// the trace message will start with nbuWrapper which would allow us to
|
|
234
|
+
// detect that this is a wrapped function and not a NoibuJS error
|
|
235
|
+
function nbuWrapper(eventName, handler, options) {
|
|
236
|
+
let callback = handler;
|
|
237
|
+
try {
|
|
238
|
+
callback = callback && (callback.__noibu_wrapped__ || callback);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
// fail silently
|
|
241
|
+
}
|
|
242
|
+
return originalFunction.call(this, eventName, callback, options);
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// we wrap all logging to catch any potential errors passed in logs
|
|
248
|
+
CONSOLE_FUNCTION_OVERRIDES.forEach(consoleFunction => {
|
|
249
|
+
if (!window.console || !window.console[consoleFunction]) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
replace(
|
|
253
|
+
window.console,
|
|
254
|
+
consoleFunction,
|
|
255
|
+
originalFunction =>
|
|
256
|
+
// We set nbuWrapper to the handler function so if this returns an error
|
|
257
|
+
// the trace message will start with nbuWrapper which would allow us to
|
|
258
|
+
// detect that this is a wrapped function and not a NoibuJS error
|
|
259
|
+
function nbuWrapper() {
|
|
260
|
+
// ignoring this linting error since we don't want
|
|
261
|
+
// potentially overide the actual functioning of console.error
|
|
262
|
+
// eslint-disable-next-line prefer-rest-params
|
|
263
|
+
originalFunction.call(window.console, ...arguments);
|
|
264
|
+
// converting the arguments to an array so that we do not nest argument objects
|
|
265
|
+
// eslint-disable-next-line prefer-rest-params
|
|
266
|
+
processErrorLogArguments(Array.from(arguments));
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Monitors the errors happening on the window
|
|
274
|
+
*/
|
|
275
|
+
function monitorErrors() {
|
|
276
|
+
// wrapping all listeners as soon as possible after we set the above listener
|
|
277
|
+
configureEventListeners();
|
|
278
|
+
global.ErrorUtils.setGlobalHandler((error, isFatal) => {
|
|
279
|
+
saveErrorToPagevisit(ERROR_EVENT_ERROR_TYPE, {
|
|
280
|
+
error,
|
|
281
|
+
});
|
|
282
|
+
}, true);
|
|
283
|
+
if (global.HermesInternal) {
|
|
284
|
+
global.HermesInternal.enablePromiseRejectionTracker({
|
|
285
|
+
allRejections: true,
|
|
286
|
+
onUnhandled: (id, rejection = {}) => {
|
|
287
|
+
saveErrorToPagevisit(ERROR_EVENT_ERROR_TYPE, {
|
|
288
|
+
error: rejection,
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export { monitorErrors, processErrorLogArguments };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { CONTENT_TYPE, SEVERITY_ERROR } from '../constants.js';
|
|
2
|
+
import { isInstanceOf, getMaxSubstringAllowed } from '../utils/function.js';
|
|
3
|
+
import ClientConfig from '../api/clientConfig.js';
|
|
4
|
+
|
|
5
|
+
const MESSAGE_ATT_NAME = 'message';
|
|
6
|
+
const EXTENSIONS_ATT_NAME = 'extensions';
|
|
7
|
+
const LOCATIONS_ATT_NAME = 'locations';
|
|
8
|
+
const PATH_ATT_NAME = 'path';
|
|
9
|
+
const LINE_ATT_NAME = 'line';
|
|
10
|
+
const COLUMN_ATT_NAME = 'column';
|
|
11
|
+
const MESSAGE_MAX_LENGTH = 1000;
|
|
12
|
+
|
|
13
|
+
/* eslint-disable no-restricted-syntax */
|
|
14
|
+
/* eslint-disable no-param-reassign */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Try detecting GraphQL errors from http response
|
|
18
|
+
*/
|
|
19
|
+
class GqlErrorValidator {
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves GQL error object based on fetch request/response
|
|
22
|
+
* @param {String} url
|
|
23
|
+
* @param {{}} options
|
|
24
|
+
* @param {Request} request
|
|
25
|
+
* @param {Response} response - cloned() from original response
|
|
26
|
+
*/
|
|
27
|
+
static async fromFetch(url, options, request, response) {
|
|
28
|
+
try {
|
|
29
|
+
const isResponseValid = isInstanceOf(response, Response) && response.ok;
|
|
30
|
+
if (!isResponseValid) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const contentType = this._getContentTypeFromFetchArguments(
|
|
34
|
+
options,
|
|
35
|
+
request,
|
|
36
|
+
);
|
|
37
|
+
if (this._shouldHandleRequest(url, contentType)) {
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
return this._validate(data, []);
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
// can't read the response if request is aborted
|
|
43
|
+
// so just ignore it
|
|
44
|
+
if (!this._isRequestAborted(options, request)) {
|
|
45
|
+
this._postError(e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves GQL error object based on XHR object
|
|
54
|
+
* @param {String} url
|
|
55
|
+
* @param {XMLHttpRequest} xhr
|
|
56
|
+
*/
|
|
57
|
+
static async fromXhr(url, xhr) {
|
|
58
|
+
try {
|
|
59
|
+
const isResponseValid =
|
|
60
|
+
isInstanceOf(xhr, XMLHttpRequest) &&
|
|
61
|
+
xhr.status >= 200 &&
|
|
62
|
+
xhr.status <= 299;
|
|
63
|
+
if (!isResponseValid) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
let contentType = null;
|
|
67
|
+
if (xhr.noibuRequestHeaders) {
|
|
68
|
+
contentType = xhr.noibuRequestHeaders.get(CONTENT_TYPE);
|
|
69
|
+
}
|
|
70
|
+
if (this._shouldHandleRequest(url, contentType)) {
|
|
71
|
+
let data = null;
|
|
72
|
+
if (xhr.responseType === 'blob') {
|
|
73
|
+
if (xhr.response.text) {
|
|
74
|
+
const content = await xhr.response.text();
|
|
75
|
+
data = this._parseJsonSafely(content);
|
|
76
|
+
}
|
|
77
|
+
} else if (xhr.responseType === 'json') {
|
|
78
|
+
data = xhr.response;
|
|
79
|
+
} else {
|
|
80
|
+
const content = xhr.responseText;
|
|
81
|
+
data = this._parseJsonSafely(content);
|
|
82
|
+
}
|
|
83
|
+
if (data) {
|
|
84
|
+
return this._validate(data, []);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
this._postError(e);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Try safely parse a string and return null if fails
|
|
96
|
+
* @param {String} content
|
|
97
|
+
*/
|
|
98
|
+
static _parseJsonSafely(content) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(content);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Try to get content type for fetch arguments
|
|
108
|
+
* @param {{}} options
|
|
109
|
+
* @param {Request} request
|
|
110
|
+
*/
|
|
111
|
+
static _getContentTypeFromFetchArguments(options, request) {
|
|
112
|
+
let headers = null;
|
|
113
|
+
if (isInstanceOf(request, Request)) {
|
|
114
|
+
headers = request.headers;
|
|
115
|
+
} else if (options && options.headers) {
|
|
116
|
+
headers = new Headers(options.headers);
|
|
117
|
+
}
|
|
118
|
+
let contentType = null;
|
|
119
|
+
if (headers) {
|
|
120
|
+
contentType = headers.get(CONTENT_TYPE);
|
|
121
|
+
}
|
|
122
|
+
return contentType;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Checks if request is aborted
|
|
127
|
+
* If it has been aborder we are not able to consume the response
|
|
128
|
+
* @param {{}} options
|
|
129
|
+
* @param {Request} request
|
|
130
|
+
*/
|
|
131
|
+
static _isRequestAborted(options, request) {
|
|
132
|
+
if (isInstanceOf(request, Request)) {
|
|
133
|
+
return request.signal && request.signal.aborted;
|
|
134
|
+
}
|
|
135
|
+
if (options && isInstanceOf(options.signal, AbortSignal)) {
|
|
136
|
+
return options.signal.aborted;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Determines if request should be processed
|
|
143
|
+
* @param {String|URL} url
|
|
144
|
+
* @param {String} contentType
|
|
145
|
+
*/
|
|
146
|
+
static _shouldHandleRequest(url, contentType) {
|
|
147
|
+
if (contentType) {
|
|
148
|
+
contentType = contentType.toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
let isGqlUrl = false;
|
|
151
|
+
if (url) {
|
|
152
|
+
if (isInstanceOf(url, URL)) {
|
|
153
|
+
url = url.toString();
|
|
154
|
+
}
|
|
155
|
+
isGqlUrl = url.toLowerCase().includes('graphql');
|
|
156
|
+
}
|
|
157
|
+
return (
|
|
158
|
+
(contentType === 'application/json' && isGqlUrl) ||
|
|
159
|
+
contentType === 'application/graphql'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sanitizes payload object
|
|
165
|
+
* @param {any} data
|
|
166
|
+
* @param {Array<String>} validationIssues
|
|
167
|
+
*/
|
|
168
|
+
static _validate(data, validationIssues) {
|
|
169
|
+
let errors = null;
|
|
170
|
+
|
|
171
|
+
if (data && Array.isArray(data.errors)) {
|
|
172
|
+
errors = data.errors;
|
|
173
|
+
|
|
174
|
+
for (const error of errors) {
|
|
175
|
+
const properties = Object.keys(error);
|
|
176
|
+
for (const property of properties) {
|
|
177
|
+
switch (property) {
|
|
178
|
+
case MESSAGE_ATT_NAME:
|
|
179
|
+
this._validateMessage(error);
|
|
180
|
+
break;
|
|
181
|
+
case LOCATIONS_ATT_NAME:
|
|
182
|
+
this._validateLocations(error, validationIssues);
|
|
183
|
+
break;
|
|
184
|
+
case PATH_ATT_NAME:
|
|
185
|
+
this._validatePath(error, validationIssues);
|
|
186
|
+
break;
|
|
187
|
+
case EXTENSIONS_ATT_NAME:
|
|
188
|
+
this._validateExtensions(error);
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
delete error[property];
|
|
192
|
+
validationIssues.push(`unexpected error.${property}`);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (validationIssues.length > 0) {
|
|
199
|
+
this._postValidationIssues(validationIssues);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return errors;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Sanitizes message object
|
|
208
|
+
* @param {any} error
|
|
209
|
+
*/
|
|
210
|
+
static _validateMessage(error) {
|
|
211
|
+
error[MESSAGE_ATT_NAME] = getMaxSubstringAllowed(
|
|
212
|
+
error[MESSAGE_ATT_NAME],
|
|
213
|
+
MESSAGE_MAX_LENGTH,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Sanitizes extensions object
|
|
219
|
+
* @param {any} error
|
|
220
|
+
* @param {Array<String>} validationIssues
|
|
221
|
+
*/
|
|
222
|
+
static _validateExtensions(error) {
|
|
223
|
+
const json = JSON.stringify(error[EXTENSIONS_ATT_NAME]);
|
|
224
|
+
error[EXTENSIONS_ATT_NAME] = getMaxSubstringAllowed(
|
|
225
|
+
json,
|
|
226
|
+
MESSAGE_MAX_LENGTH,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Sanitizes locations object
|
|
232
|
+
* @param {any} error
|
|
233
|
+
* @param {Array<String>} validationIssues
|
|
234
|
+
*/
|
|
235
|
+
static _validateLocations(error, validationIssues) {
|
|
236
|
+
const locations = error[LOCATIONS_ATT_NAME];
|
|
237
|
+
if (Array.isArray(locations)) {
|
|
238
|
+
for (const location of locations) {
|
|
239
|
+
const properties = Object.keys(location);
|
|
240
|
+
for (const property of properties) {
|
|
241
|
+
switch (property) {
|
|
242
|
+
case LINE_ATT_NAME:
|
|
243
|
+
case COLUMN_ATT_NAME:
|
|
244
|
+
if (!Number.isSafeInteger(location[property])) {
|
|
245
|
+
const value = location[property];
|
|
246
|
+
location[property] = 0;
|
|
247
|
+
validationIssues.push(
|
|
248
|
+
`unexpected ${property} value '${value}'`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
default:
|
|
253
|
+
delete location[property];
|
|
254
|
+
validationIssues.push(`unexpected error.location.${property}`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
delete error[LOCATIONS_ATT_NAME];
|
|
261
|
+
validationIssues.push(`unexpected error.locations`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Sanitizes path object
|
|
267
|
+
* @param {any} error
|
|
268
|
+
* @param {Array<String>} validationIssues
|
|
269
|
+
*/
|
|
270
|
+
static _validatePath(error, validationIssues) {
|
|
271
|
+
const path = error[PATH_ATT_NAME];
|
|
272
|
+
if (Array.isArray(path)) {
|
|
273
|
+
error[PATH_ATT_NAME] = error[PATH_ATT_NAME].map(x => x.toString());
|
|
274
|
+
} else {
|
|
275
|
+
delete error[PATH_ATT_NAME];
|
|
276
|
+
validationIssues.push(`unexpected error.path`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Posts error
|
|
282
|
+
* @param {String} message
|
|
283
|
+
*/
|
|
284
|
+
static _postError(message) {
|
|
285
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
286
|
+
`GQL parse error: ${message}`,
|
|
287
|
+
false,
|
|
288
|
+
SEVERITY_ERROR,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Posts issue found during object sanitization
|
|
294
|
+
* @param {Array<String>} validationIssues
|
|
295
|
+
*/
|
|
296
|
+
static _postValidationIssues(validationIssues) {
|
|
297
|
+
const message = validationIssues.join(',');
|
|
298
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
299
|
+
`GQL error validation warning: ${message}`,
|
|
300
|
+
false,
|
|
301
|
+
SEVERITY_ERROR,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export { GqlErrorValidator as default };
|