@openreplay/tracker 5.0.5-beta.3 → 5.0.5-beta.6
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/cjs/app/index.js +1 -1
- package/cjs/index.js +1 -1
- package/cjs/modules/axiosSpy.d.ts +1 -0
- package/cjs/modules/axiosSpy.js +88 -71
- package/cjs/modules/network.js +3 -0
- package/cjs/utils.d.ts +1 -0
- package/cjs/utils.js +21 -1
- package/lib/app/index.js +1 -1
- package/lib/index.js +1 -1
- package/lib/modules/axiosSpy.d.ts +1 -0
- package/lib/modules/axiosSpy.js +88 -71
- package/lib/modules/network.js +3 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +19 -0
- package/package.json +1 -1
package/cjs/app/index.js
CHANGED
|
@@ -33,7 +33,7 @@ class App {
|
|
|
33
33
|
this.stopCallbacks = [];
|
|
34
34
|
this.commitCallbacks = [];
|
|
35
35
|
this.activityState = ActivityState.NotActive;
|
|
36
|
-
this.version = '5.0.5-beta.
|
|
36
|
+
this.version = '5.0.5-beta.6'; // TODO: version compatability check inside each plugin.
|
|
37
37
|
this._usingOldFetchPlugin = false;
|
|
38
38
|
this.delay = 0;
|
|
39
39
|
this.projectKey = projectKey;
|
package/cjs/index.js
CHANGED
|
@@ -140,7 +140,7 @@ class API {
|
|
|
140
140
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
141
141
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
142
142
|
req.send(JSON.stringify({
|
|
143
|
-
trackerVersion: '5.0.5-beta.
|
|
143
|
+
trackerVersion: '5.0.5-beta.6',
|
|
144
144
|
projectKey: options.projectKey,
|
|
145
145
|
doNotTrack,
|
|
146
146
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -33,6 +33,7 @@ interface AxiosResponse<T = any> {
|
|
|
33
33
|
response?: AxiosRequestConfig;
|
|
34
34
|
}
|
|
35
35
|
export interface AxiosInstance extends Record<string, any> {
|
|
36
|
+
getUri: (config?: AxiosRequestConfig) => string;
|
|
36
37
|
interceptors: {
|
|
37
38
|
request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
|
|
38
39
|
response: AxiosInterceptorManager<AxiosResponse>;
|
package/cjs/modules/axiosSpy.js
CHANGED
|
@@ -1,71 +1,85 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
const messages_gen_js_1 = require("../app/messages.gen.js");
|
|
4
13
|
const utils_js_1 = require("../utils.js");
|
|
14
|
+
const exception_js_1 = require("./exception.js");
|
|
5
15
|
function default_1(app, instance, opts, sanitize, stringify) {
|
|
6
16
|
app.debug.log('Openreplay: attaching axios spy to instance', instance);
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
7
18
|
function captureResponseData(axiosResponseObj) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
app.debug.log('Openreplay: capturing axios response data', axiosResponseObj);
|
|
21
|
+
const { headers: reqHs, data: reqData, method, url } = axiosResponseObj.config;
|
|
22
|
+
const { data: rData, headers: rHs, status: globStatus, response } = axiosResponseObj;
|
|
23
|
+
const { data: resData, headers: resHs, status: resStatus } = response || {};
|
|
24
|
+
const ihOpt = opts.ignoreHeaders;
|
|
25
|
+
const isHIgnoring = Array.isArray(ihOpt) ? (name) => ihOpt.includes(name) : () => ihOpt;
|
|
26
|
+
function writeHeader(hsObj, header) {
|
|
27
|
+
if (!isHIgnoring(header[0])) {
|
|
28
|
+
hsObj[header[0]] = header[1];
|
|
29
|
+
}
|
|
17
30
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
let requestHs = {};
|
|
32
|
+
let responseHs = {};
|
|
33
|
+
if (reqHs.toJSON) {
|
|
34
|
+
requestHs = reqHs.toJSON();
|
|
35
|
+
}
|
|
36
|
+
else if (reqHs instanceof Headers) {
|
|
37
|
+
reqHs.forEach((v, n) => writeHeader(requestHs, [n, v]));
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(reqHs)) {
|
|
40
|
+
reqHs.forEach((h) => writeHeader(requestHs, h));
|
|
41
|
+
}
|
|
42
|
+
else if (typeof reqHs === 'object') {
|
|
43
|
+
Object.entries(reqHs).forEach((h) => writeHeader(requestHs, h));
|
|
44
|
+
}
|
|
45
|
+
const usedResHeader = resHs ? resHs : rHs;
|
|
46
|
+
if (usedResHeader.toJSON) {
|
|
47
|
+
responseHs = usedResHeader.toJSON();
|
|
48
|
+
}
|
|
49
|
+
else if (usedResHeader instanceof Headers) {
|
|
50
|
+
usedResHeader.forEach((v, n) => writeHeader(responseHs, [n, v]));
|
|
51
|
+
}
|
|
52
|
+
else if (Array.isArray(usedResHeader)) {
|
|
53
|
+
usedResHeader.forEach((h) => writeHeader(responseHs, h));
|
|
54
|
+
}
|
|
55
|
+
else if (typeof usedResHeader === 'object') {
|
|
56
|
+
Object.entries(usedResHeader).forEach(([n, v]) => {
|
|
57
|
+
if (!isHIgnoring(n))
|
|
58
|
+
responseHs[n] = v;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const reqResInfo = sanitize({
|
|
62
|
+
url,
|
|
63
|
+
method: method || '',
|
|
64
|
+
status: globStatus || resStatus || 0,
|
|
65
|
+
request: {
|
|
66
|
+
headers: requestHs,
|
|
67
|
+
body: reqData,
|
|
68
|
+
},
|
|
69
|
+
response: {
|
|
70
|
+
headers: responseHs,
|
|
71
|
+
body: resData || rData,
|
|
72
|
+
},
|
|
47
73
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
response: {
|
|
58
|
-
headers: responseHs,
|
|
59
|
-
body: resData || rData,
|
|
60
|
-
},
|
|
74
|
+
if (!reqResInfo) {
|
|
75
|
+
app.debug.log('Openreplay: empty request/response info, skipping');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const requestStart = axiosResponseObj.config.__openreplay_timing;
|
|
79
|
+
const duration = performance.now() - requestStart;
|
|
80
|
+
console.log(reqResInfo, 'finally');
|
|
81
|
+
app.send((0, messages_gen_js_1.NetworkRequest)('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + (0, utils_js_1.getTimeOrigin)(), duration));
|
|
61
82
|
});
|
|
62
|
-
if (!reqResInfo) {
|
|
63
|
-
app.debug.log('Openreplay: empty request/response info, skipping');
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const requestStart = axiosResponseObj.config.__openreplay_timing;
|
|
67
|
-
const duration = performance.now() - requestStart;
|
|
68
|
-
app.send((0, messages_gen_js_1.NetworkRequest)('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + (0, utils_js_1.getTimeOrigin)(), duration));
|
|
69
83
|
}
|
|
70
84
|
function getStartTime(config) {
|
|
71
85
|
app.debug.log('Openreplay: capturing API request', config);
|
|
@@ -84,26 +98,29 @@ function default_1(app, instance, opts, sanitize, stringify) {
|
|
|
84
98
|
function captureNetworkRequest(response) {
|
|
85
99
|
if (opts.failuresOnly)
|
|
86
100
|
return response;
|
|
87
|
-
captureResponseData(response);
|
|
101
|
+
void captureResponseData(response);
|
|
88
102
|
return response;
|
|
89
103
|
}
|
|
90
104
|
function captureNetworkError(error) {
|
|
91
|
-
|
|
105
|
+
app.debug.log('Openreplay: capturing API request error', error);
|
|
106
|
+
if (isAxiosError(error)) {
|
|
107
|
+
void captureResponseData(error.response);
|
|
108
|
+
}
|
|
109
|
+
else if (error instanceof Error) {
|
|
110
|
+
app.send((0, exception_js_1.getExceptionMessage)(error, []));
|
|
111
|
+
}
|
|
92
112
|
return Promise.reject(error);
|
|
93
113
|
}
|
|
94
114
|
function logRequestError(ev) {
|
|
95
115
|
app.debug.log('Openreplay: failed API request, skipping', ev);
|
|
96
116
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
const resInt = instance.interceptors.response.use(captureNetworkRequest, captureNetworkError, {
|
|
101
|
-
synchronous: true,
|
|
102
|
-
});
|
|
103
|
-
app.attachStopCallback(() => {
|
|
104
|
-
var _a, _b, _c, _d;
|
|
105
|
-
(_b = (_a = instance.interceptors.request).eject) === null || _b === void 0 ? void 0 : _b.call(_a, reqInt);
|
|
106
|
-
(_d = (_c = instance.interceptors.response).eject) === null || _d === void 0 ? void 0 : _d.call(_c, resInt);
|
|
107
|
-
});
|
|
117
|
+
instance.interceptors.request.use(getStartTime, logRequestError);
|
|
118
|
+
instance.interceptors.response.use(captureNetworkRequest, captureNetworkError);
|
|
108
119
|
}
|
|
109
120
|
exports.default = default_1;
|
|
121
|
+
function isAxiosError(payload) {
|
|
122
|
+
return isObject(payload) && payload.isAxiosError === true;
|
|
123
|
+
}
|
|
124
|
+
function isObject(thing) {
|
|
125
|
+
return thing !== null && typeof thing === 'object';
|
|
126
|
+
}
|
package/cjs/modules/network.js
CHANGED
|
@@ -165,6 +165,7 @@ function default_1(app, opts = {}) {
|
|
|
165
165
|
});
|
|
166
166
|
xhr.addEventListener('load', app.safe((e) => {
|
|
167
167
|
const { headers: reqHs, body: reqBody } = getXHRRequestDataObject(xhr);
|
|
168
|
+
app.debug.log('Openreplay: XHR load ', reqHs, reqBody, xhr, xhr.getAllResponseHeaders());
|
|
168
169
|
const duration = startTime > 0 ? e.timeStamp - startTime : 0;
|
|
169
170
|
const hString = ignoreHeaders ? '' : xhr.getAllResponseHeaders(); // might be null (though only if no response received though)
|
|
170
171
|
const resHs = hString
|
|
@@ -201,10 +202,12 @@ function default_1(app, opts = {}) {
|
|
|
201
202
|
XMLHttpRequest.prototype.send = function (body) {
|
|
202
203
|
const rdo = getXHRRequestDataObject(this);
|
|
203
204
|
rdo.body = body;
|
|
205
|
+
app.debug.log('Openreplay: ', 'XHR send', rdo, 'XHR Object', this);
|
|
204
206
|
return nativeSend.apply(this, arguments);
|
|
205
207
|
};
|
|
206
208
|
const nativeSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
207
209
|
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
|
210
|
+
app.debug.log('Openreplay: ', name, value, isHIgnored(name), getXHRRequestDataObject(this));
|
|
208
211
|
if (!isHIgnored(name)) {
|
|
209
212
|
const rdo = getXHRRequestDataObject(this);
|
|
210
213
|
rdo.headers[name] = value;
|
package/cjs/utils.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export declare const DOCS_HOST = "https://docs.openreplay.com";
|
|
|
11
11
|
export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
|
|
12
12
|
export declare function getLabelAttribute(e: Element): string | null;
|
|
13
13
|
export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
|
|
14
|
+
export declare function buildFullUrl(baseURL: string | undefined, requestedURL: string): string;
|
package/cjs/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.now = exports.getTimeOrigin = exports.adjustTimeOrigin = exports.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
|
|
3
|
+
exports.buildFullUrl = exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.now = exports.getTimeOrigin = exports.adjustTimeOrigin = exports.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
|
|
4
4
|
const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
|
|
5
5
|
exports.IN_BROWSER = !(typeof window === 'undefined');
|
|
6
6
|
exports.IS_FIREFOX = exports.IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
|
|
@@ -69,3 +69,23 @@ function hasOpenreplayAttribute(e, attr) {
|
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
exports.hasOpenreplayAttribute = hasOpenreplayAttribute;
|
|
72
|
+
// Copied from axios library because these functions haven't been exported.
|
|
73
|
+
// Why can't axios put constructed fullURL into the config object or in an additional meta information?
|
|
74
|
+
// TODO: axios feature request
|
|
75
|
+
function isAbsoluteURL(url) {
|
|
76
|
+
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
|
|
77
|
+
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
|
|
78
|
+
// by any combination of letters, digits, plus, period, or hyphen.
|
|
79
|
+
// eslint-disable-next-line no-useless-escape
|
|
80
|
+
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
|
|
81
|
+
}
|
|
82
|
+
function combineURLs(baseURL, relativeURL) {
|
|
83
|
+
return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
|
|
84
|
+
}
|
|
85
|
+
function buildFullUrl(baseURL, requestedURL) {
|
|
86
|
+
if (baseURL && !isAbsoluteURL(requestedURL)) {
|
|
87
|
+
return combineURLs(baseURL, requestedURL);
|
|
88
|
+
}
|
|
89
|
+
return requestedURL;
|
|
90
|
+
}
|
|
91
|
+
exports.buildFullUrl = buildFullUrl;
|
package/lib/app/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export default class App {
|
|
|
30
30
|
this.stopCallbacks = [];
|
|
31
31
|
this.commitCallbacks = [];
|
|
32
32
|
this.activityState = ActivityState.NotActive;
|
|
33
|
-
this.version = '5.0.5-beta.
|
|
33
|
+
this.version = '5.0.5-beta.6'; // TODO: version compatability check inside each plugin.
|
|
34
34
|
this._usingOldFetchPlugin = false;
|
|
35
35
|
this.delay = 0;
|
|
36
36
|
this.projectKey = projectKey;
|
package/lib/index.js
CHANGED
|
@@ -135,7 +135,7 @@ export default class API {
|
|
|
135
135
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
136
136
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
137
137
|
req.send(JSON.stringify({
|
|
138
|
-
trackerVersion: '5.0.5-beta.
|
|
138
|
+
trackerVersion: '5.0.5-beta.6',
|
|
139
139
|
projectKey: options.projectKey,
|
|
140
140
|
doNotTrack,
|
|
141
141
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -33,6 +33,7 @@ interface AxiosResponse<T = any> {
|
|
|
33
33
|
response?: AxiosRequestConfig;
|
|
34
34
|
}
|
|
35
35
|
export interface AxiosInstance extends Record<string, any> {
|
|
36
|
+
getUri: (config?: AxiosRequestConfig) => string;
|
|
36
37
|
interceptors: {
|
|
37
38
|
request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
|
|
38
39
|
response: AxiosInterceptorManager<AxiosResponse>;
|
package/lib/modules/axiosSpy.js
CHANGED
|
@@ -1,69 +1,83 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { NetworkRequest } from '../app/messages.gen.js';
|
|
2
11
|
import { getTimeOrigin } from '../utils.js';
|
|
12
|
+
import { getExceptionMessage } from './exception.js';
|
|
3
13
|
export default function (app, instance, opts, sanitize, stringify) {
|
|
4
14
|
app.debug.log('Openreplay: attaching axios spy to instance', instance);
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
5
16
|
function captureResponseData(axiosResponseObj) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
app.debug.log('Openreplay: capturing axios response data', axiosResponseObj);
|
|
19
|
+
const { headers: reqHs, data: reqData, method, url } = axiosResponseObj.config;
|
|
20
|
+
const { data: rData, headers: rHs, status: globStatus, response } = axiosResponseObj;
|
|
21
|
+
const { data: resData, headers: resHs, status: resStatus } = response || {};
|
|
22
|
+
const ihOpt = opts.ignoreHeaders;
|
|
23
|
+
const isHIgnoring = Array.isArray(ihOpt) ? (name) => ihOpt.includes(name) : () => ihOpt;
|
|
24
|
+
function writeHeader(hsObj, header) {
|
|
25
|
+
if (!isHIgnoring(header[0])) {
|
|
26
|
+
hsObj[header[0]] = header[1];
|
|
27
|
+
}
|
|
15
28
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
let requestHs = {};
|
|
30
|
+
let responseHs = {};
|
|
31
|
+
if (reqHs.toJSON) {
|
|
32
|
+
requestHs = reqHs.toJSON();
|
|
33
|
+
}
|
|
34
|
+
else if (reqHs instanceof Headers) {
|
|
35
|
+
reqHs.forEach((v, n) => writeHeader(requestHs, [n, v]));
|
|
36
|
+
}
|
|
37
|
+
else if (Array.isArray(reqHs)) {
|
|
38
|
+
reqHs.forEach((h) => writeHeader(requestHs, h));
|
|
39
|
+
}
|
|
40
|
+
else if (typeof reqHs === 'object') {
|
|
41
|
+
Object.entries(reqHs).forEach((h) => writeHeader(requestHs, h));
|
|
42
|
+
}
|
|
43
|
+
const usedResHeader = resHs ? resHs : rHs;
|
|
44
|
+
if (usedResHeader.toJSON) {
|
|
45
|
+
responseHs = usedResHeader.toJSON();
|
|
46
|
+
}
|
|
47
|
+
else if (usedResHeader instanceof Headers) {
|
|
48
|
+
usedResHeader.forEach((v, n) => writeHeader(responseHs, [n, v]));
|
|
49
|
+
}
|
|
50
|
+
else if (Array.isArray(usedResHeader)) {
|
|
51
|
+
usedResHeader.forEach((h) => writeHeader(responseHs, h));
|
|
52
|
+
}
|
|
53
|
+
else if (typeof usedResHeader === 'object') {
|
|
54
|
+
Object.entries(usedResHeader).forEach(([n, v]) => {
|
|
55
|
+
if (!isHIgnoring(n))
|
|
56
|
+
responseHs[n] = v;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const reqResInfo = sanitize({
|
|
60
|
+
url,
|
|
61
|
+
method: method || '',
|
|
62
|
+
status: globStatus || resStatus || 0,
|
|
63
|
+
request: {
|
|
64
|
+
headers: requestHs,
|
|
65
|
+
body: reqData,
|
|
66
|
+
},
|
|
67
|
+
response: {
|
|
68
|
+
headers: responseHs,
|
|
69
|
+
body: resData || rData,
|
|
70
|
+
},
|
|
45
71
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
response: {
|
|
56
|
-
headers: responseHs,
|
|
57
|
-
body: resData || rData,
|
|
58
|
-
},
|
|
72
|
+
if (!reqResInfo) {
|
|
73
|
+
app.debug.log('Openreplay: empty request/response info, skipping');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const requestStart = axiosResponseObj.config.__openreplay_timing;
|
|
77
|
+
const duration = performance.now() - requestStart;
|
|
78
|
+
console.log(reqResInfo, 'finally');
|
|
79
|
+
app.send(NetworkRequest('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + getTimeOrigin(), duration));
|
|
59
80
|
});
|
|
60
|
-
if (!reqResInfo) {
|
|
61
|
-
app.debug.log('Openreplay: empty request/response info, skipping');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const requestStart = axiosResponseObj.config.__openreplay_timing;
|
|
65
|
-
const duration = performance.now() - requestStart;
|
|
66
|
-
app.send(NetworkRequest('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + getTimeOrigin(), duration));
|
|
67
81
|
}
|
|
68
82
|
function getStartTime(config) {
|
|
69
83
|
app.debug.log('Openreplay: capturing API request', config);
|
|
@@ -82,25 +96,28 @@ export default function (app, instance, opts, sanitize, stringify) {
|
|
|
82
96
|
function captureNetworkRequest(response) {
|
|
83
97
|
if (opts.failuresOnly)
|
|
84
98
|
return response;
|
|
85
|
-
captureResponseData(response);
|
|
99
|
+
void captureResponseData(response);
|
|
86
100
|
return response;
|
|
87
101
|
}
|
|
88
102
|
function captureNetworkError(error) {
|
|
89
|
-
|
|
103
|
+
app.debug.log('Openreplay: capturing API request error', error);
|
|
104
|
+
if (isAxiosError(error)) {
|
|
105
|
+
void captureResponseData(error.response);
|
|
106
|
+
}
|
|
107
|
+
else if (error instanceof Error) {
|
|
108
|
+
app.send(getExceptionMessage(error, []));
|
|
109
|
+
}
|
|
90
110
|
return Promise.reject(error);
|
|
91
111
|
}
|
|
92
112
|
function logRequestError(ev) {
|
|
93
113
|
app.debug.log('Openreplay: failed API request, skipping', ev);
|
|
94
114
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
(_b = (_a = instance.interceptors.request).eject) === null || _b === void 0 ? void 0 : _b.call(_a, reqInt);
|
|
104
|
-
(_d = (_c = instance.interceptors.response).eject) === null || _d === void 0 ? void 0 : _d.call(_c, resInt);
|
|
105
|
-
});
|
|
115
|
+
instance.interceptors.request.use(getStartTime, logRequestError);
|
|
116
|
+
instance.interceptors.response.use(captureNetworkRequest, captureNetworkError);
|
|
117
|
+
}
|
|
118
|
+
function isAxiosError(payload) {
|
|
119
|
+
return isObject(payload) && payload.isAxiosError === true;
|
|
120
|
+
}
|
|
121
|
+
function isObject(thing) {
|
|
122
|
+
return thing !== null && typeof thing === 'object';
|
|
106
123
|
}
|
package/lib/modules/network.js
CHANGED
|
@@ -163,6 +163,7 @@ export default function (app, opts = {}) {
|
|
|
163
163
|
});
|
|
164
164
|
xhr.addEventListener('load', app.safe((e) => {
|
|
165
165
|
const { headers: reqHs, body: reqBody } = getXHRRequestDataObject(xhr);
|
|
166
|
+
app.debug.log('Openreplay: XHR load ', reqHs, reqBody, xhr, xhr.getAllResponseHeaders());
|
|
166
167
|
const duration = startTime > 0 ? e.timeStamp - startTime : 0;
|
|
167
168
|
const hString = ignoreHeaders ? '' : xhr.getAllResponseHeaders(); // might be null (though only if no response received though)
|
|
168
169
|
const resHs = hString
|
|
@@ -199,10 +200,12 @@ export default function (app, opts = {}) {
|
|
|
199
200
|
XMLHttpRequest.prototype.send = function (body) {
|
|
200
201
|
const rdo = getXHRRequestDataObject(this);
|
|
201
202
|
rdo.body = body;
|
|
203
|
+
app.debug.log('Openreplay: ', 'XHR send', rdo, 'XHR Object', this);
|
|
202
204
|
return nativeSend.apply(this, arguments);
|
|
203
205
|
};
|
|
204
206
|
const nativeSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
205
207
|
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
|
208
|
+
app.debug.log('Openreplay: ', name, value, isHIgnored(name), getXHRRequestDataObject(this));
|
|
206
209
|
if (!isHIgnored(name)) {
|
|
207
210
|
const rdo = getXHRRequestDataObject(this);
|
|
208
211
|
rdo.headers[name] = value;
|
package/lib/utils.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export declare const DOCS_HOST = "https://docs.openreplay.com";
|
|
|
11
11
|
export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
|
|
12
12
|
export declare function getLabelAttribute(e: Element): string | null;
|
|
13
13
|
export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
|
|
14
|
+
export declare function buildFullUrl(baseURL: string | undefined, requestedURL: string): string;
|
package/lib/utils.js
CHANGED
|
@@ -59,3 +59,22 @@ export function hasOpenreplayAttribute(e, attr) {
|
|
|
59
59
|
}
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
+
// Copied from axios library because these functions haven't been exported.
|
|
63
|
+
// Why can't axios put constructed fullURL into the config object or in an additional meta information?
|
|
64
|
+
// TODO: axios feature request
|
|
65
|
+
function isAbsoluteURL(url) {
|
|
66
|
+
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
|
|
67
|
+
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
|
|
68
|
+
// by any combination of letters, digits, plus, period, or hyphen.
|
|
69
|
+
// eslint-disable-next-line no-useless-escape
|
|
70
|
+
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
|
|
71
|
+
}
|
|
72
|
+
function combineURLs(baseURL, relativeURL) {
|
|
73
|
+
return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
|
|
74
|
+
}
|
|
75
|
+
export function buildFullUrl(baseURL, requestedURL) {
|
|
76
|
+
if (baseURL && !isAbsoluteURL(requestedURL)) {
|
|
77
|
+
return combineURLs(baseURL, requestedURL);
|
|
78
|
+
}
|
|
79
|
+
return requestedURL;
|
|
80
|
+
}
|