vigor-roblox 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,81 +85,147 @@ function partition(arr, pred) {
1835
85
  (pred(item) ? pass : fail).push(item);
1836
86
  return { pass, fail };
1837
87
  }
1838
- // ----------------------------------------------------------------
1839
- // Factory
1840
- // ----------------------------------------------------------------
88
+ class CsrfTokenManager {
89
+ tokenMap = new Map();
90
+ pendingMap = new Map();
91
+ get(cookie) {
92
+ return this.tokenMap.get(cookie) ?? null;
93
+ }
94
+ set(cookie, token) {
95
+ this.tokenMap.set(cookie, token);
96
+ }
97
+ invalidate(cookie) {
98
+ this.tokenMap.delete(cookie);
99
+ }
100
+ async refresh(cookie) {
101
+ const existing = this.pendingMap.get(cookie);
102
+ if (existing)
103
+ return existing;
104
+ const pending = (async () => {
105
+ try {
106
+ const response = await fetch('https://auth.roblox.com/v2/logout', {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Cookie': `.ROBLOSECURITY=${cookie}`,
110
+ 'User-Agent': 'Roblox/WinInet',
111
+ 'Content-Length': '0',
112
+ },
113
+ });
114
+ const token = response.headers.get('x-csrf-token');
115
+ if (!token)
116
+ throw new Error('CSRF token not found in response headers');
117
+ this.tokenMap.set(cookie, token);
118
+ return token;
119
+ }
120
+ finally {
121
+ this.pendingMap.delete(cookie);
122
+ }
123
+ })();
124
+ this.pendingMap.set(cookie, pending);
125
+ return pending;
126
+ }
127
+ async getOrRefresh(cookie) {
128
+ const cached = this.get(cookie);
129
+ if (cached)
130
+ return cached;
131
+ return this.refresh(cookie);
132
+ }
133
+ }
1841
134
  function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1842
135
  const cookiePool = cookiesList.map(cookie => ({ cookie, lastUsed: 0 }));
136
+ const csrfManager = new CsrfTokenManager();
1843
137
  function pickCookie() {
1844
138
  const entry = cookiePool.reduce((a, b) => a.lastUsed < b.lastUsed ? a : b);
1845
139
  entry.lastUsed = Date.now();
1846
140
  return entry.cookie;
1847
141
  }
142
+ function makeHeaderMiddlewares(opts) {
143
+ const { getCookie, winInet = false, csrf = false } = opts;
144
+ let builder = vigor.builders.fetch.middlewares()
145
+ .before("intercept", async (ctx, api) => {
146
+ const cookie = getCookie();
147
+ ctx.record.cookie = cookie;
148
+ const headers = {
149
+ Cookie: `.ROBLOSECURITY=${cookie}`,
150
+ };
151
+ if (winInet)
152
+ headers['User-Agent'] = 'Roblox/WinInet';
153
+ if (csrf)
154
+ headers['X-CSRF-Token'] = await csrfManager.getOrRefresh(cookie);
155
+ api.setHeaders(headers);
156
+ return ctx;
157
+ });
158
+ if (csrf) {
159
+ builder = builder.onError("intercept", async (ctx, api) => {
160
+ const cause = ctx.error;
161
+ if (isFetchFailed(cause) && cause.data.status === 403) {
162
+ const cookie = ctx.record.cookie ?? getCookie();
163
+ const newToken = cause.data.response.headers.get('x-csrf-token');
164
+ if (newToken) {
165
+ csrfManager.set(cookie, newToken);
166
+ }
167
+ else {
168
+ csrfManager.invalidate(cookie);
169
+ await csrfManager.refresh(cookie);
170
+ }
171
+ api.proceedRestart();
172
+ }
173
+ return ctx;
174
+ });
175
+ }
176
+ return builder;
177
+ }
178
+ /** Reusable `after` middleware that unwraps a `{ data: T }`-shaped response envelope. */
1848
179
  function pickKey(key) {
1849
- return vigor.builder.fetch.interceptors()
1850
- .result((ctx, api) => {
180
+ return vigor.builders.fetch.middlewares()
181
+ .after("intercept", async (ctx, api) => {
1851
182
  api.setResult(ctx.result[key]);
183
+ return ctx;
1852
184
  });
1853
185
  }
1854
186
  const dataInterceptor = pickKey('data');
1855
- const cookieInterceptor = vigor.builder.fetch.interceptors()
1856
- .before((ctx, api) => {
1857
- api.setHeaders({
1858
- ...ctx.options.headers,
1859
- Cookie: `.ROBLOSECURITY=${pickCookie()}`,
1860
- });
1861
- });
1862
- const winInetInterceptor = vigor.builder.fetch.interceptors()
1863
- .before((ctx, api) => {
1864
- api.setHeaders({
1865
- ...ctx.options.headers,
1866
- 'User-Agent': 'Roblox/WinInet',
1867
- });
1868
- });
187
+ const poolCookieMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie });
188
+ const poolCookieWinInetMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie, winInet: true });
189
+ const poolCookieCsrfMiddlewares = makeHeaderMiddlewares({ getCookie: pickCookie, winInet: true, csrf: true });
1869
190
  const usersApi = vigor.fetch('https://users.roblox.com/v1')
