noibu-react-native 0.2.1 → 0.2.3
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 +1 -1
- package/android/src/main/java/com/noibu/sessionreplay/reactnative/NoibuSessionReplayModule.kt +10 -0
- package/dist/api/clientConfig.js +225 -217
- package/dist/api/metroplexSocket.js +406 -416
- package/dist/constants.js +14 -2
- package/dist/entry/init.js +58 -56
- package/dist/monitors/appNavigationMonitor.js +2 -3
- package/dist/monitors/clickMonitor.js +16 -9
- package/dist/monitors/errorMonitor.js +30 -8
- package/dist/monitors/gqlErrorValidator.js +4 -4
- package/dist/monitors/httpDataBundler.js +525 -713
- package/dist/monitors/integrations/react-native-navigation-integration.js +4 -2
- package/dist/monitors/requestMonitor.js +350 -365
- package/dist/pageVisit/eventDebouncer.js +110 -0
- package/dist/pageVisit/pageVisitEventError.js +1 -1
- package/dist/pageVisit/pageVisitEventHTTP.js +78 -93
- package/dist/react/ErrorBoundary.js +18 -15
- package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +3 -2
- package/dist/sessionRecorder/sessionRecorder.js +151 -150
- package/dist/{api → src/api}/clientConfig.d.ts +1 -1
- package/dist/{api → src/api}/metroplexSocket.d.ts +25 -25
- package/dist/{constants.d.ts → src/constants.d.ts} +44 -0
- package/dist/{entry → src/entry}/init.d.ts +1 -1
- package/dist/{monitors → src/monitors}/clickMonitor.d.ts +1 -1
- package/dist/{monitors → src/monitors}/gqlErrorValidator.d.ts +6 -6
- package/dist/src/monitors/httpDataBundler.d.ts +127 -0
- package/dist/src/monitors/requestMonitor.d.ts +10 -0
- package/dist/src/pageVisit/eventDebouncer.d.ts +31 -0
- package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +25 -0
- package/dist/{sessionRecorder → src/sessionRecorder}/types.d.ts +1 -1
- package/dist/{storage → src/storage}/rnStorageProvider.d.ts +1 -1
- package/dist/{storage → src/storage}/storage.d.ts +1 -1
- package/dist/{storage → src/storage}/storageProvider.d.ts +3 -3
- package/dist/{utils → src/utils}/function.d.ts +25 -4
- package/dist/{utils → src/utils}/object.d.ts +9 -4
- package/dist/src/utils/piiRedactor.d.ts +11 -0
- package/dist/src/utils/polyfills.d.ts +7 -0
- package/dist/storage/rnStorageProvider.js +7 -5
- package/dist/storage/storage.js +43 -35
- package/dist/storage/storageProvider.js +23 -19
- package/dist/types/Config.d.ts +24 -20
- package/dist/types/PageVisit.types.d.ts +151 -0
- package/dist/types/PageVisitMetrics.types.d.ts +27 -0
- package/dist/types/RRWeb.d.ts +48 -0
- package/dist/types/StoredPageVisit.types.d.ts +2 -4
- package/dist/types/WrappedObjects.d.ts +6 -0
- package/dist/utils/function.js +110 -76
- package/dist/utils/object.js +58 -6
- package/dist/utils/piiRedactor.js +98 -0
- package/dist/utils/polyfills.js +24 -0
- package/package.json +24 -9
- package/dist/monitors/httpDataBundler.d.ts +0 -161
- package/dist/monitors/requestMonitor.d.ts +0 -10
- package/dist/pageVisit/pageVisitEventHTTP.d.ts +0 -18
- package/dist/types/PageVisit.d.ts +0 -22
- package/dist/types/globals.d.ts +0 -45
- /package/dist/{api → src/api}/helpCode.d.ts +0 -0
- /package/dist/{api → src/api}/inputManager.d.ts +0 -0
- /package/dist/{api → src/api}/storedMetrics.d.ts +0 -0
- /package/dist/{api → src/api}/storedPageVisit.d.ts +0 -0
- /package/dist/{const_matchers.d.ts → src/const_matchers.d.ts} +0 -0
- /package/dist/{entry → src/entry}/index.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/appNavigationMonitor.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/errorMonitor.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/inputMonitor.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/integrations/react-native-navigation-integration.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/keyboardInputMonitor.d.ts +0 -0
- /package/dist/{monitors → src/monitors}/pageMonitor.d.ts +0 -0
- /package/dist/{pageVisit → src/pageVisit}/pageVisit.d.ts +0 -0
- /package/dist/{pageVisit → src/pageVisit}/pageVisitEventError.d.ts +0 -0
- /package/dist/{pageVisit → src/pageVisit}/userStep.d.ts +0 -0
- /package/dist/{react → src/react}/ErrorBoundary.d.ts +0 -0
- /package/dist/{sessionRecorder → src/sessionRecorder}/nativeSessionRecorderSubscription.d.ts +0 -0
- /package/dist/{sessionRecorder → src/sessionRecorder}/sessionRecorder.d.ts +0 -0
- /package/dist/{utils → src/utils}/date.d.ts +0 -0
- /package/dist/{utils → src/utils}/eventlistener.d.ts +0 -0
- /package/dist/{utils → src/utils}/log.d.ts +0 -0
- /package/dist/{utils → src/utils}/performance.d.ts +0 -0
- /package/dist/{utils → src/utils}/stacktrace-parser.d.ts +0 -0
|
@@ -1,401 +1,386 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
1
2
|
import 'react-native/Libraries/Network/fetch';
|
|
2
3
|
import { saveErrorToPagevisit } from '../pageVisit/pageVisitEventError.js';
|
|
3
|
-
import {
|
|
4
|
-
import { propWriteableOrMadeWriteable, replace } from '../utils/object.js';
|
|
5
|
-
import { SEVERITY, PV_SEQ_ATT_NAME, RESPONSE_ERROR_TYPE, GQL_ERROR_TYPE, HTTP_METHOD_ATT_NAME, HTTP_RESP_CODE_ATT_NAME, URL_ATT_NAME, HTTP_RESP_TIME_ATT_NAME, HTTP_RESP_LENGTH_ATT_NAME, FETCH_EXCEPTION_ERROR_TYPE } from '../constants.js';
|
|
6
|
-
import ClientConfig from '../api/clientConfig.js';
|
|
4
|
+
import { isHttpCodeFailure, PageVisitEventHTTP } from '../pageVisit/pageVisitEventHTTP.js';
|
|
5
|
+
import { propWriteableOrMadeWriteable, replace, safeEntries } from '../utils/object.js';
|
|
7
6
|
import { HTTPDataBundler } from './httpDataBundler.js';
|
|
7
|
+
import { SEVERITY, FETCH_EXCEPTION_ERROR_TYPE, PV_SEQ_ATT_NAME, RESPONSE_ERROR_TYPE, GQL_ERROR_TYPE, XML_HTTP_REQUEST_ERROR_TYPE, BODY_USED_ERROR, HTTP_RESP_LENGTH_ATT_NAME, HTTP_METHOD_ATT_NAME, HTTP_RESP_CODE_ATT_NAME, URL_ATT_NAME, HTTP_RESP_TIME_ATT_NAME } from '../constants.js';
|
|
8
|
+
import ClientConfig from '../api/clientConfig.js';
|
|
8
9
|
import GqlErrorValidator from './gqlErrorValidator.js';
|
|
9
10
|
import { noibuLog } from '../utils/log.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import { tryGetStackTrace, safeTrim } from '../utils/function.js';
|
|
12
|
+
import { promiseAll } from '../utils/polyfills.js';
|
|
13
|
+
import { addSafeEventListener } from '../utils/eventlistener.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @param {string[]} bodyStrings an array of string HTTP bodies from the wrapped fetch() function.
|
|
21
|
-
* [0] is the response body, [1] is an optional response body.
|
|
22
|
-
* @param {string} url the URL of the request
|
|
23
|
-
* @param {string} method the method used in the fetch call
|
|
24
|
-
* @param {boolean} shouldCollectPayload
|
|
25
|
-
* @returns the return from the bundleHTTPData() call
|
|
16
|
+
* Gets a response text Promise out of Response & headers. Returns reason if bundler decides to drop text.
|
|
17
|
+
* Due to async nature of text() and async function sent to Promise.all,
|
|
18
|
+
* our code race competes with client's code consuming the response.
|
|
19
|
+
* So at this point engine somehow gets access to the original ReadableStream and marks it as locked,
|
|
20
|
+
* thus client code may throw an error.
|
|
26
21
|
*/
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
let reqBody; //
|
|
38
|
-
let reqHeaders = new Map();
|
|
39
|
-
let respHeaders = new Map();
|
|
40
|
-
if (bodyStrings[1]) {
|
|
41
|
-
// Get reqBody from request obj if it exists
|
|
42
|
-
// I don't like this notation, but the linter enforces it.
|
|
43
|
-
// This is the same as reqBody = values[1]
|
|
44
|
-
[, reqBody] = bodyStrings;
|
|
45
|
-
|
|
46
|
-
// Get reqHeaders from request obj
|
|
47
|
-
if (request.headers) {
|
|
48
|
-
reqHeaders = HTTPDataBundler.headersMapFromIterable(
|
|
49
|
-
request.headers.entries(),
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// Get reqBody from options if it hasn't been defined above.
|
|
54
|
-
// Double-equals null check on reqBody to get null or undefined but not an empty string
|
|
55
|
-
if (reqBody == null && !!options && !!options.body) reqBody = options.body;
|
|
56
|
-
|
|
57
|
-
// Get reqHeaders from options.headers
|
|
58
|
-
if (reqHeaders.size < 1 && !!options && !!options.headers) {
|
|
59
|
-
// Two cases here: headers will either be a simple object (key/value), or
|
|
60
|
-
// a Headers type object.
|
|
61
|
-
|
|
62
|
-
// Case 1: headers has a .entries() property, it is a Header object
|
|
63
|
-
if (options.headers instanceof Headers) {
|
|
64
|
-
reqHeaders = HTTPDataBundler.headersMapFromIterable(
|
|
65
|
-
options.headers.entries(),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
// Case 2: headers is a simple object, same logic as above but we call it as
|
|
69
|
-
// Object.entries()
|
|
70
|
-
else {
|
|
71
|
-
reqHeaders = HTTPDataBundler.headersMapFromIterable(
|
|
72
|
-
Object.entries(options.headers),
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// response.headers will always be a Headers object.
|
|
78
|
-
if (!!response && !!response.headers) {
|
|
79
|
-
respHeaders = HTTPDataBundler.headersMapFromIterable(
|
|
80
|
-
response.headers.entries(),
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return HTTPDataBundler.getInstance().bundleHTTPData(
|
|
85
|
-
url,
|
|
86
|
-
reqHeaders,
|
|
87
|
-
reqBody,
|
|
88
|
-
respHeaders,
|
|
89
|
-
bodyStrings[0],
|
|
90
|
-
method,
|
|
91
|
-
shouldCollectPayload,
|
|
92
|
-
);
|
|
22
|
+
function consumeResponseAndGetBodyText(response) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
if (!(response instanceof Response)) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
if (response.bodyUsed) {
|
|
28
|
+
return BODY_USED_ERROR;
|
|
29
|
+
}
|
|
30
|
+
return response.text();
|
|
31
|
+
});
|
|
93
32
|
}
|
|
94
|
-
|
|
95
33
|
/**
|
|
96
|
-
*
|
|
97
|
-
* @param {object} request a Request object
|
|
98
|
-
* @param {object} response a Response object cloned() from the original
|
|
99
|
-
* @param {string} method the method used in the fetch call
|
|
100
|
-
* @param {string} url the URL of the request
|
|
101
|
-
* @param {object} options the options parameter from the original fetch() call
|
|
102
|
-
* @param {number} respTime the time between the fetch() call and the the resolution of the
|
|
103
|
-
* original implementation's return
|
|
104
|
-
* @param {Promise<string>} requestTextPromise
|
|
105
|
-
* @returns an array in the form [httpEvent, httpData]
|
|
34
|
+
* Generates new PVEventHTTP
|
|
106
35
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
) {
|
|
116
|
-
noibuLog('_buildHttpEventDataObjectsForFetch started', method, url);
|
|
117
|
-
const httpEvent = {
|
|
118
|
-
[HTTP_METHOD_ATT_NAME]: method,
|
|
119
|
-
[HTTP_RESP_CODE_ATT_NAME]: response.status,
|
|
120
|
-
[URL_ATT_NAME]: url,
|
|
121
|
-
[HTTP_RESP_TIME_ATT_NAME]: respTime,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
let httpData = null;
|
|
125
|
-
// Only get the bodies of the request and response if the URL is one we care about.
|
|
126
|
-
if (HTTPDataBundler.getInstance().shouldContinueForURL(url)) {
|
|
36
|
+
function getPvEventHttp(status, responseHeaders, method, url, responseTime) {
|
|
37
|
+
const httpEvent = {
|
|
38
|
+
[HTTP_METHOD_ATT_NAME]: method,
|
|
39
|
+
[HTTP_RESP_CODE_ATT_NAME]: status,
|
|
40
|
+
[URL_ATT_NAME]: url,
|
|
41
|
+
[HTTP_RESP_TIME_ATT_NAME]: responseTime,
|
|
42
|
+
};
|
|
43
|
+
const bundler = HTTPDataBundler.getInstance();
|
|
127
44
|
// add response payload length, if any
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
response.headers,
|
|
131
|
-
);
|
|
132
|
-
if (contentLength > 0) {
|
|
45
|
+
const contentLength = bundler.contentLength(responseHeaders);
|
|
46
|
+
if (contentLength > 0 && bundler.shouldContinueForURL(url)) {
|
|
133
47
|
httpEvent[HTTP_RESP_LENGTH_ATT_NAME] = contentLength;
|
|
134
|
-
}
|
|
135
48
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
49
|
+
return httpEvent;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets headers from request or request options. Handles different scenarios where simple conversion is not possible
|
|
53
|
+
*/
|
|
54
|
+
function getRequestHeaders(request, options) {
|
|
55
|
+
const headers = safeEntries((request === null || request === void 0 ? void 0 : request.headers) || (options === null || options === void 0 ? void 0 : options.headers));
|
|
56
|
+
return HTTPDataBundler.headersMapFromIterable(headers);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Gets response headers.
|
|
60
|
+
* It is always a Headers object.
|
|
61
|
+
*/
|
|
62
|
+
function getResponseHeaders(response) {
|
|
63
|
+
const headers = safeEntries(response === null || response === void 0 ? void 0 : response.headers);
|
|
64
|
+
return HTTPDataBundler.headersMapFromIterable(headers);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Gets request body from body strings or Request options
|
|
68
|
+
*/
|
|
69
|
+
function getRequestBody(requestText, options) {
|
|
70
|
+
if (typeof requestText === 'string') {
|
|
71
|
+
return requestText;
|
|
156
72
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
shouldCollectPayload,
|
|
178
|
-
),
|
|
179
|
-
)
|
|
180
|
-
.then(httpDataReturn => {
|
|
181
|
-
httpData = httpDataReturn;
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
noibuLog('_buildHttpEventDataObjectsForFetch ended');
|
|
185
|
-
// This return will await the _handleFetchOnPromiseCompletion() call if necessary,
|
|
186
|
-
// but won't if not. (See if above).
|
|
187
|
-
return [httpEvent, httpData];
|
|
73
|
+
// it's fine if it's falsy or an empty string, we catch that in HTTPDataBundler.getInstance().bundleHTTPData
|
|
74
|
+
return options === null || options === void 0 ? void 0 : options.body;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns the parameters to pass the PageVisitEventHTTP constructor. Specific to the fetch wrapper.
|
|
78
|
+
*/
|
|
79
|
+
function getHttpDataFromFetch(request, response, method, url, options, isError, requestText, responseText) {
|
|
80
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
81
|
+
if (!HTTPDataBundler.getInstance().shouldContinueForURL(url)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return HTTPDataBundler.getInstance().bundleHTTPData(url, getRequestHeaders(request, options), getRequestBody(requestText, options), getResponseHeaders(response), responseText, method, isError);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Clones Response / Request or returns original object if cloning is not possible */
|
|
88
|
+
function cloneResponse(ogResponse) {
|
|
89
|
+
if (!ogResponse.bodyUsed) {
|
|
90
|
+
return ogResponse.clone();
|
|
91
|
+
}
|
|
92
|
+
return ogResponse; // body consumed already, no http response body for us
|
|
188
93
|
}
|
|
189
|
-
|
|
190
94
|
/**
|
|
191
95
|
* Handles fetch failure
|
|
192
|
-
* @param {} err
|
|
193
|
-
* @param {} url
|
|
194
96
|
*/
|
|
195
|
-
function handleFetchFailure(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
saveErrorToPagevisit(FETCH_EXCEPTION_ERROR_TYPE, errorPayload);
|
|
97
|
+
function handleFetchFailure(maybeErr, url) {
|
|
98
|
+
if (!maybeErr || typeof maybeErr !== 'object') {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const err = maybeErr;
|
|
102
|
+
// we do not need this error if we cannot extract both the message and stack
|
|
103
|
+
if (!(err === null || err === void 0 ? void 0 : err.message) || !(err === null || err === void 0 ? void 0 : err.stack)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const errorFromFetch = new Error();
|
|
107
|
+
errorFromFetch.stack = err.stack;
|
|
108
|
+
// making sure we have a url
|
|
109
|
+
const checkedURL = safeTrim(url);
|
|
110
|
+
const urlMessage = checkedURL ? ` on url ${checkedURL}` : '';
|
|
111
|
+
errorFromFetch.message = `${err.message}${urlMessage}`;
|
|
112
|
+
const errorPayload = {
|
|
113
|
+
error: errorFromFetch,
|
|
114
|
+
};
|
|
115
|
+
saveErrorToPagevisit(FETCH_EXCEPTION_ERROR_TYPE, errorPayload);
|
|
215
116
|
}
|
|
216
|
-
|
|
217
117
|
/**
|
|
218
|
-
*
|
|
219
|
-
* catch http errors
|
|
118
|
+
* Handles fetch response safely. Reports PageVisitEventHTTP and errorToPagevisit if needed.
|
|
220
119
|
*/
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!propWriteableOrMadeWriteable(proto, 'fetch')) {
|
|
225
|
-
noibuLog('setupGlobalFetchWrapper fetch is not writable');
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
noibuLog('setupGlobalFetchWrapper replacing fetch');
|
|
229
|
-
replace(
|
|
230
|
-
proto,
|
|
231
|
-
'fetch',
|
|
232
|
-
originalFunction =>
|
|
233
|
-
// We set nbuWrapper to the handler function so if this returns an error
|
|
234
|
-
// the trace message will start with nbuWrapper which would allow us to
|
|
235
|
-
// detect that this is a wrapped function and not a NoibuJS error
|
|
236
|
-
function nbuGlobalFetchWrapper(resource, options) {
|
|
237
|
-
let method;
|
|
238
|
-
let url;
|
|
239
|
-
|
|
240
|
-
// These two variables will only be defined if the resource param is a request object.
|
|
241
|
-
// request, if defined, will be a clone of the request object
|
|
242
|
-
let request;
|
|
243
|
-
// requestTextPromise, if defined, will be a promise obtained from request that resolves
|
|
244
|
-
// to the response body as text
|
|
245
|
-
let requestTextPromise;
|
|
246
|
-
|
|
247
|
-
// We wrap everything in try/catch to ensure the original function is called even if we throw
|
|
248
|
-
// an unexpected error.
|
|
120
|
+
function safeHandleFetchResponse(startTime, url, method, options, request) {
|
|
121
|
+
return (ogResponse) => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
var _a, _b;
|
|
249
123
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
method = 'GET';
|
|
255
|
-
url = '';
|
|
256
|
-
} else {
|
|
257
|
-
if (resource.method) {
|
|
258
|
-
method = resource.method;
|
|
259
|
-
url = resource.url;
|
|
260
|
-
} else {
|
|
261
|
-
// If options or options.method is missing, fallback to GET.
|
|
262
|
-
method = options && options.method ? options.method : 'GET';
|
|
263
|
-
url = resource.toString() ? resource.toString() : '';
|
|
124
|
+
if (!ogResponse) {
|
|
125
|
+
// no idea how, but this happens sometimes, esp if other libs override fetch
|
|
126
|
+
// logging to track the issue if it becomes widespread
|
|
127
|
+
return ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in custom fetch() callback: no response received`, false, SEVERITY.error);
|
|
264
128
|
}
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
),
|
|
289
|
-
clone: !!resource.clone,
|
|
290
|
-
text: !!resource.text,
|
|
291
|
-
},
|
|
292
|
-
);
|
|
129
|
+
const clonedResponse = cloneResponse(ogResponse); // gotta do it as early as possible
|
|
130
|
+
const graphqlResponse = cloneResponse(clonedResponse); // and two times more for each consumption
|
|
131
|
+
const httpEventDataResponse = cloneResponse(clonedResponse);
|
|
132
|
+
const respTime = Date.now() - startTime;
|
|
133
|
+
const status = clonedResponse.status;
|
|
134
|
+
const headers = clonedResponse.headers;
|
|
135
|
+
const gqlError = yield GqlErrorValidator.fromFetch(url, options, request, graphqlResponse);
|
|
136
|
+
const isHttpError = isHttpCodeFailure(status);
|
|
137
|
+
const [maybeRequestText, responseText] = yield promiseAll([
|
|
138
|
+
Promise.resolve((_a = request === null || request === void 0 ? void 0 : request.text) === null || _a === void 0 ? void 0 : _a.call(request)),
|
|
139
|
+
consumeResponseAndGetBodyText(httpEventDataResponse),
|
|
140
|
+
]);
|
|
141
|
+
const httpEvent = getPvEventHttp(status, headers, method, url, respTime);
|
|
142
|
+
const httpData = yield getHttpDataFromFetch(request, httpEventDataResponse, method, url, options, isHttpError || !!gqlError, maybeRequestText, responseText);
|
|
143
|
+
const pageVisitEventHTTP = new PageVisitEventHTTP(httpEvent, httpData, !!gqlError);
|
|
144
|
+
pageVisitEventHTTP.saveHTTPEvent();
|
|
145
|
+
const seq = (_b = pageVisitEventHTTP.httpData) === null || _b === void 0 ? void 0 : _b[PV_SEQ_ATT_NAME];
|
|
146
|
+
if (isHttpError) {
|
|
147
|
+
// this does not consume response body so no need to clone it
|
|
148
|
+
saveErrorToPagevisit(RESPONSE_ERROR_TYPE, clonedResponse, seq);
|
|
149
|
+
}
|
|
150
|
+
if (gqlError) {
|
|
151
|
+
gqlError.forEach(error => saveErrorToPagevisit(GQL_ERROR_TYPE, error, seq));
|
|
293
152
|
}
|
|
294
|
-
}
|
|
295
|
-
} catch (e) {
|
|
296
|
-
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
297
|
-
`Error in fetch() wrapper: ${e}`,
|
|
298
|
-
false,
|
|
299
|
-
SEVERITY.error,
|
|
300
|
-
);
|
|
301
153
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
154
|
+
catch (e) {
|
|
155
|
+
if ((e === null || e === void 0 ? void 0 : e.name) === 'AbortError') {
|
|
156
|
+
// skip it, as it's somewhat expected
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const stack = tryGetStackTrace(e);
|
|
160
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in custom fetch() callback: ${e}${stack}`, false, SEVERITY.error);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Safe function to get method and url from fetch args
|
|
166
|
+
*/
|
|
167
|
+
function getMethodUrlFromFetchArgs(resource, options) {
|
|
168
|
+
const defaultResource = { method: 'GET', url: '' };
|
|
169
|
+
try {
|
|
170
|
+
// There is no valid reason for fetch() to be called without a resource but we
|
|
171
|
+
// see it on a bunch of websites. It seems to work the same as empty string
|
|
172
|
+
if (!resource) {
|
|
173
|
+
return defaultResource;
|
|
174
|
+
}
|
|
175
|
+
if (resource instanceof Request && resource.method) {
|
|
176
|
+
return { method: resource.method, url: resource.url || '' };
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
method: (options === null || options === void 0 ? void 0 : options.method) || 'GET',
|
|
180
|
+
url: typeof resource.toString === 'function' ? resource.toString() : '',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in fetch() wrapper: ${e}`, false, SEVERITY.error);
|
|
185
|
+
}
|
|
186
|
+
return defaultResource;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Setting up the wrapper around fetch functions to try and
|
|
190
|
+
* catch http errors
|
|
191
|
+
*/
|
|
192
|
+
function setupGlobalFetchWrapper(proto) {
|
|
193
|
+
if (!propWriteableOrMadeWriteable(proto, 'fetch')) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
replace(proto, 'fetch', function (originalFunction) {
|
|
197
|
+
// please don't change the name of this function. we need this stack frame to be nbuWrapper
|
|
198
|
+
return function nbuWrapper(resource, options) {
|
|
199
|
+
let request;
|
|
200
|
+
const { url, method } = getMethodUrlFromFetchArgs(resource, options);
|
|
201
|
+
if (resource instanceof Request) {
|
|
202
|
+
/**
|
|
203
|
+
* We clone the request object, as its body can only be read once per instance.
|
|
204
|
+
* It's important that we do this before the original function is called, as it
|
|
205
|
+
* cannot be cloned afterward.
|
|
206
|
+
*/
|
|
207
|
+
request = resource.clone();
|
|
208
|
+
}
|
|
209
|
+
const startTime = Date.now();
|
|
210
|
+
const promiseFromOriginalFetch = originalFunction.call(this, resource, options);
|
|
211
|
+
promiseFromOriginalFetch
|
|
212
|
+
.then(safeHandleFetchResponse(startTime, url, method, options, request))
|
|
213
|
+
.catch(err => {
|
|
214
|
+
handleFetchFailure(err, url);
|
|
215
|
+
});
|
|
216
|
+
return promiseFromOriginalFetch;
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Does a check if data collection is enabled and combines it into HttpDataBundle
|
|
222
|
+
*/
|
|
223
|
+
function getHttpDataFromXhr(xhrObj_1, method_1, url_1, isError_1) {
|
|
224
|
+
return __awaiter(this, arguments, void 0, function* (xhrObj, method, url, isError, requestPayload = null, responseText) {
|
|
225
|
+
if (!HTTPDataBundler.getInstance().shouldContinueForURL(url)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const responseHeaders = HTTPDataBundler.headersMapFromString(xhrObj.getAllResponseHeaders());
|
|
229
|
+
return HTTPDataBundler.getInstance().bundleHTTPData(url, xhrObj.noibuRequestHeaders, requestPayload, responseHeaders, responseText, method, isError);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* on loadend we catch the event and
|
|
234
|
+
* make sure it was correct
|
|
235
|
+
*/
|
|
236
|
+
function loadendHandler(xhr, startTime, url, method, requestPayload) {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
var _a;
|
|
239
|
+
const respTime = Date.now() - startTime;
|
|
240
|
+
const gqlError = yield GqlErrorValidator.fromXhr(url, xhr);
|
|
241
|
+
const httpError = isHttpCodeFailure(xhr.status);
|
|
242
|
+
const responseText = yield HTTPDataBundler.getResponseStringFromXHR(xhr);
|
|
243
|
+
const httpEvent = getPvEventHttp(xhr.status, HTTPDataBundler.headersMapFromString(xhr.getAllResponseHeaders()), method, url, respTime);
|
|
244
|
+
const httpData = yield getHttpDataFromXhr(xhr, method, url, httpError || !!gqlError, requestPayload, responseText);
|
|
245
|
+
const pageVisitEventHTTP = new PageVisitEventHTTP(httpEvent, httpData, gqlError);
|
|
246
|
+
// Save HTTP Info
|
|
247
|
+
pageVisitEventHTTP.saveHTTPEvent();
|
|
248
|
+
const seq = (_a = pageVisitEventHTTP.httpData) === null || _a === void 0 ? void 0 : _a[PV_SEQ_ATT_NAME];
|
|
249
|
+
if (httpError) {
|
|
250
|
+
saveErrorToPagevisit(XML_HTTP_REQUEST_ERROR_TYPE, xhr, seq);
|
|
251
|
+
}
|
|
252
|
+
if (gqlError) {
|
|
253
|
+
gqlError.forEach(error => saveErrorToPagevisit(GQL_ERROR_TYPE, error, seq));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/** gets method from xhr */
|
|
258
|
+
function getMethodFromXHR(xhr, payload) {
|
|
259
|
+
if (xhr.noibuHttpMethod) {
|
|
260
|
+
return xhr.noibuHttpMethod;
|
|
261
|
+
}
|
|
262
|
+
// This is very hacky, but we do not really have an option as
|
|
263
|
+
// we do not have access to either the url nor the method when
|
|
264
|
+
// overriding the send method
|
|
265
|
+
return payload ? 'POST' : 'GET';
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Wraps the send prototype of the xmlhttp object
|
|
269
|
+
* We set nbuWrapper to the handler function so if this returns an error
|
|
270
|
+
* the trace message will start with nbuWrapper which would allow us to
|
|
271
|
+
* detect that this is a wrapped function and not a NoibuJS error
|
|
272
|
+
*/
|
|
273
|
+
function wrapXMLHTTPSend(proto) {
|
|
274
|
+
replace(proto, 'send', function (originalFunction) {
|
|
275
|
+
return function nbuWrapper(payload) {
|
|
320
276
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const gqlError = await GqlErrorValidator.fromFetch(
|
|
326
|
-
url,
|
|
327
|
-
options,
|
|
328
|
-
request,
|
|
329
|
-
graphqlResponse,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
const rEndedAt = new Date();
|
|
333
|
-
const respTime = Math.abs(rEndedAt - rCreatedAt);
|
|
334
|
-
const [httpEvent, httpData] =
|
|
335
|
-
await _buildHttpEventDataObjectsForFetch(
|
|
336
|
-
request,
|
|
337
|
-
httpEventDataResponse,
|
|
338
|
-
method,
|
|
339
|
-
url,
|
|
340
|
-
options,
|
|
341
|
-
respTime,
|
|
342
|
-
requestTextPromise,
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
let pageVisitEventHTTP = new PageVisitEventHTTP(
|
|
346
|
-
httpEvent,
|
|
347
|
-
httpData,
|
|
348
|
-
);
|
|
349
|
-
pageVisitEventHTTP.saveHTTPEvent();
|
|
350
|
-
|
|
351
|
-
const seq =
|
|
352
|
-
pageVisitEventHTTP.httpData &&
|
|
353
|
-
pageVisitEventHTTP.httpData[PV_SEQ_ATT_NAME];
|
|
354
|
-
|
|
355
|
-
if (isHttpCodeFailure(response.status)) {
|
|
356
|
-
saveErrorToPagevisit(
|
|
357
|
-
RESPONSE_ERROR_TYPE,
|
|
358
|
-
pageVisitErrorResponse,
|
|
359
|
-
seq,
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (gqlError) {
|
|
364
|
-
gqlError.forEach(error =>
|
|
365
|
-
saveErrorToPagevisit(GQL_ERROR_TYPE, error, seq),
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// helping gc
|
|
370
|
-
pageVisitEventHTTP = null;
|
|
371
|
-
} catch (e) {
|
|
372
|
-
// Catch errors with our logic in the .then() callback above
|
|
373
|
-
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
374
|
-
`Error in custom fetch() callback: ${e}`,
|
|
375
|
-
false,
|
|
376
|
-
SEVERITY.error,
|
|
377
|
-
);
|
|
277
|
+
const method = getMethodFromXHR(this, payload);
|
|
278
|
+
const startTime = Date.now();
|
|
279
|
+
addSafeEventListener(this, 'loadend', () => loadendHandler(this, startTime, this.noibuHttpUrl || this.responseURL, method, payload));
|
|
378
280
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
281
|
+
catch (e) {
|
|
282
|
+
const stack = tryGetStackTrace(e);
|
|
283
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in XHR.send() wrapper: ${e}${stack}`, false, SEVERITY.error);
|
|
284
|
+
}
|
|
285
|
+
return originalFunction.call(this, payload);
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Wraps the open prototype of the xmlhttp object
|
|
291
|
+
* We set nbuWrapper to the handler function so if this returns an error
|
|
292
|
+
* the trace message will start with nbuWrapper which would allow us to
|
|
293
|
+
* detect that this is a wrapped function and not a NoibuJS error
|
|
294
|
+
*/
|
|
295
|
+
function wrapXMLHTTPOpen(proto, shouldHandleLoadend) {
|
|
296
|
+
replace(proto, 'open', function (originalFunction) {
|
|
297
|
+
return function nbuWrapper(method, url, async = true, user = null, password = null) {
|
|
298
|
+
try {
|
|
299
|
+
/**
|
|
300
|
+
* We wrap assignment of the .noibu[...] variables in a try/catch, as we're assigning
|
|
301
|
+
* properties to a built-in object type, and have no guarantee of success.
|
|
302
|
+
*/
|
|
303
|
+
try {
|
|
304
|
+
this.noibuHttpMethod = method;
|
|
305
|
+
this.noibuHttpUrl = url;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Unable to set custom properties on XHR object: ${error}`, false, SEVERITY.warn);
|
|
309
|
+
}
|
|
310
|
+
if (shouldHandleLoadend) {
|
|
311
|
+
const startTime = Date.now();
|
|
312
|
+
addSafeEventListener(this, 'loadend', () => loadendHandler(this, startTime, url, method, null));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
const stack = tryGetStackTrace(e);
|
|
317
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in XHR.open() wrapper: ${e}${stack}`, false, SEVERITY.error);
|
|
318
|
+
}
|
|
319
|
+
return originalFunction.call(this, method, url, async, user, password);
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Replaces the native XMLHTTPRequest.setHeader() function. We use this to build an array of
|
|
325
|
+
* request headers as these are not stored by the XMLHTTPRequest object.
|
|
326
|
+
* @param {object} proto window.XMLHTTPRequest's prototype
|
|
327
|
+
*/
|
|
328
|
+
function wrapXMLHTTPSetRequestHeader(proto) {
|
|
329
|
+
replace(proto, 'setRequestHeader', function (originalFunction) {
|
|
330
|
+
return function nbuWrapper(header, value) {
|
|
331
|
+
// We wrap all of our logic in a try block to guarantee that we will complete the original
|
|
332
|
+
// implementation if we throw an error in our logic.
|
|
333
|
+
try {
|
|
334
|
+
if (!(this.noibuRequestHeaders instanceof Map)) {
|
|
335
|
+
this.noibuRequestHeaders = new Map();
|
|
336
|
+
}
|
|
337
|
+
// Ensure it's a string. This also converts null to "null"
|
|
338
|
+
const stringValue = typeof value === 'string' ? value : String(value);
|
|
339
|
+
if (typeof header === 'string') {
|
|
340
|
+
this.noibuRequestHeaders.set(header.toLowerCase(), stringValue);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Error in XHR.setRequestHeader() wrapper: ${error}`, false, SEVERITY.error);
|
|
345
|
+
}
|
|
346
|
+
return originalFunction.call(this, header, value);
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Setting up the wrapper around send functions to try and
|
|
352
|
+
* catch http errors
|
|
353
|
+
*/
|
|
354
|
+
function setupGlobalXMLHttpWrapper() {
|
|
355
|
+
if (typeof XMLHttpRequest === 'undefined') {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const XMLHttp = XMLHttpRequest;
|
|
359
|
+
// The event target needs to have a prototype and have the open function
|
|
360
|
+
const proto = XMLHttp && XMLHttp.prototype;
|
|
361
|
+
const canWriteOpen = propWriteableOrMadeWriteable(proto, 'open');
|
|
362
|
+
const canWriteSend = propWriteableOrMadeWriteable(proto, 'send');
|
|
363
|
+
const canWriteSetHeader = propWriteableOrMadeWriteable(proto, 'setRequestHeader');
|
|
364
|
+
if (canWriteOpen) {
|
|
365
|
+
// If we can't wrap the XHR.send() function, we need the wrapped .open() function to
|
|
366
|
+
// set the listener on 'loadend' events.
|
|
367
|
+
wrapXMLHTTPOpen(proto, !canWriteSend);
|
|
368
|
+
}
|
|
369
|
+
if (canWriteSend) {
|
|
370
|
+
wrapXMLHTTPSend(proto);
|
|
371
|
+
}
|
|
372
|
+
if (canWriteSetHeader) {
|
|
373
|
+
wrapXMLHTTPSetRequestHeader(proto);
|
|
374
|
+
}
|
|
389
375
|
}
|
|
390
376
|
/**
|
|
391
377
|
* Monitors all requests
|
|
392
378
|
*/
|
|
393
379
|
function monitorRequests() {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
noibuLog('monitorRequests ended');
|
|
380
|
+
noibuLog('monitorRequests started');
|
|
381
|
+
setupGlobalFetchWrapper(global);
|
|
382
|
+
setupGlobalXMLHttpWrapper();
|
|
383
|
+
noibuLog('monitorRequests ended');
|
|
399
384
|
}
|
|
400
385
|
|
|
401
386
|
export { handleFetchFailure, monitorRequests };
|