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