Skip to content

Commit

Permalink
feat: support event-source
Browse files Browse the repository at this point in the history
  • Loading branch information
hans000 committed Aug 21, 2024
1 parent cfc5618 commit 5415209
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 43 deletions.
4 changes: 2 additions & 2 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Easy Interceptor",
"version": "1.17.2",
"version": "1.18.0",
"description": "__MSG_description__",
"permissions": [
"storage",
Expand All @@ -20,7 +20,7 @@
"default_locale": "zh_CN",
"content_security_policy" : "script-src 'self' 'unsafe-eval'; script-src-elem 'self' data: blob: https://unpkg.com; worker-src 'self' data: blob:; object-src 'self'",
"browser_action": {
"default_title": "Easy Interceptor by hans000 - v1.17.2",
"default_title": "Easy Interceptor by hans000 - v1.18.0",
"default_popup": "index.html",
"default_icon": "images/128-gray.png"
},
Expand Down
28 changes: 14 additions & 14 deletions readme-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,27 @@

如何解决上述问题呢?如果可以在客户端接收数据前拦截并加以修改再返回就可以达到目的。Easy Interceptor就是利用上述思路,它可以拦截XMLHttpRequest,fetch数据请求方式的http请求,通过覆盖response,responseText字段,从而达到对数据的修改。作为一个chrome插件,天然的集成在用户测试环境,因此对使用者的心智负担极小。

- xhr: 内部实现了一个FakeXMLHttpRequest,因此使用xhr类型的请求方式可以不向后端发出请求,也无须后端服务支持

- fetch: 内部实现了一个fakeFetch

> 注意:
>
> 插件仅针对content-type: json类型有效,在不用时请关闭该插件防止出现页面加载异常
> 1. 插件仅针对content-type: json类型有效,在不用时请关闭该插件防止出现页面加载异常,或者对dev环境设置白名单,这样可以避免影响其他网站
>
> 如果你是一个熟练度拉满,有着完善的代理环境大可不必使用,仅作为特定场合的补充
> 2. 如果你是一个熟练度拉满,有着完善的代理环境大可不必使用,仅作为特定场合的补充
>
> 如果使用cdn版本,请保证能访问https://unpkg.com,首次加载会比较慢。如果加载不出来也可直接使用local版本
> 3. 如果使用cdn版本,请保证能访问https://unpkg.com,首次加载会比较慢。如果加载不出来也可直接使用local版本
## 🎉 特点

- 免费无广告推广,较好的用户体验,提供暗色模式
- 提供监听当前请求(省略手动填写的麻烦)
- 导入导出,工程序列化
- 拥有一定的js编程能力,可以动态处理数据,可打印输出信息
- 集成monaco-editor,更方便的编辑处理文本
- 集成`monaco-editor`,更方便的编辑处理文本
- 使用cdn,大幅度缩减安装包(仅cdn版本)
- 支持修改响应头,主动发送请求,支持修改请求参数(params、headers、body)
- fake模式,用于适应不同的场景需求(默认关闭,部分场景下fake模式可能会失效)
- 支持多工作空间
- 支持修改响应头,主动发送请求,支持修改请求参数(`params、headers、body`
- `fake`模式,用于适应不同的场景需求(默认关闭,部分场景下fake模式可能会失效)
- 支持多工作空间,支持网站白名单功能
- ✨支持EventSource数据(设置`chunks`字段,由于一些技术限制,当使用`xhr`请求时必须开启`fake`模式)


## 📑 使用说明

Expand All @@ -69,16 +67,16 @@
## 状态栏
- 【设置】:设置选项
- 【工作空间】:切换工作空间
- 【运行时机】:插件生效的时机,start-js注入即生效,end-DOMContentLoaded,delay-延时一定时间,trigger-当拦截到特定接口后触发,override-当window上的xhr或者fetch被重写时
- 【运行时机】:插件生效的时机,start-js注入即生效,end-DOMContentLoaded,delay-延时一定时间,trigger-当拦截到特定接口后触发,override-当window上的xhr或者fetch被重写时(通常默认就可以生效,但是部分网站不生效时可以尝试其他的模式)
- 【禁用类型】:禁用请求类型:xhr 或者 fetch
- 【存储占用率】:展示数据占用率

### 设置
- 【所有frame生效】:默认只对顶层页面生效,iframe不生效,如果需要则开启此选项
- 【切换主题】:切换主题
- 【启动时打印日志】:打印启动日志
- 【fake模式打印日志】:fake模式下打印日志
- 【匹配网站白名单】:匹配哪些网站可以生效,默认是**,即所有网站
- 【fake模式打印日志】:`fake`模式下打印日志
- 【匹配网站白名单】:匹配哪些网站可以生效,默认是`**`,即所有网站

### config面板

Expand All @@ -99,6 +97,8 @@
|responseHeaders|Record<string, string>|响应头|
|redirectUrl|string|重定向链接,不能和url一样,会死循环|
|groupId|string|分组id,相同的id会被分配到相同的工作空间|
|chunks|string[]|设置event-source数据源,response、responseText会失效|
|chunkSpeed|number|设置数据吐出的间隔,默认1_000|

### code面板
通过指定的hooks来动态的修改数据,支持的hooks有
Expand Down
29 changes: 14 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,25 @@ Imagine that it is obviously to verify a very simple thing, but the precondition

How to solve the above problems? If you can intercept and modify the data before the client receives it, you can achieve the goal. Easy Interceptor makes use of the above ideas. It can intercept http requests in XMLHttpRequest and fetch data requests, and modify data by overwriting the response and responseText fields. As a chrome extension, it is naturally integrated in the user test environment, so the mental burden on users is minimal.

- xhr: a fake XMLHttpRequest is implemented

- fetch: a fake fetch is implemented

> Notice:
>
> The extension is only valid for content type: json type. Please close the extension when do not use
> 1. The extension is only valid for content type: json type. Please close the extension when do not use. Alse setting whitelist to avoid impacting other sites
>
> If you are skilled and have a perfect agent environment, you don't need to use it
> 2. If you are skilled and have a perfect agent environment, you don't need to use it
>
> If you use the cdn version, make sure you can access https://unpkg.com. The first load will be slow. Or use the local version directly
> 3. If you use the cdn version, make sure you can access https://unpkg.com. The first load will be slow. Or use the local version directly
## 🎉 Feature

- Free advertising free promotion, better user interaction, and dark mode
- Provide monitoring of current requests (omit the trouble of manual filling)
- 🧡Free advertising free promotion, better user interaction, and dark mode
- Provide monitoring of current requests (omit the trouble of manual filling)
- Import/export, project serialization - has certain js programming ability, can dynamically process data, and can print and output information
- Integrated monaco editor for more convenient text editing and processing
- Integrated `monaco editor` for more convenient text editing and processing
- Use cdn to greatly reduce the installation package (only cdn version)
- Support modifying response headers, actively sending requests, and modifying request parameters (params, headers, body)
- 🔃Support modifying response headers, actively sending requests, and modifying request parameters (`params, headers, body`)
- Fake mode, which is used to adapt to different scenarios (it is closed by default and may fail in some scenarios)
- Support multiple workspaces
- Support multiple workspaces, website white list
- ✨Support event-source (need to set `chunks` field and 'fake' mode must be enabled when using 'XHR' requests)


## 📑 Usage
Expand Down Expand Up @@ -72,17 +69,17 @@ How to solve the above problems? If you can intercept and modify the data before
## Status Bar
- \[Setting\]: Setting
- [Work Space]: Switch work space
- [Run At]: four options can be choose. start (js injected will be work)end (DOMContentLoaded)delay (delay some times)trigger (match a url)override (window.XMLHttpRequest or window.fetch was override)
- [Run At]: four options can be choose. start (js injected will be work), end (DOMContentLoaded), delay (delay some times), trigger (match a url), override (window.XMLHttpRequest or window.fetch was override). It usually works by default, but you can try other modes if some pages don't work
- \[Ban Type\]: Ban type, xhr or fetch
- \[Quota\]: Percent of quota


### 设置
### Settings
- \[All frames\]: The default only takes effect for top-level pages, iframe does not take effect, and this option is turned on if needed
- \[Switch Theme\]: Switch theme (light or dark)
- \[Print Boot Log\]: Print boot log
- \[Print Faked Log\]: Print log in faked mode
- \[Site White List\]: Match which sites can take effect, the default is * * , that is, all sites
- \[Site White List\]: Match which sites can take effect, the default is `**` , that is, all sites

### Config Panel

Expand All @@ -103,6 +100,8 @@ How to solve the above problems? If you can intercept and modify the data before
|responseHeaders|Record<string, string>||
|redirectUrl|string|cannot be the same as the url, will cause a loop|
|groupId|string|the same group can be used a workspace|
|chunks|string[]|set event-source data source,response、responseText would be overrided|
|chunkSpeed|number|set the interval of chunk,default 1_000|

### Code Panel
call hooks function to modify data, support there hooks
Expand Down
22 changes: 21 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface MatchRule {
responseHeaders?: Record<string, string>
code?: string
redirectUrl?: string
chunks?: string[]
chunkSpeed?: number
}

if (!process.env.VITE_LOCAL) {
Expand All @@ -60,7 +62,25 @@ if (!process.env.VITE_LOCAL) {
})
}

const fields = ['url', 'redirectUrl', 'test', 'groupId', 'description', 'type', 'method', 'status', 'delay', 'params', 'requestHeaders', 'responseHeaders', 'body', 'response', 'responseText']
const fields = [
'url',
'redirectUrl',
'test',
'groupId',
'description',
'type',
'method',
'status',
'delay',
'params',
'requestHeaders',
'responseHeaders',
'body',
'response',
'responseText',
'chunks',
'chunkSpeed',
]

const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)").matches

