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