vigor-roblox 1.0.3 → 2.0.0
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/README.md +1 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +174 -1888
- package/dist/index.mjs +160 -1874
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,1746 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
INVALID_TARGET: ({ expected, received }) => `Invalid Task: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
5
|
-
EXHAUSTED: ({ maxAttempts }) => `Retry exhausted, (max ${maxAttempts})`,
|
|
6
|
-
TIMED_OUT: ({ limit, attempt }) => `Timeout: exceeded ${limit}ms (attempt: ${attempt})`,
|
|
7
|
-
INVALID_CONTENT_TYPE: ({ expected, received, response }) => `Invalid Content Type Header: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
8
|
-
PARSER_NOT_FOUND: ({ expected, received, response }) => `Parser Not Found For Header: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
9
|
-
PARSER_ALL_FAILED: ({ tried, response }) => `All Parser Failed, Tried: ${tried.join(', ')}`,
|
|
10
|
-
INVALID_PROTOCOL: ({ expected, received }) => `Invalid Protocol: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
11
|
-
INVALID_BODY: ({ expected, received }) => `Invalid Body: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
12
|
-
FETCH_FAILED: ({ status, response, url, headers, body, statusText }) => `Fetch Failed: ${status}`,
|
|
13
|
-
EMPTY_TARGET: ({}) => `Empty Body`
|
|
14
|
-
};
|
|
15
|
-
class VigorError extends Error {
|
|
16
|
-
timestamp = new Date();
|
|
17
|
-
cause;
|
|
18
|
-
code;
|
|
19
|
-
data;
|
|
20
|
-
method;
|
|
21
|
-
stats;
|
|
22
|
-
context;
|
|
23
|
-
constructor(code, options) {
|
|
24
|
-
const messageFn = VigorErrorMessageFuncs[code];
|
|
25
|
-
const message = `[${code}] ${messageFn(options?.data)}`;
|
|
26
|
-
super(message, { cause: options?.cause });
|
|
27
|
-
this.name = new.target.name;
|
|
28
|
-
this.code = code;
|
|
29
|
-
this.cause = options.cause;
|
|
30
|
-
this.data = options.data;
|
|
31
|
-
this.method = options.method;
|
|
32
|
-
this.stats = options.stats;
|
|
33
|
-
this.context = options.context;
|
|
34
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
35
|
-
Error.captureStackTrace?.(this, new.target);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
class VigorRetryError extends VigorError {
|
|
39
|
-
constructor(code, options) {
|
|
40
|
-
super(code, options);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
class VigorParseError extends VigorError {
|
|
44
|
-
constructor(code, options) {
|
|
45
|
-
super(code, options);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
class VigorFetchError extends VigorError {
|
|
49
|
-
constructor(code, options) {
|
|
50
|
-
super(code, options);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
class VigorAllError extends VigorError {
|
|
54
|
-
constructor(code, options) {
|
|
55
|
-
super(code, options);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
class VigorStatus {
|
|
59
|
-
_base;
|
|
60
|
-
ctor;
|
|
61
|
-
_config;
|
|
62
|
-
constructor(config = {}, _base, ctor) {
|
|
63
|
-
this._base = _base;
|
|
64
|
-
this.ctor = ctor;
|
|
65
|
-
this._config = { ...this._base, ...(config || {}) };
|
|
66
|
-
}
|
|
67
|
-
_mergeConfig(source, target) {
|
|
68
|
-
const isPlainObject = (val) => val !== null && typeof val === 'object' && Object.getPrototypeOf(val) === Object.prototype;
|
|
69
|
-
if (target === undefined || target === null) {
|
|
70
|
-
return source;
|
|
71
|
-
}
|
|
72
|
-
if (isPlainObject(source) && isPlainObject(target)) {
|
|
73
|
-
const result = { ...source };
|
|
74
|
-
Object.keys(target).forEach((key) => {
|
|
75
|
-
result[key] = this._mergeConfig(result[key], target[key]);
|
|
76
|
-
});
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
if (Array.isArray(source) && Array.isArray(target)) {
|
|
80
|
-
return [...source, ...target];
|
|
81
|
-
}
|
|
82
|
-
return target;
|
|
83
|
-
}
|
|
84
|
-
_next(config) { return this.ctor(this._mergeConfig(this._config, config)); }
|
|
85
|
-
_getConfig() { return this._config; }
|
|
86
|
-
_getBase() { return this._base; }
|
|
87
|
-
}
|
|
88
|
-
const VigorDefault = Symbol("DEFAULT");
|
|
89
|
-
class VigorRetrySettings extends VigorStatus {
|
|
90
|
-
constructor(config) {
|
|
91
|
-
const base = {
|
|
92
|
-
default: VigorDefault,
|
|
93
|
-
timeout: 20 * 1000,
|
|
94
|
-
attempt: 5,
|
|
95
|
-
jitter: 1000
|
|
96
|
-
};
|
|
97
|
-
super(config, base, (c) => new VigorRetrySettings(c));
|
|
98
|
-
}
|
|
99
|
-
default(unk) { return this._next({ default: unk }); }
|
|
100
|
-
timeout(num) { return this._next({ timeout: num }); }
|
|
101
|
-
attempt(num) { return this._next({ attempt: num }); }
|
|
102
|
-
jitter(num) { return this._next({ jitter: num }); }
|
|
103
|
-
}
|
|
104
|
-
class VigorRetryInterceptors extends VigorStatus {
|
|
105
|
-
constructor(config) {
|
|
106
|
-
const base = {
|
|
107
|
-
before: [],
|
|
108
|
-
after: [],
|
|
109
|
-
result: [],
|
|
110
|
-
retryIf: [],
|
|
111
|
-
onRetry: [],
|
|
112
|
-
onError: []
|
|
113
|
-
};
|
|
114
|
-
super(config, base, (c) => new VigorRetryInterceptors(c));
|
|
115
|
-
}
|
|
116
|
-
before(...funcs) { return this._next({ before: funcs.flat() }); }
|
|
117
|
-
after(...funcs) { return this._next({ after: funcs.flat() }); }
|
|
118
|
-
result(...funcs) { return this._next({ result: funcs.flat() }); }
|
|
119
|
-
retryIf(...funcs) { return this._next({ retryIf: funcs.flat() }); }
|
|
120
|
-
onRetry(...funcs) { return this._next({ onRetry: funcs.flat() }); }
|
|
121
|
-
onError(...funcs) { return this._next({ onError: funcs.flat() }); }
|
|
122
|
-
}
|
|
123
|
-
class VigorRetryAlgorithmsConstant extends VigorStatus {
|
|
124
|
-
constructor(config) {
|
|
125
|
-
const base = {
|
|
126
|
-
interval: 2000
|
|
127
|
-
};
|
|
128
|
-
super(config, base, (c) => new VigorRetryAlgorithmsConstant(c));
|
|
129
|
-
}
|
|
130
|
-
interval(num) { return this._next({ interval: num }); }
|
|
131
|
-
_calculateDelay(attempt) {
|
|
132
|
-
return this._config.interval;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
class VigorRetryAlgorithmsLinear extends VigorStatus {
|
|
136
|
-
constructor(config) {
|
|
137
|
-
const base = {
|
|
138
|
-
initial: 1000,
|
|
139
|
-
increment: 1000,
|
|
140
|
-
minDelay: 500,
|
|
141
|
-
maxDelay: 20 * 1000
|
|
142
|
-
};
|
|
143
|
-
super(config, base, (c) => new VigorRetryAlgorithmsLinear(c));
|
|
144
|
-
}
|
|
145
|
-
initial(num) { return this._next({ initial: num }); }
|
|
146
|
-
increment(num) { return this._next({ increment: num }); }
|
|
147
|
-
minDelay(num) { return this._next({ minDelay: num }); }
|
|
148
|
-
maxDelay(num) { return this._next({ maxDelay: num }); }
|
|
149
|
-
_calculateDelay(attempt) {
|
|
150
|
-
const { initial, increment, minDelay, maxDelay } = this._config;
|
|
151
|
-
return Math.max(minDelay, Math.min(maxDelay, initial + increment * attempt));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
class VigorRetryAlgorithmsBackoff extends VigorStatus {
|
|
155
|
-
constructor(config) {
|
|
156
|
-
const base = {
|
|
157
|
-
initial: 1000,
|
|
158
|
-
multiplier: 1.7,
|
|
159
|
-
unit: 1000,
|
|
160
|
-
minDelay: 500,
|
|
161
|
-
maxDelay: 20 * 1000
|
|
162
|
-
};
|
|
163
|
-
super(config, base, (c) => new VigorRetryAlgorithmsBackoff(c));
|
|
164
|
-
}
|
|
165
|
-
initial(num) { return this._next({ initial: num }); }
|
|
166
|
-
multiplier(num) { return this._next({ multiplier: num }); }
|
|
167
|
-
unit(num) { return this._next({ unit: num }); }
|
|
168
|
-
minDelay(num) { return this._next({ minDelay: num }); }
|
|
169
|
-
maxDelay(num) { return this._next({ maxDelay: num }); }
|
|
170
|
-
_calculateDelay(attempt) {
|
|
171
|
-
const { initial, multiplier, unit, minDelay, maxDelay } = this._config;
|
|
172
|
-
return Math.max(minDelay, Math.min(maxDelay, initial + unit * Math.pow(multiplier, attempt)));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
class VigorRetryAlgorithmsCustom extends VigorStatus {
|
|
176
|
-
constructor(config) {
|
|
177
|
-
const base = {
|
|
178
|
-
func: (attempt) => attempt * 1000,
|
|
179
|
-
minDelay: 500,
|
|
180
|
-
maxDelay: 20 * 1000
|
|
181
|
-
};
|
|
182
|
-
super(config, base, (c) => new VigorRetryAlgorithmsCustom(c));
|
|
183
|
-
}
|
|
184
|
-
func(num) { return this._next({ func: num }); }
|
|
185
|
-
_calculateDelay(attempt) {
|
|
186
|
-
const { func, minDelay, maxDelay } = this._config;
|
|
187
|
-
return Math.max(minDelay, Math.min(maxDelay, func(attempt)));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
class VigorRetry extends VigorStatus {
|
|
191
|
-
constructor(config) {
|
|
192
|
-
const base = {
|
|
193
|
-
target: VigorDefault,
|
|
194
|
-
settings: new VigorRetrySettings()._getBase(),
|
|
195
|
-
interceptors: new VigorRetryInterceptors()._getBase(),
|
|
196
|
-
algorithm: (attempt) => new VigorRetryAlgorithmsBackoff()._calculateDelay(attempt),
|
|
197
|
-
abortSignals: []
|
|
198
|
-
};
|
|
199
|
-
super(config, base, (c) => new VigorRetry(c));
|
|
200
|
-
}
|
|
201
|
-
RetryAlgorithms = {
|
|
202
|
-
constant: (config) => new VigorRetryAlgorithmsConstant(config),
|
|
203
|
-
linear: (config) => new VigorRetryAlgorithmsLinear(config),
|
|
204
|
-
backoff: (config) => new VigorRetryAlgorithmsBackoff(config),
|
|
205
|
-
custom: (config) => new VigorRetryAlgorithmsCustom(config)
|
|
206
|
-
};
|
|
207
|
-
_createTimelineHandler(timeline) {
|
|
208
|
-
return (action, content) => {
|
|
209
|
-
timeline.push({
|
|
210
|
-
action: action,
|
|
211
|
-
content: content,
|
|
212
|
-
time: Date.now()
|
|
213
|
-
});
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
_createInterceptorHandler(ctx, addTimeline) {
|
|
217
|
-
return async (interceptorType, api) => {
|
|
218
|
-
const interceptorsConfig = ctx["stats"]["interceptors"];
|
|
219
|
-
const interceptors = interceptorsConfig[interceptorType];
|
|
220
|
-
addTimeline("INTERCEPTOR_LOOP_STARTED", {
|
|
221
|
-
interceptorType: interceptorType,
|
|
222
|
-
interceptors,
|
|
223
|
-
});
|
|
224
|
-
const startTime = performance.now();
|
|
225
|
-
for (const func of interceptors) {
|
|
226
|
-
const scopedApi = api(interceptorType, func);
|
|
227
|
-
await func(ctx, scopedApi);
|
|
228
|
-
}
|
|
229
|
-
const endTime = performance.now();
|
|
230
|
-
addTimeline("INTERCEPTOR_LOOP_ENDED", {
|
|
231
|
-
interceptorType: interceptorType,
|
|
232
|
-
interceptors,
|
|
233
|
-
took: endTime - startTime
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
target(func) { return this._next({ target: func }); }
|
|
238
|
-
settings(func) {
|
|
239
|
-
if (func instanceof VigorRetrySettings) {
|
|
240
|
-
return this._next({ settings: func._getConfig() });
|
|
241
|
-
}
|
|
242
|
-
if (typeof func === 'function') {
|
|
243
|
-
return this._next({ settings: func(new VigorRetrySettings(this._config.settings))._getConfig() });
|
|
244
|
-
}
|
|
245
|
-
return this._next({ settings: func });
|
|
246
|
-
}
|
|
247
|
-
interceptors(func) {
|
|
248
|
-
if (func instanceof VigorRetryInterceptors) {
|
|
249
|
-
return this._next({ interceptors: func._getConfig() });
|
|
250
|
-
}
|
|
251
|
-
if (typeof func === 'function') {
|
|
252
|
-
return this._next({ interceptors: func(new VigorRetryInterceptors(this._config.interceptors))._getConfig() });
|
|
253
|
-
}
|
|
254
|
-
return this._next({ interceptors: func });
|
|
255
|
-
}
|
|
256
|
-
algorithms(func) {
|
|
257
|
-
const instance = func(this.RetryAlgorithms);
|
|
258
|
-
return this._next({ algorithm: (attempt) => instance._calculateDelay(attempt) });
|
|
259
|
-
}
|
|
260
|
-
abortSignals(...abortSignals) {
|
|
261
|
-
return this._next({ abortSignals: abortSignals.flat() });
|
|
262
|
-
}
|
|
263
|
-
async request(config, timeline = []) {
|
|
264
|
-
const stats = this._mergeConfig(this._config, config);
|
|
265
|
-
let ctx = {
|
|
266
|
-
result: VigorDefault,
|
|
267
|
-
error: VigorDefault,
|
|
268
|
-
attempt: 0,
|
|
269
|
-
delay: 0,
|
|
270
|
-
controller: VigorDefault,
|
|
271
|
-
timeline: timeline,
|
|
272
|
-
stats,
|
|
273
|
-
flag: {
|
|
274
|
-
broke: false,
|
|
275
|
-
overwritten: false,
|
|
276
|
-
restarted: false
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
const addTimeline = this._createTimelineHandler(ctx.timeline);
|
|
280
|
-
const handleInterceptor = this._createInterceptorHandler(ctx, addTimeline);
|
|
281
|
-
addTimeline("PROCESS_HANDLING", {
|
|
282
|
-
type: "REQUEST_START",
|
|
283
|
-
data: {}
|
|
284
|
-
});
|
|
285
|
-
try {
|
|
286
|
-
if (typeof stats.target !== 'function')
|
|
287
|
-
throw new VigorRetryError("INVALID_TARGET", {
|
|
288
|
-
method: "request",
|
|
289
|
-
data: {
|
|
290
|
-
expected: ["function"],
|
|
291
|
-
received: stats.target
|
|
292
|
-
},
|
|
293
|
-
stats: stats,
|
|
294
|
-
context: ctx
|
|
295
|
-
});
|
|
296
|
-
while (ctx.attempt < stats.settings.attempt) {
|
|
297
|
-
ctx.attempt++;
|
|
298
|
-
addTimeline("ATTEMPT_INCREASED", {
|
|
299
|
-
attempt: ctx.attempt
|
|
300
|
-
});
|
|
301
|
-
try {
|
|
302
|
-
addTimeline("PROCESS_HANDLING", {
|
|
303
|
-
type: "RETRY_START",
|
|
304
|
-
data: {}
|
|
305
|
-
});
|
|
306
|
-
const controller = new AbortController();
|
|
307
|
-
const timeoutController = new AbortController();
|
|
308
|
-
const signal = AbortSignal.any([controller.signal, timeoutController.signal, ...stats.abortSignals]);
|
|
309
|
-
await handleInterceptor("before", (interceptorType, func) => ({
|
|
310
|
-
abort: (error) => {
|
|
311
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
312
|
-
interceptorType,
|
|
313
|
-
interceptor: func,
|
|
314
|
-
method: "abort",
|
|
315
|
-
args: [error]
|
|
316
|
-
});
|
|
317
|
-
controller.abort(error);
|
|
318
|
-
throw error;
|
|
319
|
-
},
|
|
320
|
-
breakRetry: (error) => {
|
|
321
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
322
|
-
interceptorType,
|
|
323
|
-
interceptor: func,
|
|
324
|
-
method: "breakRetry",
|
|
325
|
-
args: [error]
|
|
326
|
-
});
|
|
327
|
-
ctx.flag.broke = true;
|
|
328
|
-
throw error;
|
|
329
|
-
},
|
|
330
|
-
throwError: (error) => {
|
|
331
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
332
|
-
interceptorType,
|
|
333
|
-
interceptor: func,
|
|
334
|
-
method: "throwError",
|
|
335
|
-
args: [error]
|
|
336
|
-
});
|
|
337
|
-
throw error;
|
|
338
|
-
}
|
|
339
|
-
}));
|
|
340
|
-
const timeoutTimer = setTimeout(() => {
|
|
341
|
-
clearTimeout(timeoutTimer);
|
|
342
|
-
timeoutController.abort(new VigorRetryError("TIMED_OUT", {
|
|
343
|
-
method: "request",
|
|
344
|
-
data: {
|
|
345
|
-
limit: stats.settings.timeout,
|
|
346
|
-
attempt: ctx.attempt
|
|
347
|
-
},
|
|
348
|
-
}));
|
|
349
|
-
}, stats.settings.timeout);
|
|
350
|
-
signal.throwIfAborted();
|
|
351
|
-
let onAbort;
|
|
352
|
-
try {
|
|
353
|
-
addTimeline("TARGET_REQUEST_STARTED", {
|
|
354
|
-
target: stats.target
|
|
355
|
-
});
|
|
356
|
-
const abort = (error) => {
|
|
357
|
-
addTimeline("TARGET_API_CALLED", {
|
|
358
|
-
target: stats.target,
|
|
359
|
-
method: "abort"
|
|
360
|
-
});
|
|
361
|
-
controller.abort(error);
|
|
362
|
-
throw error;
|
|
363
|
-
};
|
|
364
|
-
const started = performance.now();
|
|
365
|
-
ctx.result = await Promise.race([
|
|
366
|
-
stats.target(ctx, { abort, signal }),
|
|
367
|
-
new Promise((_, rej) => {
|
|
368
|
-
onAbort = () => rej(signal.reason);
|
|
369
|
-
signal.addEventListener("abort", onAbort);
|
|
370
|
-
})
|
|
371
|
-
]);
|
|
372
|
-
const endTime = performance.now();
|
|
373
|
-
addTimeline("TARGET_REQUEST_ENDED", {
|
|
374
|
-
target: stats.target,
|
|
375
|
-
took: endTime - started
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
finally {
|
|
379
|
-
clearTimeout(timeoutTimer);
|
|
380
|
-
if (onAbort)
|
|
381
|
-
signal.removeEventListener("abort", onAbort);
|
|
382
|
-
}
|
|
383
|
-
await handleInterceptor("after", (interceptorType, func) => ({
|
|
384
|
-
setResult: (unknown) => {
|
|
385
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
386
|
-
interceptorType,
|
|
387
|
-
interceptor: func,
|
|
388
|
-
method: "setResult",
|
|
389
|
-
args: [unknown]
|
|
390
|
-
});
|
|
391
|
-
ctx.result = unknown;
|
|
392
|
-
return unknown;
|
|
393
|
-
},
|
|
394
|
-
throwError: (error) => {
|
|
395
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
396
|
-
interceptorType,
|
|
397
|
-
interceptor: func,
|
|
398
|
-
method: "throwError",
|
|
399
|
-
args: [error]
|
|
400
|
-
});
|
|
401
|
-
throw error;
|
|
402
|
-
},
|
|
403
|
-
breakRetry: (error) => {
|
|
404
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
405
|
-
interceptorType,
|
|
406
|
-
interceptor: func,
|
|
407
|
-
method: "breakRetry",
|
|
408
|
-
args: [error]
|
|
409
|
-
});
|
|
410
|
-
ctx.flag.broke = true;
|
|
411
|
-
throw error;
|
|
412
|
-
},
|
|
413
|
-
}));
|
|
414
|
-
await handleInterceptor("result", (interceptorType, func) => ({
|
|
415
|
-
setResult: (unknown) => {
|
|
416
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
417
|
-
interceptorType,
|
|
418
|
-
interceptor: func,
|
|
419
|
-
method: "setResult",
|
|
420
|
-
args: [unknown]
|
|
421
|
-
});
|
|
422
|
-
ctx.result = unknown;
|
|
423
|
-
return unknown;
|
|
424
|
-
},
|
|
425
|
-
throwError: (error) => {
|
|
426
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
427
|
-
interceptorType,
|
|
428
|
-
interceptor: func,
|
|
429
|
-
method: "throwError",
|
|
430
|
-
args: [error]
|
|
431
|
-
});
|
|
432
|
-
throw error;
|
|
433
|
-
},
|
|
434
|
-
}));
|
|
435
|
-
return ctx.result;
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
ctx.error = error;
|
|
439
|
-
addTimeline("PROCESS_HANDLING", {
|
|
440
|
-
type: "RETRY_ERROR",
|
|
441
|
-
data: {
|
|
442
|
-
error
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
if (ctx.flag.broke)
|
|
446
|
-
throw error;
|
|
447
|
-
let proceed = true;
|
|
448
|
-
await handleInterceptor("retryIf", (interceptorType, func) => ({
|
|
449
|
-
proceedRetry: () => {
|
|
450
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
451
|
-
interceptorType,
|
|
452
|
-
interceptor: func,
|
|
453
|
-
method: "proceedRetry",
|
|
454
|
-
args: []
|
|
455
|
-
});
|
|
456
|
-
return proceed = true;
|
|
457
|
-
},
|
|
458
|
-
cancelRetry: () => {
|
|
459
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
460
|
-
interceptorType,
|
|
461
|
-
interceptor: func,
|
|
462
|
-
method: "cancelRetry",
|
|
463
|
-
args: []
|
|
464
|
-
});
|
|
465
|
-
return proceed = false;
|
|
466
|
-
}
|
|
467
|
-
}));
|
|
468
|
-
if (!proceed)
|
|
469
|
-
throw error;
|
|
470
|
-
ctx.delay = VigorDefault;
|
|
471
|
-
await handleInterceptor("onRetry", (interceptorType, func) => ({
|
|
472
|
-
throwError: (error) => {
|
|
473
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
474
|
-
interceptorType,
|
|
475
|
-
interceptor: func,
|
|
476
|
-
method: "throwError",
|
|
477
|
-
args: [error]
|
|
478
|
-
});
|
|
479
|
-
throw error;
|
|
480
|
-
},
|
|
481
|
-
setDelay: (number) => {
|
|
482
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
483
|
-
interceptorType,
|
|
484
|
-
interceptor: func,
|
|
485
|
-
method: "setDelay",
|
|
486
|
-
args: [number]
|
|
487
|
-
});
|
|
488
|
-
return ctx.delay = number;
|
|
489
|
-
},
|
|
490
|
-
setAttempt: (number) => {
|
|
491
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
492
|
-
interceptorType,
|
|
493
|
-
interceptor: func,
|
|
494
|
-
method: "setAttempt",
|
|
495
|
-
args: [number]
|
|
496
|
-
});
|
|
497
|
-
return ctx.attempt = number;
|
|
498
|
-
}
|
|
499
|
-
}));
|
|
500
|
-
if (typeof ctx.delay !== 'number')
|
|
501
|
-
ctx.delay = stats.algorithm(ctx.attempt) + Math.random() * stats.settings.jitter;
|
|
502
|
-
const delay = ctx.delay;
|
|
503
|
-
await new Promise(r => setTimeout(r, delay));
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
throw new VigorRetryError("EXHAUSTED", {
|
|
507
|
-
method: "request",
|
|
508
|
-
data: {
|
|
509
|
-
maxAttempts: stats.settings.attempt,
|
|
510
|
-
},
|
|
511
|
-
context: ctx
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
catch (error) {
|
|
515
|
-
ctx.error = error;
|
|
516
|
-
addTimeline("PROCESS_HANDLING", {
|
|
517
|
-
type: "REQUEST_ERROR",
|
|
518
|
-
data: {
|
|
519
|
-
error
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
await handleInterceptor("onError", (interceptorType, func) => ({
|
|
523
|
-
setResult: (unknown) => {
|
|
524
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
525
|
-
interceptorType,
|
|
526
|
-
interceptor: func,
|
|
527
|
-
method: "setResult",
|
|
528
|
-
args: [unknown]
|
|
529
|
-
});
|
|
530
|
-
ctx.result = unknown;
|
|
531
|
-
ctx.flag.overwritten = true;
|
|
532
|
-
return unknown;
|
|
533
|
-
},
|
|
534
|
-
throwError: (error) => {
|
|
535
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
536
|
-
interceptorType,
|
|
537
|
-
interceptor: func,
|
|
538
|
-
method: "throwError",
|
|
539
|
-
args: [error]
|
|
540
|
-
});
|
|
541
|
-
throw error;
|
|
542
|
-
},
|
|
543
|
-
restart: () => {
|
|
544
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
545
|
-
interceptorType,
|
|
546
|
-
interceptor: func,
|
|
547
|
-
method: "restart",
|
|
548
|
-
args: []
|
|
549
|
-
});
|
|
550
|
-
ctx.flag.restarted = true;
|
|
551
|
-
}
|
|
552
|
-
}));
|
|
553
|
-
if (ctx.flag.restarted) {
|
|
554
|
-
return await this.request(stats, ctx.timeline);
|
|
555
|
-
}
|
|
556
|
-
if (ctx.flag.overwritten)
|
|
557
|
-
return ctx.result;
|
|
558
|
-
if (stats.settings.default !== VigorDefault)
|
|
559
|
-
return stats.settings.default;
|
|
560
|
-
throw error;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
class VigorParseSettings extends VigorStatus {
|
|
565
|
-
constructor(config) {
|
|
566
|
-
const base = {
|
|
567
|
-
raw: false,
|
|
568
|
-
default: VigorDefault
|
|
569
|
-
};
|
|
570
|
-
super(config, base, (c) => new VigorParseSettings(c));
|
|
571
|
-
}
|
|
572
|
-
original(bool) { return this._next({ raw: bool }); }
|
|
573
|
-
default(unk) { return this._next({ default: unk }); }
|
|
574
|
-
}
|
|
575
|
-
class VigorParseStrategies extends VigorStatus {
|
|
576
|
-
constructor(config) {
|
|
577
|
-
const base = {
|
|
578
|
-
funcs: []
|
|
579
|
-
};
|
|
580
|
-
super(config, base, (c) => new VigorParseStrategies(c));
|
|
581
|
-
}
|
|
582
|
-
ParseAutoHeaders = [
|
|
583
|
-
{ header: "application/json", regExp: /application\/(.+\+)?json(.+\+)?/i, method: (res) => res.json() },
|
|
584
|
-
{ header: "application/xml", regExp: /application\/(.+\+)?xml(.+\+)?/i, method: (res) => res.text() },
|
|
585
|
-
{ header: "application/x-www-form-urlencoded", regExp: /application\/(.+\+)?x-www-form-urlencoded(.+\+)?/i, method: (res) => res.formData() },
|
|
586
|
-
{ header: "application/octet-stream", regExp: /application\/(.+\+)?octet-stream(.+\+)?/i, method: (res) => res.arrayBuffer() },
|
|
587
|
-
{ header: "image/*", regExp: /^image\/.+/i, method: (res) => res.blob() },
|
|
588
|
-
{ header: "audio/*", regExp: /^audio\/.+/i, method: (res) => res.blob() },
|
|
589
|
-
{ header: "video/*", regExp: /^video\/.+/i, method: (res) => res.blob() },
|
|
590
|
-
{ header: "multipart/form-data", regExp: /multipart\/(.+\+)?form-data(.+\+)?/i, method: (res) => res.formData() },
|
|
591
|
-
{ header: "text/*", regExp: /^text\/.+/i, method: (res) => res.text() },
|
|
592
|
-
];
|
|
593
|
-
ParseAutoMethods = [
|
|
594
|
-
{ title: "json", method: (res) => res.json() },
|
|
595
|
-
{ title: "formData", method: (res) => res.formData() },
|
|
596
|
-
{ title: "text", method: (res) => res.text() },
|
|
597
|
-
{ title: "blob", method: (res) => res.blob() },
|
|
598
|
-
];
|
|
599
|
-
ParseAutoAlgorithms = {
|
|
600
|
-
contentType: async (response) => {
|
|
601
|
-
const parsers = this.ParseAutoHeaders;
|
|
602
|
-
const contentTypeHeader = response.headers.get("content-type");
|
|
603
|
-
if (!contentTypeHeader)
|
|
604
|
-
throw new VigorParseError("INVALID_CONTENT_TYPE", {
|
|
605
|
-
method: "ParseAutoAlgorithms.contentType",
|
|
606
|
-
data: {
|
|
607
|
-
expected: ["string"],
|
|
608
|
-
received: contentTypeHeader,
|
|
609
|
-
response: response
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
const toDo = parsers.find(parser => parser.regExp.test(contentTypeHeader));
|
|
613
|
-
if (!toDo)
|
|
614
|
-
throw new VigorParseError("PARSER_NOT_FOUND", {
|
|
615
|
-
method: "ParseAutoAlgorithms.contentType",
|
|
616
|
-
data: {
|
|
617
|
-
expected: parsers.map(parser => parser.header),
|
|
618
|
-
received: contentTypeHeader,
|
|
619
|
-
response: response
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
return await toDo.method(response);
|
|
623
|
-
},
|
|
624
|
-
sniff: async (response) => {
|
|
625
|
-
const parsers = this.ParseAutoMethods;
|
|
626
|
-
for (const [i, parser] of parsers.entries()) {
|
|
627
|
-
const cloned = (i === parsers.length - 1)
|
|
628
|
-
? response
|
|
629
|
-
: response.clone();
|
|
630
|
-
try {
|
|
631
|
-
const data = await parser.method(cloned);
|
|
632
|
-
return data;
|
|
633
|
-
}
|
|
634
|
-
catch { }
|
|
635
|
-
}
|
|
636
|
-
throw new VigorParseError("PARSER_ALL_FAILED", {
|
|
637
|
-
method: "ParseAutoAlgorithms.sniff",
|
|
638
|
-
data: {
|
|
639
|
-
tried: parsers.map(parser => parser.title),
|
|
640
|
-
response: response
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
contentType() { return this._next({ funcs: [this.ParseAutoAlgorithms.contentType] }); }
|
|
646
|
-
sniff() { return this._next({ funcs: [this.ParseAutoAlgorithms.sniff] }); }
|
|
647
|
-
json() { return this._next({ funcs: [(res) => res.json()] }); }
|
|
648
|
-
text() { return this._next({ funcs: [(res) => res.text()] }); }
|
|
649
|
-
arrayBuffer() { return this._next({ funcs: [(res) => res.arrayBuffer()] }); }
|
|
650
|
-
blob() { return this._next({ funcs: [(res) => res.blob()] }); }
|
|
651
|
-
bytes() { return this._next({ funcs: [(res) => res.arrayBuffer().then(r => new Uint8Array(r))] }); }
|
|
652
|
-
formData() { return this._next({ funcs: [(res) => res.formData()] }); }
|
|
653
|
-
}
|
|
654
|
-
class VigorParseInterceptors extends VigorStatus {
|
|
655
|
-
constructor(config) {
|
|
656
|
-
const base = {
|
|
657
|
-
before: [],
|
|
658
|
-
after: [],
|
|
659
|
-
result: [],
|
|
660
|
-
onError: []
|
|
661
|
-
};
|
|
662
|
-
super(config, base, (c) => new VigorParseInterceptors(c));
|
|
663
|
-
}
|
|
664
|
-
before(...funcs) { return this._next({ before: funcs.flat() }); }
|
|
665
|
-
after(...funcs) { return this._next({ after: funcs.flat() }); }
|
|
666
|
-
result(...funcs) { return this._next({ result: funcs.flat() }); }
|
|
667
|
-
onError(...funcs) { return this._next({ onError: funcs.flat() }); }
|
|
668
|
-
}
|
|
669
|
-
class VigorParse extends VigorStatus {
|
|
670
|
-
constructor(config) {
|
|
671
|
-
const base = {
|
|
672
|
-
target: VigorDefault,
|
|
673
|
-
settings: new VigorParseSettings()._getBase(),
|
|
674
|
-
strategies: new VigorParseStrategies()._getBase(),
|
|
675
|
-
interceptors: new VigorParseInterceptors()._getBase()
|
|
676
|
-
};
|
|
677
|
-
super(config, base, (c) => new VigorParse(c));
|
|
678
|
-
}
|
|
679
|
-
_createTimelineHandler(timeline) {
|
|
680
|
-
return (action, content) => {
|
|
681
|
-
timeline.push({
|
|
682
|
-
action: action,
|
|
683
|
-
content: content,
|
|
684
|
-
time: Date.now()
|
|
685
|
-
});
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
_createInterceptorHandler(ctx, addTimeline) {
|
|
689
|
-
return async (interceptorType, api) => {
|
|
690
|
-
const interceptorsConfig = ctx["stats"]["interceptors"];
|
|
691
|
-
const interceptors = interceptorsConfig[interceptorType];
|
|
692
|
-
addTimeline("INTERCEPTOR_LOOP_STARTED", {
|
|
693
|
-
interceptorType: interceptorType,
|
|
694
|
-
interceptors,
|
|
695
|
-
});
|
|
696
|
-
const startTime = performance.now();
|
|
697
|
-
for (const func of interceptors) {
|
|
698
|
-
const scopedApi = api(interceptorType, func);
|
|
699
|
-
await func(ctx, scopedApi);
|
|
700
|
-
}
|
|
701
|
-
const endTime = performance.now();
|
|
702
|
-
addTimeline("INTERCEPTOR_LOOP_ENDED", {
|
|
703
|
-
interceptorType: interceptorType,
|
|
704
|
-
interceptors,
|
|
705
|
-
took: endTime - startTime
|
|
706
|
-
});
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
target(response) { return this._next({ target: response }); }
|
|
710
|
-
settings(func) {
|
|
711
|
-
if (func instanceof VigorParseSettings) {
|
|
712
|
-
return this._next({ settings: func._getConfig() });
|
|
713
|
-
}
|
|
714
|
-
if (typeof func === 'function') {
|
|
715
|
-
return this._next({ settings: func(new VigorParseSettings(this._config.settings))._getConfig() });
|
|
716
|
-
}
|
|
717
|
-
return this._next({ settings: func });
|
|
718
|
-
}
|
|
719
|
-
strategies(func) {
|
|
720
|
-
if (func instanceof VigorParseStrategies) {
|
|
721
|
-
return this._next({ strategies: func._getConfig() });
|
|
722
|
-
}
|
|
723
|
-
if (typeof func === 'function') {
|
|
724
|
-
return this._next({ strategies: func(new VigorParseStrategies(this._config.strategies))._getConfig() });
|
|
725
|
-
}
|
|
726
|
-
return this._next({ strategies: func });
|
|
727
|
-
}
|
|
728
|
-
interceptors(func) {
|
|
729
|
-
if (func instanceof VigorParseInterceptors) {
|
|
730
|
-
return this._next({ interceptors: func._getConfig() });
|
|
731
|
-
}
|
|
732
|
-
if (typeof func === 'function') {
|
|
733
|
-
return this._next({ interceptors: func(new VigorParseInterceptors(this._config.interceptors))._getConfig() });
|
|
734
|
-
}
|
|
735
|
-
return this._next({ interceptors: func });
|
|
736
|
-
}
|
|
737
|
-
async request(config, timeline = []) {
|
|
738
|
-
const stats = this._mergeConfig(this._config, config);
|
|
739
|
-
const target = stats.target;
|
|
740
|
-
let ctx = {
|
|
741
|
-
timeline: timeline,
|
|
742
|
-
stats,
|
|
743
|
-
response: target,
|
|
744
|
-
result: VigorDefault,
|
|
745
|
-
error: VigorDefault,
|
|
746
|
-
flag: {
|
|
747
|
-
overwritten: false
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
const addTimeline = this._createTimelineHandler(ctx.timeline);
|
|
751
|
-
const handleInterceptor = this._createInterceptorHandler(ctx, addTimeline);
|
|
752
|
-
addTimeline("PROCESS_HANDLING", {
|
|
753
|
-
type: "REQUEST_START",
|
|
754
|
-
data: {}
|
|
755
|
-
});
|
|
756
|
-
try {
|
|
757
|
-
if (target === VigorDefault)
|
|
758
|
-
throw new VigorParseError("INVALID_TARGET", {
|
|
759
|
-
method: "request",
|
|
760
|
-
data: {
|
|
761
|
-
expected: ["Response"],
|
|
762
|
-
received: target
|
|
763
|
-
},
|
|
764
|
-
context: ctx
|
|
765
|
-
});
|
|
766
|
-
await handleInterceptor("before", (interceptorType, func) => ({
|
|
767
|
-
throwError: (error) => {
|
|
768
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
769
|
-
interceptorType,
|
|
770
|
-
interceptor: func,
|
|
771
|
-
method: "throwError",
|
|
772
|
-
args: [error]
|
|
773
|
-
});
|
|
774
|
-
throw error;
|
|
775
|
-
},
|
|
776
|
-
}));
|
|
777
|
-
if (stats.settings.raw) {
|
|
778
|
-
ctx.result = ctx.response;
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
let parsed = false;
|
|
782
|
-
for (const [i, func] of stats.strategies.funcs.length > 0
|
|
783
|
-
? stats.strategies.funcs.entries()
|
|
784
|
-
: new VigorParseStrategies().contentType()._getConfig().funcs.entries()) {
|
|
785
|
-
const cloned = (i === stats.strategies.funcs.length - 1)
|
|
786
|
-
? ctx.response
|
|
787
|
-
: ctx.response.clone();
|
|
788
|
-
try {
|
|
789
|
-
ctx.result = await func(cloned);
|
|
790
|
-
parsed = true;
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
catch { }
|
|
794
|
-
}
|
|
795
|
-
if (!parsed)
|
|
796
|
-
throw new VigorParseError("PARSER_ALL_FAILED", {
|
|
797
|
-
method: "request",
|
|
798
|
-
data: {
|
|
799
|
-
tried: stats.strategies.funcs,
|
|
800
|
-
response: ctx.response
|
|
801
|
-
},
|
|
802
|
-
context: ctx
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
await handleInterceptor("after", (interceptorType, func) => ({
|
|
806
|
-
setResult: (unknown) => {
|
|
807
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
808
|
-
interceptorType,
|
|
809
|
-
interceptor: func,
|
|
810
|
-
method: "setResult",
|
|
811
|
-
args: [unknown]
|
|
812
|
-
});
|
|
813
|
-
ctx.result = unknown;
|
|
814
|
-
return unknown;
|
|
815
|
-
},
|
|
816
|
-
throwError: (error) => {
|
|
817
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
818
|
-
interceptorType,
|
|
819
|
-
interceptor: func,
|
|
820
|
-
method: "throwError",
|
|
821
|
-
args: [error]
|
|
822
|
-
});
|
|
823
|
-
throw error;
|
|
824
|
-
},
|
|
825
|
-
}));
|
|
826
|
-
await handleInterceptor("result", (interceptorType, func) => ({
|
|
827
|
-
setResult: (unknown) => {
|
|
828
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
829
|
-
interceptorType,
|
|
830
|
-
interceptor: func,
|
|
831
|
-
method: "setResult",
|
|
832
|
-
args: [unknown]
|
|
833
|
-
});
|
|
834
|
-
ctx.result = unknown;
|
|
835
|
-
return unknown;
|
|
836
|
-
},
|
|
837
|
-
throwError: (error) => {
|
|
838
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
839
|
-
interceptorType,
|
|
840
|
-
interceptor: func,
|
|
841
|
-
method: "throwError",
|
|
842
|
-
args: [error]
|
|
843
|
-
});
|
|
844
|
-
throw error;
|
|
845
|
-
},
|
|
846
|
-
}));
|
|
847
|
-
return ctx.result;
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
ctx.error = error;
|
|
851
|
-
addTimeline("PROCESS_HANDLING", {
|
|
852
|
-
type: "REQUEST_ERROR",
|
|
853
|
-
data: {
|
|
854
|
-
error
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
await handleInterceptor("onError", (interceptorType, func) => ({
|
|
858
|
-
setResult: (unknown) => {
|
|
859
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
860
|
-
interceptorType,
|
|
861
|
-
interceptor: func,
|
|
862
|
-
method: "setResult",
|
|
863
|
-
args: [unknown]
|
|
864
|
-
});
|
|
865
|
-
ctx.result = unknown;
|
|
866
|
-
ctx.flag.overwritten = true;
|
|
867
|
-
return unknown;
|
|
868
|
-
},
|
|
869
|
-
throwError: (error) => {
|
|
870
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
871
|
-
interceptorType,
|
|
872
|
-
interceptor: func,
|
|
873
|
-
method: "throwError",
|
|
874
|
-
args: [error]
|
|
875
|
-
});
|
|
876
|
-
throw error;
|
|
877
|
-
},
|
|
878
|
-
}));
|
|
879
|
-
if (ctx.flag.overwritten)
|
|
880
|
-
return ctx.result;
|
|
881
|
-
if (stats.settings.default !== VigorDefault)
|
|
882
|
-
return stats.settings.default;
|
|
883
|
-
throw error;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
class VigorFetchSettings extends VigorStatus {
|
|
888
|
-
constructor(config) {
|
|
889
|
-
const base = {
|
|
890
|
-
retryHeaders: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
|
|
891
|
-
unretryStatus: [400, 401, 403, 404, 405, 413, 422],
|
|
892
|
-
default: VigorDefault
|
|
893
|
-
};
|
|
894
|
-
super(config, base, (c) => new VigorFetchSettings(c));
|
|
895
|
-
}
|
|
896
|
-
retryHeaders(...strs) { return this._next({ retryHeaders: strs.flat() }); }
|
|
897
|
-
unretryStatus(...nums) { return this._next({ unretryStatus: nums.flat() }); }
|
|
898
|
-
default(unk) { return this._next({ default: unk }); }
|
|
899
|
-
}
|
|
900
|
-
class VigorFetchInterceptors extends VigorStatus {
|
|
901
|
-
constructor(config) {
|
|
902
|
-
const base = {
|
|
903
|
-
before: [],
|
|
904
|
-
after: [],
|
|
905
|
-
result: [],
|
|
906
|
-
onError: []
|
|
907
|
-
};
|
|
908
|
-
super(config, base, (c) => new VigorFetchInterceptors(c));
|
|
909
|
-
}
|
|
910
|
-
before(...funcs) { return this._next({ before: funcs.flat() }); }
|
|
911
|
-
after(...funcs) { return this._next({ after: funcs.flat() }); }
|
|
912
|
-
result(...funcs) { return this._next({ result: funcs.flat() }); }
|
|
913
|
-
onError(...funcs) { return this._next({ onError: funcs.flat() }); }
|
|
914
|
-
}
|
|
915
|
-
class VigorFetch extends VigorStatus {
|
|
916
|
-
constructor(config) {
|
|
917
|
-
const base = {
|
|
918
|
-
origin: VigorDefault,
|
|
919
|
-
path: [],
|
|
920
|
-
query: [],
|
|
921
|
-
hash: "",
|
|
922
|
-
options: {
|
|
923
|
-
headers: {},
|
|
924
|
-
body: VigorDefault
|
|
925
|
-
},
|
|
926
|
-
settings: new VigorFetchSettings()._getBase(),
|
|
927
|
-
interceptors: new VigorFetchInterceptors()._getBase(),
|
|
928
|
-
retryConfig: new VigorRetry()._getBase(),
|
|
929
|
-
parseConfig: new VigorParse()._getBase()
|
|
930
|
-
};
|
|
931
|
-
super(config, base, (c) => new VigorFetch(c));
|
|
932
|
-
}
|
|
933
|
-
_createTimelineHandler(timeline) {
|
|
934
|
-
return (action, content) => {
|
|
935
|
-
timeline.push({
|
|
936
|
-
action: action,
|
|
937
|
-
content: content,
|
|
938
|
-
time: Date.now()
|
|
939
|
-
});
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
_createInterceptorHandler(ctx, addTimeline) {
|
|
943
|
-
return async (interceptorType, api) => {
|
|
944
|
-
const interceptorsConfig = ctx["stats"]["interceptors"];
|
|
945
|
-
const interceptors = interceptorsConfig[interceptorType];
|
|
946
|
-
addTimeline("INTERCEPTOR_LOOP_STARTED", {
|
|
947
|
-
interceptorType: interceptorType,
|
|
948
|
-
interceptors,
|
|
949
|
-
});
|
|
950
|
-
const startTime = performance.now();
|
|
951
|
-
for (const func of interceptors) {
|
|
952
|
-
const scopedApi = api(interceptorType, func);
|
|
953
|
-
await func(ctx, scopedApi);
|
|
954
|
-
}
|
|
955
|
-
const endTime = performance.now();
|
|
956
|
-
addTimeline("INTERCEPTOR_LOOP_ENDED", {
|
|
957
|
-
interceptorType: interceptorType,
|
|
958
|
-
interceptors,
|
|
959
|
-
took: endTime - startTime
|
|
960
|
-
});
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
_stringifyList(unkList) {
|
|
964
|
-
return unkList
|
|
965
|
-
.filter(unk => unk !== null && unk !== undefined)
|
|
966
|
-
.map(unk => {
|
|
967
|
-
if (unk instanceof Date)
|
|
968
|
-
return unk.toISOString();
|
|
969
|
-
return String(unk);
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
method(str) { return this._next({ method: str }); }
|
|
973
|
-
origin(str) { return this._next({ origin: str }); }
|
|
974
|
-
path(...strs) { return this._next({ path: this._stringifyList(strs.flat()) }); }
|
|
975
|
-
query(...strs) { return this._next({ query: strs.flat() }); }
|
|
976
|
-
hash(str) { return this._next({ hash: str }); }
|
|
977
|
-
options(obj) { return this._next({ options: obj }); }
|
|
978
|
-
headers(obj) { return this._next({ options: { headers: obj } }); }
|
|
979
|
-
body(obj) { return this._next({ options: { headers: this._config.options.headers, body: obj } }); }
|
|
980
|
-
_buildUrl(origin, path, query, hash) {
|
|
981
|
-
const originObj = new URL(origin);
|
|
982
|
-
const baseStr = originObj.origin;
|
|
983
|
-
const pathObj = [originObj.pathname.replace(/^\/+|\/+$/g, '')];
|
|
984
|
-
for (const str of path) {
|
|
985
|
-
pathObj.push(str.replace(/^\/+|\/+$/g, ''));
|
|
986
|
-
}
|
|
987
|
-
const pathStr = pathObj.join('/');
|
|
988
|
-
const mainObj = new URL(pathStr, baseStr);
|
|
989
|
-
const parseVal = (val) => {
|
|
990
|
-
if (val instanceof Date)
|
|
991
|
-
return val.toISOString();
|
|
992
|
-
return String(val);
|
|
993
|
-
};
|
|
994
|
-
const queryObj = [...Array.from(originObj.searchParams.entries()), ...query.flatMap(qu => Object.entries(qu))];
|
|
995
|
-
for (const [key, val] of queryObj) {
|
|
996
|
-
if (val === undefined || val === null)
|
|
997
|
-
continue;
|
|
998
|
-
if (Array.isArray(val))
|
|
999
|
-
for (const e of val) {
|
|
1000
|
-
mainObj.searchParams.append(key, parseVal(e));
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
mainObj.searchParams.append(key, parseVal(val));
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
mainObj.hash = hash ?? originObj.hash;
|
|
1007
|
-
return mainObj.href;
|
|
1008
|
-
}
|
|
1009
|
-
_normalizeOptions(body) {
|
|
1010
|
-
if (body == null)
|
|
1011
|
-
return { isJson: false, headers: {}, body };
|
|
1012
|
-
if (typeof body === "string")
|
|
1013
|
-
return { isJson: false, headers: {
|
|
1014
|
-
"Content-Type": "text/plain;charset=UTF-8"
|
|
1015
|
-
}, body };
|
|
1016
|
-
if (body instanceof Blob)
|
|
1017
|
-
return { isJson: false, headers: {
|
|
1018
|
-
...(body.type && { "Content-Type": body.type })
|
|
1019
|
-
}, body };
|
|
1020
|
-
if (body instanceof ArrayBuffer)
|
|
1021
|
-
return { isJson: false, headers: {
|
|
1022
|
-
"Content-Type": "application/octet-stream"
|
|
1023
|
-
}, body };
|
|
1024
|
-
if (body instanceof URLSearchParams)
|
|
1025
|
-
return { isJson: false, headers: {
|
|
1026
|
-
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
|
|
1027
|
-
}, body };
|
|
1028
|
-
if (body instanceof FormData)
|
|
1029
|
-
return { isJson: false, headers: {}, body };
|
|
1030
|
-
if (typeof body === "object") {
|
|
1031
|
-
return { isJson: true, headers: {
|
|
1032
|
-
"Content-Type": "application/json"
|
|
1033
|
-
}, body: JSON.stringify(body) };
|
|
1034
|
-
}
|
|
1035
|
-
throw new VigorFetchError("INVALID_BODY", {
|
|
1036
|
-
method: "_normalizeBody",
|
|
1037
|
-
data: {
|
|
1038
|
-
expected: ["string", "Blob", "ArrayBuffer", "URLSearchParams", "FormData"],
|
|
1039
|
-
received: body
|
|
1040
|
-
}
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
settings(func) {
|
|
1044
|
-
if (func instanceof VigorFetchSettings) {
|
|
1045
|
-
return this._next({ settings: func._getConfig() });
|
|
1046
|
-
}
|
|
1047
|
-
if (typeof func === 'function') {
|
|
1048
|
-
return this._next({ settings: func(new VigorFetchSettings(this._config.settings))._getConfig() });
|
|
1049
|
-
}
|
|
1050
|
-
return this._next({ settings: func });
|
|
1051
|
-
}
|
|
1052
|
-
interceptors(func) {
|
|
1053
|
-
if (func instanceof VigorFetchInterceptors) {
|
|
1054
|
-
return this._next({ interceptors: func._getConfig() });
|
|
1055
|
-
}
|
|
1056
|
-
if (typeof func === 'function') {
|
|
1057
|
-
return this._next({ interceptors: func(new VigorFetchInterceptors(this._config.interceptors))._getConfig() });
|
|
1058
|
-
}
|
|
1059
|
-
return this._next({ interceptors: func });
|
|
1060
|
-
}
|
|
1061
|
-
retryConfig(func) {
|
|
1062
|
-
if (func instanceof VigorRetry) {
|
|
1063
|
-
return this._next({ retryConfig: func._getConfig() });
|
|
1064
|
-
}
|
|
1065
|
-
if (typeof func === 'function') {
|
|
1066
|
-
return this._next({ retryConfig: func(new VigorRetry(this._config.retryConfig))._getConfig() });
|
|
1067
|
-
}
|
|
1068
|
-
return this._next({ retryConfig: func });
|
|
1069
|
-
}
|
|
1070
|
-
parseConfig(func) {
|
|
1071
|
-
if (func instanceof VigorParse) {
|
|
1072
|
-
return this._next({ parseConfig: func._getConfig() });
|
|
1073
|
-
}
|
|
1074
|
-
if (typeof func === 'function') {
|
|
1075
|
-
return this._next({ parseConfig: func(new VigorParse(this._config.parseConfig))._getConfig() });
|
|
1076
|
-
}
|
|
1077
|
-
return this._next({ parseConfig: func });
|
|
1078
|
-
}
|
|
1079
|
-
async request(config, timeline = []) {
|
|
1080
|
-
const stats = this._mergeConfig(this._config, config);
|
|
1081
|
-
let ctx = {
|
|
1082
|
-
href: "",
|
|
1083
|
-
result: VigorDefault,
|
|
1084
|
-
response: VigorDefault,
|
|
1085
|
-
options: {
|
|
1086
|
-
headers: VigorDefault,
|
|
1087
|
-
body: VigorDefault
|
|
1088
|
-
},
|
|
1089
|
-
error: VigorDefault,
|
|
1090
|
-
timeline: timeline,
|
|
1091
|
-
stats,
|
|
1092
|
-
flag: {
|
|
1093
|
-
overwritten: false,
|
|
1094
|
-
restarted: false
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
const addTimeline = this._createTimelineHandler(ctx.timeline);
|
|
1098
|
-
const handleInterceptor = this._createInterceptorHandler(ctx, addTimeline);
|
|
1099
|
-
addTimeline("PROCESS_HANDLING", {
|
|
1100
|
-
type: "REQUEST_START",
|
|
1101
|
-
data: {}
|
|
1102
|
-
});
|
|
1103
|
-
try {
|
|
1104
|
-
try {
|
|
1105
|
-
new URL(stats.origin);
|
|
1106
|
-
}
|
|
1107
|
-
catch {
|
|
1108
|
-
throw new VigorFetchError("INVALID_PROTOCOL", {
|
|
1109
|
-
method: "request",
|
|
1110
|
-
data: {
|
|
1111
|
-
expected: ["valid URL protocol"],
|
|
1112
|
-
received: stats.origin
|
|
1113
|
-
}
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
ctx.href = this._buildUrl(stats.origin, stats.path, stats.query, stats.hash);
|
|
1117
|
-
addTimeline("BUILT_URL", {
|
|
1118
|
-
url: ctx.href
|
|
1119
|
-
});
|
|
1120
|
-
const { headers, body, ...others } = stats.options;
|
|
1121
|
-
const hasBody = body !== VigorDefault &&
|
|
1122
|
-
body !== undefined;
|
|
1123
|
-
const method = stats.method || (hasBody ? 'POST' : 'GET');
|
|
1124
|
-
ctx.options = {
|
|
1125
|
-
...others,
|
|
1126
|
-
method: method,
|
|
1127
|
-
headers: {}
|
|
1128
|
-
};
|
|
1129
|
-
if (hasBody) {
|
|
1130
|
-
const normalized = this._normalizeOptions(body);
|
|
1131
|
-
if (normalized.body !== undefined) {
|
|
1132
|
-
ctx.options.body = normalized.body;
|
|
1133
|
-
}
|
|
1134
|
-
Object.assign(ctx.options.headers, normalized.headers);
|
|
1135
|
-
}
|
|
1136
|
-
Object.assign(ctx.options.headers, headers);
|
|
1137
|
-
addTimeline("SET_OPTIONS", {
|
|
1138
|
-
options: ctx.options
|
|
1139
|
-
});
|
|
1140
|
-
const fetchTask = async (ctx2, { abort, signal }) => {
|
|
1141
|
-
ctx.options.signal = signal;
|
|
1142
|
-
const result = await fetch(ctx.href, ctx.options);
|
|
1143
|
-
return result;
|
|
1144
|
-
};
|
|
1145
|
-
const throwStatus = async (ctx2, api) => {
|
|
1146
|
-
const response = ctx2.result;
|
|
1147
|
-
if (!response.ok) {
|
|
1148
|
-
api.throwError(new VigorFetchError("FETCH_FAILED", {
|
|
1149
|
-
method: "request",
|
|
1150
|
-
data: {
|
|
1151
|
-
status: response.status,
|
|
1152
|
-
response: response,
|
|
1153
|
-
url: response.url,
|
|
1154
|
-
headers: response.headers,
|
|
1155
|
-
body: response.body,
|
|
1156
|
-
statusText: response.statusText
|
|
1157
|
-
}
|
|
1158
|
-
}));
|
|
1159
|
-
}
|
|
1160
|
-
};
|
|
1161
|
-
const handleBlacklist = async (ctx2, api) => {
|
|
1162
|
-
const response = ctx2.result;
|
|
1163
|
-
ctx.error = ctx2.error;
|
|
1164
|
-
if (response instanceof Response) {
|
|
1165
|
-
if (stats.settings.unretryStatus.includes(response.status))
|
|
1166
|
-
api.cancelRetry();
|
|
1167
|
-
else
|
|
1168
|
-
api.proceedRetry();
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
|
-
const handleRatelimit = async (ctx2, api) => {
|
|
1172
|
-
const response = ctx2.result;
|
|
1173
|
-
ctx.error = ctx2.error;
|
|
1174
|
-
if (response instanceof Response) {
|
|
1175
|
-
if (response.status === 429) {
|
|
1176
|
-
let retryHeader = null;
|
|
1177
|
-
for (const header of stats.settings.retryHeaders) {
|
|
1178
|
-
retryHeader = response.headers.get(header);
|
|
1179
|
-
if (retryHeader)
|
|
1180
|
-
break;
|
|
1181
|
-
}
|
|
1182
|
-
if (retryHeader) {
|
|
1183
|
-
const toNumber = Number(retryHeader);
|
|
1184
|
-
const delay = !isNaN(toNumber)
|
|
1185
|
-
? toNumber * 1000
|
|
1186
|
-
: (() => {
|
|
1187
|
-
const toDate = new Date(retryHeader).getTime();
|
|
1188
|
-
return !isNaN(toDate)
|
|
1189
|
-
? toDate - Date.now()
|
|
1190
|
-
: null;
|
|
1191
|
-
})();
|
|
1192
|
-
if (delay !== null && delay > 0)
|
|
1193
|
-
api.setDelay(delay + Math.random() * ctx2.stats.settings.jitter);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
stats.retryConfig.interceptors.after = [throwStatus, ...stats.retryConfig.interceptors.after];
|
|
1199
|
-
stats.retryConfig.interceptors.retryIf = [handleBlacklist, ...stats.retryConfig.interceptors.retryIf];
|
|
1200
|
-
stats.retryConfig.interceptors.onRetry = [handleRatelimit, ...stats.retryConfig.interceptors.onRetry];
|
|
1201
|
-
const retryEngine = new VigorRetry(stats.retryConfig)
|
|
1202
|
-
.target(fetchTask);
|
|
1203
|
-
const parseEngine = new VigorParse(stats.parseConfig);
|
|
1204
|
-
addTimeline("ENGINE_CREATED", {
|
|
1205
|
-
retryEngine,
|
|
1206
|
-
parseEngine
|
|
1207
|
-
});
|
|
1208
|
-
await handleInterceptor("before", (interceptorType, func) => ({
|
|
1209
|
-
throwError: (error) => {
|
|
1210
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1211
|
-
interceptorType,
|
|
1212
|
-
interceptor: func,
|
|
1213
|
-
method: "throwError",
|
|
1214
|
-
args: [error]
|
|
1215
|
-
});
|
|
1216
|
-
throw error;
|
|
1217
|
-
},
|
|
1218
|
-
setOptions: (unknown) => {
|
|
1219
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1220
|
-
interceptorType,
|
|
1221
|
-
interceptor: func,
|
|
1222
|
-
method: "setOptions",
|
|
1223
|
-
args: [unknown]
|
|
1224
|
-
});
|
|
1225
|
-
return ctx.options = unknown;
|
|
1226
|
-
},
|
|
1227
|
-
setHeaders: (unknown) => {
|
|
1228
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1229
|
-
interceptorType,
|
|
1230
|
-
interceptor: func,
|
|
1231
|
-
method: "setHeaders",
|
|
1232
|
-
args: [unknown]
|
|
1233
|
-
});
|
|
1234
|
-
return ctx.options.headers = unknown;
|
|
1235
|
-
},
|
|
1236
|
-
setBody: (unknown) => {
|
|
1237
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1238
|
-
interceptorType,
|
|
1239
|
-
interceptor: func,
|
|
1240
|
-
method: "setBody",
|
|
1241
|
-
args: [unknown]
|
|
1242
|
-
});
|
|
1243
|
-
return ctx.options.body = unknown;
|
|
1244
|
-
}
|
|
1245
|
-
}));
|
|
1246
|
-
addTimeline("RETRY_STARTED", {
|
|
1247
|
-
engine: retryEngine
|
|
1248
|
-
});
|
|
1249
|
-
const retryStart = performance.now();
|
|
1250
|
-
const retryTimeline = [];
|
|
1251
|
-
ctx.response = await retryEngine.request(undefined, retryTimeline);
|
|
1252
|
-
const retryEnd = performance.now();
|
|
1253
|
-
addTimeline("RETRY_ENDED", {
|
|
1254
|
-
engine: retryEngine,
|
|
1255
|
-
timeline: retryTimeline,
|
|
1256
|
-
took: retryEnd - retryStart,
|
|
1257
|
-
response: ctx.response
|
|
1258
|
-
});
|
|
1259
|
-
addTimeline("PARSE_STARTED", {
|
|
1260
|
-
engine: parseEngine
|
|
1261
|
-
});
|
|
1262
|
-
const parseStart = performance.now();
|
|
1263
|
-
const parseTimeline = [];
|
|
1264
|
-
ctx.result = await parseEngine.target(ctx.response).request(undefined, parseTimeline);
|
|
1265
|
-
const parseEnd = performance.now();
|
|
1266
|
-
addTimeline("PARSE_ENDED", {
|
|
1267
|
-
engine: parseEngine,
|
|
1268
|
-
timeline: parseTimeline,
|
|
1269
|
-
took: parseEnd - parseStart,
|
|
1270
|
-
result: ctx.result
|
|
1271
|
-
});
|
|
1272
|
-
await handleInterceptor("after", (interceptorType, func) => ({
|
|
1273
|
-
setResult: (unknown) => {
|
|
1274
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1275
|
-
interceptorType,
|
|
1276
|
-
interceptor: func,
|
|
1277
|
-
method: "setResult",
|
|
1278
|
-
args: [unknown]
|
|
1279
|
-
});
|
|
1280
|
-
ctx.result = unknown;
|
|
1281
|
-
return unknown;
|
|
1282
|
-
},
|
|
1283
|
-
throwError: (error) => {
|
|
1284
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1285
|
-
interceptorType,
|
|
1286
|
-
interceptor: func,
|
|
1287
|
-
method: "throwError",
|
|
1288
|
-
args: [error]
|
|
1289
|
-
});
|
|
1290
|
-
throw error;
|
|
1291
|
-
},
|
|
1292
|
-
}));
|
|
1293
|
-
await handleInterceptor("result", (interceptorType, func) => ({
|
|
1294
|
-
setResult: (unknown) => {
|
|
1295
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1296
|
-
interceptorType,
|
|
1297
|
-
interceptor: func,
|
|
1298
|
-
method: "setResult",
|
|
1299
|
-
args: [unknown]
|
|
1300
|
-
});
|
|
1301
|
-
ctx.result = unknown;
|
|
1302
|
-
return unknown;
|
|
1303
|
-
},
|
|
1304
|
-
throwError: (error) => {
|
|
1305
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1306
|
-
interceptorType,
|
|
1307
|
-
interceptor: func,
|
|
1308
|
-
method: "throwError",
|
|
1309
|
-
args: [error]
|
|
1310
|
-
});
|
|
1311
|
-
throw error;
|
|
1312
|
-
},
|
|
1313
|
-
}));
|
|
1314
|
-
return ctx.result;
|
|
1315
|
-
}
|
|
1316
|
-
catch (error) {
|
|
1317
|
-
ctx.error = error;
|
|
1318
|
-
addTimeline("PROCESS_HANDLING", {
|
|
1319
|
-
type: "REQUEST_ERROR",
|
|
1320
|
-
data: {
|
|
1321
|
-
error
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
await handleInterceptor("onError", (interceptorType, func) => ({
|
|
1325
|
-
setResult: (unknown) => {
|
|
1326
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1327
|
-
interceptorType,
|
|
1328
|
-
interceptor: func,
|
|
1329
|
-
method: "setResult",
|
|
1330
|
-
args: [unknown]
|
|
1331
|
-
});
|
|
1332
|
-
ctx.result = unknown;
|
|
1333
|
-
ctx.flag.overwritten = true;
|
|
1334
|
-
return unknown;
|
|
1335
|
-
},
|
|
1336
|
-
throwError: (error) => {
|
|
1337
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1338
|
-
interceptorType,
|
|
1339
|
-
interceptor: func,
|
|
1340
|
-
method: "throwError",
|
|
1341
|
-
args: [error]
|
|
1342
|
-
});
|
|
1343
|
-
throw error;
|
|
1344
|
-
},
|
|
1345
|
-
restart: () => {
|
|
1346
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1347
|
-
interceptorType,
|
|
1348
|
-
interceptor: func,
|
|
1349
|
-
method: "restart",
|
|
1350
|
-
args: []
|
|
1351
|
-
});
|
|
1352
|
-
ctx.flag.restarted = true;
|
|
1353
|
-
}
|
|
1354
|
-
}));
|
|
1355
|
-
if (ctx.flag.restarted) {
|
|
1356
|
-
return await this.request(stats, ctx.timeline);
|
|
1357
|
-
}
|
|
1358
|
-
if (ctx.flag.overwritten)
|
|
1359
|
-
return ctx.result;
|
|
1360
|
-
if (stats.settings.default !== VigorDefault)
|
|
1361
|
-
return stats.settings.default;
|
|
1362
|
-
throw error;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
class VigorAllSettings extends VigorStatus {
|
|
1367
|
-
constructor(config) {
|
|
1368
|
-
const base = {
|
|
1369
|
-
concurrency: 5,
|
|
1370
|
-
onlySuccess: false
|
|
1371
|
-
};
|
|
1372
|
-
super(config, base, (c) => new VigorAllSettings(c));
|
|
1373
|
-
}
|
|
1374
|
-
concurrency(num) { return this._next({ concurrency: num }); }
|
|
1375
|
-
onlySuccess(num) { return this._next({ onlySuccess: num }); }
|
|
1376
|
-
}
|
|
1377
|
-
class VigorAllInterceptors extends VigorStatus {
|
|
1378
|
-
constructor(config) {
|
|
1379
|
-
const base = {
|
|
1380
|
-
before: [],
|
|
1381
|
-
after: [],
|
|
1382
|
-
result: [],
|
|
1383
|
-
onError: []
|
|
1384
|
-
};
|
|
1385
|
-
super(config, base, (c) => new VigorAllInterceptors(c));
|
|
1386
|
-
}
|
|
1387
|
-
before(...funcs) { return this._next({ before: funcs.flat() }); }
|
|
1388
|
-
after(...funcs) { return this._next({ after: funcs.flat() }); }
|
|
1389
|
-
result(...funcs) { return this._next({ result: funcs.flat() }); }
|
|
1390
|
-
onError(...funcs) { return this._next({ onError: funcs.flat() }); }
|
|
1391
|
-
}
|
|
1392
|
-
class VigorAll extends VigorStatus {
|
|
1393
|
-
constructor(config) {
|
|
1394
|
-
const base = {
|
|
1395
|
-
target: [],
|
|
1396
|
-
settings: new VigorAllSettings()._getBase(),
|
|
1397
|
-
interceptors: new VigorAllInterceptors()._getBase()
|
|
1398
|
-
};
|
|
1399
|
-
super(config, base, (c) => new VigorAll(c));
|
|
1400
|
-
}
|
|
1401
|
-
_createTimelineHandler(timeline) {
|
|
1402
|
-
return (action, content) => {
|
|
1403
|
-
timeline.push({
|
|
1404
|
-
action: action,
|
|
1405
|
-
content: content,
|
|
1406
|
-
time: Date.now()
|
|
1407
|
-
});
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
_createInterceptorHandler(ctx, addTimeline) {
|
|
1411
|
-
return async (interceptorType, api) => {
|
|
1412
|
-
const interceptorsConfig = ctx["stats"]["interceptors"];
|
|
1413
|
-
const interceptors = interceptorsConfig[interceptorType];
|
|
1414
|
-
addTimeline("INTERCEPTOR_LOOP_STARTED", {
|
|
1415
|
-
interceptorType: interceptorType,
|
|
1416
|
-
interceptors,
|
|
1417
|
-
});
|
|
1418
|
-
const startTime = performance.now();
|
|
1419
|
-
for (const func of interceptors) {
|
|
1420
|
-
const scopedApi = api(interceptorType, func);
|
|
1421
|
-
await func(ctx, scopedApi);
|
|
1422
|
-
}
|
|
1423
|
-
const endTime = performance.now();
|
|
1424
|
-
addTimeline("INTERCEPTOR_LOOP_ENDED", {
|
|
1425
|
-
interceptorType: interceptorType,
|
|
1426
|
-
interceptors,
|
|
1427
|
-
took: endTime - startTime
|
|
1428
|
-
});
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
_createEachTimelineHandler(timeline) {
|
|
1432
|
-
return (action, content) => {
|
|
1433
|
-
timeline.push({
|
|
1434
|
-
action: action,
|
|
1435
|
-
content: content,
|
|
1436
|
-
time: Date.now()
|
|
1437
|
-
});
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
_createEachInterceptorHandler(ctx, addEachTimeline) {
|
|
1441
|
-
return async (interceptorType, api) => {
|
|
1442
|
-
const interceptorsConfig = ctx["stats"]["interceptors"];
|
|
1443
|
-
const interceptors = interceptorsConfig[interceptorType];
|
|
1444
|
-
addEachTimeline("INTERCEPTOR_LOOP_STARTED", {
|
|
1445
|
-
interceptorType: interceptorType,
|
|
1446
|
-
interceptors,
|
|
1447
|
-
});
|
|
1448
|
-
const startTime = performance.now();
|
|
1449
|
-
for (const func of interceptors) {
|
|
1450
|
-
const scopedApi = api(interceptorType, func);
|
|
1451
|
-
await func(ctx, scopedApi);
|
|
1452
|
-
}
|
|
1453
|
-
const endTime = performance.now();
|
|
1454
|
-
addEachTimeline("INTERCEPTOR_LOOP_ENDED", {
|
|
1455
|
-
interceptorType: interceptorType,
|
|
1456
|
-
interceptors,
|
|
1457
|
-
took: endTime - startTime
|
|
1458
|
-
});
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
target(...funcs) { return this._next({ target: funcs.flat() }); }
|
|
1462
|
-
settings(func) {
|
|
1463
|
-
if (func instanceof VigorAllSettings) {
|
|
1464
|
-
return this._next({ settings: func._getConfig() });
|
|
1465
|
-
}
|
|
1466
|
-
if (typeof func === 'function') {
|
|
1467
|
-
return this._next({ settings: func(new VigorAllSettings(this._config.settings))._getConfig() });
|
|
1468
|
-
}
|
|
1469
|
-
return this._next({ settings: func });
|
|
1470
|
-
}
|
|
1471
|
-
interceptors(func) {
|
|
1472
|
-
if (func instanceof VigorAllInterceptors) {
|
|
1473
|
-
return this._next({ interceptors: func._getConfig() });
|
|
1474
|
-
}
|
|
1475
|
-
if (typeof func === 'function') {
|
|
1476
|
-
return this._next({ interceptors: func(new VigorAllInterceptors(this._config.interceptors))._getConfig() });
|
|
1477
|
-
}
|
|
1478
|
-
return this._next({ interceptors: func });
|
|
1479
|
-
}
|
|
1480
|
-
async runTask(task, { stats, root }, semaphore) {
|
|
1481
|
-
let ctx = {
|
|
1482
|
-
result: VigorDefault,
|
|
1483
|
-
error: VigorDefault,
|
|
1484
|
-
timeline: [],
|
|
1485
|
-
stats,
|
|
1486
|
-
root,
|
|
1487
|
-
target: task,
|
|
1488
|
-
semaphore,
|
|
1489
|
-
flag: {
|
|
1490
|
-
overwritten: false
|
|
1491
|
-
}
|
|
1492
|
-
};
|
|
1493
|
-
const addEachTimeline = this._createEachTimelineHandler(ctx.timeline);
|
|
1494
|
-
const handleEachInterceptor = this._createEachInterceptorHandler(ctx, addEachTimeline);
|
|
1495
|
-
addEachTimeline("PROCESS_HANDLING", {
|
|
1496
|
-
type: "TASK_START",
|
|
1497
|
-
data: {}
|
|
1498
|
-
});
|
|
1499
|
-
try {
|
|
1500
|
-
try {
|
|
1501
|
-
await semaphore.acquire();
|
|
1502
|
-
addEachTimeline("TASK_ACQUIRED", {
|
|
1503
|
-
target: ctx.target
|
|
1504
|
-
});
|
|
1505
|
-
await handleEachInterceptor("before", (interceptorType, func) => ({
|
|
1506
|
-
throwError: (error) => {
|
|
1507
|
-
addEachTimeline("INTERCEPTOR_API_CALLED", {
|
|
1508
|
-
interceptorType,
|
|
1509
|
-
interceptor: func,
|
|
1510
|
-
method: "throwError",
|
|
1511
|
-
args: [error]
|
|
1512
|
-
});
|
|
1513
|
-
throw error;
|
|
1514
|
-
}
|
|
1515
|
-
}));
|
|
1516
|
-
addEachTimeline("TASK_STARTED", {
|
|
1517
|
-
target: ctx.target
|
|
1518
|
-
});
|
|
1519
|
-
const startTime = performance.now();
|
|
1520
|
-
ctx.result = await ctx.target(ctx);
|
|
1521
|
-
const endTime = performance.now();
|
|
1522
|
-
addEachTimeline("TASK_ENDED", {
|
|
1523
|
-
target: ctx.target,
|
|
1524
|
-
took: endTime - startTime
|
|
1525
|
-
});
|
|
1526
|
-
await handleEachInterceptor("after", (interceptorType, func) => ({
|
|
1527
|
-
setResult: (unknown) => {
|
|
1528
|
-
addEachTimeline("INTERCEPTOR_API_CALLED", {
|
|
1529
|
-
interceptorType,
|
|
1530
|
-
interceptor: func,
|
|
1531
|
-
method: "setResult",
|
|
1532
|
-
args: [unknown]
|
|
1533
|
-
});
|
|
1534
|
-
ctx.result = unknown;
|
|
1535
|
-
return unknown;
|
|
1536
|
-
},
|
|
1537
|
-
throwError: (error) => {
|
|
1538
|
-
addEachTimeline("INTERCEPTOR_API_CALLED", {
|
|
1539
|
-
interceptorType,
|
|
1540
|
-
interceptor: func,
|
|
1541
|
-
method: "throwError",
|
|
1542
|
-
args: [error]
|
|
1543
|
-
});
|
|
1544
|
-
throw error;
|
|
1545
|
-
}
|
|
1546
|
-
}));
|
|
1547
|
-
}
|
|
1548
|
-
finally {
|
|
1549
|
-
semaphore.release();
|
|
1550
|
-
addEachTimeline("TASK_RELEASED", {
|
|
1551
|
-
target: ctx.target
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
catch (error) {
|
|
1556
|
-
ctx.error = error;
|
|
1557
|
-
addEachTimeline("PROCESS_HANDLING", {
|
|
1558
|
-
type: "TASK_ERROR",
|
|
1559
|
-
data: {
|
|
1560
|
-
error
|
|
1561
|
-
}
|
|
1562
|
-
});
|
|
1563
|
-
await handleEachInterceptor("onError", (interceptorType, func) => ({
|
|
1564
|
-
setResult: (unknown) => {
|
|
1565
|
-
addEachTimeline("INTERCEPTOR_API_CALLED", {
|
|
1566
|
-
interceptorType,
|
|
1567
|
-
interceptor: func,
|
|
1568
|
-
method: "setResult",
|
|
1569
|
-
args: [unknown]
|
|
1570
|
-
});
|
|
1571
|
-
ctx.result = unknown;
|
|
1572
|
-
ctx.flag.overwritten = true;
|
|
1573
|
-
return unknown;
|
|
1574
|
-
},
|
|
1575
|
-
throwError: (error) => {
|
|
1576
|
-
addEachTimeline("INTERCEPTOR_API_CALLED", {
|
|
1577
|
-
interceptorType,
|
|
1578
|
-
interceptor: func,
|
|
1579
|
-
method: "throwError",
|
|
1580
|
-
args: [error]
|
|
1581
|
-
});
|
|
1582
|
-
throw error;
|
|
1583
|
-
},
|
|
1584
|
-
}));
|
|
1585
|
-
if (ctx.flag.overwritten)
|
|
1586
|
-
return ctx.result;
|
|
1587
|
-
throw error;
|
|
1588
|
-
}
|
|
1589
|
-
return ctx.result;
|
|
1590
|
-
}
|
|
1591
|
-
async request(config, timeline = []) {
|
|
1592
|
-
const stats = this._mergeConfig(this._config, config);
|
|
1593
|
-
let ctx = {
|
|
1594
|
-
result: VigorDefault,
|
|
1595
|
-
timeline,
|
|
1596
|
-
stats,
|
|
1597
|
-
queue: new Set(),
|
|
1598
|
-
active: 0
|
|
1599
|
-
};
|
|
1600
|
-
const addTimeline = this._createTimelineHandler(ctx.timeline);
|
|
1601
|
-
const handleInterceptor = this._createInterceptorHandler(ctx, addTimeline);
|
|
1602
|
-
addTimeline("PROCESS_HANDLING", {
|
|
1603
|
-
type: "REQUEST_START",
|
|
1604
|
-
data: {}
|
|
1605
|
-
});
|
|
1606
|
-
if (stats.target.length === 0)
|
|
1607
|
-
throw new VigorAllError("EMPTY_TARGET", {
|
|
1608
|
-
method: "request",
|
|
1609
|
-
data: {}
|
|
1610
|
-
});
|
|
1611
|
-
const waitQueue = [];
|
|
1612
|
-
const acquire = () => {
|
|
1613
|
-
if (ctx.active < stats.settings.concurrency) {
|
|
1614
|
-
ctx.active++;
|
|
1615
|
-
return Promise.resolve();
|
|
1616
|
-
}
|
|
1617
|
-
return new Promise((res) => waitQueue.push(() => { ctx.active++; res(); }));
|
|
1618
|
-
};
|
|
1619
|
-
const release = () => {
|
|
1620
|
-
ctx.active--;
|
|
1621
|
-
if (waitQueue.length > 0) {
|
|
1622
|
-
const next = waitQueue.shift();
|
|
1623
|
-
if (next)
|
|
1624
|
-
next();
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
for (const task of stats.target) {
|
|
1628
|
-
let promise;
|
|
1629
|
-
promise = this.runTask(task, { stats, root: ctx }, { acquire, release })
|
|
1630
|
-
.then(res => ({ success: true, value: res }))
|
|
1631
|
-
.catch(err => ({ success: false, value: err }))
|
|
1632
|
-
.finally(() => ctx.queue.delete(promise));
|
|
1633
|
-
ctx.queue.add(promise);
|
|
1634
|
-
}
|
|
1635
|
-
addTimeline("QUEUE_REQUEST_STARTED", {
|
|
1636
|
-
queue: ctx.queue
|
|
1637
|
-
});
|
|
1638
|
-
const startTime = performance.now();
|
|
1639
|
-
const raw = await Promise.all(ctx.queue);
|
|
1640
|
-
const endTime = performance.now();
|
|
1641
|
-
addTimeline("QUEUE_REQUEST_ENDED", {
|
|
1642
|
-
queue: ctx.queue,
|
|
1643
|
-
took: endTime - startTime
|
|
1644
|
-
});
|
|
1645
|
-
ctx.result = stats.settings.onlySuccess
|
|
1646
|
-
? raw.filter(r => r.success).map(r => r.value)
|
|
1647
|
-
: raw.map(r => r.value);
|
|
1648
|
-
await handleInterceptor("result", (interceptorType, func) => ({
|
|
1649
|
-
setResult: (unknown) => {
|
|
1650
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1651
|
-
interceptorType,
|
|
1652
|
-
interceptor: func,
|
|
1653
|
-
method: "setResult",
|
|
1654
|
-
args: [unknown]
|
|
1655
|
-
});
|
|
1656
|
-
ctx.result = unknown;
|
|
1657
|
-
return unknown;
|
|
1658
|
-
},
|
|
1659
|
-
throwError: (error) => {
|
|
1660
|
-
addTimeline("INTERCEPTOR_API_CALLED", {
|
|
1661
|
-
interceptorType,
|
|
1662
|
-
interceptor: func,
|
|
1663
|
-
method: "throwError",
|
|
1664
|
-
args: [error]
|
|
1665
|
-
});
|
|
1666
|
-
throw error;
|
|
1667
|
-
},
|
|
1668
|
-
}));
|
|
1669
|
-
return ctx.result;
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
const VigorEntry = {
|
|
1673
|
-
retry: {
|
|
1674
|
-
main: VigorRetry,
|
|
1675
|
-
settings: VigorRetrySettings,
|
|
1676
|
-
interceptors: VigorRetryInterceptors,
|
|
1677
|
-
error: VigorRetryError,
|
|
1678
|
-
algorithms: {
|
|
1679
|
-
constant: VigorRetryAlgorithmsConstant,
|
|
1680
|
-
linear: VigorRetryAlgorithmsLinear,
|
|
1681
|
-
backoff: VigorRetryAlgorithmsBackoff,
|
|
1682
|
-
custom: VigorRetryAlgorithmsCustom
|
|
1683
|
-
}
|
|
1684
|
-
},
|
|
1685
|
-
parse: {
|
|
1686
|
-
main: VigorParse,
|
|
1687
|
-
settings: VigorParseSettings,
|
|
1688
|
-
interceptors: VigorParseInterceptors,
|
|
1689
|
-
error: VigorParseError,
|
|
1690
|
-
strategies: VigorParseStrategies
|
|
1691
|
-
},
|
|
1692
|
-
fetch: {
|
|
1693
|
-
main: VigorFetch,
|
|
1694
|
-
settings: VigorFetchSettings,
|
|
1695
|
-
interceptors: VigorFetchInterceptors,
|
|
1696
|
-
error: VigorFetchError,
|
|
1697
|
-
},
|
|
1698
|
-
all: {
|
|
1699
|
-
main: VigorAll,
|
|
1700
|
-
settings: VigorAllSettings,
|
|
1701
|
-
interceptors: VigorAllInterceptors,
|
|
1702
|
-
error: VigorAllError
|
|
1703
|
-
}
|
|
1704
|
-
};
|
|
1705
|
-
const vigor = {
|
|
1706
|
-
use: async (func, config) => {
|
|
1707
|
-
return await func(VigorEntry, config);
|
|
1708
|
-
},
|
|
1709
|
-
fetch: (str) => {
|
|
1710
|
-
return new VigorFetch().origin(str);
|
|
1711
|
-
},
|
|
1712
|
-
retry: (target) => {
|
|
1713
|
-
return new VigorRetry().target(target);
|
|
1714
|
-
},
|
|
1715
|
-
parse: (response) => {
|
|
1716
|
-
return new VigorParse().target(response);
|
|
1717
|
-
},
|
|
1718
|
-
all: (...funcs) => {
|
|
1719
|
-
return new VigorAll().target(...funcs);
|
|
1720
|
-
},
|
|
1721
|
-
builder: {
|
|
1722
|
-
fetch: {
|
|
1723
|
-
settings: (c) => new VigorFetchSettings(c),
|
|
1724
|
-
interceptors: (c) => new VigorFetchInterceptors(c),
|
|
1725
|
-
},
|
|
1726
|
-
retry: {
|
|
1727
|
-
settings: (c) => new VigorRetrySettings(c),
|
|
1728
|
-
interceptors: (c) => new VigorRetryInterceptors(c),
|
|
1729
|
-
},
|
|
1730
|
-
parse: {
|
|
1731
|
-
settings: (c) => new VigorParseSettings(c),
|
|
1732
|
-
interceptors: (c) => new VigorParseInterceptors(c),
|
|
1733
|
-
},
|
|
1734
|
-
all: {
|
|
1735
|
-
settings: (c) => new VigorAllSettings(c),
|
|
1736
|
-
interceptors: (c) => new VigorAllInterceptors(c),
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
3
|
+
var vigorFetch = require('vigor-fetch');
|
|
1740
4
|
|
|
1741
|
-
// ----------------------------------------------------------------
|
|
1742
|
-
// Error system
|
|
1743
|
-
// ----------------------------------------------------------------
|
|
1744
5
|
const RobloxErrorMessageFuncs = {
|
|
1745
6
|
AUTH_FAILED: ({ status, cookie }) => `Cookie authentication failed (status: ${status ?? 'unknown'}, cookie: ${cookie.slice(0, 8)}...)`,
|
|
1746
7
|
RATE_LIMITED: ({ status, url, retryAfterMs }) => `Rate limited (status: ${status}, url: ${url ?? 'unknown'}, retryAfter: ${retryAfterMs ?? 'unknown'}ms)`,
|
|
@@ -1751,7 +12,6 @@ class RobloxApiError extends Error {
|
|
|
1751
12
|
cause;
|
|
1752
13
|
code;
|
|
1753
14
|
data;
|
|
1754
|
-
timeline;
|
|
1755
15
|
context;
|
|
1756
16
|
constructor(code, options) {
|
|
1757
17
|
const messageFn = RobloxErrorMessageFuncs[code];
|
|
@@ -1760,7 +20,6 @@ class RobloxApiError extends Error {
|
|
|
1760
20
|
this.code = code;
|
|
1761
21
|
this.cause = options.cause;
|
|
1762
22
|
this.data = options.data;
|
|
1763
|
-
this.timeline = options.timeline ?? [];
|
|
1764
23
|
this.context = options.context;
|
|
1765
24
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
1766
25
|
Error.captureStackTrace?.(this, new.target);
|
|
@@ -1782,14 +41,7 @@ class RobloxRequestError extends RobloxApiError {
|
|
|
1782
41
|
}
|
|
1783
42
|
}
|
|
1784
43
|
function isFetchFailed(cause) {
|
|
1785
|
-
return cause instanceof VigorFetchError && cause.code === 'FETCH_FAILED' && cause.data != null;
|
|
1786
|
-
}
|
|
1787
|
-
function extractTimeline(cause) {
|
|
1788
|
-
if (cause instanceof VigorFetchError)
|
|
1789
|
-
return (cause.context?.timeline ?? []);
|
|
1790
|
-
if (cause instanceof VigorRetryError)
|
|
1791
|
-
return (cause.context?.timeline ?? []);
|
|
1792
|
-
return [];
|
|
44
|
+
return cause instanceof vigorFetch.VigorFetchError && cause.code === 'FETCH_FAILED' && cause.data != null;
|
|
1793
45
|
}
|
|
1794
46
|
function extractStatus(cause) {
|
|
1795
47
|
return isFetchFailed(cause) ? cause.data.status : null;
|
|
@@ -1816,14 +68,12 @@ function extractRetryAfterMs(cause) {
|
|
|
1816
68
|
function wrapVigorError(cause) {
|
|
1817
69
|
const status = extractStatus(cause);
|
|
1818
70
|
const url = extractUrl(cause);
|
|
1819
|
-
const timeline = extractTimeline(cause);
|
|
1820
71
|
if (status === 429)
|
|
1821
72
|
return new RobloxRateLimitError({
|
|
1822
73
|
data: { status, url, retryAfterMs: extractRetryAfterMs(cause) },
|
|
1823
|
-
timeline,
|
|
1824
74
|
cause,
|
|
1825
75
|
});
|
|
1826
|
-
return new RobloxRequestError({ data: { status, url },
|
|
76
|
+
return new RobloxRequestError({ data: { status, url }, cause });
|
|
1827
77
|
}
|
|
1828
78
|
function chunk(arr, size) {
|
|
1829
79
|
const out = [];
|
|
@@ -1837,81 +87,147 @@ function partition(arr, pred) {
|
|
|
1837
87
|
(pred(item) ? pass : fail).push(item);
|
|
1838
88
|
return { pass, fail };
|
|
1839
89
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
90
|
+
class CsrfTokenManager {
|
|
91
|
+
tokenMap = new Map();
|
|
92
|
+
pendingMap = new Map();
|
|
93
|
+
get(cookie) {
|
|
94
|
+
return this.tokenMap.get(cookie) ?? null;
|
|
95
|
+
}
|
|
96
|
+
set(cookie, token) {
|
|
97
|
+
this.tokenMap.set(cookie, token);
|
|
98
|
+
}
|
|
99
|
+
invalidate(cookie) {
|
|
100
|
+
this.tokenMap.delete(cookie);
|
|
101
|
+
}
|
|
102
|
+
async refresh(cookie) {
|
|
103
|
+
const existing = this.pendingMap.get(cookie);
|
|
104
|
+
if (existing)
|
|
105
|
+
return existing;
|
|
106
|
+
const pending = (async () => {
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch('https://auth.roblox.com/v2/logout', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Cookie': `.ROBLOSECURITY=${cookie}`,
|
|
112
|
+
'User-Agent': 'Roblox/WinInet',
|
|
113
|
+
'Content-Length': '0',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const token = response.headers.get('x-csrf-token');
|
|
117
|
+
if (!token)
|
|
118
|
+
throw new Error('CSRF token not found in response headers');
|
|
119
|
+
this.tokenMap.set(cookie, token);
|
|
120
|
+
return token;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
this.pendingMap.delete(cookie);
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
this.pendingMap.set(cookie, pending);
|
|
127
|
+
return pending;
|
|
128
|
+
}
|
|
129
|
+
async getOrRefresh(cookie) {
|
|
130
|
+
const cached = this.get(cookie);
|
|
131
|
+
if (cached)
|
|
132
|
+
return cached;
|
|
133
|
+
return this.refresh(cookie);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
1843
136
|
function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
1844
137
|
const cookiePool = cookiesList.map(cookie => ({ cookie, lastUsed: 0 }));
|
|
138
|
+
const csrfManager = new CsrfTokenManager();
|
|
1845
139
|
function pickCookie() {
|
|
1846
140
|
const entry = cookiePool.reduce((a, b) => a.lastUsed < b.lastUsed ? a : b);
|
|
1847
141
|
entry.lastUsed = Date.now();
|
|
1848
142
|
return entry.cookie;
|
|
1849
143
|
}
|
|
144
|
+
function makeHeaderMiddlewares(opts) {
|
|
145
|
+
const { getCookie, winInet = false, csrf = false } = opts;
|
|
146
|
+
let builder = vigorFetch.vigor.builders.fetch.middlewares()
|
|
147
|
+
.before("intercept", async (ctx, api) => {
|
|
148
|
+
const cookie = getCookie();
|
|
149
|
+
ctx.record.cookie = cookie;
|
|
150
|
+
const headers = {
|
|
151
|
+
Cookie: `.ROBLOSECURITY=${cookie}`,
|
|
152
|
+
};
|
|
153
|
+
if (winInet)
|
|
154
|
+
headers['User-Agent'] = 'Roblox/WinInet';
|
|
155
|
+
if (csrf)
|
|
156
|
+
headers['X-CSRF-Token'] = await csrfManager.getOrRefresh(cookie);
|
|
157
|
+
api.setHeaders(headers);
|
|
158
|
+
return ctx;
|
|
159
|
+
});
|
|
160
|
+
if (csrf) {
|
|
161
|
+
builder = builder.onError("intercept", async (ctx, api) => {
|
|
162
|
+
const cause = ctx.error;
|
|
163
|
+
if (isFetchFailed(cause) && cause.data.status === 403) {
|
|
164
|
+
const cookie = ctx.record.cookie ?? getCookie();
|
|
165
|
+
const newToken = cause.data.response.headers.get('x-csrf-token');
|
|
166
|
+
if (newToken) {
|
|
167
|
+
csrfManager.set(cookie, newToken);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
csrfManager.invalidate(cookie);
|
|
171
|
+
await csrfManager.refresh(cookie);
|
|
172
|
+
}
|
|
173
|
+
api.proceedRestart();
|
|
174
|
+
}
|
|
175
|
+
return ctx;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return builder;
|
|
179
|
+
}
|
|
180
|
+
/** Reusable `after` middleware that unwraps a `{ data: T }`-shaped response envelope. */
|
|
1850
181
|
function pickKey(key) {
|
|
1851
|
-
return vigor.
|
|
1852
|
-
.
|
|
182
|
+
return vigorFetch.vigor.builders.fetch.middlewares()
|
|
183
|
+
.after("intercept", async (ctx, api) => {
|
|
1853
184
|
api.setResult(ctx.result[key]);
|
|
185
|
+
return ctx;
|
|
1854
186
|
});
|
|
1855
187
|
}
|
|
1856
188
|
const dataInterceptor = pickKey('data');
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
});
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
.
|
|
1873
|
-
.
|
|
1874
|
-
.
|
|
1875
|
-
|
|
1876
|
-
.
|
|
1877
|
-
|
|
1878
|
-
.
|
|
1879
|
-
.
|
|
1880
|
-
|
|
1881
|
-
.
|
|
1882
|
-
.
|
|
1883
|
-
|
|
1884
|
-
.
|
|
1885
|
-
|
|
1886
|
-
.
|
|
1887
|
-
.
|
|
1888
|
-
|
|
1889
|
-
.
|
|
1890
|
-
|
|
1891
|
-
.
|
|
1892
|
-
.
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
.
|
|
1896
|
-
.
|
|
1897
|
-
.
|
|
1898
|
-
|
|
1899
|
-
.interceptors(cookieInterceptor)
|
|
1900
|
-
.retryConfig(c => c
|
|
1901
|
-
.settings(s => s.attempt(7))
|
|
1902
|
-
.algorithms(a => a.backoff().initial(500).multiplier(1.5)));
|
|
1903
|
-
const ipgeolocationApi = vigor.fetch('https://api.ipgeolocation.io')
|
|
1904
|
-
.retryConfig(c => c
|
|
1905
|
-
.settings(s => s.attempt(4))
|
|
1906
|
-
.algorithms(a => a.backoff().initial(500).multiplier(2)));
|
|
1907
|
-
// friends.roblox.com — used for friend list lookups, sending friend
|
|
1908
|
-
// requests, and unfriending. Same cookie + WinInet headers as usersApi.
|
|
1909
|
-
const friendsApi = vigor.fetch('https://friends.roblox.com/v1')
|
|
1910
|
-
.interceptors(cookieInterceptor)
|
|
1911
|
-
.interceptors(winInetInterceptor)
|
|
1912
|
-
.retryConfig(c => c
|
|
1913
|
-
.settings(s => s.attempt(5))
|
|
1914
|
-
.algorithms(a => a.backoff().initial(500).multiplier(2)));
|
|
189
|
+
const poolCookieMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie });
|
|
190
|
+
const poolCookieWinInetMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie, winInet: true });
|
|
191
|
+
const poolCookieCsrfMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie, winInet: true, csrf: true });
|
|
192
|
+
const usersApi = vigorFetch.vigor.fetch('https://users.roblox.com/v1')
|
|
193
|
+
.middlewares(poolCookieWinInetMiddlewares)
|
|
194
|
+
.retry(r => r
|
|
195
|
+
.settings(s => s.maxAttempts(7))
|
|
196
|
+
.algorithms(a => a.backoff({ initial: 200, unit: 800, multiplier: 1.7 })));
|
|
197
|
+
const thumbnailsApi = vigorFetch.vigor.fetch('https://thumbnails.roblox.com/v1')
|
|
198
|
+
.middlewares(poolCookieWinInetMiddlewares)
|
|
199
|
+
.retry(r => r
|
|
200
|
+
.settings(s => s.maxAttempts(5))
|
|
201
|
+
.algorithms(a => a.backoff({ initial: 1000, multiplier: 2.5 })));
|
|
202
|
+
const gamesApi = vigorFetch.vigor.fetch('https://games.roblox.com/v1')
|
|
203
|
+
.middlewares(poolCookieMiddlewares)
|
|
204
|
+
.retry(r => r
|
|
205
|
+
.settings(s => s.maxAttempts(5))
|
|
206
|
+
.algorithms(a => a.backoff({ initial: 1000, multiplier: 2.5 })));
|
|
207
|
+
const presenceApi = vigorFetch.vigor.fetch('https://presence.roblox.com/v1')
|
|
208
|
+
.middlewares(poolCookieMiddlewares)
|
|
209
|
+
.retry(r => r
|
|
210
|
+
.settings(s => s.maxAttempts(5))
|
|
211
|
+
.algorithms(a => a.backoff({ initial: 500, multiplier: 2 })));
|
|
212
|
+
const apisRoblox = vigorFetch.vigor.fetch('https://apis.roblox.com')
|
|
213
|
+
.middlewares(poolCookieMiddlewares)
|
|
214
|
+
.retry(r => r
|
|
215
|
+
.settings(s => s.maxAttempts(5))
|
|
216
|
+
.algorithms(a => a.backoff({ initial: 1000, multiplier: 2 })));
|
|
217
|
+
const gamejoinApi = vigorFetch.vigor.fetch('https://gamejoin.roblox.com/v1')
|
|
218
|
+
.middlewares(poolCookieMiddlewares)
|
|
219
|
+
.retry(r => r
|
|
220
|
+
.settings(s => s.maxAttempts(7))
|
|
221
|
+
.algorithms(a => a.backoff({ initial: 500, multiplier: 1.5 })));
|
|
222
|
+
const ipgeolocationApi = vigorFetch.vigor.fetch('https://api.ipgeolocation.io')
|
|
223
|
+
.retry(r => r
|
|
224
|
+
.settings(s => s.maxAttempts(4))
|
|
225
|
+
.algorithms(a => a.backoff({ initial: 500, multiplier: 2 })));
|
|
226
|
+
const friendsApi = vigorFetch.vigor.fetch('https://friends.roblox.com/v1')
|
|
227
|
+
.middlewares(poolCookieCsrfMiddlewares)
|
|
228
|
+
.retry(r => r
|
|
229
|
+
.settings(s => s.maxAttempts(5))
|
|
230
|
+
.algorithms(a => a.backoff({ initial: 500, multiplier: 2 })));
|
|
1915
231
|
async function withCache(opts) {
|
|
1916
232
|
const { type, keys, ttlMs, getKey, fetchMissing, fallback } = opts;
|
|
1917
233
|
const cached = await cache.select(type, keys);
|
|
@@ -1925,15 +241,8 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
1925
241
|
return keys.map(k => cacheMap.get(k) ?? fallback);
|
|
1926
242
|
}
|
|
1927
243
|
async function authenticated(cookies) {
|
|
1928
|
-
|
|
1929
|
-
const
|
|
1930
|
-
.before((ctx, api) => {
|
|
1931
|
-
api.setHeaders({
|
|
1932
|
-
...ctx.options.headers,
|
|
1933
|
-
Cookie: `.ROBLOSECURITY=${cookie}`,
|
|
1934
|
-
});
|
|
1935
|
-
});
|
|
1936
|
-
const base = usersApi.interceptors(fixedCookieInterceptor);
|
|
244
|
+
const results = await vigorFetch.vigor.all(...cookies.map(cookie => async () => {
|
|
245
|
+
const base = usersApi.middlewares(makeHeaderMiddlewares({ getCookie: () => cookie, winInet: true }));
|
|
1937
246
|
const [user, description, birthdate, gender, ageBracket, countryCode, roles] = await Promise.allSettled([
|
|
1938
247
|
base.path('users', 'authenticated').request(),
|
|
1939
248
|
base.path('description').request(),
|
|
@@ -1946,14 +255,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
1946
255
|
if (user.status === 'rejected') {
|
|
1947
256
|
const cause = user.reason;
|
|
1948
257
|
const status = extractStatus(cause);
|
|
1949
|
-
const timeline = extractTimeline(cause);
|
|
1950
258
|
if (status === 429)
|
|
1951
259
|
throw new RobloxRateLimitError({
|
|
1952
260
|
data: { status, url: extractUrl(cause), retryAfterMs: extractRetryAfterMs(cause) },
|
|
1953
|
-
timeline,
|
|
1954
261
|
cause,
|
|
1955
262
|
});
|
|
1956
|
-
throw new RobloxAuthError({ data: { status, cookie },
|
|
263
|
+
throw new RobloxAuthError({ data: { status, cookie }, cause });
|
|
1957
264
|
}
|
|
1958
265
|
return {
|
|
1959
266
|
...user.value,
|
|
@@ -1965,6 +272,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
1965
272
|
...(roles.status === 'fulfilled' ? roles.value : {}),
|
|
1966
273
|
};
|
|
1967
274
|
})).request();
|
|
275
|
+
return results;
|
|
1968
276
|
}
|
|
1969
277
|
async function usersSimple(userIds) {
|
|
1970
278
|
return withCache({
|
|
@@ -1975,16 +283,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
1975
283
|
fallback: {},
|
|
1976
284
|
fetchMissing: async (missing) => {
|
|
1977
285
|
try {
|
|
1978
|
-
const
|
|
286
|
+
const grouped = await vigorFetch.vigor.all(...chunk(missing.map(Number), 100).map(group => () => usersApi
|
|
1979
287
|
.path('users')
|
|
1980
|
-
.body({ userIds: group, excludeBannedUsers: false })
|
|
1981
|
-
.
|
|
1982
|
-
.request()))
|
|
1983
|
-
|
|
1984
|
-
.result((ctx, api) => {
|
|
1985
|
-
api.setResult(ctx.result.flat());
|
|
1986
|
-
}))
|
|
1987
|
-
.request();
|
|
288
|
+
.body("overwrite", { userIds: group, excludeBannedUsers: false })
|
|
289
|
+
.middlewares(dataInterceptor)
|
|
290
|
+
.request())).request();
|
|
291
|
+
const results = grouped.flat();
|
|
1988
292
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null);
|
|
1989
293
|
}
|
|
1990
294
|
catch (cause) {
|
|
@@ -2002,7 +306,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2002
306
|
fallback: {},
|
|
2003
307
|
fetchMissing: async (missing) => {
|
|
2004
308
|
try {
|
|
2005
|
-
const results = await vigor.all(...missing.map(id => () => usersApi.path('users', id).request()))
|
|
309
|
+
const results = await vigorFetch.vigor.all(...missing.map(id => () => usersApi.path('users', id).request()))
|
|
2006
310
|
.settings(s => s.concurrency(2))
|
|
2007
311
|
.request();
|
|
2008
312
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null && u.description != null);
|
|
@@ -2022,16 +326,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2022
326
|
fallback: {},
|
|
2023
327
|
fetchMissing: async (missing) => {
|
|
2024
328
|
try {
|
|
2025
|
-
const
|
|
329
|
+
const grouped = await vigorFetch.vigor.all(...chunk(missing, 100).map(group => () => usersApi
|
|
2026
330
|
.path('usernames', 'users')
|
|
2027
|
-
.body({ usernames: group, excludeBannedUsers: false })
|
|
2028
|
-
.
|
|
2029
|
-
.request()))
|
|
2030
|
-
|
|
2031
|
-
.result((ctx, api) => {
|
|
2032
|
-
api.setResult(ctx.result.flat());
|
|
2033
|
-
}))
|
|
2034
|
-
.request();
|
|
331
|
+
.body("overwrite", { usernames: group, excludeBannedUsers: false })
|
|
332
|
+
.middlewares(dataInterceptor)
|
|
333
|
+
.request())).request();
|
|
334
|
+
const results = grouped.flat();
|
|
2035
335
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null);
|
|
2036
336
|
}
|
|
2037
337
|
catch (cause) {
|
|
@@ -2042,16 +342,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2042
342
|
}
|
|
2043
343
|
async function presence(userIds) {
|
|
2044
344
|
try {
|
|
2045
|
-
|
|
345
|
+
const grouped = await vigorFetch.vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
|
|
2046
346
|
.path('presence', 'users')
|
|
2047
|
-
.body({ userIds: group })
|
|
2048
|
-
.
|
|
2049
|
-
.request()))
|
|
2050
|
-
|
|
2051
|
-
.result((ctx, api) => {
|
|
2052
|
-
api.setResult(ctx.result.flat());
|
|
2053
|
-
}))
|
|
2054
|
-
.request();
|
|
347
|
+
.body("overwrite", { userIds: group })
|
|
348
|
+
.middlewares(pickKey('userPresences'))
|
|
349
|
+
.request())).request();
|
|
350
|
+
return grouped.flat();
|
|
2055
351
|
}
|
|
2056
352
|
catch (cause) {
|
|
2057
353
|
throw wrapVigorError(cause);
|
|
@@ -2060,16 +356,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2060
356
|
async function thumbnailAssets(opts) {
|
|
2061
357
|
const { assetIds, size = '150x150', format = 'Png' } = opts;
|
|
2062
358
|
try {
|
|
2063
|
-
const
|
|
359
|
+
const grouped = await vigorFetch.vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
|
|
2064
360
|
.path('assets')
|
|
2065
361
|
.query({ assetIds: group.join(','), size, format })
|
|
2066
|
-
.
|
|
2067
|
-
.request()))
|
|
2068
|
-
|
|
2069
|
-
.result((ctx, api) => {
|
|
2070
|
-
api.setResult(ctx.result.flat());
|
|
2071
|
-
}))
|
|
2072
|
-
.request();
|
|
362
|
+
.middlewares(dataInterceptor)
|
|
363
|
+
.request())).request();
|
|
364
|
+
const results = grouped.flat();
|
|
2073
365
|
return results.map(t => ({ ...t, url: t.state === 'Completed' ? t.imageUrl : null }));
|
|
2074
366
|
}
|
|
2075
367
|
catch (cause) {
|
|
@@ -2087,16 +379,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2087
379
|
const batch = targets.map((t, i) => ({ ...defaults, ...t, requestId: String(i) }));
|
|
2088
380
|
const batchMap = new Map(batch.map(t => [t.requestId, t]));
|
|
2089
381
|
try {
|
|
2090
|
-
const
|
|
382
|
+
const grouped = await vigorFetch.vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
|
|
2091
383
|
.path('batch')
|
|
2092
|
-
.body(group)
|
|
2093
|
-
.
|
|
2094
|
-
.request()))
|
|
2095
|
-
|
|
2096
|
-
.result((ctx, api) => {
|
|
2097
|
-
api.setResult(ctx.result.flat());
|
|
2098
|
-
}))
|
|
2099
|
-
.request();
|
|
384
|
+
.body("overwrite", group)
|
|
385
|
+
.middlewares(dataInterceptor)
|
|
386
|
+
.request())).request();
|
|
387
|
+
const results = grouped.flat();
|
|
2100
388
|
return results.map(item => {
|
|
2101
389
|
const original = batchMap.get(item.requestId) ?? {};
|
|
2102
390
|
const { requestId: _rid, ...rest } = item;
|
|
@@ -2168,20 +456,21 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2168
456
|
fallback: {},
|
|
2169
457
|
fetchMissing: async (missing) => {
|
|
2170
458
|
try {
|
|
2171
|
-
const universeEntries = await vigor.all(...missing.map(placeId => () => apisRoblox
|
|
459
|
+
const universeEntries = await vigorFetch.vigor.all(...missing.map(placeId => () => apisRoblox
|
|
2172
460
|
.path('universes', 'v1', 'places', placeId, 'universe')
|
|
2173
|
-
.
|
|
2174
|
-
.
|
|
461
|
+
.middlewares(vigorFetch.vigor.builders.fetch.middlewares()
|
|
462
|
+
.after("intercept", async (ctx, api) => {
|
|
2175
463
|
const r = ctx.result;
|
|
2176
464
|
api.setResult({ placeId: Number(placeId), universeId: r?.universeId ?? null });
|
|
465
|
+
return ctx;
|
|
2177
466
|
}))
|
|
2178
467
|
.request())).request();
|
|
2179
|
-
const metaList = await vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
|
|
468
|
+
const metaList = await vigorFetch.vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
|
|
2180
469
|
if (!universeId)
|
|
2181
470
|
return { placeId, universeId: null, info: null, assetIds: [] };
|
|
2182
471
|
const [details, media] = await Promise.all([
|
|
2183
|
-
gamesApi.path('games').query({ universeIds: universeId }).
|
|
2184
|
-
gamesApi.path('games', universeId, 'media').
|
|
472
|
+
gamesApi.path('games').query({ universeIds: universeId }).middlewares(dataInterceptor).request(),
|
|
473
|
+
gamesApi.path('games', universeId, 'media').middlewares(dataInterceptor).request(),
|
|
2185
474
|
]);
|
|
2186
475
|
return {
|
|
2187
476
|
placeId,
|
|
@@ -2296,7 +585,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2296
585
|
try {
|
|
2297
586
|
const res = await gamejoinApi
|
|
2298
587
|
.path('join-game-instance')
|
|
2299
|
-
.body({ placeId, gameId: jobId })
|
|
588
|
+
.body("overwrite", { placeId, gameId: jobId })
|
|
2300
589
|
.request();
|
|
2301
590
|
return {
|
|
2302
591
|
publicIp: res?.joinScript?.UdmuxEndpoint?.[0]?.Address ?? null,
|
|
@@ -2341,7 +630,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2341
630
|
const missJobIds = jobIds.filter(id => !jobHitMap.has(id));
|
|
2342
631
|
if (missJobIds.length === 0)
|
|
2343
632
|
return jobIds.map(id => jobHitMap.get(id));
|
|
2344
|
-
const extracted = await vigor.all(...missJobIds.map(jobId => async () => {
|
|
633
|
+
const extracted = await vigorFetch.vigor.all(...missJobIds.map(jobId => async () => {
|
|
2345
634
|
const { publicIp, machineAddress } = await extractIps(placeId, jobId);
|
|
2346
635
|
return { jobId, publicIp, machineAddress };
|
|
2347
636
|
}))
|
|
@@ -2364,7 +653,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2364
653
|
const ipHitMap = new Map(cachedByIp.map(({ separator, data }) => [separator, data]));
|
|
2365
654
|
const stillMissIps = missPublicIps.filter(ip => !ipHitMap.has(ip));
|
|
2366
655
|
if (stillMissIps.length > 0) {
|
|
2367
|
-
const fetched = await vigor.all(...stillMissIps.map(ip => async () => ({ ip, loc: await fetchIpLocation(ip) })))
|
|
656
|
+
const fetched = await vigorFetch.vigor.all(...stillMissIps.map(ip => async () => ({ ip, loc: await fetchIpLocation(ip) })))
|
|
2368
657
|
.settings(s => s.concurrency(5))
|
|
2369
658
|
.request();
|
|
2370
659
|
const toUpsertIp = fetched.filter((e) => e.loc !== null);
|
|
@@ -2406,14 +695,11 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2406
695
|
]);
|
|
2407
696
|
return jobIds.flatMap(id => { const loc = resultMap.get(id); return loc ? [loc] : []; });
|
|
2408
697
|
}
|
|
2409
|
-
// ----------------------------------------------------------------
|
|
2410
|
-
// Friends
|
|
2411
|
-
// ----------------------------------------------------------------
|
|
2412
698
|
async function friends(userId) {
|
|
2413
699
|
try {
|
|
2414
700
|
return await friendsApi
|
|
2415
701
|
.path('users', userId, 'friends')
|
|
2416
|
-
.
|
|
702
|
+
.middlewares(dataInterceptor)
|
|
2417
703
|
.request();
|
|
2418
704
|
}
|
|
2419
705
|
catch (cause) {
|
|
@@ -2424,7 +710,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2424
710
|
try {
|
|
2425
711
|
await friendsApi
|
|
2426
712
|
.path('users', targetUserId, 'request-friendship')
|
|
2427
|
-
.body({})
|
|
713
|
+
.body("overwrite", {})
|
|
2428
714
|
.request();
|
|
2429
715
|
}
|
|
2430
716
|
catch (cause) {
|
|
@@ -2435,7 +721,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2435
721
|
try {
|
|
2436
722
|
await friendsApi
|
|
2437
723
|
.path('users', targetUserId, 'unfriend')
|
|
2438
|
-
.body({})
|
|
724
|
+
.body("overwrite", {})
|
|
2439
725
|
.request();
|
|
2440
726
|
}
|
|
2441
727
|
catch (cause) {
|