From 658f59eb24b67c94c082cba41a3c12b952ad178e Mon Sep 17 00:00:00 2001 From: Jorrin <43169049+JorrinKievit@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:41:20 +0100 Subject: [PATCH 1/5] serialize body --- src/background/messages/makeRequest.ts | 55 +++++++++++- src/background/messages/prepareStream.ts | 103 +-------------------- src/utils/declarativeNetRequest.ts | 110 +++++++++++++++++++++++ 3 files changed, 166 insertions(+), 102 deletions(-) create mode 100644 src/utils/declarativeNetRequest.ts diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 5c37429..4f221da 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -2,9 +2,20 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; +import { setDynamicRules } from '~utils/declarativeNetRequest'; import { makeFullUrl } from '~utils/fetcher'; import { assertDomainWhitelist } from '~utils/storage'; +type Body = + | { + bodyType: 'string'; + value: string; + } + | { + bodyType: 'FormData' | 'URLSearchParams' | 'Object'; + value: Record; + }; + export interface Request extends BaseRequest { baseUrl?: string; headers?: Record; @@ -12,7 +23,7 @@ export interface Request extends BaseRequest { query?: Record; readHeaders?: Record; url: string; - body?: string | FormData | URLSearchParams; + body?: Body; } type Response = BaseResponse<{ @@ -24,14 +35,52 @@ type Response = BaseResponse<{ }; }>; +const mapBodyToFetchBody = (body: Request['body']): BodyInit => { + if (body?.bodyType === 'FormData') { + const formData = new FormData(); + Object.entries(body.value).forEach(([key, value]) => { + formData.append(key, value); + }); + return formData; + } + if (body?.bodyType === 'URLSearchParams') { + const searchParams = new URLSearchParams(); + Object.entries(body.value).forEach(([key, value]) => { + searchParams.set(key, value); + }); + return searchParams; + } + if (body?.bodyType === 'Object') { + return JSON.stringify(body.value); + } + if (body?.bodyType === 'string') { + return body.value; + } + return undefined; +}; + const handler: PlasmoMessaging.MessageHandler> = async (req, res) => { try { await assertDomainWhitelist(req.sender.tab.url); + console.log(req.body.headers['User-Agent']); + if (req.body.headers['User-Agent']) { + console.log('preparing stream'); + await setDynamicRules({ + ruleId: 23498, + targetDomains: [req.body.url], + requestHeaders: { + 'User-Agent': req.body.headers['User-Agent'], + }, + }); + const rules = await chrome.declarativeNetRequest.getDynamicRules(); + console.log(rules); + } + const response = await fetch(makeFullUrl(req.body.url, req.body), { method: req.body.method, headers: req.body.headers, - body: req.body.body, + body: mapBodyToFetchBody(req.body.body), }); const contentType = response.headers.get('content-type'); const body = contentType?.includes('application/json') ? await response.json() : await response.text(); @@ -46,6 +95,8 @@ const handler: PlasmoMessaging.MessageHandler> = async (r }, }); } catch (err) { + console.log('error'); + console.log(err); res.send({ success: false, error: err.message, diff --git a/src/background/messages/prepareStream.ts b/src/background/messages/prepareStream.ts index d984c67..688285d 100644 --- a/src/background/messages/prepareStream.ts +++ b/src/background/messages/prepareStream.ts @@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; -import { isChrome } from '~utils/extension'; +import { setDynamicRules } from '~utils/declarativeNetRequest'; import { assertDomainWhitelist } from '~utils/storage'; interface Request extends BaseRequest { @@ -13,112 +13,15 @@ interface Request extends BaseRequest { responseHeaders?: Record; } -const mapHeadersToDeclarativeNetRequestHeaders = ( - headers: Record, - op: string, -): { header: string; operation: any; value: string }[] => { - return Object.entries(headers).map(([name, value]) => ({ - header: name, - operation: op, - value, - })); -}; - const handler: PlasmoMessaging.MessageHandler = async (req, res) => { try { await assertDomainWhitelist(req.sender.tab.url); - if (isChrome()) { - await chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: [req.body.ruleId], - addRules: [ - { - id: req.body.ruleId, - condition: { - ...(req.body.targetDomains && { requestDomains: req.body.targetDomains }), - ...(req.body.targetRegex && { regexFilter: req.body.targetRegex }), - }, - action: { - type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, - ...(req.body.requestHeaders && Object.keys(req.body.requestHeaders).length > 0 - ? { - requestHeaders: mapHeadersToDeclarativeNetRequestHeaders( - req.body.requestHeaders, - chrome.declarativeNetRequest.HeaderOperation.SET, - ), - } - : {}), - responseHeaders: [ - { - header: 'Access-Control-Allow-Origin', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: '*', - }, - { - header: 'Access-Control-Allow-Methods', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - }, - { - header: 'Access-Control-Allow-Headers', - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - value: '*', - }, - ...mapHeadersToDeclarativeNetRequestHeaders( - req.body.responseHeaders ?? {}, - chrome.declarativeNetRequest.HeaderOperation.SET, - ), - ], - }, - }, - ], - }); - if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message); - } else { - await browser.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: [req.body.ruleId], - addRules: [ - { - id: req.body.ruleId, - condition: { - ...(req.body.targetDomains && { requestDomains: req.body.targetDomains }), - ...(req.body.targetRegex && { regexFilter: req.body.targetRegex }), - }, - action: { - type: 'modifyHeaders', - ...(req.body.requestHeaders && Object.keys(req.body.requestHeaders).length > 0 - ? { - requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(req.body.requestHeaders, 'set'), - } - : {}), - responseHeaders: [ - { - header: 'Access-Control-Allow-Origin', - operation: 'set', - value: '*', - }, - { - header: 'Access-Control-Allow-Methods', - operation: 'set', - value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - }, - { - header: 'Access-Control-Allow-Headers', - operation: 'set', - value: '*', - }, - ...mapHeadersToDeclarativeNetRequestHeaders(req.body.responseHeaders ?? {}, 'set'), - ], - }, - }, - ], - }); - if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message); - } - + await setDynamicRules(req.body); res.send({ success: true, }); } catch (err) { + console.log(err); res.send({ success: false, error: err.message, diff --git a/src/utils/declarativeNetRequest.ts b/src/utils/declarativeNetRequest.ts new file mode 100644 index 0000000..15be683 --- /dev/null +++ b/src/utils/declarativeNetRequest.ts @@ -0,0 +1,110 @@ +import { isChrome } from './extension'; + +interface DynamicRule { + ruleId: number; + targetDomains?: [string, ...string[]]; + targetRegex?: string; + requestHeaders?: Record; + responseHeaders?: Record; +} + +const mapHeadersToDeclarativeNetRequestHeaders = ( + headers: Record, + op: string, +): { header: string; operation: any; value: string }[] => { + return Object.entries(headers).map(([name, value]) => ({ + header: name, + operation: op, + value, + })); +}; + +export const setDynamicRules = async (body: DynamicRule) => { + if (isChrome()) { + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [body.ruleId], + addRules: [ + { + id: body.ruleId, + condition: { + ...(body.targetDomains && { requestDomains: body.targetDomains }), + ...(body.targetRegex && { regexFilter: body.targetRegex }), + }, + action: { + type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, + ...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0 + ? { + requestHeaders: mapHeadersToDeclarativeNetRequestHeaders( + body.requestHeaders, + chrome.declarativeNetRequest.HeaderOperation.SET, + ), + } + : {}), + responseHeaders: [ + { + header: 'Access-Control-Allow-Origin', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: '*', + }, + { + header: 'Access-Control-Allow-Methods', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + }, + { + header: 'Access-Control-Allow-Headers', + operation: chrome.declarativeNetRequest.HeaderOperation.SET, + value: '*', + }, + ...mapHeadersToDeclarativeNetRequestHeaders( + body.responseHeaders ?? {}, + chrome.declarativeNetRequest.HeaderOperation.SET, + ), + ], + }, + }, + ], + }); + if (chrome.runtime.lastError?.message) throw new Error(chrome.runtime.lastError.message); + } else { + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [body.ruleId], + addRules: [ + { + id: body.ruleId, + condition: { + ...(body.targetDomains && { requestDomains: body.targetDomains }), + ...(body.targetRegex && { regexFilter: body.targetRegex }), + }, + action: { + type: 'modifyHeaders', + ...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0 + ? { + requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(body.requestHeaders, 'set'), + } + : {}), + responseHeaders: [ + { + header: 'Access-Control-Allow-Origin', + operation: 'set', + value: '*', + }, + { + header: 'Access-Control-Allow-Methods', + operation: 'set', + value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + }, + { + header: 'Access-Control-Allow-Headers', + operation: 'set', + value: '*', + }, + ...mapHeadersToDeclarativeNetRequestHeaders(body.responseHeaders ?? {}, 'set'), + ], + }, + }, + ], + }); + if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message); + } +}; From a2647a58d639967d7ed1dd9e012c68851351421a Mon Sep 17 00:00:00 2001 From: Jorrin <43169049+JorrinKievit@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:38:04 +0100 Subject: [PATCH 2/5] cleanup and fix targetDomain --- src/background/messages/makeRequest.ts | 12 +++--------- src/background/messages/prepareStream.ts | 1 - 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 4f221da..7cc44fd 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -12,7 +12,7 @@ type Body = value: string; } | { - bodyType: 'FormData' | 'URLSearchParams' | 'Object'; + bodyType: 'FormData' | 'URLSearchParams' | 'object'; value: Record; }; @@ -50,7 +50,7 @@ const mapBodyToFetchBody = (body: Request['body']): BodyInit => { }); return searchParams; } - if (body?.bodyType === 'Object') { + if (body?.bodyType === 'object') { return JSON.stringify(body.value); } if (body?.bodyType === 'string') { @@ -63,18 +63,14 @@ const handler: PlasmoMessaging.MessageHandler> = async (r try { await assertDomainWhitelist(req.sender.tab.url); - console.log(req.body.headers['User-Agent']); if (req.body.headers['User-Agent']) { - console.log('preparing stream'); await setDynamicRules({ ruleId: 23498, - targetDomains: [req.body.url], + targetDomains: [new URL(req.body.url).hostname], requestHeaders: { 'User-Agent': req.body.headers['User-Agent'], }, }); - const rules = await chrome.declarativeNetRequest.getDynamicRules(); - console.log(rules); } const response = await fetch(makeFullUrl(req.body.url, req.body), { @@ -95,8 +91,6 @@ const handler: PlasmoMessaging.MessageHandler> = async (r }, }); } catch (err) { - console.log('error'); - console.log(err); res.send({ success: false, error: err.message, diff --git a/src/background/messages/prepareStream.ts b/src/background/messages/prepareStream.ts index 688285d..2833433 100644 --- a/src/background/messages/prepareStream.ts +++ b/src/background/messages/prepareStream.ts @@ -21,7 +21,6 @@ const handler: PlasmoMessaging.MessageHandler = async (re success: true, }); } catch (err) { - console.log(err); res.send({ success: false, error: err.message, From 65ff4ab91a8f56c65081a4b831f666134c08c6af Mon Sep 17 00:00:00 2001 From: Jorrin <43169049+JorrinKievit@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:25:44 +0100 Subject: [PATCH 3/5] mr feedback --- src/background/messages/makeRequest.ts | 42 +++++++++++--------------- src/utils/declarativeNetRequest.ts | 7 +++++ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 7cc44fd..118c17a 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -2,19 +2,11 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; -import { setDynamicRules } from '~utils/declarativeNetRequest'; +import { removeDynamicRules, setDynamicRules } from '~utils/declarativeNetRequest'; import { makeFullUrl } from '~utils/fetcher'; import { assertDomainWhitelist } from '~utils/storage'; -type Body = - | { - bodyType: 'string'; - value: string; - } - | { - bodyType: 'FormData' | 'URLSearchParams' | 'object'; - value: Record; - }; +const MAKE_REQUEST_DYNAMIC_RULE = 23498; export interface Request extends BaseRequest { baseUrl?: string; @@ -23,7 +15,8 @@ export interface Request extends BaseRequest { query?: Record; readHeaders?: Record; url: string; - body?: Body; + body?: any; + bodyType: 'string' | 'FormData' | 'URLSearchParams' | 'object'; } type Response = BaseResponse<{ @@ -35,26 +28,26 @@ type Response = BaseResponse<{ }; }>; -const mapBodyToFetchBody = (body: Request['body']): BodyInit => { - if (body?.bodyType === 'FormData') { +const mapBodyToFetchBody = (body: Request['body'], bodyType: Request['bodyType']): BodyInit => { + if (bodyType === 'FormData') { const formData = new FormData(); - Object.entries(body.value).forEach(([key, value]) => { - formData.append(key, value); + Object.entries(body).forEach(([key, value]) => { + formData.append(key, value.toString()); }); return formData; } - if (body?.bodyType === 'URLSearchParams') { + if (bodyType === 'URLSearchParams') { const searchParams = new URLSearchParams(); - Object.entries(body.value).forEach(([key, value]) => { - searchParams.set(key, value); + Object.entries(body).forEach(([key, value]) => { + searchParams.set(key, value.toString()); }); return searchParams; } - if (body?.bodyType === 'object') { - return JSON.stringify(body.value); + if (bodyType === 'object') { + return JSON.stringify(body); } - if (body?.bodyType === 'string') { - return body.value; + if (bodyType === 'string') { + return body; } return undefined; }; @@ -65,7 +58,7 @@ const handler: PlasmoMessaging.MessageHandler> = async (r if (req.body.headers['User-Agent']) { await setDynamicRules({ - ruleId: 23498, + ruleId: MAKE_REQUEST_DYNAMIC_RULE, targetDomains: [new URL(req.body.url).hostname], requestHeaders: { 'User-Agent': req.body.headers['User-Agent'], @@ -76,8 +69,9 @@ const handler: PlasmoMessaging.MessageHandler> = async (r const response = await fetch(makeFullUrl(req.body.url, req.body), { method: req.body.method, headers: req.body.headers, - body: mapBodyToFetchBody(req.body.body), + body: mapBodyToFetchBody(req.body.body, req.body.bodyType), }); + await removeDynamicRules([MAKE_REQUEST_DYNAMIC_RULE]); const contentType = response.headers.get('content-type'); const body = contentType?.includes('application/json') ? await response.json() : await response.text(); diff --git a/src/utils/declarativeNetRequest.ts b/src/utils/declarativeNetRequest.ts index 15be683..5dac227 100644 --- a/src/utils/declarativeNetRequest.ts +++ b/src/utils/declarativeNetRequest.ts @@ -108,3 +108,10 @@ export const setDynamicRules = async (body: DynamicRule) => { if (browser.runtime.lastError?.message) throw new Error(browser.runtime.lastError.message); } }; + +export const removeDynamicRules = async (ruleIds: number[]) => { + await (chrome || browser).declarativeNetRequest.updateDynamicRules({ + removeRuleIds: ruleIds, + }); + if ((chrome || browser).runtime.lastError?.message) throw new Error((chrome || browser).runtime.lastError.message); +}; From f49cd01b7a391cceb847d42d0e991dab95c052e8 Mon Sep 17 00:00:00 2001 From: Jorrin <43169049+JorrinKievit@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:27:19 +0100 Subject: [PATCH 4/5] return body if body type is undefined --- src/background/messages/makeRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 118c17a..69dd495 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -16,7 +16,7 @@ export interface Request extends BaseRequest { readHeaders?: Record; url: string; body?: any; - bodyType: 'string' | 'FormData' | 'URLSearchParams' | 'object'; + bodyType?: 'string' | 'FormData' | 'URLSearchParams' | 'object'; } type Response = BaseResponse<{ @@ -49,7 +49,7 @@ const mapBodyToFetchBody = (body: Request['body'], bodyType: Request['bodyType'] if (bodyType === 'string') { return body; } - return undefined; + return body; }; const handler: PlasmoMessaging.MessageHandler> = async (req, res) => { From c740bc6685014b28796967ce9c94dba5fe5b4bee Mon Sep 17 00:00:00 2001 From: Jorrin <43169049+JorrinKievit@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:38:06 +0100 Subject: [PATCH 5/5] Replace record type logic with their own bodyType --- src/background/messages/makeRequest.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 69dd495..cd778a8 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -31,17 +31,12 @@ type Response = BaseResponse<{ const mapBodyToFetchBody = (body: Request['body'], bodyType: Request['bodyType']): BodyInit => { if (bodyType === 'FormData') { const formData = new FormData(); - Object.entries(body).forEach(([key, value]) => { + body.forEach(([key, value]) => { formData.append(key, value.toString()); }); - return formData; } if (bodyType === 'URLSearchParams') { - const searchParams = new URLSearchParams(); - Object.entries(body).forEach(([key, value]) => { - searchParams.set(key, value.toString()); - }); - return searchParams; + return new URLSearchParams(body); } if (bodyType === 'object') { return JSON.stringify(body);