Expand Down
9 changes: 9 additions & 0 deletions src/components/MainEditor/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ export const ConfigSchema: JSONSchema7 = {
}
}
},
chunks: {
type: 'array',
items: {
type: 'string'
}
},
chunkSpeed: {
type: 'number'
}
},
}

Expand Down
18 changes: 16 additions & 2 deletions src/injected/proxy/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* The AGPL License (AGPL)
* Copyright (c) 2022 hans000
*/
import { delayRun, tryToProxyUrl } from "../../tools";
import { asyncGenerator, delayRun, tryToProxyUrl } from "../../tools";
import { log } from "../../tools/log";
import { parseUrl } from "../../tools";
import { Options, __global__ } from "./globalVar";
Expand Down Expand Up @@ -56,7 +56,21 @@ export function proxyFetch(options: Options) {

return new Promise(resolve => {
delayRun(async () => {
const res = response || realResponse
let res: Response = response || realResponse

const chunks = matchItem.chunks || []
const isEventSource = !!chunks.length
if (isEventSource) {
res = new Response(new ReadableStream({
async start(controller) {
for await (const value of asyncGenerator(chunks, matchItem.chunkSpeed)) {
controller.enqueue(new TextEncoder().encode(value));
}
controller.close();
},
}))
}

resolve(res)
if (loggable) {
const body = await res.clone().json()
Expand Down
36 changes: 27 additions & 9 deletions src/injected/proxy/handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { MatchRule } from "../../App"
import { delayRun, modifyXhrProto, modifyXhrProtoProps, toTitleCase, tryToProxyUrl } from "../../tools"
import { asyncGenerator, delayRun, modifyXhrProto, modifyXhrProtoProps, toTitleCase, tryToProxyUrl } from "../../tools"
import { log } from "../../tools/log"
import { parseUrl, parseXML, stringifyHeaders } from "../../tools"
import { HttpStatusCodes } from "./constants"
Expand Down Expand Up @@ -123,8 +123,8 @@ export function handleStateChange(state) {

export function dispatchCustomEvent(type: string) {
this.dispatchEvent(new Event(type))
const handle = this['on' + type]
handle && handle()
// const handle = this['on' + type]
// handle && handle()
}

export function proxyXhrInstance(inst: ProxyXMLHttpRequest) {
Expand Down Expand Up @@ -196,25 +196,43 @@ export function proxyFakeXhrInstance(inst: ProxyXMLHttpRequest, options: Options
const { status = 200, responseHeaders, response, responseText } = matchItem
setResponseHeaders.call(inst, responseHeaders)
handleStateChange.call(inst, XMLHttpRequest.LOADING)

const chunks = inst._matchItem.chunks || []
const isEventSource = !!chunks.length

if (isEventSource) {
// @ts-ignore inst field has been proxy
inst.responseText = ''
for await (const item of asyncGenerator(inst._matchItem.chunks, inst._matchItem.chunkSpeed)) {
// @ts-ignore inst field has been proxy
inst.responseText += item
handleStateChange.call(inst, XMLHttpRequest.LOADING)
}
}

// @ts-ignore inst field has been proxy
inst.readyState = XMLHttpRequest.DONE
{
const result = await options.onXhrIntercept(matchItem).call(inst, inst)
const { status = 200, responseHeaders, response, responseText } = { ...matchItem, ...result } as MatchRule

const mergedResponse = formatResponse.call(inst, response)
const mergedResponseText = responseText === undefined ? formatResponseText(mergedResponse) : responseText
// @ts-ignore inst field has been proxy
inst.status = status
// @ts-ignore inst field has been proxy
inst.statusText = HttpStatusCodes[inst.status]
// @ts-ignore inst field has been proxy
inst.responseText = mergedResponseText
// @ts-ignore inst field has been proxy
inst.response = mergedResponse
// @ts-ignore inst field has been proxy
inst.responseHeaders = responseHeaders

if (!isEventSource) {
const mergedResponse = formatResponse.call(inst, response)
const mergedResponseText = responseText === undefined ? formatResponseText(mergedResponse) : responseText
// @ts-ignore inst field has been proxy
inst.responseText = mergedResponseText
// @ts-ignore inst field has been proxy
inst.response = mergedResponse
}
}

handleStateChange.call(inst, XMLHttpRequest.DONE)

if (loggable) {
Expand Down
7 changes: 7 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ export function createSymbol(attr: string) {
return Symbol.for(attr)
}

export async function* asyncGenerator(data: string[], delay = 1000) {
for (const item of data) {
await new Promise(resolve => setTimeout(resolve, delay))
yield item
}
}

export function modifyXhrProtoProps(props: {
response?: string
responseText?: string
Expand Down

0 comments on commit 5415209

Please sign in to comment.