feat(tracker-axios): 3.4.0 capture headers

This commit is contained in:
ShiKhu 2021-10-01 19:47:34 +02:00
parent d6914cc3c8
commit f44f2273a7
4 changed files with 62 additions and 26 deletions

View file

@ -21,22 +21,25 @@ const tracker = new Tracker({
});
tracker.start();
tracker.use(trackerAxios());
tracker.use(trackerAxios({ /* options here*/ }));
```
Options:
```ts
{
instance: AxiosInstance; // default: axios
failuresOnly: boolean; // default: true
failuresOnly: boolean; // default: false
captureWhen: (AxiosRequestConfig) => boolean; // default: () => true
sessionTokenHeader: string; // default: undefined
ingoreHeaders: Array<string> | boolean, // default [ 'Cookie', 'Set-Cookie', 'Authorization' ]
}
```
By default plugin connects to the static `axios` instance, but you can specify one with the `instance` option.
Set `failuresOnly` option to `false` if you want to record every single request regardless of the status code. By default only failed requests are captured, when the axios' promise is rejected. You can also [regulate](https://github.com/axios/axios#request-config) this axios behaviour with the `validateStatus` option.
Set `failuresOnly` option to `true` if you want to record only failed requests, when the axios' promise is rejected. You can also [regulate](https://github.com/axios/axios#request-config) axios failing behaviour with the `validateStatus` option.
`captureWhen` parameter allows you to set a filter on what should be captured. The function will be called with the axios config object and expected to return `true` or `false`.
In case you use [OpenReplay integrations (sentry, bugsnag or others)](https://docs.openreplay.com/integrations), you can use `sessionTokenHeader` option to specify the header name. This header will be appended automatically to the each axios request and will contain OpenReplay session identificator value.
You can define list of headers that you don't want to capture with the `ingoreHeaders` options. Set its value to `false` if you want to catch them all (`true` if opposite). By default plugin ignores the list of headers that might be sensetive such as `[ 'Cookie', 'Set-Cookie', 'Authorization' ]`.

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-axios",
"description": "Tracker plugin for axios requests recording",
"version": "3.0.1",
"version": "3.4.0",
"keywords": [
"axios",
"logging",

View file

@ -9,16 +9,16 @@ export interface Options {
instance: AxiosInstance;
failuresOnly: boolean;
captureWhen: (AxiosRequestConfig) => boolean;
//ingoreHeaders: Array<string> | boolean;
ingoreHeaders: Array<string> | boolean;
}
export default function(opts: Partial<Options> = {}) {
const options: Options = Object.assign(
{
instance: axios,
failuresOnly: true,
failuresOnly: false,
captureWhen: () => true,
//ingoreHeaders: [ 'Cookie', 'Set-Cookie', 'Authorization' ],
ingoreHeaders: [ 'Cookie', 'Set-Cookie', 'Authorization' ],
},
opts,
);
@ -27,48 +27,80 @@ export default function(opts: Partial<Options> = {}) {
return;
}
const sendFetchMessage = (response: AxiosResponse) => {
const ihOpt = options.ingoreHeaders
const isHIgnoring = Array.isArray(ihOpt)
? name => ihOpt.includes(name)
: () => ihOpt
const sendFetchMessage = (res: AxiosResponse) => {
// @ts-ignore
const startTime: number = response.config.__openreplayStartTs;
const startTime: number = res.config.__openreplayStartTs;
const duration = performance.now() - startTime;
if (typeof startTime !== 'number') {
return;
}
let requestData: string = '';
if (typeof response.config.data === 'string') {
requestData = response.config.data;
let reqBody: string = '';
if (typeof res.config.data === 'string') {
reqBody = res.config.data;
} else {
try {
requestData = JSON.stringify(response.config.data) || '';
reqBody = JSON.stringify(res.config.data) || '';
} catch (e) {} // TODO: app debug
}
let resBody: string = '';
if (typeof res.data === 'string') {
resBody = res.data;
} else {
try {
resBody = JSON.stringify(res.data) || '';
} catch (e) {}
}
let responseData: string = '';
if (typeof response.data === 'string') {
responseData = response.data;
} else {
try {
responseData = JSON.stringify(response.data) || '';
} catch (e) {}
const reqHs: Record<string, string> = {}
const resHs: Record<string, string> = {}
// TODO: type safe axios headers
if (ihOpt !== true) {
function writeReqHeader([n, v]: [string, string]) {
if (!isHIgnoring(n)) { reqHs[n] = v }
}
if (res.config.headers instanceof Headers) {
res.config.headers.forEach((v, n) => writeReqHeader([n, v]))
} else if (Array.isArray(res.config.headers)) {
res.config.headers.forEach(writeReqHeader);
} else if (typeof res.config.headers === 'object') {
Object.entries(res.config.headers as Record<string, string>).forEach(writeReqHeader)
}
// TODO: type safe axios headers
if (typeof res.headers === 'object') {
Object.entries(res.headers as Record<string, string>).forEach(([v, n]) => { if (!isHIgnoring(n)) resHs[n] = v })
}
}
// Why can't axios propogate the final request URL somewhere?
const fullURL = buildFullPath(response.config.baseURL, options.instance.getUri(response.config));
const fullURL = buildFullPath(res.config.baseURL, options.instance.getUri(res.config));
app.send(
Messages.Fetch(
typeof response.config.method === 'string' ? response.config.method.toUpperCase() : 'GET',
typeof res.config.method === 'string' ? res.config.method.toUpperCase() : 'GET',
fullURL,
requestData,
responseData,
response.status,
JSON.stringify({
headers: reqHs,
body: reqBody,
}),
JSON.stringify({
headers: resHs,
body: resBody,
}),
res.status,
startTime + performance.timing.navigationStart,
duration,
),
);
}
// TODO: why app.safe doesn't work here?
options.instance.interceptors.request.use(function (config) {
if (options.sessionTokenHeader) {
const sessionToken = app.getSessionToken();
@ -80,7 +112,7 @@ export default function(opts: Partial<Options> = {}) {
config.headers.append(options.sessionTokenHeader, sessionToken);
} else if (Array.isArray(config.headers)) {
config.headers.push([options.sessionTokenHeader, sessionToken]);
} else {
} else if (typeof config.headers === 'object') {
config.headers[options.sessionTokenHeader] = sessionToken;
}
}

View file

@ -7,6 +7,7 @@
"module": "es6",
"moduleResolution": "node",
"declaration": true,
"outDir": "./lib"
"outDir": "./lib",
"lib": ["es6", "dom", "es2017"] // is all necessary?
}
}