@openreplay/tracker-axios
Advanced tools
Comparing version 3.4.3 to 3.5.0
import type { AxiosInstance } from 'axios'; | ||
import { App } from '@openreplay/tracker/cjs'; | ||
interface RequestData { | ||
body: BodyInit | null | undefined; | ||
headers: Record<string, string>; | ||
} | ||
interface ResponseData { | ||
body: string | Object | null; | ||
headers: Record<string, string>; | ||
} | ||
interface RequestResponseData { | ||
readonly status: number; | ||
readonly method: string; | ||
url: string; | ||
request: RequestData; | ||
response: ResponseData; | ||
} | ||
export interface Options { | ||
@@ -9,3 +24,5 @@ sessionTokenHeader?: string; | ||
ignoreHeaders: Array<string> | boolean; | ||
sanitiser?: (RequestResponseData: any) => RequestResponseData | null; | ||
} | ||
export default function (opts?: Partial<Options>): (app: App | null) => void; | ||
export {}; |
@@ -19,2 +19,3 @@ "use strict"; | ||
ignoreHeaders: ['Cookie', 'Set-Cookie', 'Authorization'], | ||
sanitiser: null, | ||
}, opts); | ||
@@ -84,11 +85,43 @@ return (app) => { | ||
} | ||
// TODO: split the code on functions & files | ||
// Why can't axios propogate the final request URL somewhere? | ||
const fullURL = (0, url_js_1.buildFullPath)(res.config.baseURL, options.instance.getUri(res.config)); | ||
app.send(tracker_1.Messages.Fetch(typeof res.config.method === 'string' ? res.config.method.toUpperCase() : 'GET', fullURL, JSON.stringify({ | ||
headers: reqHs, | ||
body: reqBody, | ||
}), JSON.stringify({ | ||
headers: resHs, | ||
body: resBody, | ||
}), res.status, startTime + performance.timing.navigationStart, duration)); | ||
const url = url_js_1.buildFullPath(res.config.baseURL, options.instance.getUri(res.config)); | ||
const method = typeof res.config.method === 'string' ? res.config.method.toUpperCase() : 'GET'; | ||
const status = res.status; | ||
let reqResData = { | ||
status, | ||
method, | ||
url, | ||
request: { | ||
headers: reqHs, | ||
body: reqBody, | ||
}, | ||
response: { | ||
headers: resHs, | ||
body: resBody, | ||
}, | ||
}; | ||
if (options.sanitiser) { | ||
try { | ||
reqResData.response.body = JSON.parse(resBody); // Why the returning type is "any"? | ||
} | ||
catch (_a) { } | ||
reqResData = options.sanitiser(reqResData); | ||
if (!reqResData) { | ||
return; | ||
} | ||
} | ||
const getStj = (r) => { | ||
if (r && typeof r.body !== 'string') { | ||
try { | ||
r.body = JSON.stringify(r.body); | ||
} | ||
catch (_a) { | ||
r.body = "<unable to stringify>"; | ||
//app.log.warn("Openreplay fetch") // TODO: version check | ||
} | ||
} | ||
return JSON.stringify(r); | ||
}; | ||
app.send(tracker_1.Messages.Fetch(method, String(reqResData.url), getStj(reqResData.request), getStj(reqResData.response), status, startTime + performance.timing.navigationStart, duration)); | ||
}; | ||
@@ -123,3 +156,3 @@ options.instance.interceptors.request.use(function (config) { | ||
if (error instanceof Error) { | ||
app.send((0, exception_js_1.getExceptionMessage)(error, [])); | ||
app.send(exception_js_1.getExceptionMessage(error, [])); | ||
} | ||
@@ -138,3 +171,3 @@ return Promise.reject(error); | ||
else if (!axios_1.default.isCancel(error) && error instanceof Error) { | ||
app.send((0, exception_js_1.getExceptionMessage)(error, [])); | ||
app.send(exception_js_1.getExceptionMessage(error, [])); | ||
} | ||
@@ -141,0 +174,0 @@ // TODO: common case (selector option) for modified responses |
import type { AxiosInstance } from 'axios'; | ||
import { App } from '@openreplay/tracker'; | ||
interface RequestData { | ||
body: BodyInit | null | undefined; | ||
headers: Record<string, string>; | ||
} | ||
interface ResponseData { | ||
body: string | Object | null; | ||
headers: Record<string, string>; | ||
} | ||
interface RequestResponseData { | ||
readonly status: number; | ||
readonly method: string; | ||
url: string; | ||
request: RequestData; | ||
response: ResponseData; | ||
} | ||
export interface Options { | ||
@@ -9,3 +24,5 @@ sessionTokenHeader?: string; | ||
ignoreHeaders: Array<string> | boolean; | ||
sanitiser?: (RequestResponseData: any) => RequestResponseData | null; | ||
} | ||
export default function (opts?: Partial<Options>): (app: App | null) => void; | ||
export {}; |
@@ -17,2 +17,3 @@ import axios from 'axios'; | ||
ignoreHeaders: ['Cookie', 'Set-Cookie', 'Authorization'], | ||
sanitiser: null, | ||
}, opts); | ||
@@ -82,11 +83,43 @@ return (app) => { | ||
} | ||
// TODO: split the code on functions & files | ||
// Why can't axios propogate the final request URL somewhere? | ||
const fullURL = buildFullPath(res.config.baseURL, options.instance.getUri(res.config)); | ||
app.send(Messages.Fetch(typeof res.config.method === 'string' ? res.config.method.toUpperCase() : 'GET', fullURL, JSON.stringify({ | ||
headers: reqHs, | ||
body: reqBody, | ||
}), JSON.stringify({ | ||
headers: resHs, | ||
body: resBody, | ||
}), res.status, startTime + performance.timing.navigationStart, duration)); | ||
const url = buildFullPath(res.config.baseURL, options.instance.getUri(res.config)); | ||
const method = typeof res.config.method === 'string' ? res.config.method.toUpperCase() : 'GET'; | ||
const status = res.status; | ||
let reqResData = { | ||
status, | ||
method, | ||
url, | ||
request: { | ||
headers: reqHs, | ||
body: reqBody, | ||
}, | ||
response: { | ||
headers: resHs, | ||
body: resBody, | ||
}, | ||
}; | ||
if (options.sanitiser) { | ||
try { | ||
reqResData.response.body = JSON.parse(resBody); // Why the returning type is "any"? | ||
} | ||
catch (_a) { } | ||
reqResData = options.sanitiser(reqResData); | ||
if (!reqResData) { | ||
return; | ||
} | ||
} | ||
const getStj = (r) => { | ||
if (r && typeof r.body !== 'string') { | ||
try { | ||
r.body = JSON.stringify(r.body); | ||
} | ||
catch (_a) { | ||
r.body = "<unable to stringify>"; | ||
//app.log.warn("Openreplay fetch") // TODO: version check | ||
} | ||
} | ||
return JSON.stringify(r); | ||
}; | ||
app.send(Messages.Fetch(method, String(reqResData.url), getStj(reqResData.request), getStj(reqResData.response), status, startTime + performance.timing.navigationStart, duration)); | ||
}; | ||
@@ -93,0 +126,0 @@ options.instance.interceptors.request.use(function (config) { |
{ | ||
"name": "@openreplay/tracker-axios", | ||
"description": "Tracker plugin for axios requests recording", | ||
"version": "3.4.3", | ||
"version": "3.5.0", | ||
"keywords": [ | ||
@@ -6,0 +6,0 @@ "axios", |
@@ -35,4 +35,7 @@ # OpenReplay Tracker Axios plugin | ||
ignoreHeaders: Array<string> | boolean, // default [ 'Cookie', 'Set-Cookie', 'Authorization' ] | ||
sanitiser: (RequestResponseData) => RequestResponseData | null, // default: undefined | ||
} | ||
``` | ||
By default plugin connects to the static `axios` instance, but you can specify one with the `instance` option. | ||
@@ -42,3 +45,3 @@ | ||
`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`. | ||
`captureWhen` parameter allows you to set a filter on request should be captured. The function will be called with the axios config object and expected to return `true` or `false`. | ||
@@ -48,1 +51,56 @@ 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 `ignoreHeaders` 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' ]`. | ||
Sanitise sensitive data from fetch request/response or ignore request comletely with `sanitiser`. You can redact fields on the request object by modifying then returning it from the function: | ||
```typescript | ||
interface RequestData { | ||
body: BodyInit | null | undefined; // whatewer you've put in the init.body in fetch(url, init) | ||
headers: Record<string, string>; | ||
} | ||
interface ResponseData { | ||
body: string | Object | null; // Object if response is of JSON type | ||
headers: Record<string, string>; | ||
} | ||
interface RequestResponseData { | ||
readonly status: number; | ||
readonly method: string; | ||
url: string; | ||
request: RequestData; | ||
response: ResponseData; | ||
} | ||
sanitiser: (data: RequestResponseData) => { // sanitise the body or headers | ||
if (data.url === "/auth") { | ||
data.request.body = null | ||
} | ||
if (data.request.headers['x-auth-token']) { // can also use ignoreHeaders option instead | ||
data.request.headers['x-auth-token'] = 'SANITISED'; | ||
} | ||
// Sanitise response | ||
if (data.status < 400 && data.response.body.token) { | ||
data.response.body.token = "<TOKEN>" | ||
} | ||
return data | ||
} | ||
// OR | ||
sanitiser: data => { // ignore requests that start with /secure | ||
if (data.url.startsWith("/secure")) { | ||
return null | ||
} | ||
return data | ||
} | ||
// OR | ||
sanitiser: data => { // sanitise request url: replace all numbers | ||
data.url = data.url.replace(/\d/g, "*") | ||
return data | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23685
459
104