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/dist/index.mjs CHANGED
@@ -1,1744 +1,5 @@
1
- const VigorErrorMessageFuncs = {
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 }, timeline, cause });
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.builder.fetch.interceptors()
1925
- .result((ctx, api) => {
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
- // CSRF 인터셉터 팩토리
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
- .interceptors(cookieInterceptor)
1990
- .interceptors(winInetInterceptor)
1991
- .retryConfig(c => c
1992
- .settings(s => s.attempt(7))
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
- .interceptors(cookieInterceptor)
1996
- .interceptors(winInetInterceptor)
1997
- .retryConfig(c => c
1998
- .settings(s => s.attempt(5))
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
- .interceptors(cookieInterceptor)
2002
- .retryConfig(c => c
2003
- .settings(s => s.attempt(5))
2004
- .algorithms(a => a.backoff().initial(1000).multiplier(2.5)));
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
- .interceptors(cookieInterceptor)
2007
- .retryConfig(c => c
2008
- .settings(s => s.attempt(5))
2009
- .algorithms(a => a.backoff().initial(500).multiplier(2)));
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
- .interceptors(cookieInterceptor)
2012
- .retryConfig(c => c
2013
- .settings(s => s.attempt(5))
2014
- .algorithms(a => a.backoff().initial(1000).multiplier(2)));
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
- .interceptors(cookieInterceptor)
2017
- .retryConfig(c => c
2018
- .settings(s => s.attempt(7))
2019
- .algorithms(a => a.backoff().initial(500).multiplier(1.5)));
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
- .retryConfig(c => c
2022
- .settings(s => s.attempt(4))
2023
- .algorithms(a => a.backoff().initial(500).multiplier(2)));
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
- .interceptors(cookieInterceptor)
2028
- .interceptors(winInetInterceptor)
2029
- .interceptors(csrfInterceptor) // ← CSRF 추가
2030
- .retryConfig(c => c
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
- return vigor.all(...cookies.map(cookie => async () => {
2047
- const fixedCookieInterceptor = vigor.builder.fetch.interceptors()
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 }, timeline, cause });
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 results = await vigor.all(...chunk(missing.map(Number), 100).map(group => () => usersApi
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
- .interceptors(dataInterceptor)
2100
- .request()))
2101
- .interceptors(vigor.builder.all.interceptors()
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 results = await vigor.all(...chunk(missing, 100).map(group => () => usersApi
327
+ const grouped = await vigor.all(...chunk(missing, 100).map(group => () => usersApi
2144
328
  .path('usernames', 'users')
2145
- .body({ usernames: group, excludeBannedUsers: false })
2146
- .interceptors(dataInterceptor)
2147
- .request()))
2148
- .interceptors(vigor.builder.all.interceptors()
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
- return await vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
343
+ const grouped = await vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
2164
344
  .path('presence', 'users')
2165
- .body({ userIds: group })
2166
- .interceptors(pickKey('userPresences'))
2167
- .request()))
2168
- .interceptors(vigor.builder.all.interceptors()
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 results = await vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
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
- .interceptors(dataInterceptor)
2185
- .request()))
2186
- .interceptors(vigor.builder.all.interceptors()
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 results = await vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
380
+ const grouped = await vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
2209
381
  .path('batch')
2210
- .body(group)
2211
- .interceptors(dataInterceptor)
2212
- .request()))
2213
- .interceptors(vigor.builder.all.interceptors()
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
- .interceptors(vigor.builder.fetch.interceptors()
2292
- .result((ctx, api) => {
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 }).interceptors(dataInterceptor).request(),
2302
- gamesApi.path('games', universeId, 'media').interceptors(dataInterceptor).request(),
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
- .interceptors(dataInterceptor)
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) {