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 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
- if (needReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
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 (needReopen || !self.headersEqual(oldRequest.headers, newRequest.headers)) hooker.req.headers.forEach((val, key) => {
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
- Reflect.apply(listener, receiver, args);
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
- if (needReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
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 (needReopen || !self.headersEqual(oldRequest.headers, newRequest.headers)) hooker.req.headers.forEach((val, key) => {
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
- Reflect.apply(listener, receiver, args);
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({
@@ -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
- if (needReopen) self.nativeXhrPrototype.open.apply(target, [ hooker.req.method, hooker.req.url, ...hooker.xhrOpenRestArgs || [] ]);
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 (needReopen || !self.headersEqual(oldRequest.headers, newRequest.headers)) hooker.req.headers.forEach((val, key) => {
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
- Reflect.apply(listener, receiver, args);
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({
@@ -21,6 +21,7 @@ export declare class XhrInterceptor {
21
21
  private parseHeaders;
22
22
  private responseProcessor;
23
23
  private headersEqual;
24
+ private getCaptureOption;
24
25
  private getAttrHandler;
25
26
  private _generateProxyXMLHttpRequest;
26
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajax-hooker",
3
- "version": "1.2.1",
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",