ajax-hooker 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +54 -3
- package/dist/esm/index.js +54 -3
- package/dist/iife/index.js +54 -3
- package/dist/types/xhr.d.ts +1 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -98,10 +98,12 @@ class XhrInterceptor {
|
|
|
98
98
|
}
|
|
99
99
|
hooker.req = newRequest;
|
|
100
100
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
101
|
-
|
|
101
|
+
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
102
|
+
const shouldReopen = needReopen || headersChanged;
|
|
103
|
+
if (shouldReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
|
|
102
104
|
const xhrProps = [ "responseType", "withCredentials", "timeout" ];
|
|
103
105
|
for (const prop of xhrProps) if (newRequest[prop] !== oldRequest[prop]) target[prop] = newRequest[prop];
|
|
104
|
-
if (
|
|
106
|
+
if (shouldReopen) hooker.req.headers.forEach((val, key) => {
|
|
105
107
|
target.setRequestHeader(key, val);
|
|
106
108
|
});
|
|
107
109
|
self.nativeXhrPrototype.send.apply(target, [ hooker.req.data ]);
|
|
@@ -117,12 +119,29 @@ class XhrInterceptor {
|
|
|
117
119
|
addEventListener: function(self, target, receiver) {
|
|
118
120
|
return function(type, listener, ...args) {
|
|
119
121
|
const isResponseEvent = self.xhrResponseEvents.includes(type);
|
|
122
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
123
|
+
const capture = self.getCaptureOption(args[0]);
|
|
120
124
|
const newListener = async function(...args) {
|
|
121
125
|
if (isResponseEvent && target.readyState === 4) await self.responseProcessor(target);
|
|
122
|
-
|
|
126
|
+
if (typeof listener === "function") {
|
|
127
|
+
Reflect.apply(listener, receiver, args);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const [event] = args;
|
|
131
|
+
listener.handleEvent?.(event);
|
|
123
132
|
};
|
|
133
|
+
hooker.saveWrappedEventListener(type, capture, listener, newListener);
|
|
124
134
|
target.addEventListener(type, newListener, ...args);
|
|
125
135
|
};
|
|
136
|
+
},
|
|
137
|
+
removeEventListener: function(self, target) {
|
|
138
|
+
return function(type, listener, options) {
|
|
139
|
+
if (!listener) return self.nativeXhrPrototype.removeEventListener.apply(target, [ type, listener, options ]);
|
|
140
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
141
|
+
const capture = self.getCaptureOption(options);
|
|
142
|
+
const wrappedListener = hooker.getWrappedEventListener(type, capture, listener) || listener;
|
|
143
|
+
self.nativeXhrPrototype.removeEventListener.apply(target, [ type, wrappedListener, options ]);
|
|
144
|
+
};
|
|
126
145
|
}
|
|
127
146
|
};
|
|
128
147
|
constructor() {}
|
|
@@ -165,10 +184,20 @@ class XhrInterceptor {
|
|
|
165
184
|
const hooker = target[CYCLE_SCHEDULER];
|
|
166
185
|
if (hooker.xhrAlreadyReturned) return;
|
|
167
186
|
hooker.xhrAlreadyReturned = true;
|
|
187
|
+
let responseText;
|
|
188
|
+
if (target.responseType === "" || target.responseType === "text") try {
|
|
189
|
+
responseText = target.responseText;
|
|
190
|
+
} catch (_error) {}
|
|
191
|
+
let responseXML;
|
|
192
|
+
try {
|
|
193
|
+
responseXML = target.responseXML;
|
|
194
|
+
} catch (_error) {}
|
|
168
195
|
hooker.resp = {
|
|
169
196
|
status: target.status,
|
|
170
197
|
statusText: target.statusText,
|
|
171
198
|
response: target.response,
|
|
199
|
+
responseText: responseText,
|
|
200
|
+
responseXML: responseXML,
|
|
172
201
|
headers: new Headers(this.parseHeaders(target.getAllResponseHeaders())),
|
|
173
202
|
finalUrl: target.responseURL || ""
|
|
174
203
|
};
|
|
@@ -187,6 +216,10 @@ class XhrInterceptor {
|
|
|
187
216
|
};
|
|
188
217
|
return toSortedString(a) === toSortedString(b);
|
|
189
218
|
}
|
|
219
|
+
getCaptureOption(options) {
|
|
220
|
+
if (typeof options === "boolean") return options;
|
|
221
|
+
return !!options?.capture;
|
|
222
|
+
}
|
|
190
223
|
getAttrHandler(target, attr, receiver) {
|
|
191
224
|
if (this.xhrInstanceAttr.includes(attr)) return this.xhrInstanceAttrHandler[attr](target);
|
|
192
225
|
if (this.xhrMethodsHandler[attr]) return this.xhrMethodsHandler[attr](this, target, receiver);
|
|
@@ -228,12 +261,30 @@ class XhrCycleScheduler extends CycleScheduler {
|
|
|
228
261
|
xhrAlreadyReturned=false;
|
|
229
262
|
xhrOpenRestArgs=[];
|
|
230
263
|
xhrSetRequestHeadersAfterOpen=new Headers;
|
|
264
|
+
xhrWrappedEventListeners=new Map;
|
|
265
|
+
getListenerBucket(type) {
|
|
266
|
+
if (!this.xhrWrappedEventListeners.has(type)) this.xhrWrappedEventListeners.set(type, {
|
|
267
|
+
captureTrue: new WeakMap,
|
|
268
|
+
captureFalse: new WeakMap
|
|
269
|
+
});
|
|
270
|
+
return this.xhrWrappedEventListeners.get(type);
|
|
271
|
+
}
|
|
272
|
+
saveWrappedEventListener(type, capture, original, wrapped) {
|
|
273
|
+
const bucket = this.getListenerBucket(type);
|
|
274
|
+
(capture ? bucket.captureTrue : bucket.captureFalse).set(original, wrapped);
|
|
275
|
+
}
|
|
276
|
+
getWrappedEventListener(type, capture, original) {
|
|
277
|
+
const bucket = this.xhrWrappedEventListeners.get(type);
|
|
278
|
+
if (!bucket) return null;
|
|
279
|
+
return (capture ? bucket.captureTrue : bucket.captureFalse).get(original) ?? null;
|
|
280
|
+
}
|
|
231
281
|
xhrReset() {
|
|
232
282
|
this.req = {};
|
|
233
283
|
this.resp = {};
|
|
234
284
|
this.xhrOpenRestArgs = [];
|
|
235
285
|
this.xhrSetRequestHeadersAfterOpen = new Headers;
|
|
236
286
|
this.xhrAlreadyReturned = false;
|
|
287
|
+
this.xhrWrappedEventListeners.clear();
|
|
237
288
|
}
|
|
238
289
|
constructor({req: req = {}} = {}) {
|
|
239
290
|
super({
|
package/dist/esm/index.js
CHANGED
|
@@ -92,10 +92,12 @@ class XhrInterceptor {
|
|
|
92
92
|
}
|
|
93
93
|
hooker.req = newRequest;
|
|
94
94
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
95
|
-
|
|
95
|
+
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
96
|
+
const shouldReopen = needReopen || headersChanged;
|
|
97
|
+
if (shouldReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
|
|
96
98
|
const xhrProps = [ "responseType", "withCredentials", "timeout" ];
|
|
97
99
|
for (const prop of xhrProps) if (newRequest[prop] !== oldRequest[prop]) target[prop] = newRequest[prop];
|
|
98
|
-
if (
|
|
100
|
+
if (shouldReopen) hooker.req.headers.forEach((val, key) => {
|
|
99
101
|
target.setRequestHeader(key, val);
|
|
100
102
|
});
|
|
101
103
|
self.nativeXhrPrototype.send.apply(target, [ hooker.req.data ]);
|
|
@@ -111,12 +113,29 @@ class XhrInterceptor {
|
|
|
111
113
|
addEventListener: function(self, target, receiver) {
|
|
112
114
|
return function(type, listener, ...args) {
|
|
113
115
|
const isResponseEvent = self.xhrResponseEvents.includes(type);
|
|
116
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
117
|
+
const capture = self.getCaptureOption(args[0]);
|
|
114
118
|
const newListener = async function(...args) {
|
|
115
119
|
if (isResponseEvent && target.readyState === 4) await self.responseProcessor(target);
|
|
116
|
-
|
|
120
|
+
if (typeof listener === "function") {
|
|
121
|
+
Reflect.apply(listener, receiver, args);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const [event] = args;
|
|
125
|
+
listener.handleEvent?.(event);
|
|
117
126
|
};
|
|
127
|
+
hooker.saveWrappedEventListener(type, capture, listener, newListener);
|
|
118
128
|
target.addEventListener(type, newListener, ...args);
|
|
119
129
|
};
|
|
130
|
+
},
|
|
131
|
+
removeEventListener: function(self, target) {
|
|
132
|
+
return function(type, listener, options) {
|
|
133
|
+
if (!listener) return self.nativeXhrPrototype.removeEventListener.apply(target, [ type, listener, options ]);
|
|
134
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
135
|
+
const capture = self.getCaptureOption(options);
|
|
136
|
+
const wrappedListener = hooker.getWrappedEventListener(type, capture, listener) || listener;
|
|
137
|
+
self.nativeXhrPrototype.removeEventListener.apply(target, [ type, wrappedListener, options ]);
|
|
138
|
+
};
|
|
120
139
|
}
|
|
121
140
|
};
|
|
122
141
|
constructor() {}
|
|
@@ -159,10 +178,20 @@ class XhrInterceptor {
|
|
|
159
178
|
const hooker = target[CYCLE_SCHEDULER];
|
|
160
179
|
if (hooker.xhrAlreadyReturned) return;
|
|
161
180
|
hooker.xhrAlreadyReturned = true;
|
|
181
|
+
let responseText;
|
|
182
|
+
if (target.responseType === "" || target.responseType === "text") try {
|
|
183
|
+
responseText = target.responseText;
|
|
184
|
+
} catch (_error) {}
|
|
185
|
+
let responseXML;
|
|
186
|
+
try {
|
|
187
|
+
responseXML = target.responseXML;
|
|
188
|
+
} catch (_error) {}
|
|
162
189
|
hooker.resp = {
|
|
163
190
|
status: target.status,
|
|
164
191
|
statusText: target.statusText,
|
|
165
192
|
response: target.response,
|
|
193
|
+
responseText: responseText,
|
|
194
|
+
responseXML: responseXML,
|
|
166
195
|
headers: new Headers(this.parseHeaders(target.getAllResponseHeaders())),
|
|
167
196
|
finalUrl: target.responseURL || ""
|
|
168
197
|
};
|
|
@@ -181,6 +210,10 @@ class XhrInterceptor {
|
|
|
181
210
|
};
|
|
182
211
|
return toSortedString(a) === toSortedString(b);
|
|
183
212
|
}
|
|
213
|
+
getCaptureOption(options) {
|
|
214
|
+
if (typeof options === "boolean") return options;
|
|
215
|
+
return !!options?.capture;
|
|
216
|
+
}
|
|
184
217
|
getAttrHandler(target, attr, receiver) {
|
|
185
218
|
if (this.xhrInstanceAttr.includes(attr)) return this.xhrInstanceAttrHandler[attr](target);
|
|
186
219
|
if (this.xhrMethodsHandler[attr]) return this.xhrMethodsHandler[attr](this, target, receiver);
|
|
@@ -222,12 +255,30 @@ class XhrCycleScheduler extends CycleScheduler {
|
|
|
222
255
|
xhrAlreadyReturned=false;
|
|
223
256
|
xhrOpenRestArgs=[];
|
|
224
257
|
xhrSetRequestHeadersAfterOpen=new Headers;
|
|
258
|
+
xhrWrappedEventListeners=new Map;
|
|
259
|
+
getListenerBucket(type) {
|
|
260
|
+
if (!this.xhrWrappedEventListeners.has(type)) this.xhrWrappedEventListeners.set(type, {
|
|
261
|
+
captureTrue: new WeakMap,
|
|
262
|
+
captureFalse: new WeakMap
|
|
263
|
+
});
|
|
264
|
+
return this.xhrWrappedEventListeners.get(type);
|
|
265
|
+
}
|
|
266
|
+
saveWrappedEventListener(type, capture, original, wrapped) {
|
|
267
|
+
const bucket = this.getListenerBucket(type);
|
|
268
|
+
(capture ? bucket.captureTrue : bucket.captureFalse).set(original, wrapped);
|
|
269
|
+
}
|
|
270
|
+
getWrappedEventListener(type, capture, original) {
|
|
271
|
+
const bucket = this.xhrWrappedEventListeners.get(type);
|
|
272
|
+
if (!bucket) return null;
|
|
273
|
+
return (capture ? bucket.captureTrue : bucket.captureFalse).get(original) ?? null;
|
|
274
|
+
}
|
|
225
275
|
xhrReset() {
|
|
226
276
|
this.req = {};
|
|
227
277
|
this.resp = {};
|
|
228
278
|
this.xhrOpenRestArgs = [];
|
|
229
279
|
this.xhrSetRequestHeadersAfterOpen = new Headers;
|
|
230
280
|
this.xhrAlreadyReturned = false;
|
|
281
|
+
this.xhrWrappedEventListeners.clear();
|
|
231
282
|
}
|
|
232
283
|
constructor({req: req = {}} = {}) {
|
|
233
284
|
super({
|
package/dist/iife/index.js
CHANGED
|
@@ -86,10 +86,12 @@ var AjaxHooker = function(exports) {
|
|
|
86
86
|
}
|
|
87
87
|
hooker.req = newRequest;
|
|
88
88
|
const needReopen = oldRequest.method !== newRequest.method || oldRequest.url !== newRequest.url;
|
|
89
|
-
|
|
89
|
+
const headersChanged = !self.headersEqual(oldRequest.headers, newRequest.headers);
|
|
90
|
+
const shouldReopen = needReopen || headersChanged;
|
|
91
|
+
if (shouldReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
|
|
90
92
|
const xhrProps = [ "responseType", "withCredentials", "timeout" ];
|
|
91
93
|
for (const prop of xhrProps) if (newRequest[prop] !== oldRequest[prop]) target[prop] = newRequest[prop];
|
|
92
|
-
if (
|
|
94
|
+
if (shouldReopen) hooker.req.headers.forEach((val, key) => {
|
|
93
95
|
target.setRequestHeader(key, val);
|
|
94
96
|
});
|
|
95
97
|
self.nativeXhrPrototype.send.apply(target, [ hooker.req.data ]);
|
|
@@ -105,12 +107,29 @@ var AjaxHooker = function(exports) {
|
|
|
105
107
|
addEventListener: function(self, target, receiver) {
|
|
106
108
|
return function(type, listener, ...args) {
|
|
107
109
|
const isResponseEvent = self.xhrResponseEvents.includes(type);
|
|
110
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
111
|
+
const capture = self.getCaptureOption(args[0]);
|
|
108
112
|
const newListener = async function(...args) {
|
|
109
113
|
if (isResponseEvent && target.readyState === 4) await self.responseProcessor(target);
|
|
110
|
-
|
|
114
|
+
if (typeof listener === "function") {
|
|
115
|
+
Reflect.apply(listener, receiver, args);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const [event] = args;
|
|
119
|
+
listener.handleEvent?.(event);
|
|
111
120
|
};
|
|
121
|
+
hooker.saveWrappedEventListener(type, capture, listener, newListener);
|
|
112
122
|
target.addEventListener(type, newListener, ...args);
|
|
113
123
|
};
|
|
124
|
+
},
|
|
125
|
+
removeEventListener: function(self, target) {
|
|
126
|
+
return function(type, listener, options) {
|
|
127
|
+
if (!listener) return self.nativeXhrPrototype.removeEventListener.apply(target, [ type, listener, options ]);
|
|
128
|
+
const hooker = target[CYCLE_SCHEDULER];
|
|
129
|
+
const capture = self.getCaptureOption(options);
|
|
130
|
+
const wrappedListener = hooker.getWrappedEventListener(type, capture, listener) || listener;
|
|
131
|
+
self.nativeXhrPrototype.removeEventListener.apply(target, [ type, wrappedListener, options ]);
|
|
132
|
+
};
|
|
114
133
|
}
|
|
115
134
|
};
|
|
116
135
|
constructor() {}
|
|
@@ -153,10 +172,20 @@ var AjaxHooker = function(exports) {
|
|
|
153
172
|
const hooker = target[CYCLE_SCHEDULER];
|
|
154
173
|
if (hooker.xhrAlreadyReturned) return;
|
|
155
174
|
hooker.xhrAlreadyReturned = true;
|
|
175
|
+
let responseText;
|
|
176
|
+
if (target.responseType === "" || target.responseType === "text") try {
|
|
177
|
+
responseText = target.responseText;
|
|
178
|
+
} catch (_error) {}
|
|
179
|
+
let responseXML;
|
|
180
|
+
try {
|
|
181
|
+
responseXML = target.responseXML;
|
|
182
|
+
} catch (_error) {}
|
|
156
183
|
hooker.resp = {
|
|
157
184
|
status: target.status,
|
|
158
185
|
statusText: target.statusText,
|
|
159
186
|
response: target.response,
|
|
187
|
+
responseText: responseText,
|
|
188
|
+
responseXML: responseXML,
|
|
160
189
|
headers: new Headers(this.parseHeaders(target.getAllResponseHeaders())),
|
|
161
190
|
finalUrl: target.responseURL || ""
|
|
162
191
|
};
|
|
@@ -175,6 +204,10 @@ var AjaxHooker = function(exports) {
|
|
|
175
204
|
};
|
|
176
205
|
return toSortedString(a) === toSortedString(b);
|
|
177
206
|
}
|
|
207
|
+
getCaptureOption(options) {
|
|
208
|
+
if (typeof options === "boolean") return options;
|
|
209
|
+
return !!options?.capture;
|
|
210
|
+
}
|
|
178
211
|
getAttrHandler(target, attr, receiver) {
|
|
179
212
|
if (this.xhrInstanceAttr.includes(attr)) return this.xhrInstanceAttrHandler[attr](target);
|
|
180
213
|
if (this.xhrMethodsHandler[attr]) return this.xhrMethodsHandler[attr](this, target, receiver);
|
|
@@ -215,12 +248,30 @@ var AjaxHooker = function(exports) {
|
|
|
215
248
|
xhrAlreadyReturned=false;
|
|
216
249
|
xhrOpenRestArgs=[];
|
|
217
250
|
xhrSetRequestHeadersAfterOpen=new Headers;
|
|
251
|
+
xhrWrappedEventListeners=new Map;
|
|
252
|
+
getListenerBucket(type) {
|
|
253
|
+
if (!this.xhrWrappedEventListeners.has(type)) this.xhrWrappedEventListeners.set(type, {
|
|
254
|
+
captureTrue: new WeakMap,
|
|
255
|
+
captureFalse: new WeakMap
|
|
256
|
+
});
|
|
257
|
+
return this.xhrWrappedEventListeners.get(type);
|
|
258
|
+
}
|
|
259
|
+
saveWrappedEventListener(type, capture, original, wrapped) {
|
|
260
|
+
const bucket = this.getListenerBucket(type);
|
|
261
|
+
(capture ? bucket.captureTrue : bucket.captureFalse).set(original, wrapped);
|
|
262
|
+
}
|
|
263
|
+
getWrappedEventListener(type, capture, original) {
|
|
264
|
+
const bucket = this.xhrWrappedEventListeners.get(type);
|
|
265
|
+
if (!bucket) return null;
|
|
266
|
+
return (capture ? bucket.captureTrue : bucket.captureFalse).get(original) ?? null;
|
|
267
|
+
}
|
|
218
268
|
xhrReset() {
|
|
219
269
|
this.req = {};
|
|
220
270
|
this.resp = {};
|
|
221
271
|
this.xhrOpenRestArgs = [];
|
|
222
272
|
this.xhrSetRequestHeadersAfterOpen = new Headers;
|
|
223
273
|
this.xhrAlreadyReturned = false;
|
|
274
|
+
this.xhrWrappedEventListeners.clear();
|
|
224
275
|
}
|
|
225
276
|
constructor({req: req = {}} = {}) {
|
|
226
277
|
super({
|
package/dist/types/xhr.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajax-hooker",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Browser AJAX interceptor for XMLHttpRequest and fetch with unified hooks, request/response mutation, and streaming response support.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|