├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── ajaxhook.png
├── build.js
├── change-list.md
├── dist
├── ajaxhook.core.js
├── ajaxhook.core.min.js
├── ajaxhook.core.min.js.map
├── ajaxhook.js
├── ajaxhook.min.js
├── ajaxhook.min.js.map
├── ajaxhook.umd.js
├── ajaxhook.umd.min.js
└── ajaxhook.umd.min.js.map
├── examples
├── esm.html
├── hook.html
├── iframe
│ ├── inner.html
│ └── main.html
├── proxy-request.html
├── proxy.html
└── xhr.html
├── index.d.ts
├── index.js
├── package.json
├── pay.png
├── src
├── cdn-core.js
├── cdn.js
├── xhr-hook.js
└── xhr-proxy.js
├── test
├── es.js
├── hook.ts
├── proxy.ts
└── test.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | /node_modules
4 | /test/hook.js
5 | /test/proxy.js
6 | /test/dist
7 |
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples/
2 | node_modules/
3 | build.js
4 | .idea/
5 | ajaxhook.png
6 | pay.png
7 | dist.js
8 | .travis.yml
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ajax-hook
2 |
3 | [](https://www.npmjs.org/package/ajax-hook) [](https://travis-ci.org/wendux/Ajax-hook) [](https://opensource.org/licenses/mit-license.php)  [](https://unpkg.com/ajax-hook@2.0.0/dist/ajaxhook.core.min.js)
4 |
5 |
6 | ## 简介
7 |
8 | ajax-hook是用于拦截浏览器 XMLHttpRequest 对象的轻量库,它可以在 XMLHttpRequest 对象发起请求之前、收到响应内容之后以及发生错误时获得处理权,通过它你可以提前对请求、响应以及错误进行一些预处理。Ajax-hook具有很好的兼容性,可以在任何支持**ES5**的浏览器上运行,因为它并不依赖 ES6 特性。
9 |
10 | ## 使用
11 |
12 | ### 安装
13 |
14 | - CDN引入
15 |
16 | ```html
17 |
18 | ```
19 |
20 | 引入后会有一个名为"ah"(ajax hook)的全局对象,通过它可以调用ajax-hook的API,如`ah.proxy(hooks)`
21 |
22 | - NPM引入
23 |
24 | ```shell
25 | npm install ajax-hook
26 | ```
27 |
28 | 一个简单示例:
29 |
30 | ```javascript
31 | import { proxy } from "ajax-hook";
32 | proxy({
33 | //请求发起前进入
34 | onRequest: (config, handler) => {
35 | console.log(config.url)
36 | handler.next(config);
37 | },
38 | //请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功
39 | onError: (err, handler) => {
40 | console.log(err.type)
41 | handler.next(err)
42 | },
43 | //请求成功后进入
44 | onResponse: (response, handler) => {
45 | console.log(response.response)
46 | handler.next(response)
47 | }
48 | })
49 | ```
50 |
51 | 现在,我们便拦截了浏览器中通过`XMLHttpRequest`发起的所有网络请求!在请求发起前,会先进入`onRequest`钩子,调用`handler.next(config)` 请求继续,如果请求成功,则会进入`onResponse`钩子,如果请求发生错误,则会进入`onError` 。我们可以更改回调钩子的第一个参数来修改修改数据。
52 |
53 | [点击查看更多项目示例](https://github.com/wendux/ajax-hook/tree/master/examples)。
54 |
55 | ### API介绍
56 |
57 | #### `proxy(proxyObject, [window])`
58 |
59 | 拦截全局`XMLHttpRequest`
60 |
61 | > 注意:proxy 是通过ES5的getter和setter特性实现的,并没有使用ES6 的Proxy对象,所以可以兼容ES5浏览器。
62 |
63 | 参数:
64 |
65 | - `proxyObject`是一个对象,包含三个可选的钩子`onRequest`、`onResponse`、`onError`,我们可以直接在这三个钩子中对请求进行预处理。
66 | - `window`:可选参数,默认情况会使用当前窗口的`window`对象,如果要拦截iframe中的请求,可以将`iframe.contentWindow` 传入,注意,只能拦截**同源**的iframe页面(不能跨域)。
67 |
68 | 返回值:
69 | `ProxyReturnObject`
70 |
71 | ProxyReturnObject 是一个对象,包含了 `unProxy` 和 `originXhr`
72 | - `unProxy([window])`:取消拦截;取消后 `XMLHttpRequest` 将不会再被代理,浏览器原生`XMLHttpRequest`会恢复到全局变量空间
73 | - `originXhr`: 浏览器原生的 `XMLHttpRequest`
74 |
75 |
76 |
77 | ### 钩子函数的签名
78 |
79 | #### Handler
80 |
81 | 在钩子函数中,我们可以通过其第二个参数`handler`对象提供的方法来决定请求的后续流程,`handler`对象它有个方法:
82 |
83 | 1. `next(arg)`:继续进入后续流程;如果不调用,则请求链便会暂停,这种机制可以支持在钩子中执行一些异步任务。该方法在`onResponse`钩子中等价于`resolve`,在`onError`钩子中等价于`reject`
84 | 2. `resolve(response)`:调用后,请求后续流程会被阻断,直接返回响应数据,上层`xhr.onreadystatechange`或`xhr.onload`会被调用。
85 | 3. `reject(err)`:调用后,请求后续流程会被阻断,直接返回错误,上层的`xhr.onerror`、`xhr.ontimeout`、`xhr.onabort`之一会被调用,具体调用哪个取决于`err.type`的值,比如我们设置`err.type`为"timeout",则`xhr.ontimeout`会被调用。
86 |
87 | > 关于`config`、`response`、`err`的结构定义请参考[类型定义文件](./index.d.ts)中的`XhrRequestConfig`、`XhrResponse`、`XhrError`
88 |
89 | #### 示例
90 |
91 | ```javascript
92 | const { unProxy, originXhr } = proxy({
93 | onRequest: (config, handler) => {
94 | if (config.url === 'https://aa/') {
95 | handler.resolve({
96 | config: config,
97 | status: 200,
98 | headers: {'content-type': 'text/text'},
99 | response: 'hi world'
100 | })
101 | } else {
102 | handler.next(config);
103 | }
104 | },
105 | onError: (err, handler) => {
106 | if (err.config.url === 'https://bb/') {
107 | handler.resolve({
108 | config: err.config,
109 | status: 200,
110 | headers: {'content-type': 'text/text'},
111 | response: 'hi world'
112 | })
113 | } else {
114 | handler.next(err)
115 | }
116 | },
117 | onResponse: (response, handler) => {
118 | if (response.config.url === location.href) {
119 | handler.reject({
120 | config: response.config,
121 | type: 'error'
122 | })
123 | } else {
124 | handler.next(response)
125 | }
126 | }
127 | })
128 |
129 | // 使用jQuery发起网络请求
130 | function testJquery(url) {
131 | $.get(url).done(function (d) {
132 | console.log(d)
133 | }).fail(function (e) {
134 | console.log('hi world')
135 | })
136 | }
137 |
138 | //测试
139 | testJquery('https://aa/');
140 | testJquery('https://bb/');
141 | testJquery(location.href)
142 |
143 | // 取消拦截
144 | unProxy();
145 | ```
146 |
147 | 运行后,控制台输出3次 "hi world"。
148 |
149 |
150 |
151 | ## 核心API - `hook(hooks,[window])`
152 |
153 | Ajax-hook在1.x版本中只提供了一个核心拦截功能的库,在1.x中,我们通过`hookAjax()` 方法(2.x中改名为`hook()`)实现了对`XMLHttpRequest`对象**具体属性、方法、回调的细粒度拦截**。而2.x中的`proxy()`方法则是基于`hook`的一个封装。
154 |
155 | ### `hook(Hooks,[window])`
156 |
157 | 拦截全局`XMLHttpRequest`,此方法调用后,浏览器原生的`XMLHttpRequest`将会被代理,代理对象会覆盖浏览器原生`XMLHttpRequest`,直到调用`unHook(...)`后才会取消代理。
158 |
159 | 参数:
160 |
161 | - `hooks`:钩子对象,里面是XMLHttpRequest对象的回调、方法、属性的钩子函数,钩子函数会在执行`XMLHttpRequest`对象真正的回调、方法、属性访问器前执行。
162 | - `window`:可选参数,默认情况会使用当前窗口的`window`对象,如果要拦截iframe中的请求,可以将`iframe.contentWindow` 传入,注意,只能拦截**同源**的iframe页面(不能跨域)。
163 |
164 | 返回值:
165 | `HookReturnObject`
166 |
167 | HookReturnObject 是一个对象,包含了 `unHook` 和 `originXhr`
168 | - `unHook([window])`:取消拦截;取消后 `XMLHttpRequest` 将不会再被代理,浏览器原生`XMLHttpRequest` 会恢复到全局变量空间
169 | - `originXhr`: 浏览器原生的 `XMLHttpRequest`
170 |
171 |
172 | #### 示例
173 |
174 | 下面我们看一下如何使用`hook`方法来拦截`XMLHttpRequest`对象:
175 |
176 | ```javascript
177 | import { hook } from "ajax-hook"
178 | const { unHook, originXhr } = hook({
179 | //拦截回调
180 | onreadystatechange:function(xhr,event){
181 | console.log("onreadystatechange called: %O")
182 | //返回false表示不阻断,拦截函数执行完后会接着执行真正的xhr.onreadystatechange回调.
183 | //返回true则表示阻断,拦截函数执行完后将不会执行xhr.onreadystatechange.
184 | return false
185 | },
186 | onload:function(xhr,event){
187 | console.log("onload called")
188 | return false
189 | },
190 | //拦截方法
191 | open:function(args,xhr){
192 | console.log("open called: method:%s,url:%s,async:%s",args[0],args[1],args[2])
193 | //拦截方法的返回值含义同拦截回调的返回值
194 | return false
195 | }
196 | })
197 |
198 | // 取消拦截
199 | unHook();
200 | ```
201 |
202 | 这样拦截就生效了,拦截的全局的`XMLHttpRequest`,所以,无论你使用的是哪种JavaScript http请求库,它们只要最终是使用`XMLHttpRequest`发起的网络请求,那么拦截都会生效。下面我们用jQuery发起一个请求:
203 |
204 | ```javascript
205 | // 获取当前页面的源码(Chrome中测试)
206 | $.get().done(function(d){
207 | console.log(d.substr(0,30)+"...")
208 | })
209 | ```
210 |
211 | 输出:
212 |
213 | ```
214 | > open called: method:GET,url:http://localhost:63342/Ajax-hook/demo.html,async:true
215 | > onload called
216 | >
217 |
218 |
假设对于同一个请求,你需要在其`open`的拦截函数和`onload` 回调中共享一个变量,但由于拦截的是全局XMLHttpRequest对象,所有网络请求会无次序的走到拦截的方法中,这时你可以通过`xhr`来对应请求的上下文信息。在上述场景中,你可以在`open`拦截函数中给`xhr`设置一个属性,然后在`onload`回调中获取即可。
252 |
253 | - `XMLHttpRequest`的所有方法如`open`、`send等`,他们拦截函数的第一个参数是一个数组,数组的内容是其对应的原生方法的参数列表,第二个参数是本次请求对应的`XMLHttpRequest`对象(已代理)。返回值类型是一个布尔值,为true时会阻断对应的请求。
254 |
255 | - 对于属性拦截器,为了避免循环调用导致的栈溢出,不可以在其getter拦截器中再读取其同名属性或在其setter拦截器中在给其同名属性赋值。
256 |
257 | - 本库需要在支持ES5的浏览器环境中运行(不支持IE8),但本库并不依赖ES6新特性。
258 |
259 | ### 拦截`XMLHttpRequest`的属性
260 |
261 | 除了拦截`XMLHttpRequest`回调和方法外,也可以拦截属性的**读/写**操作。比如设置超时`timeout`,读取响应内容`responseText`等。下面我们通过两个示例说明。
262 |
263 | - 假设为了避免用户设置不合理的超时时间,如,小于30ms,那么这将导致超过30ms的网络请求都将触发超时,因此,我们在底层做一个判断,确保超时时间最小为1s:
264 |
265 | ```javascript
266 | hook(
267 | //需要拦截的属性名
268 | timeout: {
269 | //拦截写操作
270 | setter: function (v, xhr) {
271 | //超时最短为1s,返回值为最终值。
272 | return Math.max(v, 1000);
273 | }
274 | }
275 | )
276 | ```
277 |
278 | - 假设在请求成功后,但在返回给用户之前,如果发现响应内容是JSON文本,那么我们想自动将JSON文本转为对象,要实现这个功能,有两种方方法:
279 |
280 | - 拦截成功回调
281 |
282 | ```javascript
283 |
284 | function tryParseJson1(xhr){
285 | var contentType=xhr.getResponseHeader("content-type")||"";
286 | if(contentType.toLocaleLowerCase().indexOf("json")!==-1){
287 | xhr.responseText=JSON.parse(xhr.responseText);
288 | }
289 | }
290 |
291 | hookAjax({
292 | //拦截回调
293 | onreadystatechange:tryParseJson1,
294 | onload:tryParseJson1
295 | });
296 | ```
297 |
298 |
299 |
300 | - 拦截`responseText` 和 `response`读操作
301 |
302 | ```javascript
303 | function tryParseJson2(v,xhr){
304 | var contentType=xhr.getResponseHeader("content-type")||"";
305 | if(contentType.toLocaleLowerCase().indexOf("json")!==-1){
306 | v=JSON.parse(v);
307 | //不能在属性的getter钩子中再读取该属性,这会导致循环调用
308 | //v=JSON.parse(xhr.responseText);
309 | }
310 | return v;
311 | }
312 |
313 | //因为无法确定上层使用的是responseText还是response属性,为了保险起见,两个属性都拦截一下
314 | hook(
315 | responseText: {
316 | getter: tryParseJson2
317 | },
318 | response: {
319 | getter:tryParseJson2
320 | }
321 | )
322 | ```
323 |
324 | ### ajax-hook.core.js
325 |
326 | 如果你只想使用`hook(...)`方法(不需要使用`proxy()`),我们提供了只包含`hook()`方法的核心库,你可以在[dist目录](https://github.com/wendux/Ajax-hook/tree/master/dist)找到名为`ajax-hook.core.js`的文件,直接使用它即可。
327 |
328 |
329 |
330 | ## `proxy(...)` vs `hook(...)`
331 |
332 | `proxy()` 和`hook()`都可以用于拦截全局`XMLHttpRequest`。它们的区别是:`hook()`的拦截粒度细,可以具体到`XMLHttpRequest`对象的某一方法、属性、回调,但是使用起来比较麻烦,很多时候,不仅业务逻辑需要散落在各个回调当中,而且还容易出错。而`proxy()`抽象度高,并且构建了请求上下文(请求信息config在各个回调中都可以直接获取),使用起来更简单、高效。
333 |
334 | 大多数情况下,我们建议使用`proxy()` 方法,除非`proxy()` 方法不能满足你的需求。
335 |
336 |
337 |
338 | ## 拦截iframe
339 |
340 | 显示指定iframe 的 window 对象即可,比如:
341 | ```javascript
342 | var iframeWindow = ...;
343 | const { unProxy } = proxy({...},iframeWindow)
344 | unProxy(iframeWindow)
345 | //或
346 | const { unHook } = hook({...},iframeWindow)
347 | unHook(frameWindow)
348 | ```
349 | 完整示例见:[拦截iframe中的请求](https://github.com/wendux/ajax-hook/tree/master/examples/iframe)。
350 |
351 | > 注意:只能拦截**同源**的iframe页面(不能跨域)。
352 |
353 | ## 原理解析
354 | 通过ES5 getter和setter特性实现对 XMLHttpRequest 对象的代理:
355 | 
356 |
357 | 具体原理解析请查看:[ajax-hook 原理解析](https://juejin.cn/post/6844903470181384206)。
358 |
359 | ## 最后
360 |
361 | 本库在2016年首次开源,最初只是个人研究所用,源码50行左右,实现了一个Ajax拦截的核心,并非一个完整可商用的项目。自开源后,有好多人对这个黑科技比较感兴趣,于是我便写了篇介绍的博客,由于代码比较精炼,所以对于JavaScript不是很精通的同学可能看起来比较吃力,之后专门写了一篇原理解析的文章,现在已经有很多公司已经将 ajax-hook 用于线上项目中,直到我知道美团、滴滴也用到之后,笔者对此库进行了修改和扩展以增强其健壮性和实用性,现在已经达到商用的标准,本库也将进行技术支持。如果你喜欢,欢迎Star,如果有问题,欢迎提Issue, 如果你觉得对自己有用想请作者喝杯咖啡的话,请扫描下面二维码:
362 |
363 |
364 |
365 |
--------------------------------------------------------------------------------
/ajaxhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wendux/ajax-hook/93d62e0eaf8c2592cbc81efb292ee0d931852118/ajaxhook.png
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by du on 16/9/24.
3 | */
4 | let path = require('path');
5 | let webpack = require('webpack');
6 | let env = process.argv[2] || "dev"
7 | console.log(env)
8 | let entry = {
9 | "ajaxhook": "./index.js",
10 | }
11 |
12 | let plugins = [];
13 |
14 | var output={
15 | path: path.resolve("./dist/")
16 |
17 | }
18 |
19 | if (env === "test") {
20 | entry = {
21 | "es": "./test/es.js",
22 | 'hook': './test/hook.js',
23 | 'proxy': './test/proxy.js'
24 | }
25 | output = {
26 | path: path.resolve("./test/dist"),
27 | filename: "[name].js"
28 | }
29 | } else if (env === "cdn") {
30 | entry = {
31 | "ajaxhook": "./src/cdn.js",
32 | "ajaxhook.core": "./src/cdn-core.js",
33 | }
34 | output.filename = "[name].js"
35 | output.libraryTarget = 'window'
36 | } else if (env === "cdn-min") {
37 | entry = {
38 | "ajaxhook": "./src/cdn.js",
39 | "ajaxhook.core": "./src/cdn-core.js",
40 | // "ajaxhook": "./src/xhr-proxy.js",
41 | // "ajaxhook.core": "./src/xhr-hook.js",
42 | }
43 | output.filename = "[name].min.js"
44 | output.libraryTarget = 'window'
45 | plugins.push(new webpack.optimize.UglifyJsPlugin({
46 | // compress: {
47 | // warnings: true
48 | // },
49 | sourceMap: true
50 | }))
51 | }
52 | else if (env === "umd") {
53 | output.libraryTarget = "umd"
54 | output.filename = "[name].umd.js"
55 | }else if (env === "umd-min") {
56 | output.libraryTarget = "umd"
57 | output.filename = "[name].umd.min.js"
58 | plugins.push(new webpack.optimize.UglifyJsPlugin({
59 | // compress: {
60 | // warnings: true
61 | // },
62 | sourceMap: true
63 | }))
64 | }
65 |
66 |
67 | let config = {
68 | devtool: env.endsWith('min')?'source-map':'none',
69 | entry: entry,
70 | output: output,
71 | module: {
72 | rules: [
73 | {
74 | test: /\.js$/,
75 | include: [path.resolve('./src'), path.resolve('./test'), path.resolve('./index.js')],
76 | use: [
77 | {
78 | loader: "babel-loader",
79 | options: {
80 | presets: ['es2015']
81 | }
82 | },
83 | ]
84 | }
85 | ]
86 | },
87 | watch: true,
88 | watchOptions: {
89 | ignored: '**/node_modules',
90 | },
91 | plugins: plugins
92 | }
93 |
94 |
95 | webpack(config, function (err, stats) {
96 | if (err) throw err;
97 | process.stdout.write(stats.toString({
98 | colors: true,
99 | modules: false,
100 | children: false,
101 | chunks: false,
102 | chunkModules: false
103 | }) + '\n')
104 | });
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/change-list.md:
--------------------------------------------------------------------------------
1 | # Change list
2 |
3 | ## 3.0.0
4 | API签名相对2.x发生了变化,故增加大版本号,更新列表:
5 | - 支持多次拦截请求,修改取消拦截的方式,调用 `proxy` 或 `unHook` 时返回 `unProxy` 或 `unHook` 方法
6 | - fix #103
7 |
8 | ## 2.1.2
9 |
10 | - 添加可选参数[window],可以指定iframe的window对象来拦截同源iframe页面内的ajax请求。
11 |
12 |
13 | ## 2.0.3
14 |
15 | - 兼容IE fix #49 #52
16 |
17 | ## 2.0.2
18 |
19 | - fix #47
20 |
21 | ## 2.0
22 |
23 | - 添加了两个新API: `proxy(...)` 、 `unProxy()`
24 |
25 | - 将 `hookAjax(...)` 重命名为 `hook(...)` , `unHookAjax(...)` 重命名为 `unHook(...)`
26 |
27 | - 通过`hook()`拦截时,2.0中,XHR事件回调钩子函数(以"on"开头的),如`onreadystatechange`、`onload`等,他们的拦截函数的第一个参数都为"**原生xhr对象**" ,而1.x中为**代理xhr对象**。
28 |
29 | - npm包支持es6 module;cdn方式引入后API会挂在名为“ah”的全局变量下;
30 |
31 | - 支持 TypeScript
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/dist/ajaxhook.core.js:
--------------------------------------------------------------------------------
1 | (function(e, a) { for(var i in a) e[i] = a[i]; }(window, /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // identity function for calling harmony imports with the correct context
37 | /******/ __webpack_require__.i = function(value) { return value; };
38 | /******/
39 | /******/ // define getter function for harmony exports
40 | /******/ __webpack_require__.d = function(exports, name, getter) {
41 | /******/ if(!__webpack_require__.o(exports, name)) {
42 | /******/ Object.defineProperty(exports, name, {
43 | /******/ configurable: false,
44 | /******/ enumerable: true,
45 | /******/ get: getter
46 | /******/ });
47 | /******/ }
48 | /******/ };
49 | /******/
50 | /******/ // getDefaultExport function for compatibility with non-harmony modules
51 | /******/ __webpack_require__.n = function(module) {
52 | /******/ var getter = module && module.__esModule ?
53 | /******/ function getDefault() { return module['default']; } :
54 | /******/ function getModuleExports() { return module; };
55 | /******/ __webpack_require__.d(getter, 'a', getter);
56 | /******/ return getter;
57 | /******/ };
58 | /******/
59 | /******/ // Object.prototype.hasOwnProperty.call
60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
61 | /******/
62 | /******/ // __webpack_public_path__
63 | /******/ __webpack_require__.p = "";
64 | /******/
65 | /******/ // Load entry module and return exports
66 | /******/ return __webpack_require__(__webpack_require__.s = 2);
67 | /******/ })
68 | /************************************************************************/
69 | /******/ ([
70 | /* 0 */
71 | /***/ (function(module, exports, __webpack_require__) {
72 |
73 | "use strict";
74 |
75 |
76 | Object.defineProperty(exports, "__esModule", {
77 | value: true
78 | });
79 |
80 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
81 |
82 | exports.configEvent = configEvent;
83 | exports.hook = hook;
84 | /*
85 | * author: wendux
86 | * email: 824783146@qq.com
87 | * source code: https://github.com/wendux/Ajax-hook
88 | */
89 |
90 | var events = exports.events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort'];
91 |
92 | var OriginXhr = '__origin_xhr';
93 |
94 | function configEvent(event, xhrProxy) {
95 | var e = {};
96 | for (var attr in event) {
97 | e[attr] = event[attr];
98 | } // xhrProxy instead
99 | e.target = e.currentTarget = xhrProxy;
100 | return e;
101 | }
102 |
103 | function hook(proxy, win) {
104 | win = win || window;
105 | var originXhr = win.XMLHttpRequest;
106 |
107 | var hooking = true;
108 |
109 | var HookXMLHttpRequest = function HookXMLHttpRequest() {
110 | // We shouldn't hookAjax XMLHttpRequest.prototype because we can't
111 | // guarantee that all attributes are on the prototype。
112 | // Instead, hooking XMLHttpRequest instance can avoid this problem.
113 |
114 | var xhr = new originXhr();
115 |
116 | // Generate all callbacks(eg. onload) are enumerable (not undefined).
117 | for (var i = 0; i < events.length; ++i) {
118 | var key = 'on' + events[i];
119 | if (xhr[key] === undefined) xhr[key] = null;
120 | }
121 |
122 | for (var attr in xhr) {
123 | var type = "";
124 | try {
125 | type = _typeof(xhr[attr]); // May cause exception on some browser
126 | } catch (e) {}
127 | if (type === "function") {
128 | // hookAjax methods of xhr, such as `open`、`send` ...
129 | this[attr] = hookFunction(attr);
130 | } else if (attr !== OriginXhr) {
131 | Object.defineProperty(this, attr, {
132 | get: getterFactory(attr),
133 | set: setterFactory(attr),
134 | enumerable: true
135 | });
136 | }
137 | }
138 | var that = this;
139 | xhr.getProxy = function () {
140 | return that;
141 | };
142 | this[OriginXhr] = xhr;
143 | };
144 |
145 | HookXMLHttpRequest.prototype = originXhr.prototype;
146 | HookXMLHttpRequest.prototype.constructor = HookXMLHttpRequest;
147 |
148 | win.XMLHttpRequest = HookXMLHttpRequest;
149 |
150 | Object.assign(win.XMLHttpRequest, { UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4 });
151 |
152 | // Generate getter for attributes of xhr
153 | function getterFactory(attr) {
154 | return function () {
155 | var originValue = this[OriginXhr][attr];
156 | if (hooking) {
157 | var v = this.hasOwnProperty(attr + "_") ? this[attr + "_"] : originValue;
158 | var attrGetterHook = (proxy[attr] || {})["getter"];
159 | return attrGetterHook && attrGetterHook(v, this) || v;
160 | } else {
161 | return originValue;
162 | }
163 | };
164 | }
165 |
166 | // Generate setter for attributes of xhr; by this we have an opportunity
167 | // to hookAjax event callbacks (eg: `onload`) of xhr;
168 | function setterFactory(attr) {
169 | return function (v) {
170 | var xhr = this[OriginXhr];
171 | if (hooking) {
172 | var that = this;
173 | var hook = proxy[attr];
174 | // hookAjax event callbacks such as `onload`、`onreadystatechange`...
175 | if (attr.substring(0, 2) === 'on') {
176 | that[attr + "_"] = v;
177 | xhr[attr] = function (e) {
178 | e = configEvent(e, that);
179 | var ret = proxy[attr] && proxy[attr].call(that, xhr, e);
180 | ret || v.call(that, e);
181 | };
182 | } else {
183 | //If the attribute isn't writable, generate proxy attribute
184 | var attrSetterHook = (hook || {})["setter"];
185 | v = attrSetterHook && attrSetterHook(v, that) || v;
186 | this[attr + "_"] = v;
187 | try {
188 | // Not all attributes of xhr are writable(setter may undefined).
189 | xhr[attr] = v;
190 | } catch (e) {}
191 | }
192 | } else {
193 | xhr[attr] = v;
194 | }
195 | };
196 | }
197 |
198 | // Hook methods of xhr.
199 | function hookFunction(fun) {
200 | return function () {
201 | var args = [].slice.call(arguments);
202 | if (proxy[fun] && hooking) {
203 | var ret = proxy[fun].call(this, args, this[OriginXhr]);
204 | // If the proxy return value exists, return it directly,
205 | // otherwise call the function of xhr.
206 | if (ret) return ret;
207 | }
208 | return this[OriginXhr][fun].apply(this[OriginXhr], args);
209 | };
210 | }
211 |
212 | function unHook() {
213 | hooking = false;
214 | if (win.XMLHttpRequest === HookXMLHttpRequest) {
215 | win.XMLHttpRequest = originXhr;
216 | HookXMLHttpRequest.prototype.constructor = originXhr;
217 | originXhr = undefined;
218 | }
219 | }
220 |
221 | // Return the real XMLHttpRequest and unHook func
222 | return { originXhr: originXhr, unHook: unHook };
223 | }
224 |
225 | /***/ }),
226 | /* 1 */,
227 | /* 2 */
228 | /***/ (function(module, exports, __webpack_require__) {
229 |
230 | "use strict";
231 |
232 |
233 | Object.defineProperty(exports, "__esModule", {
234 | value: true
235 | });
236 | exports.ah = undefined;
237 |
238 | var _xhrHook = __webpack_require__(0);
239 |
240 | var ah = exports.ah = { hook: _xhrHook.hook }; /*
241 | * author: wendux
242 | * email: 824783146@qq.com
243 | * source code: https://github.com/wendux/Ajax-hook
244 | */
245 |
246 | /***/ })
247 | /******/ ])));
--------------------------------------------------------------------------------
/dist/ajaxhook.core.min.js:
--------------------------------------------------------------------------------
1 | !function(t,r){for(var e in r)t[e]=r[e]}(window,function(t){function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var e={};return r.m=t,r.c=e,r.i=function(t){return t},r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:n})},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},r.p="",r(r.s=2)}([function(t,r,e){"use strict";function n(t,r){var e={};for(var n in t)e[n]=t[n];return e.target=e.currentTarget=r,e}function o(t,r){function e(r){return function(){var e=this[c][r];if(l){var n=this.hasOwnProperty(r+"_")?this[r+"_"]:e,o=(t[r]||{}).getter;return o&&o(n,this)||n}return e}}function o(r){return function(e){var o=this[c];if(l){var i=this,u=t[r];if("on"===r.substring(0,2))i[r+"_"]=e,o[r]=function(u){u=n(u,i),t[r]&&t[r].call(i,o,u)||e.call(i,u)};else{var a=(u||{}).setter;e=a&&a(e,i)||e,this[r+"_"]=e;try{o[r]=e}catch(t){}}}else o[r]=e}}function a(r){return function(){var e=[].slice.call(arguments);if(t[r]&&l){var n=t[r].call(this,e,this[c]);if(n)return n}return this[c][r].apply(this[c],e)}}function s(){l=!1,r.XMLHttpRequest===p&&(r.XMLHttpRequest=f,p.prototype.constructor=f,f=void 0)}r=r||window;var f=r.XMLHttpRequest,l=!0,p=function(){for(var t=new f,r=0;r
2 |
3 |
4 |
5 | Ajax hook Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
26 | open console panel to view log.
27 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/hook.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ajax hook Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
26 | open console panel to view log.
27 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/examples/iframe/inner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | proxy request
6 |
7 |
8 |
9 |
10 | 连续点击按钮并查看日志
11 |
12 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/iframe/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | hook iframe
6 |
7 |
8 |
9 |
10 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/examples/proxy-request.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | proxy request
6 |
7 |
8 |
9 |
10 |
11 | 连续点击按钮并查看日志
12 |
13 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/examples/proxy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ajax hook Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
24 | open console panel to view log.
25 |
26 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/examples/xhr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ajax hook Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
24 | open console panel to view log.
25 |
26 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | interface XMLHttpRequestProxy extends XMLHttpRequest {
2 | responseText: string,
3 | readyState: number;
4 | response: any;
5 | responseURL: string;
6 | responseXML: Document | null;
7 | status: number;
8 | statusText: string;
9 | xhr: OriginXMLHttpRequest
10 | }
11 |
12 | interface OriginXMLHttpRequest extends XMLHttpRequest {
13 | getProxy(): XMLHttpRequestProxy;
14 | }
15 |
16 | interface AttrGetterAndSetter {
17 | getter?: (value: T, xhr: OriginXMLHttpRequest) => T;
18 | setter?: (value: T, xhr: OriginXMLHttpRequest) => T;
19 | }
20 |
21 | interface XhrRequestConfig {
22 | method: string,
23 | url: string,
24 | headers: any,
25 | body: any,
26 | async: boolean,
27 | user: string,
28 | password: string,
29 | withCredentials: boolean
30 | xhr: OriginXMLHttpRequest,
31 | }
32 |
33 | interface XhrResponse {
34 | config: XhrRequestConfig,
35 | headers: any,
36 | response: any,
37 | status: number,
38 | statusText?: string,
39 | }
40 |
41 | type XhrErrorType = 'error' | 'timeout' | 'abort'
42 |
43 |
44 | interface XhrError {
45 | config: XhrRequestConfig,
46 | type: XhrErrorType
47 | }
48 |
49 | interface Hooks {
50 | onreadystatechange?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
51 | onabort?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
52 | onerror?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
53 | onload?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
54 | onloadend?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
55 | onloadstart?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
56 | onprogress?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
57 | ontimeout?: ((this: XMLHttpRequestProxy, xhr: OriginXMLHttpRequest, ev: ProgressEvent) => any) | null;
58 | abort?: ((args: Array, xhr: OriginXMLHttpRequest) => any);
59 | getAllResponseHeaders?: (args: Array, xhr: OriginXMLHttpRequest) => any;
60 | getResponseHeader?: (args: Array, xhr: OriginXMLHttpRequest) => any;
61 | open?: (args: Array, xhr: OriginXMLHttpRequest) => any;
62 | overrideMimeType?: (args: Array, xhr: OriginXMLHttpRequest) => any;
63 | send?: (args: Array, xhr: OriginXMLHttpRequest) => any;
64 | setRequestHeader?: (args: Array, xhr: OriginXMLHttpRequest) => any;
65 | addEventListener?: (args: Array, xhr: OriginXMLHttpRequest) => any;
66 | removeEventListener?: (args: Array, xhr: OriginXMLHttpRequest) => any;
67 |
68 | response?: AttrGetterAndSetter,
69 | responseText?: AttrGetterAndSetter,
70 | readyState?: AttrGetterAndSetter,
71 | responseType?: AttrGetterAndSetter;
72 | responseURL?: AttrGetterAndSetter;
73 | responseXML?: AttrGetterAndSetter;
74 | status?: AttrGetterAndSetter;
75 | statusText?: AttrGetterAndSetter;
76 | timeout?: AttrGetterAndSetter;
77 | upload?: AttrGetterAndSetter;
78 | withCredentials?: AttrGetterAndSetter;
79 | }
80 |
81 | interface XhrHandler {
82 | resolve(response: XhrResponse): void
83 |
84 | reject(err: XhrError): void
85 | }
86 |
87 | interface XhrRequestHandler extends XhrHandler {
88 | next(config: XhrRequestConfig): void
89 | }
90 |
91 | interface XhrResponseHandler extends XhrHandler {
92 | next(response: XhrResponse): void
93 | }
94 |
95 | interface XhrErrorHandler extends XhrHandler {
96 | next(error: XhrError): void
97 | }
98 |
99 | interface Proxy {
100 | onRequest?: (config: XhrRequestConfig, handler: XhrRequestHandler) => void,
101 | onResponse?: (response: XhrResponse, handler: XhrResponseHandler) => void,
102 | onError?: (err: XhrError, handler: XhrErrorHandler) => void,
103 | }
104 |
105 | export function proxy(proxy: Proxy, win?: Window): { originXhr: XMLHttpRequest, unProxy: () => void };
106 |
107 | export function unProxy(win?: Window);
108 |
109 | export function hook(hooks: Hooks,win?: Window): { originXhr: XMLHttpRequest, unHook: () => void };
110 |
111 | export function unHook(win?: Window);
112 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * author: wendux
3 | * email: 824783146@qq.com
4 | * source code: https://github.com/wendux/Ajax-hook
5 | **/
6 | export { hook }from "./src/xhr-hook"
7 | export { proxy } from "./src/xhr-proxy"
8 |
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ajax-hook",
3 | "version": "3.0.3",
4 | "main": "index.js",
5 | "typings": "./index.d.ts",
6 | "author": {
7 | "name": "wendux",
8 | "email": "824783146@qq.com",
9 | "url": "https://github.com/wendux"
10 | },
11 | "description": "Hooking XMLHttpRequest object in browser",
12 | "keywords": [
13 | "ajax hook",
14 | "hook ajax",
15 | "ajax-hook",
16 | "intercept ajax"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/wendux/Ajax-hook"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/wendux/Ajax-hook/issues"
24 | },
25 | "scripts": {
26 | "dev": "tsc test/hook.ts & tsc test/proxy.ts & node build.js test",
27 | "build": "node build.js umd & node build.js umd-min & node build.js cdn & node build.js cdn-min"
28 | },
29 | "devDependencies": {
30 | "babel-core": "^6.0.0",
31 | "babel-loader": "^6.0.0",
32 | "babel-plugin-transform-runtime": "^6.0.0",
33 | "babel-preset-es2015": "^6.0.0",
34 | "babel-preset-es2017": "^6.24.1",
35 | "webpack": "^2.6.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wendux/ajax-hook/93d62e0eaf8c2592cbc81efb292ee0d931852118/pay.png
--------------------------------------------------------------------------------
/src/cdn-core.js:
--------------------------------------------------------------------------------
1 | /*
2 | * author: wendux
3 | * email: 824783146@qq.com
4 | * source code: https://github.com/wendux/Ajax-hook
5 | */
6 |
7 | import { hook } from "./xhr-hook";
8 |
9 | export var ah = { hook };
10 |
--------------------------------------------------------------------------------
/src/cdn.js:
--------------------------------------------------------------------------------
1 | /*
2 | * author: wendux
3 | * email: 824783146@qq.com
4 | * source code: https://github.com/wendux/Ajax-hook
5 | */
6 |
7 | import { hook } from "./xhr-hook";
8 | import { proxy } from "./xhr-proxy";
9 | // ah(ajax hook)
10 | export var ah = {
11 | proxy,
12 | hook,
13 | };
14 |
--------------------------------------------------------------------------------
/src/xhr-hook.js:
--------------------------------------------------------------------------------
1 | /*
2 | * author: wendux
3 | * email: 824783146@qq.com
4 | * source code: https://github.com/wendux/Ajax-hook
5 | */
6 |
7 | export var events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort'];
8 |
9 | var OriginXhr = '__origin_xhr';
10 |
11 | export function configEvent(event, xhrProxy) {
12 | var e = {};
13 | for (var attr in event) e[attr] = event[attr];
14 | // xhrProxy instead
15 | e.target = e.currentTarget = xhrProxy
16 | return e;
17 | }
18 |
19 | export function hook(proxy, win) {
20 | win = win || window;
21 | var originXhr = win.XMLHttpRequest;
22 |
23 | var hooking = true;
24 |
25 | var HookXMLHttpRequest = function () {
26 | // We shouldn't hookAjax XMLHttpRequest.prototype because we can't
27 | // guarantee that all attributes are on the prototype。
28 | // Instead, hooking XMLHttpRequest instance can avoid this problem.
29 |
30 | var xhr = new originXhr();
31 |
32 | // Generate all callbacks(eg. onload) are enumerable (not undefined).
33 | for (var i = 0; i < events.length; ++i) {
34 | var key='on'+events[i];
35 | if (xhr[key] === undefined) xhr[key] = null;
36 | }
37 |
38 | for (var attr in xhr) {
39 | var type = "";
40 | try {
41 | type = typeof xhr[attr] // May cause exception on some browser
42 | } catch (e) {
43 | }
44 | if (type === "function") {
45 | // hookAjax methods of xhr, such as `open`、`send` ...
46 | this[attr] = hookFunction(attr);
47 | } else if (attr !== OriginXhr) {
48 | Object.defineProperty(this, attr, {
49 | get: getterFactory(attr),
50 | set: setterFactory(attr),
51 | enumerable: true
52 | })
53 | }
54 | }
55 | var that = this;
56 | xhr.getProxy = function () {
57 | return that
58 | }
59 | this[OriginXhr] = xhr;
60 | }
61 |
62 | HookXMLHttpRequest.prototype = originXhr.prototype;
63 | HookXMLHttpRequest.prototype.constructor = HookXMLHttpRequest;
64 |
65 | win.XMLHttpRequest = HookXMLHttpRequest;
66 |
67 | Object.assign(win.XMLHttpRequest, {UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4});
68 |
69 | // Generate getter for attributes of xhr
70 | function getterFactory(attr) {
71 | return function () {
72 | var originValue = this[OriginXhr][attr];
73 | if (hooking) {
74 | var v = this.hasOwnProperty(attr + "_") ? this[attr + "_"] : originValue;
75 | var attrGetterHook = (proxy[attr] || {})["getter"];
76 | return attrGetterHook && attrGetterHook(v, this) || v;
77 | } else {
78 | return originValue;
79 | }
80 | }
81 | }
82 |
83 | // Generate setter for attributes of xhr; by this we have an opportunity
84 | // to hookAjax event callbacks (eg: `onload`) of xhr;
85 | function setterFactory(attr) {
86 | return function (v) {
87 | var xhr = this[OriginXhr];
88 | if (hooking) {
89 | var that = this;
90 | var hook = proxy[attr];
91 | // hookAjax event callbacks such as `onload`、`onreadystatechange`...
92 | if (attr.substring(0, 2) === 'on') {
93 | that[attr + "_"] = v;
94 | xhr[attr] = function (e) {
95 | e = configEvent(e, that)
96 | var ret = proxy[attr] && proxy[attr].call(that, xhr, e)
97 | ret || v.call(that, e);
98 | }
99 | } else {
100 | //If the attribute isn't writable, generate proxy attribute
101 | var attrSetterHook = (hook || {})["setter"];
102 | v = attrSetterHook && attrSetterHook(v, that) || v
103 | this[attr + "_"] = v;
104 | try {
105 | // Not all attributes of xhr are writable(setter may undefined).
106 | xhr[attr] = v;
107 | } catch (e) {
108 | }
109 | }
110 | } else {
111 | xhr[attr] = v;
112 | }
113 | }
114 | }
115 |
116 | // Hook methods of xhr.
117 | function hookFunction(fun) {
118 | return function () {
119 | var args = [].slice.call(arguments);
120 | if (proxy[fun] && hooking) {
121 | var ret = proxy[fun].call(this, args, this[OriginXhr])
122 | // If the proxy return value exists, return it directly,
123 | // otherwise call the function of xhr.
124 | if (ret) return ret;
125 | }
126 | return this[OriginXhr][fun].apply(this[OriginXhr], args);
127 | }
128 | }
129 |
130 | function unHook() {
131 | hooking = false;
132 | if (win.XMLHttpRequest === HookXMLHttpRequest) {
133 | win.XMLHttpRequest = originXhr;
134 | HookXMLHttpRequest.prototype.constructor = originXhr;
135 | originXhr = undefined;
136 | }
137 | }
138 |
139 | // Return the real XMLHttpRequest and unHook func
140 | return { originXhr, unHook };
141 | }
142 |
143 |
144 |
--------------------------------------------------------------------------------
/src/xhr-proxy.js:
--------------------------------------------------------------------------------
1 | /*
2 | * author: wendux
3 | * email: 824783146@qq.com
4 | * source code: https://github.com/wendux/Ajax-hook
5 | */
6 |
7 | import {hook, configEvent, events} from "./xhr-hook";
8 |
9 | var eventLoad = events[0],
10 | eventLoadEnd = events[1],
11 | eventTimeout = events[2],
12 | eventError = events[3],
13 | eventReadyStateChange = events[4],
14 | eventAbort = events[5];
15 |
16 |
17 | var prototype = 'prototype';
18 |
19 |
20 | export function proxy(proxy, win) {
21 | win = win || window;
22 | return proxyAjax(proxy, win);
23 | }
24 |
25 | function trim(str) {
26 | return str.replace(/^\s+|\s+$/g, '');
27 | }
28 |
29 | function getEventTarget(xhr) {
30 | return xhr.watcher || (xhr.watcher = document.createElement('a'));
31 | }
32 |
33 | function triggerListener(xhr, name) {
34 | var xhrProxy = xhr.getProxy();
35 | var callback = 'on' + name + '_';
36 | var event = configEvent({type: name}, xhrProxy);
37 | xhrProxy[callback] && xhrProxy[callback](event);
38 | var evt;
39 | if (typeof(Event) === 'function') {
40 | evt = new Event(name, {bubbles: false});
41 | } else {
42 | // https://stackoverflow.com/questions/27176983/dispatchevent-not-working-in-ie11
43 | evt = document.createEvent('Event');
44 | evt.initEvent(name, false, true);
45 | }
46 | getEventTarget(xhr).dispatchEvent(evt);
47 | }
48 |
49 |
50 | function Handler(xhr) {
51 | this.xhr = xhr;
52 | this.xhrProxy = xhr.getProxy();
53 | }
54 |
55 | Handler[prototype] = Object.create({
56 | resolve: function resolve(response) {
57 | var xhrProxy = this.xhrProxy;
58 | var xhr = this.xhr;
59 | xhrProxy.readyState = 4;
60 | xhr.resHeader = response.headers;
61 | xhrProxy.response = xhrProxy.responseText = response.response;
62 | xhrProxy.statusText = response.statusText;
63 | xhrProxy.status = response.status;
64 | triggerListener(xhr, eventReadyStateChange);
65 | triggerListener(xhr, eventLoad);
66 | triggerListener(xhr, eventLoadEnd);
67 | },
68 | reject: function reject(error) {
69 | this.xhrProxy.status = 0;
70 | triggerListener(this.xhr, error.type);
71 | triggerListener(this.xhr, eventLoadEnd);
72 | }
73 | });
74 |
75 | function makeHandler(next) {
76 | function sub(xhr) {
77 | Handler.call(this, xhr);
78 | }
79 |
80 | sub[prototype] = Object.create(Handler[prototype]);
81 | sub[prototype].next = next
82 | return sub;
83 | }
84 |
85 | var RequestHandler = makeHandler(function (rq) {
86 | var xhr = this.xhr;
87 | rq = rq || xhr.config;
88 | xhr.withCredentials = rq.withCredentials;
89 | xhr.open(rq.method, rq.url, rq.async !== false, rq.user, rq.password);
90 | for (var key in rq.headers) {
91 | xhr.setRequestHeader(key, rq.headers[key]);
92 | }
93 | xhr.send(rq.body);
94 | });
95 |
96 | var ResponseHandler = makeHandler(function (response) {
97 | this.resolve(response);
98 | });
99 |
100 | var ErrorHandler = makeHandler(function (error) {
101 | this.reject(error);
102 | });
103 |
104 | function proxyAjax(proxy, win) {
105 | var onRequest = proxy.onRequest,
106 | onResponse = proxy.onResponse,
107 | onError = proxy.onError;
108 |
109 | function getResponseData(xhrProxy) {
110 | var responseType = xhrProxy.responseType;
111 | if (!responseType || responseType === 'text') {
112 | return xhrProxy.responseText;
113 | }
114 | // reference: https://shanabrian.com/web/html-css-js-technics/js-ie10-ie11-xhr-json-string.php
115 | // reference: https://github.com/axios/axios/issues/2390
116 | // json - W3C standard - xhrProxy.response = JSON object; responseText is unobtainable
117 | // For details, see https://github.com/wendux/ajax-hook/issues/117
118 | // IE 9, 10 & 11 - only responseText
119 | var response = xhrProxy.response;
120 | if (responseType === 'json' && !response) {
121 | try {
122 | return JSON.parse(xhrProxy.responseText);
123 | } catch (e) {
124 | console.warn(e);
125 | }
126 | }
127 | return response;
128 | };
129 |
130 | function handleResponse(xhr, xhrProxy) {
131 | var handler = new ResponseHandler(xhr);
132 | var ret = {
133 | response: getResponseData(xhrProxy),
134 | status: xhrProxy.status,
135 | statusText: xhrProxy.statusText,
136 | config: xhr.config,
137 | headers: xhr.resHeader || xhr.getAllResponseHeaders().split('\r\n').reduce(function (ob, str) {
138 | if (str === "") return ob;
139 | var m = str.split(":");
140 | ob[m.shift()] = trim(m.join(':'));
141 | return ob;
142 | }, {})
143 | };
144 | if (!onResponse) return handler.resolve(ret);
145 | onResponse(ret, handler);
146 | }
147 |
148 | function onerror(xhr, xhrProxy, error, errorType) {
149 | var handler = new ErrorHandler(xhr);
150 | error = {config: xhr.config, error: error, type: errorType};
151 | if (onError) {
152 | onError(error, handler);
153 | } else {
154 | handler.next(error);
155 | }
156 | }
157 |
158 | function preventXhrProxyCallback() {
159 | return true;
160 | }
161 |
162 | function errorCallback(errorType) {
163 | return function (xhr, e) {
164 | onerror(xhr, this, e, errorType);
165 | return true;
166 | }
167 | }
168 |
169 | function stateChangeCallback(xhr, xhrProxy) {
170 | if (xhr.readyState === 4 && xhr.status !== 0) {
171 | handleResponse(xhr, xhrProxy);
172 | } else if (xhr.readyState !== 4) {
173 | triggerListener(xhr, eventReadyStateChange);
174 | }
175 | return true;
176 | }
177 |
178 |
179 | var { originXhr, unHook } = hook({
180 | onload: preventXhrProxyCallback,
181 | onloadend: preventXhrProxyCallback,
182 | onerror: errorCallback(eventError),
183 | ontimeout: errorCallback(eventTimeout),
184 | onabort: errorCallback(eventAbort),
185 | onreadystatechange: function (xhr) {
186 | return stateChangeCallback(xhr, this);
187 | },
188 | open: function open(args, xhr) {
189 | var _this = this;
190 | var config = xhr.config = {headers: {}};
191 | config.method = args[0];
192 | config.url = args[1];
193 | config.async = args[2];
194 | config.user = args[3];
195 | config.password = args[4];
196 | config.xhr = xhr;
197 | var evName = 'on' + eventReadyStateChange;
198 | if (!xhr[evName]) {
199 | xhr[evName] = function () {
200 | return stateChangeCallback(xhr, _this);
201 | };
202 | }
203 |
204 | // 如果有请求拦截器,则在调用onRequest后再打开链接。因为onRequest最佳调用时机是在send前,
205 | // 所以我们在send拦截函数中再手动调用open,因此返回true阻止xhr.open调用。
206 | //
207 | // 如果没有请求拦截器,则不用阻断xhr.open调用
208 | if (onRequest) return true;
209 | },
210 | send: function (args, xhr) {
211 | var config = xhr.config
212 | config.withCredentials = xhr.withCredentials
213 | config.body = args[0];
214 | if (onRequest) {
215 | // In 'onRequest', we may call XHR's event handler, such as `xhr.onload`.
216 | // However, XHR's event handler may not be set until xhr.send is called in
217 | // the user's code, so we use `setTimeout` to avoid this situation
218 | var req = function () {
219 | onRequest(config, new RequestHandler(xhr));
220 | }
221 | config.async === false ? req() : setTimeout(req)
222 | return true;
223 | }
224 | },
225 | setRequestHeader: function (args, xhr) {
226 | // Collect request headers
227 | xhr.config.headers[args[0].toLowerCase()] = args[1];
228 | if (onRequest) return true;
229 | },
230 | addEventListener: function (args, xhr) {
231 | var _this = this;
232 | if (events.indexOf(args[0]) !== -1) {
233 | var handler = args[1];
234 | getEventTarget(xhr).addEventListener(args[0], function (e) {
235 | var event = configEvent(e, _this);
236 | event.type = args[0];
237 | event.isTrusted = true;
238 | handler.call(_this, event);
239 | });
240 | return true;
241 | }
242 | },
243 | getAllResponseHeaders: function (_, xhr) {
244 | var headers = xhr.resHeader
245 | if (headers) {
246 | var header = "";
247 | for (var key in headers) {
248 | header += key + ': ' + headers[key] + '\r\n';
249 | }
250 | return header;
251 | }
252 | },
253 | getResponseHeader: function (args, xhr) {
254 | var headers = xhr.resHeader
255 | if (headers) {
256 | return headers[(args[0] || '').toLowerCase()];
257 | }
258 | }
259 | }, win);
260 |
261 | return {
262 | originXhr,
263 | unProxy: unHook
264 | }
265 | }
266 |
267 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/test/es.js:
--------------------------------------------------------------------------------
1 | import {proxy} from "../index";
2 | import {testProxy} from './test'
3 |
4 | proxy({
5 | onRequest: (config, handler) => {
6 | if (config.url === 'https://aa/') {
7 | handler.resolve({
8 | config: config,
9 | status: 200,
10 | headers: {'content-type': 'text/text'},
11 | response: 'hi world'
12 |
13 | })
14 | } else {
15 | handler.next(config);
16 | }
17 | },
18 | onError: (err, handler) => {
19 | if (err.config.url === 'https://bb/') {
20 | handler.resolve({
21 | config: err.config,
22 | status: 200,
23 | headers: {'content-type': 'text/text'},
24 | response: 'hi world'
25 | })
26 | } else {
27 | handler.next(err)
28 | }
29 | },
30 | onResponse: (response, handler) => {
31 | if (response.config.url === location.href) {
32 | handler.reject({
33 | config: response.config,
34 | type: 'error'
35 | })
36 | } else {
37 | handler.next(response)
38 | }
39 | }
40 | })
41 |
42 | testProxy()
43 |
44 | proxy({
45 | onRequest: (config, handler) => {
46 | console.log(config.url)
47 | handler.next(config);
48 |
49 | },
50 | onError: (err, handler) => {
51 | console.log(err.type)
52 | handler.next(err)
53 | },
54 | onResponse: (response, handler) => {
55 | console.log(response.response)
56 | handler.next(response)
57 | }
58 | })
59 |
60 |
--------------------------------------------------------------------------------
/test/hook.ts:
--------------------------------------------------------------------------------
1 | import { hook } from "../index"
2 | import { testRequest } from './test'
3 |
4 | function testHook() {
5 | testRequest(location.href);
6 | // testRequest('https://aa');
7 | }
8 |
9 | const { unHook: unHook1, originXhr: originXhr1 } = hook({
10 | onreadystatechange: function (xhr) {
11 | console.log("1. onreadystatechange")
12 | },
13 | onload: function (xhr) {
14 | console.log("1. onload")
15 | // xhr.getProxy().responseText='xhr.responseText'
16 | this.responseText = "hookAjax" + xhr.responseText;
17 | },
18 | onerror: function (xhr) {
19 | console.log("1. onerror");
20 | },
21 | open: function (arg, xhr) {
22 | //add tag
23 | arg[1] += "&hook_tag=1";
24 | console.log("1. open");
25 |
26 | },
27 | send: function (arg, xhr) {
28 | console.log("1. send");
29 | xhr.setRequestHeader("_custom_header_1", "ajaxhook1");
30 | },
31 | setRequestHeader: function (args, xhr) {
32 | console.log("1. setRequestHeader");
33 | },
34 | response: {
35 | getter: () => {
36 | return { data: '1 res' }
37 | },
38 | setter(value, target) {
39 | console.log('1. set response');
40 | return '1. set response: set response value';
41 | },
42 | }
43 | });
44 |
45 | const { unHook: unHook2, originXhr: originXhr2 } = hook({
46 | onreadystatechange: function (xhr) {
47 | console.log("2. onreadystatechange")
48 | },
49 | onload: function (xhr) {
50 | console.log("2. onload")
51 | // xhr.getProxy().responseText='xhr.responseText'
52 | this.responseText = "hookAjax" + xhr.responseText;
53 | },
54 | onerror: function (xhr) {
55 | console.log("2. onerror");
56 | },
57 | open: function (arg, xhr) {
58 | //add tag
59 | arg[1] += "?hook_tag=2";
60 | console.log("2. open");
61 | },
62 | send: function (arg, xhr) {
63 | console.log("2. send");
64 | xhr.setRequestHeader("_custom_header_2", "ajaxhook2");
65 | },
66 | setRequestHeader: function (args, xhr) {
67 | console.log("2. setRequestHeader")
68 | },
69 | response: {
70 | getter: () => {
71 | return { data: '2. res' }
72 | },
73 | setter(value, target) {
74 | console.log('2. set response');
75 | return '2. set response';
76 | },
77 | }
78 | });
79 |
80 | unHook1();
81 |
82 | // unHook2();
83 |
84 | testHook()
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/test/proxy.ts:
--------------------------------------------------------------------------------
1 | import { proxy } from "../index"
2 | import { testRequest } from './test'
3 |
4 |
5 | export function testProxy() {
6 | testRequest('https://cc/');
7 | }
8 |
9 | const { unProxy: unProxy1, originXhr: originXhr1 } = proxy({
10 | onRequest: (config, handler) => {
11 | console.log(`1. onRequest: ${config.url}`);
12 | config.headers = { 'content-type': 'text/text', customHeader1: 'customHeader1', ...config.headers };
13 | handler.next(config);
14 |
15 | },
16 | onError: (err, handler) => {
17 | console.log(`1. onError: ${err.config.url}`);
18 | handler.next(err);
19 | },
20 | onResponse: (response, handler) => {
21 | console.log(`1. onResponse: ${response.config.url}`);
22 | handler.next(response);
23 | }
24 | }, window);
25 |
26 |
27 | // const { unProxy: unProxy2, originXhr: originXhr2 } = proxy({
28 | // onRequest: (config, handler) => {
29 | // console.log(`2. onRequest: ${config.url}`);
30 | // config.headers = { 'content-type': 'text/text', customHeader2: 'customHeader2', ...config.headers };
31 | // handler.next(config);
32 |
33 | // },
34 | // onError: (err, handler) => {
35 | // console.log(`2. onError: ${err.config.url}`);
36 | // handler.next(err);
37 | // },
38 | // onResponse: (response, handler) => {
39 | // console.log(`2. onResponse: ${response.config.url}`);
40 | // handler.next(response)
41 | // }
42 | // }, window);
43 |
44 |
45 | // unProxy1();
46 | // unProxy2();
47 |
48 | testProxy()
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | export function testRequest(url) {
2 | var xhr = new XMLHttpRequest();
3 | xhr.open('get', url);
4 |
5 | xhr.onload = () => {
6 | xhr.response = 'xhr response has been reset';
7 | console.log('origin onload :', xhr.response);
8 | }
9 |
10 | xhr.onerror = () => {
11 | console.log(`${url}: xhr error`);
12 | }
13 |
14 | xhr.onreadystatechange = (...args) => {}
15 | xhr.setRequestHeader('header1', 'header1');
16 | xhr.send();
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------