vigor-roblox 1.0.4 → 2.0.1
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 +127 -1961
- package/dist/index.mjs +113 -1947
- package/package.json +7 -5
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,49 +87,24 @@ function partition(arr, pred) {
|
|
|
1837
87
|
(pred(item) ? pass : fail).push(item);
|
|
1838
88
|
return { pass, fail };
|
|
1839
89
|
}
|
|
1840
|
-
// ----------------------------------------------------------------
|
|
1841
|
-
// CSRF Token Manager
|
|
1842
|
-
// ----------------------------------------------------------------
|
|
1843
90
|
class CsrfTokenManager {
|
|
1844
|
-
// cookie → token 매핑 (쿠키별로 독립 토큰 관리)
|
|
1845
91
|
tokenMap = new Map();
|
|
1846
|
-
// 쿠키별 진행 중인 갱신 Promise (중복 갱신 방지)
|
|
1847
92
|
pendingMap = new Map();
|
|
1848
|
-
/**
|
|
1849
|
-
* 저장된 토큰을 반환합니다. 없으면 null.
|
|
1850
|
-
*/
|
|
1851
93
|
get(cookie) {
|
|
1852
94
|
return this.tokenMap.get(cookie) ?? null;
|
|
1853
95
|
}
|
|
1854
|
-
/**
|
|
1855
|
-
* 토큰을 수동으로 저장합니다.
|
|
1856
|
-
* (403 응답 헤더의 x-csrf-token 값을 받아서 저장할 때 사용)
|
|
1857
|
-
*/
|
|
1858
96
|
set(cookie, token) {
|
|
1859
97
|
this.tokenMap.set(cookie, token);
|
|
1860
98
|
}
|
|
1861
|
-
/**
|
|
1862
|
-
* 토큰을 무효화합니다.
|
|
1863
|
-
* (재시도 전 갱신이 필요할 때 호출)
|
|
1864
|
-
*/
|
|
1865
99
|
invalidate(cookie) {
|
|
1866
100
|
this.tokenMap.delete(cookie);
|
|
1867
101
|
}
|
|
1868
|
-
/**
|
|
1869
|
-
* Roblox의 CSRF 토큰 갱신 방식:
|
|
1870
|
-
* POST /v1/logout 등 아무 인증 엔드포인트에 빈 body로 요청하면
|
|
1871
|
-
* 403 응답과 함께 x-csrf-token 헤더로 새 토큰을 내려줍니다.
|
|
1872
|
-
*
|
|
1873
|
-
* 동일 쿠키에 대해 동시에 여러 갱신 요청이 오면
|
|
1874
|
-
* 하나만 실제로 요청하고 나머지는 그 결과를 공유합니다.
|
|
1875
|
-
*/
|
|
1876
102
|
async refresh(cookie) {
|
|
1877
103
|
const existing = this.pendingMap.get(cookie);
|
|
1878
104
|
if (existing)
|
|
1879
105
|
return existing;
|
|
1880
106
|
const pending = (async () => {
|
|
1881
107
|
try {
|
|
1882
|
-
// Roblox는 POST에 body가 없어도 403 + x-csrf-token을 내려줌
|
|
1883
108
|
const response = await fetch('https://auth.roblox.com/v2/logout', {
|
|
1884
109
|
method: 'POST',
|
|
1885
110
|
headers: {
|
|
@@ -1901,9 +126,6 @@ class CsrfTokenManager {
|
|
|
1901
126
|
this.pendingMap.set(cookie, pending);
|
|
1902
127
|
return pending;
|
|
1903
128
|
}
|
|
1904
|
-
/**
|
|
1905
|
-
* 저장된 토큰을 반환하되, 없으면 자동으로 갱신 후 반환합니다.
|
|
1906
|
-
*/
|
|
1907
129
|
async getOrRefresh(cookie) {
|
|
1908
130
|
const cached = this.get(cookie);
|
|
1909
131
|
if (cached)
|
|
@@ -1911,9 +133,6 @@ class CsrfTokenManager {
|
|
|
1911
133
|
return this.refresh(cookie);
|
|
1912
134
|
}
|
|
1913
135
|
}
|
|
1914
|
-
// ----------------------------------------------------------------
|
|
1915
|
-
// Factory
|
|
1916
|
-
// ----------------------------------------------------------------
|
|
1917
136
|
function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
1918
137
|
const cookiePool = cookiesList.map(cookie => ({ cookie, lastUsed: 0 }));
|
|
1919
138
|
const csrfManager = new CsrfTokenManager();
|
|
@@ -1922,116 +141,93 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
1922
141
|
entry.lastUsed = Date.now();
|
|
1923
142
|
return entry.cookie;
|
|
1924
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. */
|
|
1925
181
|
function pickKey(key) {
|
|
1926
|
-
return vigor.
|
|
1927
|
-
.
|
|
182
|
+
return vigorFetch.vigor.builders.fetch.middlewares()
|
|
183
|
+
.after("intercept", async (ctx, api) => {
|
|
1928
184
|
api.setResult(ctx.result[key]);
|
|
185
|
+
return ctx;
|
|
1929
186
|
});
|
|
1930
187
|
}
|
|
1931
188
|
const dataInterceptor = pickKey('data');
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
})
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
.before((ctx, api) => {
|
|
1975
|
-
api.setHeaders({
|
|
1976
|
-
...ctx.options.headers,
|
|
1977
|
-
Cookie: `.ROBLOSECURITY=${pickCookie()}`,
|
|
1978
|
-
});
|
|
1979
|
-
});
|
|
1980
|
-
const winInetInterceptor = vigor.builder.fetch.interceptors()
|
|
1981
|
-
.before((ctx, api) => {
|
|
1982
|
-
api.setHeaders({
|
|
1983
|
-
...ctx.options.headers,
|
|
1984
|
-
'User-Agent': 'Roblox/WinInet',
|
|
1985
|
-
});
|
|
1986
|
-
});
|
|
1987
|
-
// CSRF가 필요한 POST 엔드포인트에 붙일 공용 인터셉터
|
|
1988
|
-
// pickCookie()로 현재 선택된 쿠키를 기준으로 토큰 관리
|
|
1989
|
-
const csrfInterceptor = makeCsrfInterceptor(pickCookie);
|
|
1990
|
-
const usersApi = vigor.fetch('https://users.roblox.com/v1')
|
|
1991
|
-
.interceptors(cookieInterceptor)
|
|
1992
|
-
.interceptors(winInetInterceptor)
|
|
1993
|
-
.retryConfig(c => c
|
|
1994
|
-
.settings(s => s.attempt(7))
|
|
1995
|
-
.algorithms(a => a.backoff().initial(200).unit(800).multiplier(1.7)));
|
|
1996
|
-
const thumbnailsApi = vigor.fetch('https://thumbnails.roblox.com/v1')
|
|
1997
|
-
.interceptors(cookieInterceptor)
|
|
1998
|
-
.interceptors(winInetInterceptor)
|
|
1999
|
-
.retryConfig(c => c
|
|
2000
|
-
.settings(s => s.attempt(5))
|
|
2001
|
-
.algorithms(a => a.backoff().initial(1000).multiplier(2.5)));
|
|
2002
|
-
const gamesApi = vigor.fetch('https://games.roblox.com/v1')
|
|
2003
|
-
.interceptors(cookieInterceptor)
|
|
2004
|
-
.retryConfig(c => c
|
|
2005
|
-
.settings(s => s.attempt(5))
|
|
2006
|
-
.algorithms(a => a.backoff().initial(1000).multiplier(2.5)));
|
|
2007
|
-
const presenceApi = vigor.fetch('https://presence.roblox.com/v1')
|
|
2008
|
-
.interceptors(cookieInterceptor)
|
|
2009
|
-
.retryConfig(c => c
|
|
2010
|
-
.settings(s => s.attempt(5))
|
|
2011
|
-
.algorithms(a => a.backoff().initial(500).multiplier(2)));
|
|
2012
|
-
const apisRoblox = vigor.fetch('https://apis.roblox.com')
|
|
2013
|
-
.interceptors(cookieInterceptor)
|
|
2014
|
-
.retryConfig(c => c
|
|
2015
|
-
.settings(s => s.attempt(5))
|
|
2016
|
-
.algorithms(a => a.backoff().initial(1000).multiplier(2)));
|
|
2017
|
-
const gamejoinApi = vigor.fetch('https://gamejoin.roblox.com/v1')
|
|
2018
|
-
.interceptors(cookieInterceptor)
|
|
2019
|
-
.retryConfig(c => c
|
|
2020
|
-
.settings(s => s.attempt(7))
|
|
2021
|
-
.algorithms(a => a.backoff().initial(500).multiplier(1.5)));
|
|
2022
|
-
const ipgeolocationApi = vigor.fetch('https://api.ipgeolocation.io')
|
|
2023
|
-
.retryConfig(c => c
|
|
2024
|
-
.settings(s => s.attempt(4))
|
|
2025
|
-
.algorithms(a => a.backoff().initial(500).multiplier(2)));
|
|
2026
|
-
// friends.roblox.com — CSRF 인터셉터 포함
|
|
2027
|
-
// sendFriendRequest / unfriend 는 POST라 CSRF 토큰 필요
|
|
2028
|
-
const friendsApi = vigor.fetch('https://friends.roblox.com/v1')
|
|
2029
|
-
.interceptors(cookieInterceptor)
|
|
2030
|
-
.interceptors(winInetInterceptor)
|
|
2031
|
-
.interceptors(csrfInterceptor) // ← CSRF 추가
|
|
2032
|
-
.retryConfig(c => c
|
|
2033
|
-
.settings(s => s.attempt(5))
|
|
2034
|
-
.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 })));
|
|
2035
231
|
async function withCache(opts) {
|
|
2036
232
|
const { type, keys, ttlMs, getKey, fetchMissing, fallback } = opts;
|
|
2037
233
|
const cached = await cache.select(type, keys);
|
|
@@ -2045,15 +241,8 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2045
241
|
return keys.map(k => cacheMap.get(k) ?? fallback);
|
|
2046
242
|
}
|
|
2047
243
|
async function authenticated(cookies) {
|
|
2048
|
-
|
|
2049
|
-
const
|
|
2050
|
-
.before((ctx, api) => {
|
|
2051
|
-
api.setHeaders({
|
|
2052
|
-
...ctx.options.headers,
|
|
2053
|
-
Cookie: `.ROBLOSECURITY=${cookie}`,
|
|
2054
|
-
});
|
|
2055
|
-
});
|
|
2056
|
-
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 }));
|
|
2057
246
|
const [user, description, birthdate, gender, ageBracket, countryCode, roles] = await Promise.allSettled([
|
|
2058
247
|
base.path('users', 'authenticated').request(),
|
|
2059
248
|
base.path('description').request(),
|
|
@@ -2066,14 +255,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2066
255
|
if (user.status === 'rejected') {
|
|
2067
256
|
const cause = user.reason;
|
|
2068
257
|
const status = extractStatus(cause);
|
|
2069
|
-
const timeline = extractTimeline(cause);
|
|
2070
258
|
if (status === 429)
|
|
2071
259
|
throw new RobloxRateLimitError({
|
|
2072
260
|
data: { status, url: extractUrl(cause), retryAfterMs: extractRetryAfterMs(cause) },
|
|
2073
|
-
timeline,
|
|
2074
261
|
cause,
|
|
2075
262
|
});
|
|
2076
|
-
throw new RobloxAuthError({ data: { status, cookie },
|
|
263
|
+
throw new RobloxAuthError({ data: { status, cookie }, cause });
|
|
2077
264
|
}
|
|
2078
265
|
return {
|
|
2079
266
|
...user.value,
|
|
@@ -2085,6 +272,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2085
272
|
...(roles.status === 'fulfilled' ? roles.value : {}),
|
|
2086
273
|
};
|
|
2087
274
|
})).request();
|
|
275
|
+
return results;
|
|
2088
276
|
}
|
|
2089
277
|
async function usersSimple(userIds) {
|
|
2090
278
|
return withCache({
|
|
@@ -2095,16 +283,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2095
283
|
fallback: {},
|
|
2096
284
|
fetchMissing: async (missing) => {
|
|
2097
285
|
try {
|
|
2098
|
-
const
|
|
286
|
+
const grouped = await vigorFetch.vigor.all(...chunk(missing.map(Number), 100).map(group => () => usersApi
|
|
2099
287
|
.path('users')
|
|
2100
|
-
.body({ userIds: group, excludeBannedUsers: false })
|
|
2101
|
-
.
|
|
2102
|
-
.request()))
|
|
2103
|
-
|
|
2104
|
-
.result((ctx, api) => {
|
|
2105
|
-
api.setResult(ctx.result.flat());
|
|
2106
|
-
}))
|
|
2107
|
-
.request();
|
|
288
|
+
.body("overwrite", { userIds: group, excludeBannedUsers: false })
|
|
289
|
+
.middlewares(dataInterceptor)
|
|
290
|
+
.request())).request();
|
|
291
|
+
const results = grouped.flat();
|
|
2108
292
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null);
|
|
2109
293
|
}
|
|
2110
294
|
catch (cause) {
|
|
@@ -2122,7 +306,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2122
306
|
fallback: {},
|
|
2123
307
|
fetchMissing: async (missing) => {
|
|
2124
308
|
try {
|
|
2125
|
-
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()))
|
|
2126
310
|
.settings(s => s.concurrency(2))
|
|
2127
311
|
.request();
|
|
2128
312
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null && u.description != null);
|
|
@@ -2142,16 +326,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2142
326
|
fallback: {},
|
|
2143
327
|
fetchMissing: async (missing) => {
|
|
2144
328
|
try {
|
|
2145
|
-
const
|
|
329
|
+
const grouped = await vigorFetch.vigor.all(...chunk(missing, 100).map(group => () => usersApi
|
|
2146
330
|
.path('usernames', 'users')
|
|
2147
|
-
.body({ usernames: group, excludeBannedUsers: false })
|
|
2148
|
-
.
|
|
2149
|
-
.request()))
|
|
2150
|
-
|
|
2151
|
-
.result((ctx, api) => {
|
|
2152
|
-
api.setResult(ctx.result.flat());
|
|
2153
|
-
}))
|
|
2154
|
-
.request();
|
|
331
|
+
.body("overwrite", { usernames: group, excludeBannedUsers: false })
|
|
332
|
+
.middlewares(dataInterceptor)
|
|
333
|
+
.request())).request();
|
|
334
|
+
const results = grouped.flat();
|
|
2155
335
|
return results.filter(u => u.id != null && u.name != null && u.displayName != null);
|
|
2156
336
|
}
|
|
2157
337
|
catch (cause) {
|
|
@@ -2162,16 +342,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2162
342
|
}
|
|
2163
343
|
async function presence(userIds) {
|
|
2164
344
|
try {
|
|
2165
|
-
|
|
345
|
+
const grouped = await vigorFetch.vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
|
|
2166
346
|
.path('presence', 'users')
|
|
2167
|
-
.body({ userIds: group })
|
|
2168
|
-
.
|
|
2169
|
-
.request()))
|
|
2170
|
-
|
|
2171
|
-
.result((ctx, api) => {
|
|
2172
|
-
api.setResult(ctx.result.flat());
|
|
2173
|
-
}))
|
|
2174
|
-
.request();
|
|
347
|
+
.body("overwrite", { userIds: group })
|
|
348
|
+
.middlewares(pickKey('userPresences'))
|
|
349
|
+
.request())).request();
|
|
350
|
+
return grouped.flat();
|
|
2175
351
|
}
|
|
2176
352
|
catch (cause) {
|
|
2177
353
|
throw wrapVigorError(cause);
|
|
@@ -2180,16 +356,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2180
356
|
async function thumbnailAssets(opts) {
|
|
2181
357
|
const { assetIds, size = '150x150', format = 'Png' } = opts;
|
|
2182
358
|
try {
|
|
2183
|
-
const
|
|
359
|
+
const grouped = await vigorFetch.vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
|
|
2184
360
|
.path('assets')
|
|
2185
361
|
.query({ assetIds: group.join(','), size, format })
|
|
2186
|
-
.
|
|
2187
|
-
.request()))
|
|
2188
|
-
|
|
2189
|
-
.result((ctx, api) => {
|
|
2190
|
-
api.setResult(ctx.result.flat());
|
|
2191
|
-
}))
|
|
2192
|
-
.request();
|
|
362
|
+
.middlewares(dataInterceptor)
|
|
363
|
+
.request())).request();
|
|
364
|
+
const results = grouped.flat();
|
|
2193
365
|
return results.map(t => ({ ...t, url: t.state === 'Completed' ? t.imageUrl : null }));
|
|
2194
366
|
}
|
|
2195
367
|
catch (cause) {
|
|
@@ -2207,16 +379,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2207
379
|
const batch = targets.map((t, i) => ({ ...defaults, ...t, requestId: String(i) }));
|
|
2208
380
|
const batchMap = new Map(batch.map(t => [t.requestId, t]));
|
|
2209
381
|
try {
|
|
2210
|
-
const
|
|
382
|
+
const grouped = await vigorFetch.vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
|
|
2211
383
|
.path('batch')
|
|
2212
|
-
.body(group)
|
|
2213
|
-
.
|
|
2214
|
-
.request()))
|
|
2215
|
-
|
|
2216
|
-
.result((ctx, api) => {
|
|
2217
|
-
api.setResult(ctx.result.flat());
|
|
2218
|
-
}))
|
|
2219
|
-
.request();
|
|
384
|
+
.body("overwrite", group)
|
|
385
|
+
.middlewares(dataInterceptor)
|
|
386
|
+
.request())).request();
|
|
387
|
+
const results = grouped.flat();
|
|
2220
388
|
return results.map(item => {
|
|
2221
389
|
const original = batchMap.get(item.requestId) ?? {};
|
|
2222
390
|
const { requestId: _rid, ...rest } = item;
|
|
@@ -2288,20 +456,21 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2288
456
|
fallback: {},
|
|
2289
457
|
fetchMissing: async (missing) => {
|
|
2290
458
|
try {
|
|
2291
|
-
const universeEntries = await vigor.all(...missing.map(placeId => () => apisRoblox
|
|
459
|
+
const universeEntries = await vigorFetch.vigor.all(...missing.map(placeId => () => apisRoblox
|
|
2292
460
|
.path('universes', 'v1', 'places', placeId, 'universe')
|
|
2293
|
-
.
|
|
2294
|
-
.
|
|
461
|
+
.middlewares(vigorFetch.vigor.builders.fetch.middlewares()
|
|
462
|
+
.after("intercept", async (ctx, api) => {
|
|
2295
463
|
const r = ctx.result;
|
|
2296
464
|
api.setResult({ placeId: Number(placeId), universeId: r?.universeId ?? null });
|
|
465
|
+
return ctx;
|
|
2297
466
|
}))
|
|
2298
467
|
.request())).request();
|
|
2299
|
-
const metaList = await vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
|
|
468
|
+
const metaList = await vigorFetch.vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
|
|
2300
469
|
if (!universeId)
|
|
2301
470
|
return { placeId, universeId: null, info: null, assetIds: [] };
|
|
2302
471
|
const [details, media] = await Promise.all([
|
|
2303
|
-
gamesApi.path('games').query({ universeIds: universeId }).
|
|
2304
|
-
gamesApi.path('games', universeId, 'media').
|
|
472
|
+
gamesApi.path('games').query({ universeIds: universeId }).middlewares(dataInterceptor).request(),
|
|
473
|
+
gamesApi.path('games', universeId, 'media').middlewares(dataInterceptor).request(),
|
|
2305
474
|
]);
|
|
2306
475
|
return {
|
|
2307
476
|
placeId,
|
|
@@ -2416,7 +585,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2416
585
|
try {
|
|
2417
586
|
const res = await gamejoinApi
|
|
2418
587
|
.path('join-game-instance')
|
|
2419
|
-
.body({ placeId, gameId: jobId })
|
|
588
|
+
.body("overwrite", { placeId, gameId: jobId })
|
|
2420
589
|
.request();
|
|
2421
590
|
return {
|
|
2422
591
|
publicIp: res?.joinScript?.UdmuxEndpoint?.[0]?.Address ?? null,
|
|
@@ -2461,7 +630,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2461
630
|
const missJobIds = jobIds.filter(id => !jobHitMap.has(id));
|
|
2462
631
|
if (missJobIds.length === 0)
|
|
2463
632
|
return jobIds.map(id => jobHitMap.get(id));
|
|
2464
|
-
const extracted = await vigor.all(...missJobIds.map(jobId => async () => {
|
|
633
|
+
const extracted = await vigorFetch.vigor.all(...missJobIds.map(jobId => async () => {
|
|
2465
634
|
const { publicIp, machineAddress } = await extractIps(placeId, jobId);
|
|
2466
635
|
return { jobId, publicIp, machineAddress };
|
|
2467
636
|
}))
|
|
@@ -2484,7 +653,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2484
653
|
const ipHitMap = new Map(cachedByIp.map(({ separator, data }) => [separator, data]));
|
|
2485
654
|
const stillMissIps = missPublicIps.filter(ip => !ipHitMap.has(ip));
|
|
2486
655
|
if (stillMissIps.length > 0) {
|
|
2487
|
-
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) })))
|
|
2488
657
|
.settings(s => s.concurrency(5))
|
|
2489
658
|
.request();
|
|
2490
659
|
const toUpsertIp = fetched.filter((e) => e.loc !== null);
|
|
@@ -2526,14 +695,11 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2526
695
|
]);
|
|
2527
696
|
return jobIds.flatMap(id => { const loc = resultMap.get(id); return loc ? [loc] : []; });
|
|
2528
697
|
}
|
|
2529
|
-
// ----------------------------------------------------------------
|
|
2530
|
-
// Friends
|
|
2531
|
-
// ----------------------------------------------------------------
|
|
2532
698
|
async function friends(userId) {
|
|
2533
699
|
try {
|
|
2534
700
|
return await friendsApi
|
|
2535
701
|
.path('users', userId, 'friends')
|
|
2536
|
-
.
|
|
702
|
+
.middlewares(dataInterceptor)
|
|
2537
703
|
.request();
|
|
2538
704
|
}
|
|
2539
705
|
catch (cause) {
|
|
@@ -2544,7 +710,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2544
710
|
try {
|
|
2545
711
|
await friendsApi
|
|
2546
712
|
.path('users', targetUserId, 'request-friendship')
|
|
2547
|
-
.body({})
|
|
713
|
+
.body("overwrite", {})
|
|
2548
714
|
.request();
|
|
2549
715
|
}
|
|
2550
716
|
catch (cause) {
|
|
@@ -2555,7 +721,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
|
|
|
2555
721
|
try {
|
|
2556
722
|
await friendsApi
|
|
2557
723
|
.path('users', targetUserId, 'unfriend')
|
|
2558
|
-
.body({})
|
|
724
|
+
.body("overwrite", {})
|
|
2559
725
|
.request();
|
|
2560
726
|
}
|
|
2561
727
|
catch (cause) {
|