From e66423dcf431a8bb64a226edc1a83efbdfa66066 Mon Sep 17 00:00:00 2001 From: Delirium Date: Mon, 30 Sep 2024 09:47:27 +0200 Subject: [PATCH] Spot network refactoring (#2617) * start refactoring network * separate network module, refactor spot network capture Signed-off-by: nick-delirium * some console refactoring, display network results in ui * detect gql error param * fix proxy ignore file, fix network tracking, fix tab tracking * some code quality improvements... * handle graphql in network lib (.2 ver), update tracker to use last version of lib * remove debug logs, change request type to gql (if its gql!) in lib, display gql in ui --------- Signed-off-by: nick-delirium --- .../Spots/SpotPlayer/spotPlayerStore.ts | 4 +- frontend/app/player/web/types/resource.ts | 3 + networkProxy/.gitignore | 31 ++ networkProxy/LICENSE | 19 + networkProxy/README.md | 46 ++ networkProxy/package-lock.json | 29 ++ networkProxy/package.json | 19 + .../src}/beaconProxy.ts | 11 +- .../src}/fetchProxy.ts | 11 +- networkProxy/src/index.ts | 96 ++++ .../src}/networkMessage.ts | 59 ++- networkProxy/src/types.ts | 39 ++ .../Network => networkProxy/src}/utils.ts | 0 .../Network => networkProxy/src}/xhrProxy.ts | 11 +- networkProxy/tsconfig.json | 14 + spot/bun.lockb | Bin 0 -> 264795 bytes spot/entrypoints/background.ts | 77 ++- spot/entrypoints/content/ControlsBox.tsx | 2 +- .../entrypoints/content/RecordingControls.tsx | 14 +- spot/entrypoints/content/SavingControls.tsx | 4 +- spot/entrypoints/content/index.tsx | 39 +- spot/entrypoints/injected.js | 153 ------ spot/entrypoints/injected.ts | 24 + spot/entrypoints/popup/App.tsx | 14 +- spot/entrypoints/popup/Login.tsx | 20 +- spot/entrypoints/popup/Settings.tsx | 59 +-- spot/package.json | 5 +- spot/tsconfig.json | 9 + spot/utils/consoleTracking.ts | 142 ++++++ spot/utils/networkTracking.ts | 474 +++++++----------- spot/utils/networkTrackingUtils.ts | 168 +++++++ spot/utils/proxyNetworkTracking.ts | 101 ++++ spot/yarn.lock | 15 +- tracker/tracker/CHANGELOG.md | 4 + tracker/tracker/bun.lockb | Bin 207314 -> 207697 bytes tracker/tracker/package.json | 3 +- .../tracker/src/main/modules/Network/index.ts | 53 -- .../tracker/src/main/modules/Network/types.ts | 15 - tracker/tracker/src/main/modules/network.ts | 21 +- tracker/tracker/tsconfig-base.json | 4 +- 40 files changed, 1128 insertions(+), 684 deletions(-) create mode 100644 networkProxy/.gitignore create mode 100644 networkProxy/LICENSE create mode 100644 networkProxy/README.md create mode 100644 networkProxy/package-lock.json create mode 100644 networkProxy/package.json rename {tracker/tracker/src/main/modules/Network => networkProxy/src}/beaconProxy.ts (89%) rename {tracker/tracker/src/main/modules/Network => networkProxy/src}/fetchProxy.ts (97%) create mode 100644 networkProxy/src/index.ts rename {tracker/tracker/src/main/modules/Network => networkProxy/src}/networkMessage.ts (68%) create mode 100644 networkProxy/src/types.ts rename {tracker/tracker/src/main/modules/Network => networkProxy/src}/utils.ts (100%) rename {tracker/tracker/src/main/modules/Network => networkProxy/src}/xhrProxy.ts (96%) create mode 100644 networkProxy/tsconfig.json create mode 100755 spot/bun.lockb delete mode 100644 spot/entrypoints/injected.js create mode 100644 spot/entrypoints/injected.ts create mode 100644 spot/utils/consoleTracking.ts create mode 100644 spot/utils/networkTrackingUtils.ts create mode 100644 spot/utils/proxyNetworkTracking.ts delete mode 100644 tracker/tracker/src/main/modules/Network/index.ts delete mode 100644 tracker/tracker/src/main/modules/Network/types.ts diff --git a/frontend/app/components/Spots/SpotPlayer/spotPlayerStore.ts b/frontend/app/components/Spots/SpotPlayer/spotPlayerStore.ts index 45e7ac7c2..d5ceb9c65 100644 --- a/frontend/app/components/Spots/SpotPlayer/spotPlayerStore.ts +++ b/frontend/app/components/Spots/SpotPlayer/spotPlayerStore.ts @@ -43,6 +43,8 @@ const mapSpotNetworkToEv = (ev: SpotNetworkRequest): any => { return 'xhr'; case 'fetch': return 'fetch'; + case 'graphql': + return 'graphql'; case 'resource': return 'resource'; default: @@ -56,7 +58,7 @@ const mapSpotNetworkToEv = (ev: SpotNetworkRequest): any => { }) const response = JSON.stringify({ headers: ev.responseHeaders, - body: { warn: "Chrome Manifest V3 -- No response body available in Chrome 93+" } + body: ev.responseBody ?? { warn: "Chrome Manifest V3 -- No response body available in Chrome 93+" } }) return ({ ...ev, diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts index d4520d8aa..8344e0c33 100644 --- a/frontend/app/player/web/types/resource.ts +++ b/frontend/app/player/web/types/resource.ts @@ -10,6 +10,7 @@ export const enum ResourceType { IMG = 'img', MEDIA = 'media', WS = 'websocket', + GRAPHQL = 'graphql', OTHER = 'other', } @@ -47,6 +48,8 @@ export function getResourceType(initiator: string, url: string): ResourceType { case "avi": case "mp3": return ResourceType.MEDIA + case "graphql": + return ResourceType.GRAPHQL default: return ResourceType.OTHER } diff --git a/networkProxy/.gitignore b/networkProxy/.gitignore new file mode 100644 index 000000000..a4c70b46e --- /dev/null +++ b/networkProxy/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.output +stats.html +stats-*.json +.wxt +web-ext.config.ts + +!public + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +dist/* +dist \ No newline at end of file diff --git a/networkProxy/LICENSE b/networkProxy/LICENSE new file mode 100644 index 000000000..4933f212f --- /dev/null +++ b/networkProxy/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Asayer, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/networkProxy/README.md b/networkProxy/README.md new file mode 100644 index 000000000..3b267f787 --- /dev/null +++ b/networkProxy/README.md @@ -0,0 +1,46 @@ +this tiny library helps us (OpenReplay folks) to create proxy objects for fetch, +XHR and beacons for proper request tracking in @openreplay/tracker and Spot extension. + +example usage: +``` +import createNetworkProxy from '@openreplay/network-proxy'; + +const context = this; +const ignoreHeaders = ['Authorization']; +const tokenUrlMatcher = /\/auth\/token/; +function setSessionTokenHeader(setRequestHeader: (name: string, value: string) => void) { + const header = 'X-Session-Token + const sessionToken = getToken() // for exmaple, => `session #123123`; + if (sessionToken) { + setRequestHeader(header, sessionToken) + } +} +function sanitize(reqResInfo) { + if (reqResInfo.request) { + delete reqResInfo.request.body + } + return reqResInfo +} + +const onMsg = (networkReq) => console.log(networkReq) +const isIgnoredUrl = (url) => url.includes('google.com') + +// Gets current tracker request’s url and returns boolean. If present, +// sessionTokenHeader will only be applied when this function returns true. +// Default: undefined +const tokenUrlMatcher = (url) => url.includes('google.com'); + +// this will observe global network requests +createNetworkProxy( + context, + options.ignoreHeaders, + setSessionTokenHeader, + sanitize, + (message) => app.send(message), + (url) => app.isServiceURL(url), + options.tokenUrlMatcher, +) + +// to stop it, you can save this.fetch/other apis before appliying the proxy +// and then restore them +``` diff --git a/networkProxy/package-lock.json b/networkProxy/package-lock.json new file mode 100644 index 000000000..5e3c8ae4e --- /dev/null +++ b/networkProxy/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "network-proxy", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "network-proxy", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "typescript": "^5.6.2" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/networkProxy/package.json b/networkProxy/package.json new file mode 100644 index 000000000..032d91af9 --- /dev/null +++ b/networkProxy/package.json @@ -0,0 +1,19 @@ +{ + "name": "@openreplay/network-proxy", + "version": "1.0.3", + "description": "this library helps us to create proxy objects for fetch, XHR and beacons for proper request tracking.", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc" + }, + "author": "Nikita ", + "license": "MIT", + "devDependencies": { + "typescript": "^5.6.2" + } +} diff --git a/tracker/tracker/src/main/modules/Network/beaconProxy.ts b/networkProxy/src/beaconProxy.ts similarity index 89% rename from tracker/tracker/src/main/modules/Network/beaconProxy.ts rename to networkProxy/src/beaconProxy.ts index 2433bd950..11eac6e33 100644 --- a/tracker/tracker/src/main/modules/Network/beaconProxy.ts +++ b/networkProxy/src/beaconProxy.ts @@ -1,7 +1,6 @@ -import { NetworkRequest } from '../../../common/messages.gen.js' -import NetworkMessage from './networkMessage.js' -import { RequestResponseData } from './types.js' -import { genStringBody, getURL } from './utils.js' +import NetworkMessage from './networkMessage' +import { RequestState, INetworkMessage, RequestResponseData } from './types'; +import { genStringBody, getURL } from './utils' // https://fetch.spec.whatwg.org/#concept-bodyinit-extract const getContentType = (data?: BodyInit) => { @@ -22,7 +21,7 @@ export class BeaconProxyHandler implement private readonly ignoredHeaders: boolean | string[], private readonly setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, private readonly sanitize: (data: RequestResponseData) => RequestResponseData | null, - private readonly sendMessage: (item: NetworkRequest) => void, + private readonly sendMessage: (item: INetworkMessage) => void, private readonly isServiceUrl: (url: string) => boolean, ) {} @@ -85,7 +84,7 @@ export default class BeaconProxy { ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, - sendMessage: (item: NetworkRequest) => void, + sendMessage: (item: INetworkMessage) => void, isServiceUrl: (url: string) => boolean, ) { if (!BeaconProxy.hasSendBeacon()) { diff --git a/tracker/tracker/src/main/modules/Network/fetchProxy.ts b/networkProxy/src/fetchProxy.ts similarity index 97% rename from tracker/tracker/src/main/modules/Network/fetchProxy.ts rename to networkProxy/src/fetchProxy.ts index 22d3b5163..483efb46b 100644 --- a/tracker/tracker/src/main/modules/Network/fetchProxy.ts +++ b/networkProxy/src/fetchProxy.ts @@ -5,10 +5,9 @@ * we can intercept the network requests * in not-so-hacky way * */ -import NetworkMessage, { RequestState } from './networkMessage.js' -import { formatByteSize, genStringBody, getStringResponseByType, getURL } from './utils.js' -import { RequestResponseData } from './types.js' -import { NetworkRequest } from '../../../common/messages.gen.js' +import NetworkMessage from './networkMessage' +import { RequestState, INetworkMessage, RequestResponseData } from './types'; +import { formatByteSize, genStringBody, getStringResponseByType, getURL } from './utils' export class ResponseProxyHandler implements ProxyHandler { public resp: Response @@ -123,7 +122,7 @@ export class FetchProxyHandler implements ProxyHandler void) => void, private readonly sanitize: (data: RequestResponseData) => RequestResponseData | null, - private readonly sendMessage: (item: NetworkRequest) => void, + private readonly sendMessage: (item: INetworkMessage) => void, private readonly isServiceUrl: (url: string) => boolean, private readonly tokenUrlMatcher?: (url: string) => boolean, ) {} @@ -311,7 +310,7 @@ export default class FetchProxy { ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, - sendMessage: (item: NetworkRequest) => void, + sendMessage: (item: INetworkMessage) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean, ) { diff --git a/networkProxy/src/index.ts b/networkProxy/src/index.ts new file mode 100644 index 000000000..f5cd0ca68 --- /dev/null +++ b/networkProxy/src/index.ts @@ -0,0 +1,96 @@ +import BeaconProxy from "./beaconProxy"; +import FetchProxy from "./fetchProxy"; +import XHRProxy from "./xhrProxy"; +import { INetworkMessage, RequestResponseData } from "./types"; + +export { + BeaconProxy, + FetchProxy, + XHRProxy, + INetworkMessage, + RequestResponseData, +}; + +const getWarning = (api: string) => { + const str = `Openreplay: Can't find ${api} in global context.`; + console.warn(str); +}; + +/** + * Creates network proxies for XMLHttpRequest, fetch, and sendBeacon to intercept and monitor network requests and + * responses. + * + * @param {Window | typeof globalThis} context - The global context object (e.g., window or globalThis). + * @param {boolean | string[]} ignoredHeaders - Headers to ignore from requests. If `true`, all headers are ignored; if + * an array of strings, those header names are ignored. + * @param {(cb: (name: string, value: string) => void) => void} setSessionTokenHeader - Function to set a session token + * header; accepts a callback that sets the header name and value. + * @param {(data: RequestResponseData) => RequestResponseData | null} sanitize - Function to sanitize request and + * response data; should return sanitized data or `null` to ignore the data. + * @param {(message: INetworkMessage) => void} sendMessage - Function to send network messages for further processing + * or logging. + * @param {(url: string) => boolean} isServiceUrl - Function to determine if a URL is a service URL that should be + * ignored by the proxy. + * @param {Object} [modules] - Modules to apply the proxies to. + * @param {boolean} [modules.xhr=true] - Whether to proxy XMLHttpRequest. + * @param {boolean} [modules.fetch=true] - Whether to proxy the fetch API. + * @param {boolean} [modules.beacon=true] - Whether to proxy navigator.sendBeacon. + * @param {(url: string) => boolean} [tokenUrlMatcher] - Optional function; the session token header will only be + * applied to requests matching this function. + * + * @returns {void} + */ +export default function createNetworkProxy( + context: typeof globalThis, + ignoredHeaders: boolean | string[], + setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, + sanitize: (data: RequestResponseData) => RequestResponseData | null, + sendMessage: (message: INetworkMessage) => void, + isServiceUrl: (url: string) => boolean, + modules: { xhr: boolean; fetch: boolean; beacon: boolean } = { + xhr: true, + fetch: true, + beacon: true, + }, + tokenUrlMatcher?: (url: string) => boolean, +): void { + if (modules.xhr) { + if (context.XMLHttpRequest) { + context.XMLHttpRequest = XHRProxy.create( + ignoredHeaders, + setSessionTokenHeader, + sanitize, + sendMessage, + isServiceUrl, + tokenUrlMatcher, + ); + } else { + getWarning("XMLHttpRequest"); + } + } + if (modules.fetch) { + if (context.fetch) { + context.fetch = FetchProxy.create( + ignoredHeaders, + setSessionTokenHeader, + sanitize, + sendMessage, + isServiceUrl, + tokenUrlMatcher, + ); + } else { + getWarning("fetch"); + } + } + if (modules.beacon) { + if (context?.navigator?.sendBeacon) { + context.navigator.sendBeacon = BeaconProxy.create( + ignoredHeaders, + setSessionTokenHeader, + sanitize, + sendMessage, + isServiceUrl, + ); + } + } +} diff --git a/tracker/tracker/src/main/modules/Network/networkMessage.ts b/networkProxy/src/networkMessage.ts similarity index 68% rename from tracker/tracker/src/main/modules/Network/networkMessage.ts rename to networkProxy/src/networkMessage.ts index 7ef4da96a..b0dade2cc 100644 --- a/tracker/tracker/src/main/modules/Network/networkMessage.ts +++ b/networkProxy/src/networkMessage.ts @@ -1,19 +1,9 @@ -import { NetworkRequest } from '../../app/messages.gen.js' -import { RequestResponseData } from './types.js' -import { getTimeOrigin } from '../../utils.js' - -export type httpMethod = - // '' is a rare case of error - '' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH' - -export enum RequestState { - UNSENT = 0, - OPENED = 1, - HEADERS_RECEIVED = 2, - LOADING = 3, - DONE = 4, -} - +import { + RequestResponseData, + INetworkMessage, + httpMethod, + RequestState, +} from './types' /** * I know we're not using most of the information from this class * but it can be useful in the future if we will decide to display more stuff in our ui @@ -30,9 +20,9 @@ export default class NetworkMessage { readyState?: RequestState = 0 header: { [key: string]: string } = {} responseType: XMLHttpRequest['responseType'] = '' - requestType: 'xhr' | 'fetch' | 'ping' | 'custom' | 'beacon' + requestType: 'xhr' | 'fetch' | 'ping' | 'custom' | 'beacon' | 'graphql' = 'xhr' requestHeader: HeadersInit = {} - response: any + response: string responseSize = 0 // bytes responseSizeText = '' startTime = 0 @@ -47,7 +37,7 @@ export default class NetworkMessage { private readonly sanitize: (data: RequestResponseData) => RequestResponseData | null, ) {} - getMessage() { + getMessage(): INetworkMessage | null { const { reqHs, resHs } = this.writeHeaders() const request = { headers: reqHs, @@ -63,19 +53,26 @@ export default class NetworkMessage { response, }) - if (!messageInfo) return + if (!messageInfo) return null; - return NetworkRequest( - this.requestType, - messageInfo.method, - messageInfo.url, - JSON.stringify(messageInfo.request), - JSON.stringify(messageInfo.response), - messageInfo.status, - this.startTime + getTimeOrigin(), - this.duration, - this.responseSize, - ) + const isGraphql = messageInfo.url.includes("/graphql"); + if (isGraphql && messageInfo.response.body && typeof messageInfo.response.body === 'string') { + const isError = messageInfo.response.body.includes("errors"); + messageInfo.status = isError ? 400 : 200; + this.requestType = 'graphql'; + } + + return { + requestType: this.requestType, + method: messageInfo.method as httpMethod, + url: messageInfo.url, + request: JSON.stringify(messageInfo.request), + response: JSON.stringify(messageInfo.response), + status: messageInfo.status, + startTime: this.startTime, + duration: this.duration, + responseSize: this.responseSize, + } } writeHeaders() { diff --git a/networkProxy/src/types.ts b/networkProxy/src/types.ts new file mode 100644 index 000000000..8e2ae038a --- /dev/null +++ b/networkProxy/src/types.ts @@ -0,0 +1,39 @@ +export interface RequestResponseData { + status: number + readonly method: string + url: string + request: { + body: string | null + headers: Record + } + response: { + body: string | null + headers: Record + } +} + +export interface INetworkMessage { + requestType: 'xhr' | 'fetch' | 'ping' | 'custom' | 'beacon' | 'graphql', + method: httpMethod, + url: string, + /** stringified JSON { headers: {}, body: {} } */ + request: string, + /** stringified JSON { headers: {}, body: {} } */ + response: string, + status: number, + startTime: number, + duration: number, + responseSize: number, +} + +export type httpMethod = + // '' is a rare case of error + '' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH' + +export enum RequestState { + UNSENT = 0, + OPENED = 1, + HEADERS_RECEIVED = 2, + LOADING = 3, + DONE = 4, +} diff --git a/tracker/tracker/src/main/modules/Network/utils.ts b/networkProxy/src/utils.ts similarity index 100% rename from tracker/tracker/src/main/modules/Network/utils.ts rename to networkProxy/src/utils.ts diff --git a/tracker/tracker/src/main/modules/Network/xhrProxy.ts b/networkProxy/src/xhrProxy.ts similarity index 96% rename from tracker/tracker/src/main/modules/Network/xhrProxy.ts rename to networkProxy/src/xhrProxy.ts index f300b0a4a..a15806481 100644 --- a/tracker/tracker/src/main/modules/Network/xhrProxy.ts +++ b/networkProxy/src/xhrProxy.ts @@ -6,10 +6,9 @@ * in not-so-hacky way * */ -import NetworkMessage, { RequestState } from './networkMessage.js' -import { genGetDataByUrl, formatByteSize, genStringBody, getStringResponseByType } from './utils.js' -import { RequestResponseData } from './types.js' -import { NetworkRequest } from '../../../common/messages.gen.js' +import NetworkMessage from './networkMessage' +import { RequestState, INetworkMessage, RequestResponseData } from './types'; +import { genGetDataByUrl, formatByteSize, genStringBody, getStringResponseByType } from './utils' export class XHRProxyHandler implements ProxyHandler { public XMLReq: XMLHttpRequest @@ -20,7 +19,7 @@ export class XHRProxyHandler implements ProxyHandler void) => void, private readonly sanitize: (data: RequestResponseData) => RequestResponseData | null, - private readonly sendMessage: (message: NetworkRequest) => void, + private readonly sendMessage: (message: INetworkMessage) => void, private readonly isServiceUrl: (url: string) => boolean, private readonly tokenUrlMatcher?: (url: string) => boolean, ) { @@ -239,7 +238,7 @@ export default class XHRProxy { ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, - sendMessage: (data: NetworkRequest) => void, + sendMessage: (data: INetworkMessage) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean, ) { diff --git a/networkProxy/tsconfig.json b/networkProxy/tsconfig.json new file mode 100644 index 000000000..6378fdbb1 --- /dev/null +++ b/networkProxy/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "ES2022", + "declaration": true, + "outDir": "./dist", + "strict": false, + "esModuleInterop": true, + "moduleResolution": "node", + "sourceMap": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"] +} diff --git a/spot/bun.lockb b/spot/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..0ff64f229ac87e2230c620500612f449d95926f8 GIT binary patch literal 264795 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 fa} literal 0 HcmV?d00001 diff --git a/spot/entrypoints/background.ts b/spot/entrypoints/background.ts index 58b7e4e48..9373275be 100644 --- a/spot/entrypoints/background.ts +++ b/spot/entrypoints/background.ts @@ -1,13 +1,4 @@ -import { - createSpotNetworkRequest, - stopTrackingNetwork, - startTrackingNetwork, - SpotNetworkRequest, - rawRequests, -} from "../utils/networkTracking"; -import { - isTokenExpired -} from '../utils/jwt'; +import { isTokenExpired } from "~/utils/jwt"; let checkBusy = false; @@ -65,6 +56,7 @@ export default defineBackground(() => { injected: { from: { bumpLogs: "ort:bump-logs", + bumpNetwork: "ort:bump-network", }, }, offscreen: { @@ -218,7 +210,7 @@ export default defineBackground(() => { setJWTToken(""); } const { jwtToken, settings } = data; - const refreshUrl = `${safeApiUrl(settings.ingestPoint)}/api/spot/refresh` + const refreshUrl = `${safeApiUrl(settings.ingestPoint)}/api/spot/refresh`; if (!isTokenExpired(jwtToken) || !jwtToken) { if (refreshInt) { clearInterval(refreshInt); @@ -305,12 +297,12 @@ export default defineBackground(() => { void browser.runtime.sendMessage({ type: messages.popup.to.noLogin, }); - checkBusy = false + checkBusy = false; return; } const ok = await refreshToken(); if (ok) { - setJWTToken(data.jwtToken) + setJWTToken(data.jwtToken); if (!refreshInt) { refreshInt = setInterval(() => { void refreshToken(); @@ -322,7 +314,7 @@ export default defineBackground(() => { }, PING_INT); } } - checkBusy = false + checkBusy = false; } // @ts-ignore browser.runtime.onMessage.addListener((request, sender, respond) => { @@ -408,6 +400,9 @@ export default defineBackground(() => { settings.networkLogs, settings.consoleLogs, () => recordingState.recording, + (hook) => { + onStop = hook; + }, ); // @ts-ignore this is false positive respond(true); @@ -579,6 +574,10 @@ export default defineBackground(() => { finalSpotObj.logs.push(...request.logs); return "pong"; } + if (request.type === messages.injected.from.bumpNetwork) { + finalSpotObj.network.push(request.event); + return "pong"; + } if (request.type === messages.content.from.bumpClicks) { finalSpotObj.clicks.push(...request.clicks); return "pong"; @@ -739,25 +738,7 @@ export default defineBackground(() => { }); } if (request.type === messages.content.from.saveSpotData) { - stopTrackingNetwork(); - const finalNetwork: SpotNetworkRequest[] = []; - const tab = - recordingState.area === "tab" ? recordingState.activeTabId : undefined; - let lastIn = 0; - try { - rawRequests.forEach((r, i) => { - lastIn = i; - const spotNetworkRequest = createSpotNetworkRequest(r, tab); - if (spotNetworkRequest) { - finalNetwork.push(spotNetworkRequest); - } - }); - } catch (e) { - console.error("cant parse network", e, rawRequests[lastIn]); - } - Object.assign(finalSpotObj, request.spot, { - network: finalNetwork, - }); + Object.assign(finalSpotObj, request.spot); return "pong"; } if (request.type === messages.content.from.saveSpotVidChunk) { @@ -897,7 +878,7 @@ export default defineBackground(() => { url: `${link}/view-spot/${id}`, active: settings.openInNewTab, }); - }, 250) + }, 250); const blob = base64ToBlob(videoData); const mPromise = fetch(mobURL, { @@ -974,7 +955,7 @@ export default defineBackground(() => { async function initializeOffscreenDocument() { const existingContexts = await browser.runtime.getContexts({ - contextTypes: ['OFFSCREEN_DOCUMENT'], + contextTypes: ["OFFSCREEN_DOCUMENT"], }); const offscreenDocument = existingContexts.find( @@ -997,7 +978,7 @@ export default defineBackground(() => { justification: "Recording from chrome.tabCapture API", }); } catch (e) { - console.log('cant create new offscreen document', e) + console.error("cant create new offscreen document", e); } return; @@ -1025,7 +1006,12 @@ export default defineBackground(() => { if (contentArmy[sendTo]) { await browser.tabs.sendMessage(sendTo, message); } else { - console.error("Content script might not be ready in tab", sendTo); + console.trace( + "Content script might not be ready in tab", + sendTo, + contentArmy, + message, + ); await browser.tabs.sendMessage(sendTo, message); } } @@ -1063,7 +1049,7 @@ export default defineBackground(() => { withNetwork: boolean, withConsole: boolean, getRecState: () => string, - setOnStop?: (hook: any) => void, + setOnStop: (hook: any) => void, ) { let activeTabs = await browser.tabs.query({ active: true, @@ -1108,17 +1094,16 @@ export default defineBackground(() => { slackChannels, activeTabId, withConsole, + withNetwork, state: "recording", // by default this is already handled by :start event // that triggers mount with countdown shouldMount: false, }; void sendToActiveTab(mountMsg); - if (withNetwork) { - startTrackingNetwork(); - } - let previousTab: number | null = usedTab ?? null; + + /** moves ui to active tab when screen recording */ function tabActivatedListener({ tabId }: { tabId: number }) { const state = getRecState(); if (state === REC_STATE.stopped) { @@ -1147,14 +1132,17 @@ export default defineBackground(() => { previousTab = tabId; } } + /** moves ui to active tab when screen recording */ function startTabActivationListening() { browser.tabs.onActivated.addListener(tabActivatedListener); } + /** moves ui to active tab when screen recording */ function stopTabActivationListening() { browser.tabs.onActivated.removeListener(tabActivatedListener); } const trackedTab: number | null = usedTab ?? null; + /** reloads ui on currently active tab once its reloads itself */ function tabUpdateListener(tabId: number, changeInfo: any) { const state = getRecState(); if (state === REC_STATE.stopped) { @@ -1186,6 +1174,7 @@ export default defineBackground(() => { }); } + /** discards recording if was recording single tab and its now closed */ function tabRemovedListener(tabId: number) { if (tabId === trackedTab) { void browser.runtime.sendMessage({ @@ -1221,12 +1210,14 @@ export default defineBackground(() => { startTabListening(); if (area === "desktop") { + // if desktop, watch for tab change events startTabActivationListening(); } if (area === "tab") { + // if tab, watch for tab remove changes to discard recording startRemovedListening(); } - setOnStop?.(() => { + setOnStop(() => { stopTabListening(); if (area === "desktop") { stopTabActivationListening(); diff --git a/spot/entrypoints/content/ControlsBox.tsx b/spot/entrypoints/content/ControlsBox.tsx index 38fa3a9de..f9e585245 100644 --- a/spot/entrypoints/content/ControlsBox.tsx +++ b/spot/entrypoints/content/ControlsBox.tsx @@ -1,4 +1,4 @@ -import Countdown from "@/entrypoints/content/Countdown"; +import Countdown from "~/entrypoints/content/Countdown"; import "~/assets/main.css"; import "./style.css"; import { createSignal } from "solid-js"; diff --git a/spot/entrypoints/content/RecordingControls.tsx b/spot/entrypoints/content/RecordingControls.tsx index 989111efd..ecbef8c85 100644 --- a/spot/entrypoints/content/RecordingControls.tsx +++ b/spot/entrypoints/content/RecordingControls.tsx @@ -1,6 +1,6 @@ import { createSignal, onCleanup, createEffect } from "solid-js"; -import { STATES, formatMsToTime } from "@/entrypoints/content/utils"; -import micOn from "@/assets/mic-on.svg"; +import { STATES, formatMsToTime } from "~/entrypoints/content/utils"; +import micOn from "~/assets/mic-on.svg"; import { createDraggable } from "@neodrag/solid"; interface IRControls { @@ -128,7 +128,7 @@ function RecordingControls({ handleRef.classList.remove("popupanimated"); }, 250); - const audioPerm = getAudioPerm() + const audioPerm = getAudioPerm(); return (
0 ? mic() ? "Switch Off Mic" : "Switch On Mic" : "Microphone disabled"} + data-tip={ + audioPerm > 0 + ? mic() + ? "Switch Off Mic" + : "Switch On Mic" + : "Microphone disabled" + } onClick={audioPerm > 0 ? toggleMic : undefined} > {mic() ? ( diff --git a/spot/entrypoints/content/SavingControls.tsx b/spot/entrypoints/content/SavingControls.tsx index 88e34df70..1576eaeb6 100644 --- a/spot/entrypoints/content/SavingControls.tsx +++ b/spot/entrypoints/content/SavingControls.tsx @@ -1,7 +1,7 @@ // noinspection SpellCheckingInspection import { createSignal, onCleanup, createEffect } from "solid-js"; -import { formatMsToTime } from "@/entrypoints/content/utils"; +import { formatMsToTime } from "~/entrypoints/content/utils"; import "./style.css"; import "./dragControls.css"; @@ -152,7 +152,7 @@ function SavingControls({ const trim = bounds[0] + bounds[1] === 0 ? null - : [Math.floor(bounds[0] * 1000), Math.ceil(bounds[1] * 1000)] + : [Math.floor(bounds[0] * 1000), Math.ceil(bounds[1] * 1000)]; const dataObj = { blob: videoBlob(), name: name(), diff --git a/spot/entrypoints/content/index.tsx b/spot/entrypoints/content/index.tsx index e4d824d5e..56a913776 100644 --- a/spot/entrypoints/content/index.tsx +++ b/spot/entrypoints/content/index.tsx @@ -5,7 +5,7 @@ import { startClickRecording, stopClickRecording, } from "./eventTrackers"; -import ControlsBox from "@/entrypoints/content/ControlsBox"; +import ControlsBox from "~/entrypoints/content/ControlsBox"; import { convertBlobToBase64, getChromeFullVersion } from "./utils"; import "./style.css"; @@ -253,20 +253,41 @@ export default defineContentScript({ logs: event.data.logs, }); } + if (event.data.type === "ort:bump-network") { + void chrome.runtime.sendMessage({ + type: "ort:bump-network", + event: event.data.event, + }); + } }); - function startConsoleTracking() { + let injected = false; + function injectScript() { + if (injected) return; + injected = true; const scriptEl = document.createElement("script"); scriptEl.src = browser.runtime.getURL("/injected.js"); document.head.appendChild(scriptEl); - + } + function startConsoleTracking() { + injectScript() setTimeout(() => { - window.postMessage({ type: "injected:start" }); + window.postMessage({ type: "injected:c-start" }); }, 100); } + function startNetworkTracking() { + injectScript() + setTimeout(() => { + window.postMessage({ type: "injected:n-start" }); + }, 100) + } function stopConsoleTracking() { - window.postMessage({ type: "injected:stop" }); + window.postMessage({ type: "injected:c-stop" }); + } + + function stopNetworkTracking() { + window.postMessage({ type: "injected:n-stop" }); } function onRestart() { @@ -323,7 +344,12 @@ export default defineContentScript({ micResponse = null; startClickRecording(); startLocationRecording(); - startConsoleTracking(); + if (message.withConsole) { + startConsoleTracking(); + } + if (message.withNetwork) { + startNetworkTracking(); + } browser.runtime.sendMessage({ type: "ort:started" }); if (message.shouldMount) { ui.mount(); @@ -343,6 +369,7 @@ export default defineContentScript({ stopClickRecording(); stopLocationRecording(); stopConsoleTracking(); + stopNetworkTracking(); recState = "stopped"; ui.remove(); return "unmounted"; diff --git a/spot/entrypoints/injected.js b/spot/entrypoints/injected.js deleted file mode 100644 index b810aa469..000000000 --- a/spot/entrypoints/injected.js +++ /dev/null @@ -1,153 +0,0 @@ -export default defineUnlistedScript(() => { - const printError = - "InstallTrigger" in window // detect Firefox - ? (e) => e.message + "\n" + e.stack - : (e) => e.stack || e.message; - - function printString(arg) { - if (arg === undefined) { - return "undefined"; - } - if (arg === null) { - return "null"; - } - if (arg instanceof Error) { - return printError(arg); - } - if (Array.isArray(arg)) { - return `Array(${arg.length})`; - } - return String(arg); - } - - function printFloat(arg) { - if (typeof arg !== "number") return "NaN"; - return arg.toString(); - } - - function printInt(arg) { - if (typeof arg !== "number") return "NaN"; - return Math.floor(arg).toString(); - } - - function printObject(arg) { - if (arg === undefined) { - return "undefined"; - } - if (arg === null) { - return "null"; - } - if (arg instanceof Error) { - return printError(arg); - } - if (Array.isArray(arg)) { - const length = arg.length; - const values = arg.slice(0, 10).map(printString).join(", "); - return `Array(${length})[${values}]`; - } - if (typeof arg === "object") { - const res = []; - let i = 0; - for (const k in arg) { - if (++i === 10) { - break; - } - const v = arg[k]; - res.push(k + ": " + printString(v)); - } - return "{" + res.join(", ") + "}"; - } - return arg.toString(); - } - - function printf(args) { - if (typeof args[0] === "string") { - args.unshift( - args.shift().replace(/%(o|s|f|d|i)/g, (s, t) => { - const arg = args.shift(); - if (arg === undefined) return s; - switch (t) { - case "o": - return printObject(arg); - case "s": - return printString(arg); - case "f": - return printFloat(arg); - case "d": - case "i": - return printInt(arg); - default: - return s; - } - }), - ); - } - return args.map(printObject).join(" "); - } - - const consoleMethods = ["log", "info", "warn", "error", "debug", "assert"]; - - const patchConsole = (console, ctx) => { - if (window.revokeSpotPatch || window.__or_proxy_revocable) { - return; - } - let n = 0; - const reset = () => { - n = 0; - }; - let int = setInterval(reset, 1000); - - const sendConsoleLog = (level, args) => { - const msg = printf(args); - const truncated = - msg.length > 5000 ? `Truncated: ${msg.slice(0, 5000)}...` : msg; - const logs = [{ level, msg: truncated, time: Date.now() }]; - window.postMessage({ type: "ort:bump-logs", logs }, "*"); - }; - - const handler = (level) => ({ - apply: function (target, thisArg, argumentsList) { - Reflect.apply(target, ctx, argumentsList); - n = n + 1; - if (n > 10) { - return; - } else { - sendConsoleLog(level, argumentsList); // Pass the correct level - } - }, - }); - - window.__or_proxy_revocable = []; - consoleMethods.forEach((method) => { - if (consoleMethods.indexOf(method) === -1) { - return; - } - const fn = ctx.console[method]; - // is there any way to preserve the original console trace? - const revProxy = Proxy.revocable(fn, handler(method)); - console[method] = revProxy.proxy; - window.__or_proxy_revocable.push(revProxy); - }); - - return () => { - clearInterval(int); - window.__or_proxy_revocable.forEach((revocable) => { - revocable.revoke(); - }); - }; - }; - - window.addEventListener("message", (event) => { - if (event.data.type === "injected:start") { - if (!window.__or_revokeSpotPatch) { - window.__or_revokeSpotPatch = patchConsole(console, window); - } - } - if (event.data.type === "injected:stop") { - if (window.__or_revokeSpotPatch) { - window.__or_revokeSpotPatch(); - window.__or_revokeSpotPatch = null; - } - } - }); -}); diff --git a/spot/entrypoints/injected.ts b/spot/entrypoints/injected.ts new file mode 100644 index 000000000..9ecebc13a --- /dev/null +++ b/spot/entrypoints/injected.ts @@ -0,0 +1,24 @@ +import { startNetwork, stopNetwork } from "~/utils/proxyNetworkTracking"; +import { patchConsole } from "~/utils/consoleTracking"; + +export default defineUnlistedScript(() => { + window.addEventListener("message", (event) => { + if (event.data.type === "injected:c-start") { + if (!window.__or_revokeSpotPatch) { + window.__or_revokeSpotPatch = patchConsole(console, window); + } + } + if (event.data.type === "injected:n-start") { + startNetwork(); + } + if (event.data.type === "injected:n-stop") { + stopNetwork(); + } + if (event.data.type === "injected:c-stop") { + if (window.__or_revokeSpotPatch) { + window.__or_revokeSpotPatch(); + window.__or_revokeSpotPatch = null; + } + } + }); +}); diff --git a/spot/entrypoints/popup/App.tsx b/spot/entrypoints/popup/App.tsx index 196ebebfd..3d9314f76 100644 --- a/spot/entrypoints/popup/App.tsx +++ b/spot/entrypoints/popup/App.tsx @@ -1,11 +1,11 @@ -import orLogo from "@/assets/orSpot.svg"; -import micOff from "@/assets/mic-off-red.svg"; -import micOn from "@/assets/mic-on-dark.svg"; -import Login from "@/entrypoints/popup/Login"; -import Settings from "@/entrypoints/popup/Settings"; +import orLogo from "~/assets/orSpot.svg"; +import micOff from "~/assets/mic-off-red.svg"; +import micOn from "~/assets/mic-on-dark.svg"; +import Login from "~/entrypoints/popup/Login"; +import Settings from "~/entrypoints/popup/Settings"; import { createSignal, createEffect, onMount } from "solid-js"; -import Dropdown from "@/entrypoints/popup/Dropdown"; -import Button from "@/entrypoints/popup/Button"; +import Dropdown from "~/entrypoints/popup/Dropdown"; +import Button from "~/entrypoints/popup/Button"; import { ChevronSvg, RecordDesktopSvg, diff --git a/spot/entrypoints/popup/Login.tsx b/spot/entrypoints/popup/Login.tsx index 846ae6fc8..33d2a2d85 100644 --- a/spot/entrypoints/popup/Login.tsx +++ b/spot/entrypoints/popup/Login.tsx @@ -1,5 +1,3 @@ -import Button from "@/entrypoints/popup/Button"; - function Login() { const onOpenLoginPage = async () => { const { settings } = await chrome.storage.local.get("settings"); @@ -11,8 +9,20 @@ function Login() { }; return (
- - + +
); } @@ -46,4 +56,4 @@ function openLoginPage(instanceUrl: string) { }); } -export default Login; \ No newline at end of file +export default Login; diff --git a/spot/entrypoints/popup/Settings.tsx b/spot/entrypoints/popup/Settings.tsx index 664b6e872..4f0cfb049 100644 --- a/spot/entrypoints/popup/Settings.tsx +++ b/spot/entrypoints/popup/Settings.tsx @@ -1,6 +1,6 @@ import { createSignal, onMount } from "solid-js"; -import orLogo from "@/assets/orSpot.svg"; -import arrowLeft from "@/assets/arrow-left.svg"; +import orLogo from "~/assets/orSpot.svg"; +import arrowLeft from "~/assets/arrow-left.svg"; function Settings({ goBack }: { goBack: () => void }) { const [includeDevTools, setIncludeDevTools] = createSignal(true); @@ -13,7 +13,8 @@ function Settings({ goBack }: { goBack: () => void }) { onMount(() => { chrome.storage.local.get("settings", (data: any) => { if (data.settings) { - const ingest = data.settings.ingestPoint || "https://app.openreplay.com"; + const ingest = + data.settings.ingestPoint || "https://app.openreplay.com"; const devToolsEnabled = data.settings.consoleLogs && data.settings.networkLogs; setOpenInNewTab(data.settings.openInNewTab ?? false); @@ -89,7 +90,8 @@ function Settings({ goBack }: { goBack: () => void }) { return (
-
@@ -159,7 +161,8 @@ function Settings({ goBack }: { goBack: () => void }) {

- Set this URL if you are self-hosting OpenReplay so it points to your instance. + Set this URL if you are self-hosting OpenReplay so it points to your + instance.

{showIngest() && ( @@ -191,16 +194,16 @@ function Settings({ goBack }: { goBack: () => void }) { ) : ( -
- {ingest()} - -
- )} +
+ {ingest()} + +
+ )} )} diff --git a/spot/package.json b/spot/package.json index f3ac9c69f..0d5997b74 100644 --- a/spot/package.json +++ b/spot/package.json @@ -2,7 +2,7 @@ "name": "wxt-starter", "description": "manifest.json description", "private": true, - "version": "1.0.8", + "version": "1.0.9", "type": "module", "scripts": { "dev": "wxt", @@ -17,6 +17,7 @@ }, "dependencies": { "@neodrag/solid": "^2.0.4", + "@openreplay/network-proxy": "^1.0.3", "@thedutchcoder/postcss-rem-to-px": "^0.0.2", "autoprefixer": "^10.4.19", "install": "^0.13.0", @@ -31,6 +32,6 @@ "@wxt-dev/module-solid": "^1.1.2", "daisyui": "^4.12.10", "typescript": "^5.4.5", - "wxt": "0.19.9" + "wxt": "0.19.10" } } diff --git a/spot/tsconfig.json b/spot/tsconfig.json index db3e31422..70a38bef5 100644 --- a/spot/tsconfig.json +++ b/spot/tsconfig.json @@ -1,5 +1,14 @@ { "extends": "./.wxt/tsconfig.json", + "paths": { + "~": [".."], + "~/*": ["../*"], + "@@": [".."], + "@@/*": ["../*"], + "~~": [".."], + "~~/*": ["../*"] + }, + "lib": ["es2022", "DOM"], "compilerOptions": { "jsx": "preserve", "jsxImportSource": "solid-js" diff --git a/spot/utils/consoleTracking.ts b/spot/utils/consoleTracking.ts new file mode 100644 index 000000000..80de7ffde --- /dev/null +++ b/spot/utils/consoleTracking.ts @@ -0,0 +1,142 @@ +function printString(arg) { + const printError = + "InstallTrigger" in window // detect Firefox + ? (e) => e.message + "\n" + e.stack + : (e) => e.stack || e.message; + + if (arg === undefined) { + return "undefined"; + } + if (arg === null) { + return "null"; + } + if (arg instanceof Error) { + return printError(arg); + } + if (Array.isArray(arg)) { + return `Array(${arg.length})`; + } + return String(arg); +} + +function printFloat(arg) { + if (typeof arg !== "number") return "NaN"; + return arg.toString(); +} + +function printInt(arg) { + if (typeof arg !== "number") return "NaN"; + return Math.floor(arg).toString(); +} + +function printObject(arg) { + const printError = + "InstallTrigger" in window // detect Firefox + ? (e) => e.message + "\n" + e.stack + : (e) => e.stack || e.message; + + if (arg === undefined) { + return "undefined"; + } + if (arg === null) { + return "null"; + } + if (arg instanceof Error) { + return printError(arg); + } + if (Array.isArray(arg)) { + const length = arg.length; + const values = arg.slice(0, 10).map(printString).join(", "); + return `Array(${length})[${values}]`; + } + if (typeof arg === "object") { + const res = []; + let i = 0; + for (const k in arg) { + if (++i === 10) { + break; + } + const v = arg[k]; + res.push(k + ": " + printString(v)); + } + return "{" + res.join(", ") + "}"; + } + return arg.toString(); +} + +function printf(args) { + if (typeof args[0] === "string") { + args.unshift( + args.shift().replace(/%(o|s|f|d|i)/g, (s, t) => { + const arg = args.shift(); + if (arg === undefined) return s; + switch (t) { + case "o": + return printObject(arg); + case "s": + return printString(arg); + case "f": + return printFloat(arg); + case "d": + case "i": + return printInt(arg); + default: + return s; + } + }), + ); + } + return args.map(printObject).join(" "); +} + +const consoleMethods = ["log", "info", "warn", "error", "debug", "assert"]; + +export const patchConsole = (console, ctx) => { + if (window.revokeSpotPatch || window.__or_proxy_revocable) { + return; + } + let n = 0; + const reset = () => { + n = 0; + }; + let int = setInterval(reset, 1000); + + const sendConsoleLog = (level, args) => { + const msg = printf(args); + const truncated = + msg.length > 5000 ? `Truncated: ${msg.slice(0, 5000)}...` : msg; + const logs = [{ level, msg: truncated, time: Date.now() }]; + window.postMessage({ type: "ort:bump-logs", logs }, "*"); + }; + + const handler = (level) => ({ + apply: function (target, thisArg, argumentsList) { + Reflect.apply(target, ctx, argumentsList); + n = n + 1; + if (n > 10) { + return; + } else { + sendConsoleLog(level, argumentsList); // Pass the correct level + } + }, + }); + + window.__or_proxy_revocable = []; + consoleMethods.forEach((method) => { + if (consoleMethods.indexOf(method) === -1) { + return; + } + const fn = ctx.console[method]; + // is there any way to preserve the original console trace? + const revProxy = Proxy.revocable(fn, handler(method)); + console[method] = revProxy.proxy; + window.__or_proxy_revocable.push(revProxy); + }); + + return () => { + clearInterval(int); + window.__or_proxy_revocable.forEach((revocable) => { + revocable.revoke(); + }); + }; +}; \ No newline at end of file diff --git a/spot/utils/networkTracking.ts b/spot/utils/networkTracking.ts index 069de9593..e6e4d67fe 100644 --- a/spot/utils/networkTracking.ts +++ b/spot/utils/networkTracking.ts @@ -1,305 +1,169 @@ -import { WebRequest } from "webextension-polyfill"; -export type TrackedRequest = { - statusCode: number; - requestHeaders: Record; - responseHeaders: Record; -} & ( - | WebRequest.OnBeforeRequestDetailsType - | WebRequest.OnBeforeSendHeadersDetailsType - | WebRequest.OnCompletedDetailsType - | WebRequest.OnErrorOccurredDetailsType - | WebRequest.OnResponseStartedDetailsType -); - -export interface SpotNetworkRequest { - encodedBodySize: number; - responseBodySize: number; - duration: number; - method: TrackedRequest["method"]; - type: string; - time: TrackedRequest["timeStamp"]; - statusCode: number; - error?: string; - url: TrackedRequest["url"]; - fromCache: boolean; - body: string; - requestHeaders: Record; - responseHeaders: Record; -} -export const rawRequests: (TrackedRequest & { - startTs: number; - duration: number; -})[] = []; - -const sensitiveParams = new Set([ - "password", - "pass", - "pwd", - "mdp", - "token", - "bearer", - "jwt", - "api_key", - "api-key", - "apiKey", - "key", - "secret", - "id", - "user", - "userId", - "email", - "ssn", - "name", - "firstname", - "lastname", - "birthdate", - "dob", - "address", - "zip", - "zipcode", - "x-api-key", - "www-authenticate", - "x-csrf-token", - "x-requested-with", - "x-forwarded-for", - "x-real-ip", - "cookie", - "authorization", - "auth", - "proxy-authorization", - "set-cookie", - "account_key", -]); - -function filterHeaders(headers: Record) { - const filteredHeaders: Record = {}; - if (Array.isArray(headers)) { - headers.forEach(({ name, value }) => { - if (sensitiveParams.has(name.toLowerCase())) { - filteredHeaders[name] = "******"; - } else { - filteredHeaders[name] = value; - } - }); - } else { - for (const [key, value] of Object.entries(headers)) { - if (sensitiveParams.has(key.toLowerCase())) { - filteredHeaders[key] = "******"; - } else { - filteredHeaders[key] = value; - } - } - } - return filteredHeaders; -} - -// JSON or form data -function filterBody(body: any) { - if (!body) { - return body; - } - - let parsedBody; - let isJSON = false; - - try { - parsedBody = JSON.parse(body); - isJSON = true; - } catch (e) { - // not json - } - - if (isJSON) { - obscureSensitiveData(parsedBody); - return JSON.stringify(parsedBody); - } else { - const params = new URLSearchParams(body); - for (const key of params.keys()) { - if (sensitiveParams.has(key.toLowerCase())) { - params.set(key, "******"); - } - } - - return params.toString(); - } -} - -function obscureSensitiveData(obj: Record | any[]) { - if (Array.isArray(obj)) { - obj.forEach(obscureSensitiveData); - } else if (obj && typeof obj === "object") { - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - if (sensitiveParams.has(key.toLowerCase())) { - obj[key] = "******"; - } else if (obj[key] !== null && typeof obj[key] === "object") { - obscureSensitiveData(obj[key]); - } - } - } - } -} - -function tryFilterUrl(url: string) { - if (!url) return ""; - try { - const urlObj = new URL(url); - if (urlObj.searchParams) { - for (const key of urlObj.searchParams.keys()) { - if (sensitiveParams.has(key.toLowerCase())) { - urlObj.searchParams.set(key, "******"); - } - } - } - return urlObj.toString(); - } catch (e) { - return url; - } -} - -export function createSpotNetworkRequest( - trackedRequest: TrackedRequest, - trackedTab?: number, -) { - if (trackedRequest.tabId === -1) { - return; - } - if (trackedTab && trackedTab !== trackedRequest.tabId) { - return; - } - if ( - ["ping", "beacon", "image", "script", "font"].includes(trackedRequest.type) - ) { - if (!trackedRequest.statusCode || trackedRequest.statusCode < 400) { - return; - } - } - const type = ["stylesheet", "script", "image", "media", "font"].includes( - trackedRequest.type, - ) - ? "resource" - : trackedRequest.type; - - const requestHeaders = trackedRequest.requestHeaders - ? filterHeaders(trackedRequest.requestHeaders) - : {}; - const responseHeaders = trackedRequest.responseHeaders - ? filterHeaders(trackedRequest.responseHeaders) - : {}; - - const reqSize = trackedRequest.reqBody - ? trackedRequest.requestSize || trackedRequest.reqBody.length - : 0; - - const status = getRequestStatus(trackedRequest); - let body; - if (trackedRequest.reqBody) { - try { - body = filterBody(trackedRequest.reqBody); - } catch (e) { - body = "Error parsing body"; - console.error(e); - } - } else { - body = ""; - } - const request: SpotNetworkRequest = { - method: trackedRequest.method, - type, - body, - requestHeaders, - responseHeaders, - time: trackedRequest.timeStamp, - statusCode: status, - error: trackedRequest.error, - url: tryFilterUrl(trackedRequest.url), - fromCache: trackedRequest.fromCache || false, - encodedBodySize: reqSize, - responseBodySize: trackedRequest.responseSize, - duration: trackedRequest.duration, - }; - - return request; -} - -function modifyOnSpot(request: TrackedRequest) { - const id = request.requestId; - const index = rawRequests.findIndex((r) => r.requestId === id); - const ts = Date.now(); - const start = rawRequests[index]?.startTs ?? ts; - rawRequests[index] = { - ...rawRequests[index], - ...request, - duration: ts - start, - }; -} - -const trackOnBefore = ( - details: WebRequest.OnBeforeRequestDetailsType & { reqBody: string }, -) => { - if (details.method === "POST" && details.requestBody) { - const requestBody = details.requestBody; - if (requestBody.formData) { - details.reqBody = JSON.stringify(requestBody.formData); - } else if (requestBody.raw) { - const raw = requestBody.raw[0]?.bytes; - if (raw) { - details.reqBody = new TextDecoder("utf-8").decode(raw); - } - } - } - rawRequests.push({ ...details, startTs: Date.now(), duration: 0 }); -}; -const trackOnCompleted = (details: WebRequest.OnCompletedDetailsType) => { - modifyOnSpot(details); -}; -const trackOnHeaders = (details: WebRequest.OnBeforeSendHeadersDetailsType) => { - modifyOnSpot(details); -}; -const trackOnError = (details: WebRequest.OnErrorOccurredDetailsType) => { - modifyOnSpot(details); -}; -export function startTrackingNetwork() { - rawRequests.length = 0; - browser.webRequest.onBeforeRequest.addListener( - // @ts-ignore - trackOnBefore, - { urls: [""] }, - ["requestBody"], - ); - browser.webRequest.onBeforeSendHeaders.addListener( - trackOnHeaders, - { urls: [""] }, - ["requestHeaders"], - ); - browser.webRequest.onCompleted.addListener( - trackOnCompleted, - { - urls: [""], - }, - ["responseHeaders"], - ); - browser.webRequest.onErrorOccurred.addListener( - trackOnError, - { - urls: [""], - }, - ["extraHeaders"], - ); -} - -export function stopTrackingNetwork() { - browser.webRequest.onBeforeRequest.removeListener(trackOnBefore); - browser.webRequest.onCompleted.removeListener(trackOnCompleted); - browser.webRequest.onErrorOccurred.removeListener(trackOnError); -} - -function getRequestStatus(request: any): number { - if (request.statusCode) { - return request.statusCode; - } - if (request.error) { - return 0; - } - return 200; -} +// import { +// SpotNetworkRequest, +// filterBody, +// filterHeaders, +// tryFilterUrl, +// TrackedRequest, +// } from "./networkTrackingUtils"; +// +// export const rawRequests: (TrackedRequest & { +// startTs: number; +// duration: number; +// })[] = []; +// +// export function createSpotNetworkRequestV1( +// trackedRequest: TrackedRequest, +// trackedTab?: number, +// ) { +// if (trackedRequest.tabId === -1) { +// return; +// } +// if (trackedTab && trackedTab !== trackedRequest.tabId) { +// return; +// } +// if ( +// ["ping", "beacon", "image", "script", "font"].includes(trackedRequest.type) +// ) { +// if (!trackedRequest.statusCode || trackedRequest.statusCode < 400) { +// return; +// } +// } +// const type = ["stylesheet", "script", "image", "media", "font"].includes( +// trackedRequest.type, +// ) +// ? "resource" +// : trackedRequest.type; +// +// const requestHeaders = trackedRequest.requestHeaders +// ? filterHeaders(trackedRequest.requestHeaders) +// : {}; +// const responseHeaders = trackedRequest.responseHeaders +// ? filterHeaders(trackedRequest.responseHeaders) +// : {}; +// +// const reqSize = trackedRequest.reqBody +// ? trackedRequest.requestSize || trackedRequest.reqBody.length +// : 0; +// +// const status = getRequestStatus(trackedRequest); +// let body; +// if (trackedRequest.reqBody) { +// try { +// body = filterBody(trackedRequest.reqBody); +// } catch (e) { +// body = "Error parsing body"; +// console.error(e); +// } +// } else { +// body = ""; +// } +// const request: SpotNetworkRequest = { +// method: trackedRequest.method, +// type, +// body, +// responseBody: "", +// requestHeaders, +// responseHeaders, +// time: trackedRequest.timeStamp, +// statusCode: status, +// error: trackedRequest.error, +// url: tryFilterUrl(trackedRequest.url), +// fromCache: trackedRequest.fromCache || false, +// encodedBodySize: reqSize, +// responseBodySize: trackedRequest.responseSize, +// duration: trackedRequest.duration, +// }; +// +// return request; +// } +// +// function modifyOnSpot(request: TrackedRequest) { +// const id = request.requestId; +// const index = rawRequests.findIndex((r) => r.requestId === id); +// const ts = Date.now(); +// const start = rawRequests[index]?.startTs ?? ts; +// rawRequests[index] = { +// ...rawRequests[index], +// ...request, +// duration: ts - start, +// }; +// } +// +// const trackOnBefore = ( +// details: WebRequest.OnBeforeRequestDetailsType & { reqBody: string }, +// ) => { +// if (details.method === "POST" && details.requestBody) { +// const requestBody = details.requestBody; +// if (requestBody.formData) { +// details.reqBody = JSON.stringify(requestBody.formData); +// } else if (requestBody.raw) { +// const raw = requestBody.raw[0]?.bytes; +// if (raw) { +// details.reqBody = new TextDecoder("utf-8").decode(raw); +// } +// } +// } +// rawRequests.push({ ...details, startTs: Date.now(), duration: 0 }); +// }; +// const trackOnCompleted = (details: WebRequest.OnCompletedDetailsType) => { +// modifyOnSpot(details); +// }; +// const trackOnHeaders = (details: WebRequest.OnBeforeSendHeadersDetailsType) => { +// modifyOnSpot(details); +// }; +// const trackOnError = (details: WebRequest.OnErrorOccurredDetailsType) => { +// modifyOnSpot(details); +// }; +// export function startTrackingNetwork() { +// rawRequests.length = 0; +// browser.webRequest.onBeforeRequest.addListener( +// // @ts-ignore +// trackOnBefore, +// { urls: [""] }, +// ["requestBody"], +// ); +// browser.webRequest.onBeforeSendHeaders.addListener( +// trackOnHeaders, +// { urls: [""] }, +// ["requestHeaders"], +// ); +// browser.webRequest.onCompleted.addListener( +// trackOnCompleted, +// { +// urls: [""], +// }, +// ["responseHeaders"], +// ); +// browser.webRequest.onErrorOccurred.addListener( +// trackOnError, +// { +// urls: [""], +// }, +// ["extraHeaders"], +// ); +// } +// +// export function stopTrackingNetwork() { +// browser.webRequest.onBeforeRequest.removeListener(trackOnBefore); +// browser.webRequest.onCompleted.removeListener(trackOnCompleted); +// browser.webRequest.onErrorOccurred.removeListener(trackOnError); +// } +// +// function getRequestStatus(request: any): number { +// if (request.statusCode) { +// return request.statusCode; +// } +// if (request.error) { +// return 0; +// } +// return 200; +// } +// +// export function getFinalRequests(tabId: number): SpotNetworkRequest[] { +// const finalRequests = rawRequests +// .map((r) => createSpotNetworkRequest(r, tabId)) +// .filter((r) => r !== undefined); +// rawRequests.length = 0; +// +// return finalRequests; +// } diff --git a/spot/utils/networkTrackingUtils.ts b/spot/utils/networkTrackingUtils.ts new file mode 100644 index 000000000..2bd5e4208 --- /dev/null +++ b/spot/utils/networkTrackingUtils.ts @@ -0,0 +1,168 @@ +import { WebRequest } from "webextension-polyfill"; +export type TrackedRequest = { + statusCode: number; + requestHeaders: Record; + responseHeaders: Record; +} & ( + | WebRequest.OnBeforeRequestDetailsType + | WebRequest.OnBeforeSendHeadersDetailsType + | WebRequest.OnCompletedDetailsType + | WebRequest.OnErrorOccurredDetailsType + | WebRequest.OnResponseStartedDetailsType +); + +export function getTopWindow(): Window { + let currentWindow = window; + try { + while (currentWindow !== currentWindow.parent) { + currentWindow = currentWindow.parent; + } + } catch (e) { + // Accessing currentWindow.parent threw an exception due to cross-origin policy + // currentWindow is the topmost accessible window + } + return currentWindow; +} + +export interface SpotNetworkRequest { + encodedBodySize: number; + responseBodySize: number; + duration: number; + method: TrackedRequest["method"]; + type: string; + time: TrackedRequest["timeStamp"]; + statusCode: number; + error?: string; + url: TrackedRequest["url"]; + fromCache: boolean; + body: string; + responseBody: string; + requestHeaders: Record; + responseHeaders: Record; +} + +export const sensitiveParams = new Set([ + "password", + "pass", + "pwd", + "mdp", + "token", + "bearer", + "jwt", + "api_key", + "api-key", + "apiKey", + "key", + "secret", + "id", + "user", + "userId", + "email", + "ssn", + "name", + "firstname", + "lastname", + "birthdate", + "dob", + "address", + "zip", + "zipcode", + "x-api-key", + "www-authenticate", + "x-csrf-token", + "x-requested-with", + "x-forwarded-for", + "x-real-ip", + "cookie", + "authorization", + "auth", + "proxy-authorization", + "set-cookie", + "account_key", +]); + +export function filterHeaders(headers: Record | { name: string; value: string }[]) { + const filteredHeaders: Record = {}; + if (Array.isArray(headers)) { + headers.forEach(({ name, value }) => { + if (sensitiveParams.has(name.toLowerCase())) { + filteredHeaders[name] = "******"; + } else { + filteredHeaders[name] = value; + } + }); + } else { + for (const [key, value] of Object.entries(headers)) { + if (sensitiveParams.has(key.toLowerCase())) { + filteredHeaders[key] = "******"; + } else { + filteredHeaders[key] = value; + } + } + } + return filteredHeaders; +} + +// JSON or form data +export function filterBody(body: any): string { + if (!body) { + return body; + } + + let parsedBody; + let isJSON = false; + + try { + parsedBody = JSON.parse(body); + isJSON = true; + } catch (e) { + // not json + } + + if (isJSON) { + obscureSensitiveData(parsedBody); + return JSON.stringify(parsedBody); + } else { + const params = new URLSearchParams(body); + for (const key of params.keys()) { + if (sensitiveParams.has(key.toLowerCase())) { + params.set(key, "******"); + } + } + + return params.toString(); + } +} + +export function obscureSensitiveData(obj: Record | any[]) { + if (Array.isArray(obj)) { + obj.forEach(obscureSensitiveData); + } else if (obj && typeof obj === "object") { + for (const key in obj) { + if (Object.hasOwn(obj, key)) { + if (sensitiveParams.has(key.toLowerCase())) { + obj[key] = "******"; + } else if (obj[key] !== null && typeof obj[key] === "object") { + obscureSensitiveData(obj[key]); + } + } + } + } +} + +export function tryFilterUrl(url: string) { + if (!url) return ""; + try { + const urlObj = new URL(url); + if (urlObj.searchParams) { + for (const key of urlObj.searchParams.keys()) { + if (sensitiveParams.has(key.toLowerCase())) { + urlObj.searchParams.set(key, "******"); + } + } + } + return urlObj.toString(); + } catch (e) { + return url; + } +} diff --git a/spot/utils/proxyNetworkTracking.ts b/spot/utils/proxyNetworkTracking.ts new file mode 100644 index 000000000..fb3a7185e --- /dev/null +++ b/spot/utils/proxyNetworkTracking.ts @@ -0,0 +1,101 @@ +import createNetworkProxy, { INetworkMessage } from "@openreplay/network-proxy"; +import { + SpotNetworkRequest, + filterBody, + filterHeaders, + tryFilterUrl, + getTopWindow, +} from "./networkTrackingUtils"; + +let defaultFetch: typeof fetch | undefined; +let defaultXhr: typeof XMLHttpRequest | undefined; +let defaultBeacon: typeof navigator.sendBeacon | undefined; + +export function startNetwork() { + const context = getTopWindow(); + defaultXhr = context.XMLHttpRequest; + defaultBeacon = context.navigator.sendBeacon; + defaultFetch = context.fetch; + createNetworkProxy( + context, + [], // headers + () => null, + (reqRes) => reqRes, + (msg) => { + const event = createSpotNetworkRequest(msg); + window.postMessage({ type: "ort:bump-network", event }, "*"); + }, + (url) => + url.includes("/spot/") || url.includes(".mob?") || url.includes(".mobe?"), + { xhr: true, fetch: true, beacon: true }, + ); +} + +function getBody(req: { body?: string | Record }): string { + let body; + + if (req.body) { + try { + body = filterBody(req.body); + } catch (e) { + body = "Error parsing body"; + console.error(e); + } + } else { + body = ""; + } + + return body; +} + +export function createSpotNetworkRequest( + msg: INetworkMessage, +): SpotNetworkRequest { + let request: Record = {} + let response: Record = {}; + try { + request = JSON.parse(msg.request); + } catch (e) { + console.error("Error parsing request", e); + } + try { + response = JSON.parse(msg.response); + } catch (e) { + console.error("Error parsing response", e); + } + const reqHeaders = request.headers ? filterHeaders(request.headers) : {}; + const resHeaders = response.headers ? filterHeaders(response.headers) : {}; + const responseBodySize = msg.responseSize || 0; + const reqSize = msg.request ? msg.request.length : 0; + const body = getBody(request); + const responseBody = getBody(response); + + return { + method: msg.method, + type: msg.requestType, + body, + responseBody, + requestHeaders: reqHeaders, + responseHeaders: resHeaders, + time: msg.startTime, + statusCode: msg.status || 0, + error: undefined, + url: tryFilterUrl(msg.url), + fromCache: false, + encodedBodySize: reqSize, + responseBodySize, + duration: msg.duration, + }; +} + +export function stopNetwork() { + if (defaultFetch) { + window.fetch = defaultFetch; + } + if (defaultXhr) { + window.XMLHttpRequest = defaultXhr; + } + if (defaultBeacon) { + window.navigator.sendBeacon = defaultBeacon; + } +} diff --git a/spot/yarn.lock b/spot/yarn.lock index 127633c78..468f7f6a5 100644 --- a/spot/yarn.lock +++ b/spot/yarn.lock @@ -707,6 +707,11 @@ proc-log "^4.0.0" which "^4.0.0" +"@openreplay/network-proxy@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@openreplay/network-proxy/-/network-proxy-1.0.3.tgz#82c07f0742db01456f3764bae6c0b5ff280557a9" + integrity sha512-vm/x8Ioo1BKJIyZf58tK44CgtzHA0tIwu2uZ2WdIzrBOODF+A2qV4mELC8Zdp9WqV1O7uxXGaA4J38HU3th14g== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" @@ -5331,10 +5336,10 @@ ws@8.18.0: resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -wxt@0.19.9: - version "0.19.9" - resolved "https://registry.yarnpkg.com/wxt/-/wxt-0.19.9.tgz#b8f7f838cab00d66f4ee22f483c49ad8f6527af8" - integrity sha512-XUbF4JNyx2jTDpXwx2c/esaJcUD2Dr482C2GGenkGRMH2UnerzOIchGCtaa1hb2U8eAed7Akda0yRoMJU0uxUw== +wxt@0.19.10: + version "0.19.10" + resolved "https://registry.yarnpkg.com/wxt/-/wxt-0.19.10.tgz#557d57e63ab5fcf3b026791aae3706c400b5f7cb" + integrity sha512-lX/dzAaau79SDsU7QZKUgxJUf9nBsaQXMiqsDNgGoqZO1wrRpFq0kijcN3/mNjGbXM989VJhEl7B6f3ttxKAnQ== dependencies: "@aklinker1/rollup-plugin-visualizer" "5.12.0" "@types/chrome" "^0.0.269" @@ -5350,6 +5355,7 @@ wxt@0.19.9: consola "^3.2.3" defu "^6.1.4" dequal "^2.0.3" + dotenv "^16.4.5" esbuild "^0.23.0" execa "^9.3.1" fast-glob "^3.3.2" @@ -5371,6 +5377,7 @@ wxt@0.19.9: ohash "^1.1.3" open "^10.1.0" ora "^8.1.0" + perfect-debounce "^1.0.0" picocolors "^1.0.1" prompts "^2.4.2" publish-browser-extension "^2.1.3" diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index f1dad5d24..060b724b0 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,7 @@ +# 14.0.8 + +- use separate library to handle network requests ([@openreplay/network-proxy](https://www.npmjs.com/package/@openreplay/network-proxy)) + # 14.0.7 - check for stopping status during restarts diff --git a/tracker/tracker/bun.lockb b/tracker/tracker/bun.lockb index 2b5bba11e7b592f68d21acf94a6cf7a810cc6fcb..fca47c24eebf857b26be6b84b198197f3f5102b3 100755 GIT binary patch delta 38263 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%0D6WsU!4+L%nnYO*`$9Vp!hed?9;nHp38Lj#zsAxT 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 delta 37999 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 - console.warn(`Openreplay: Can't find ${api} in global context. -If you're using serverside rendering in your app, make sure that tracker is loaded dynamically, otherwise ${api} won't be tracked.`) - -export default function setProxy( - context: typeof globalThis, - ignoredHeaders: boolean | string[], - setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, - sanitize: (data: RequestResponseData) => RequestResponseData | null, - sendMessage: (message: NetworkRequest) => void, - isServiceUrl: (url: string) => boolean, - tokenUrlMatcher?: (url: string) => boolean, -) { - if (context.XMLHttpRequest) { - context.XMLHttpRequest = XHRProxy.create( - ignoredHeaders, - setSessionTokenHeader, - sanitize, - sendMessage, - isServiceUrl, - tokenUrlMatcher, - ) - } else { - getWarning('XMLHttpRequest') - } - if (context.fetch) { - context.fetch = FetchProxy.create( - ignoredHeaders, - setSessionTokenHeader, - sanitize, - sendMessage, - isServiceUrl, - tokenUrlMatcher, - ) - } else { - getWarning('fetch') - } - if (context?.navigator?.sendBeacon) { - context.navigator.sendBeacon = BeaconProxy.create( - ignoredHeaders, - setSessionTokenHeader, - sanitize, - sendMessage, - isServiceUrl, - ) - } -} diff --git a/tracker/tracker/src/main/modules/Network/types.ts b/tracker/tracker/src/main/modules/Network/types.ts deleted file mode 100644 index 14364700e..000000000 --- a/tracker/tracker/src/main/modules/Network/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface RequestResponseData { - readonly status: number - readonly method: string - url: string - request: { - body: string | null - headers: Record - } - response: { - body: string | null - headers: Record - } -} - -// we only support sanitizing for json/string data because how you're gonna sanitize binary data? diff --git a/tracker/tracker/src/main/modules/network.ts b/tracker/tracker/src/main/modules/network.ts index b16657b03..d50b11f8c 100644 --- a/tracker/tracker/src/main/modules/network.ts +++ b/tracker/tracker/src/main/modules/network.ts @@ -3,7 +3,7 @@ import { NetworkRequest } from '../app/messages.gen.js' import { getTimeOrigin } from '../utils.js' import type { AxiosInstance } from './axiosSpy.js' import axiosSpy from './axiosSpy.js' -import setProxy from './Network/index.js' +import createNetworkProxy from '@openreplay/network-proxy' type WindowFetch = typeof window.fetch type XHRRequestBody = Parameters[0] @@ -130,13 +130,28 @@ export default function (app: App, opts: Partial = {}) { const patchWindow = (context: typeof globalThis) => { /* ====== modern way ====== */ if (options.useProxy) { - return setProxy( + return createNetworkProxy( context, options.ignoreHeaders, setSessionTokenHeader, sanitize, - (message) => app.send(message), + (message) => { + app.send( + NetworkRequest( + message.requestType, + message.method, + message.url, + message.request, + message.response, + message.status, + message.startTime + getTimeOrigin(), + message.duration, + message.responseSize, + ), + ) + }, (url) => app.isServiceURL(url), + { xhr: true, fetch: true, beacon: true }, options.tokenUrlMatcher, ) } diff --git a/tracker/tracker/tsconfig-base.json b/tracker/tracker/tsconfig-base.json index f74da1469..5bda21c51 100644 --- a/tracker/tracker/tsconfig-base.json +++ b/tracker/tracker/tsconfig-base.json @@ -9,8 +9,8 @@ "alwaysStrict": true, "target": "es2020", "module": "es6", - "moduleResolution": "nodenext", - "esModuleInterop": true + "moduleResolution": "node", + "esModuleInterop": true, }, "exclude": ["**/*.test.ts"] }