1870
- .interceptors(cookieInterceptor)
1871
- .interceptors(winInetInterceptor)
1872
- .retryConfig(c => c
1873
- .settings(s => s.attempt(7))
1874
- .algorithms(a => a.backoff().initial(200).unit(800).multiplier(1.7)));
191
+ .middlewares(poolCookieWinInetMiddlewares)
192
+ .retry(r => r
193
+ .settings(s => s.maxAttempts(7))
194
+ .algorithms(a => a.backoff({ initial: 200, unit: 800, multiplier: 1.7 })));
1875
195
  const thumbnailsApi = vigor.fetch('https://thumbnails.roblox.com/v1')
1876
- .interceptors(cookieInterceptor)
1877
- .interceptors(winInetInterceptor)
1878
- .retryConfig(c => c
1879
- .settings(s => s.attempt(5))
1880
- .algorithms(a => a.backoff().initial(1000).multiplier(2.5)));
196
+ .middlewares(poolCookieWinInetMiddlewares)
197
+ .retry(r => r
198
+ .settings(s => s.maxAttempts(5))
199
+ .algorithms(a => a.backoff({ initial: 1000, multiplier: 2.5 })));
1881
200
  const gamesApi = vigor.fetch('https://games.roblox.com/v1')
1882
- .interceptors(cookieInterceptor)
1883
- .retryConfig(c => c
1884
- .settings(s => s.attempt(5))
1885
- .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 })));
1886
205
  const presenceApi = vigor.fetch('https://presence.roblox.com/v1')
1887
- .interceptors(cookieInterceptor)
1888
- .retryConfig(c => c
1889
- .settings(s => s.attempt(5))
1890
- .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 })));
1891
210
  const apisRoblox = vigor.fetch('https://apis.roblox.com')
1892
- .interceptors(cookieInterceptor)
1893
- .retryConfig(c => c
1894
- .settings(s => s.attempt(5))
1895
- .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 })));
1896
215
  const gamejoinApi = vigor.fetch('https://gamejoin.roblox.com/v1')
1897
- .interceptors(cookieInterceptor)
1898
- .retryConfig(c => c
1899
- .settings(s => s.attempt(7))
1900
- .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 })));
1901
220
  const ipgeolocationApi = vigor.fetch('https://api.ipgeolocation.io')
1902
- .retryConfig(c => c
1903
- .settings(s => s.attempt(4))
1904
- .algorithms(a => a.backoff().initial(500).multiplier(2)));
1905
- // friends.roblox.com — used for friend list lookups, sending friend
1906
- // requests, and unfriending. Same cookie + WinInet headers as usersApi.
221
+ .retry(r => r
222
+ .settings(s => s.maxAttempts(4))
223
+ .algorithms(a => a.backoff({ initial: 500, multiplier: 2 })));
1907
224
  const friendsApi = vigor.fetch('https://friends.roblox.com/v1')
1908
- .interceptors(cookieInterceptor)
1909
- .interceptors(winInetInterceptor)
1910
- .retryConfig(c => c
1911
- .settings(s => s.attempt(5))
1912
- .algorithms(a => a.backoff().initial(500).multiplier(2)));
225
+ .middlewares(poolCookieCsrfMiddlewares)
226
+ .retry(r => r
227
+ .settings(s => s.maxAttempts(5))
228
+ .algorithms(a => a.backoff({ initial: 500, multiplier: 2 })));
1913
229
  async function withCache(opts) {
1914
230
  const { type, keys, ttlMs, getKey, fetchMissing, fallback } = opts;
1915
231
  const cached = await cache.select(type, keys);
@@ -1923,15 +239,8 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1923
239
  return keys.map(k => cacheMap.get(k) ?? fallback);
1924
240
  }
