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