1925
241
  async function authenticated(cookies) {
1926
- return vigor.all(...cookies.map(cookie => async () => {
1927
- const fixedCookieInterceptor = vigor.builder.fetch.interceptors()
1928
- .before((ctx, api) => {
1929
- api.setHeaders({
1930
- ...ctx.options.headers,
1931
- Cookie: `.ROBLOSECURITY=${cookie}`,
1932
- });
1933
- });
1934
- const base = usersApi.interceptors(fixedCookieInterceptor);
242
+ const results = await vigor.all(...cookies.map(cookie => async () => {
243
+ const base = usersApi.middlewares(makeHeaderMiddlewares({ getCookie: () => cookie, winInet: true }));
1935
244
  const [user, description, birthdate, gender, ageBracket, countryCode, roles] = await Promise.allSettled([
1936
245
  base.path('users', 'authenticated').request(),
1937
246
  base.path('description').request(),
@@ -1944,14 +253,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1944
253
  if (user.status === 'rejected') {
1945
254
  const cause = user.reason;
1946
255
  const status = extractStatus(cause);
1947
- const timeline = extractTimeline(cause);
1948
256
  if (status === 429)
1949
257
  throw new RobloxRateLimitError({
1950
258
  data: { status, url: extractUrl(cause), retryAfterMs: extractRetryAfterMs(cause) },
1951
- timeline,
1952
259
  cause,
1953
260
  });
1954
- throw new RobloxAuthError({ data: { status, cookie }, timeline, cause });
261
+ throw new RobloxAuthError({ data: { status, cookie }, cause });
1955
262
  }
1956
263
  return {
1957
264
  ...user.value,
@@ -1963,6 +270,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1963
270
  ...(roles.status === 'fulfilled' ? roles.value : {}),
1964
271
  };
1965
272
  })).request();
273
+ return results;
1966
274
  }
1967
275
  async function usersSimple(userIds) {
1968
276
  return withCache({
@@ -1973,16 +281,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1973
281
  fallback: {},
1974
282
  fetchMissing: async (missing) => {
1975
283
  try {
1976
- const 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
1977
285
  .path('users')
1978
- .body({ userIds: group, excludeBannedUsers: false })
1979
- .interceptors(dataInterceptor)
1980
- .request()))
1981
- .interceptors(vigor.builder.all.interceptors()
1982
- .result((ctx, api) => {
1983
- api.setResult(ctx.result.flat());
1984
- }))
1985
- .request();
286
+ .body("overwrite", { userIds: group, excludeBannedUsers: false })
287
+ .middlewares(dataInterceptor)
288
+ .request())).request();
289
+ const results = grouped.flat();
1986
290
  return results.filter(u => u.id != null && u.name != null && u.displayName != null);
1987
291
  }
1988
292
  catch (cause) {
@@ -2020,16 +324,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2020
324
  fallback: {},
2021
325
  fetchMissing: async (missing) => {
2022
326
  try {
2023
- const results = await vigor.all(...chunk(missing, 100).map(group => () => usersApi
327
+ const grouped = await vigor.all(...chunk(missing, 100).map(group => () => usersApi
2024
328
  .path('usernames', 'users')
2025
- .body({ usernames: group, excludeBannedUsers: false })
2026
- .interceptors(dataInterceptor)
2027
- .request()))
2028
- .interceptors(vigor.builder.all.interceptors()
2029
- .result((ctx, api) => {
2030
- api.setResult(ctx.result.flat());
2031
- }))
2032
- .request();
329
+ .body("overwrite", { usernames: group, excludeBannedUsers: false })
330
+ .middlewares(dataInterceptor)
331
+ .request())).request();
332
+ const results = grouped.flat();
2033
333
  return results.filter(u => u.id != null && u.name != null && u.displayName != null);
2034
334
  }
2035
335
  catch (cause) {
@@ -2040,16 +340,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2040
340
  }
2041
341
  async function presence(userIds) {
2042
342
  try {
2043
- return await vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
343
+ const grouped = await vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
2044
344
  .path('presence', 'users')
2045
- .body({ userIds: group })
2046
- .interceptors(pickKey('userPresences'))
2047
- .request()))
2048
- .interceptors(vigor.builder.all.interceptors()
2049
- .result((ctx, api) => {
2050
- api.setResult(ctx.result.flat());
2051
- }))
2052
- .request();
345
+ .body("overwrite", { userIds: group })
346
+ .middlewares(pickKey('userPresences'))
347
+ .request())).request();
348
+ return grouped.flat();
2053
349
  }
2054
350
  catch (cause) {
2055
351
  throw wrapVigorError(cause);
@@ -2058,16 +354,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2058
354
  async function thumbnailAssets(opts) {
2059
355
  const { assetIds, size = '150x150', format = 'Png' } = opts;
2060
356
  try {
2061
- const results = await vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
357
+ const grouped = await vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
2062
358
  .path('assets')
2063
359
  .query({ assetIds: group.join(','), size, format })
2064
- .interceptors(dataInterceptor)
2065
- .request()))
2066
- .interceptors(vigor.builder.all.interceptors()
2067
- .result((ctx, api) => {
2068
- api.setResult(ctx.result.flat());
2069
- }))
2070
- .request();
360
+ .middlewares(dataInterceptor)
361
+ .request())).request();
362
+ const results = grouped.flat();
2071
363
  return results.map(t => ({ ...t, url: t.state === 'Completed' ? t.imageUrl : null }));
2072
364
  }
2073
365
  catch (cause) {
@@ -2085,16 +377,12 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2085
377
  const batch = targets.map((t, i) => ({ ...defaults, ...t, requestId: String(i) }));
2086
378
  const batchMap = new Map(batch.map(t => [t.requestId, t]));
2087
379
  try {
2088
- const results = await vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
380
+ const grouped = await vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
2089
381
  .path('batch')
2090
- .body(group)
2091
- .interceptors(dataInterceptor)
2092
- .request()))
2093
- .interceptors(vigor.builder.all.interceptors()
2094
- .result((ctx, api) => {
2095
- api.setResult(ctx.result.flat());
2096
- }))
2097
- .request();
382
+ .body("overwrite", group)
383
+ .middlewares(dataInterceptor)
384
+ .request())).request();
385
+ const results = grouped.flat();
2098
386
  return results.map(item => {
2099
387
  const original = batchMap.get(item.requestId) ?? {};
2100
388
  const { requestId: _rid, ...rest } = item;
@@ -2168,18 +456,19 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2168
456
  try {
2169
457
  const universeEntries = await vigor.all(...missing.map(placeId => () => apisRoblox
2170
458
  .path('universes', 'v1', 'places', placeId, 'universe')
2171
- .interceptors(vigor.builder.fetch.interceptors()
2172
- .result((ctx, api) => {
459
+ .middlewares(vigor.builders.fetch.middlewares()
460
+ .after("intercept", async (ctx, api) => {
2173
461
  const r = ctx.result;
2174
462
  api.setResult({ placeId: Number(placeId), universeId: r?.universeId ?? null });
463
+ return ctx;
2175
464
  }))
2176
465
  .request())).request();
2177
466
  const metaList = await vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
2178
467
  if (!universeId)
2179
468
  return { placeId, universeId: null, info: null, assetIds: [] };
2180
469
  const [details, media] = await Promise.all([
2181
- gamesApi.path('games').query({ universeIds: universeId }).interceptors(dataInterceptor).request(),
2182
- 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(),
2183
472
  ]);
2184
473
  return {
2185
474
  placeId,
@@ -2294,7 +583,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2294
583
  try {
2295
584
  const res = await gamejoinApi
2296
585
  .path('join-game-instance')
2297
- .body({ placeId, gameId: jobId })
586
+ .body("overwrite", { placeId, gameId: jobId })
2298
587
  .request();
2299
588
  return {
2300
589
  publicIp: res?.joinScript?.UdmuxEndpoint?.[0]?.Address ?? null,
@@ -2404,14 +693,11 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2404
693
  ]);
2405
694
  return jobIds.flatMap(id => { const loc = resultMap.get(id); return loc ? [loc] : []; });
2406
695
  }
2407
- // ----------------------------------------------------------------
2408
- // Friends
2409
- // ----------------------------------------------------------------
2410
696
  async function friends(userId) {
2411
697
  try {
2412
698
  return await friendsApi
2413
699
  .path('users', userId, 'friends')
2414
- .interceptors(dataInterceptor)
700
+ .middlewares(dataInterceptor)
2415
701
  .request();
2416
702
  }
2417
703
  catch (cause) {
@@ -2422,7 +708,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2422
708
  try {
2423
709
  await friendsApi
2424
710
  .path('users', targetUserId, 'request-friendship')
2425
- .body({})
711
+ .body("overwrite", {})
2426
712
  .request();
2427
713
  }
2428
714
  catch (cause) {
@@ -2433,7 +719,7 @@ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
2433
719
  try {
2434
720
  await friendsApi
2435
721
  .path('users', targetUserId, 'unfriend')
2436
- .body({})
722
+ .body("overwrite", {})
2437
723
  .request();
2438
724
  }
2439
725
  catch (cause) {