vigor-roblox 1.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 ADDED
@@ -0,0 +1,2418 @@
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[0]);
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
+ };
1738
+
1739
+ // ----------------------------------------------------------------
1740
+ // Error system
1741
+ // ----------------------------------------------------------------
1742
+ const RobloxErrorMessageFuncs = {
1743
+ AUTH_FAILED: ({ status, cookie }) => `Cookie authentication failed (status: ${status ?? 'unknown'}, cookie: ${cookie.slice(0, 8)}...)`,
1744
+ RATE_LIMITED: ({ status, url, retryAfterMs }) => `Rate limited (status: ${status}, url: ${url ?? 'unknown'}, retryAfter: ${retryAfterMs ?? 'unknown'}ms)`,
1745
+ REQUEST_FAILED: ({ status, url }) => `Request failed (status: ${status ?? 'unknown'}, url: ${url ?? 'unknown'})`,
1746
+ };
1747
+ class RobloxApiError extends Error {
1748
+ timestamp = new Date();
1749
+ cause;
1750
+ code;
1751
+ data;
1752
+ timeline;
1753
+ context;
1754
+ constructor(code, options) {
1755
+ const messageFn = RobloxErrorMessageFuncs[code];
1756
+ super(`[${code}] ${messageFn(options.data)}`, { cause: options.cause });
1757
+ this.name = new.target.name;
1758
+ this.code = code;
1759
+ this.cause = options.cause;
1760
+ this.data = options.data;
1761
+ this.timeline = options.timeline ?? [];
1762
+ this.context = options.context;
1763
+ Object.setPrototypeOf(this, new.target.prototype);
1764
+ Error.captureStackTrace?.(this, new.target);
1765
+ }
1766
+ }
1767
+ class RobloxAuthError extends RobloxApiError {
1768
+ constructor(options) {
1769
+ super('AUTH_FAILED', options);
1770
+ }
1771
+ }
1772
+ class RobloxRateLimitError extends RobloxApiError {
1773
+ constructor(options) {
1774
+ super('RATE_LIMITED', options);
1775
+ }
1776
+ }
1777
+ class RobloxRequestError extends RobloxApiError {
1778
+ constructor(options) {
1779
+ super('REQUEST_FAILED', options);
1780
+ }
1781
+ }
1782
+ function isFetchFailed(cause) {
1783
+ return cause instanceof VigorFetchError && cause.code === 'FETCH_FAILED' && cause.data != null;
1784
+ }
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
+ function extractStatus(cause) {
1793
+ return isFetchFailed(cause) ? cause.data.status : null;
1794
+ }
1795
+ function extractUrl(cause) {
1796
+ return isFetchFailed(cause) ? cause.data.url : null;
1797
+ }
1798
+ function extractRetryAfterMs(cause) {
1799
+ if (!isFetchFailed(cause))
1800
+ return null;
1801
+ const headers = cause.data.response.headers;
1802
+ const raw = headers.get('retry-after')
1803
+ ?? headers.get('ratelimit-reset')
1804
+ ?? headers.get('x-ratelimit-reset')
1805
+ ?? null;
1806
+ if (raw === null)
1807
+ return null;
1808
+ const asNum = Number(raw);
1809
+ if (!isNaN(asNum))
1810
+ return asNum * 1000;
1811
+ const asDate = new Date(raw).getTime();
1812
+ return !isNaN(asDate) ? asDate - Date.now() : null;
1813
+ }
1814
+ function wrapVigorError(cause) {
1815
+ const status = extractStatus(cause);
1816
+ const url = extractUrl(cause);
1817
+ const timeline = extractTimeline(cause);
1818
+ if (status === 429)
1819
+ return new RobloxRateLimitError({
1820
+ data: { status, url, retryAfterMs: extractRetryAfterMs(cause) },
1821
+ timeline,
1822
+ cause,
1823
+ });
1824
+ return new RobloxRequestError({ data: { status, url }, timeline, cause });
1825
+ }
1826
+ function chunk(arr, size) {
1827
+ const out = [];
1828
+ for (let i = 0; i < arr.length; i += size)
1829
+ out.push(arr.slice(i, i + size));
1830
+ return out;
1831
+ }
1832
+ function partition(arr, pred) {
1833
+ const pass = [], fail = [];
1834
+ for (const item of arr)
1835
+ (pred(item) ? pass : fail).push(item);
1836
+ return { pass, fail };
1837
+ }
1838
+ // ----------------------------------------------------------------
1839
+ // Factory
1840
+ // ----------------------------------------------------------------
1841
+ function createRobloxApi({ cache, cookies: cookiesList, ipgeolocationKey, }) {
1842
+ const cookiePool = cookiesList.map(cookie => ({ cookie, lastUsed: 0 }));
1843
+ function pickCookie() {
1844
+ const entry = cookiePool.reduce((a, b) => a.lastUsed < b.lastUsed ? a : b);
1845
+ entry.lastUsed = Date.now();
1846
+ return entry.cookie;
1847
+ }
1848
+ function pickKey(key) {
1849
+ return vigor.builder.fetch.interceptors()
1850
+ .result((ctx, api) => {
1851
+ api.setResult(ctx.result[key]);
1852
+ });
1853
+ }
1854
+ 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
+ });
1869
+ 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)));
1875
+ 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)));
1881
+ 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)));
1886
+ 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)));
1891
+ 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)));
1896
+ 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)));
1901
+ 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
+ async function withCache(opts) {
1906
+ const { type, keys, ttlMs, getKey, fetchMissing, fallback } = opts;
1907
+ const cached = await cache.select(type, keys);
1908
+ const cacheMap = new Map(cached.map(({ separator, data }) => [separator, data]));
1909
+ const missing = keys.filter(k => !cacheMap.has(k));
1910
+ if (missing.length > 0) {
1911
+ const fetched = await fetchMissing(missing);
1912
+ await cache.upsert(type, ttlMs, fetched.map(item => ({ separator: getKey(item), data: item })));
1913
+ fetched.forEach(item => cacheMap.set(getKey(item), item));
1914
+ }
1915
+ return keys.map(k => cacheMap.get(k) ?? fallback);
1916
+ }
1917
+ async function authenticated(cookies) {
1918
+ return vigor.all(...cookies.map(cookie => async () => {
1919
+ const fixedCookieInterceptor = vigor.builder.fetch.interceptors()
1920
+ .before((ctx, api) => {
1921
+ api.setHeaders({
1922
+ ...ctx.options.headers,
1923
+ Cookie: `.ROBLOSECURITY=${cookie}`,
1924
+ });
1925
+ });
1926
+ const base = usersApi.interceptors(fixedCookieInterceptor);
1927
+ const [user, description, birthdate, gender, ageBracket, countryCode, roles] = await Promise.allSettled([
1928
+ base.path('users', 'authenticated').request(),
1929
+ base.path('description').request(),
1930
+ base.path('birthdate').request(),
1931
+ base.path('gender').request(),
1932
+ base.path('users', 'authenticated', 'age-bracket').request(),
1933
+ base.path('users', 'authenticated', 'country-code').request(),
1934
+ base.path('users', 'authenticated', 'roles').request(),
1935
+ ]);
1936
+ if (user.status === 'rejected') {
1937
+ const cause = user.reason;
1938
+ const status = extractStatus(cause);
1939
+ const timeline = extractTimeline(cause);
1940
+ if (status === 429)
1941
+ throw new RobloxRateLimitError({
1942
+ data: { status, url: extractUrl(cause), retryAfterMs: extractRetryAfterMs(cause) },
1943
+ timeline,
1944
+ cause,
1945
+ });
1946
+ throw new RobloxAuthError({ data: { status, cookie }, timeline, cause });
1947
+ }
1948
+ return {
1949
+ ...user.value,
1950
+ ...(description.status === 'fulfilled' ? description.value : {}),
1951
+ ...(birthdate.status === 'fulfilled' ? birthdate.value : {}),
1952
+ ...(gender.status === 'fulfilled' ? gender.value : {}),
1953
+ ...(ageBracket.status === 'fulfilled' ? ageBracket.value : {}),
1954
+ ...(countryCode.status === 'fulfilled' ? countryCode.value : {}),
1955
+ ...(roles.status === 'fulfilled' ? roles.value : {}),
1956
+ };
1957
+ })).request();
1958
+ }
1959
+ async function usersSimple(userIds) {
1960
+ return withCache({
1961
+ type: 'usersSimple',
1962
+ keys: userIds.map(String),
1963
+ ttlMs: 30 * 60 * 1000,
1964
+ getKey: item => String(item.id),
1965
+ fallback: {},
1966
+ fetchMissing: async (missing) => {
1967
+ try {
1968
+ const results = await vigor.all(...chunk(missing.map(Number), 100).map(group => () => usersApi
1969
+ .path('users')
1970
+ .body({ userIds: group, excludeBannedUsers: false })
1971
+ .interceptors(dataInterceptor)
1972
+ .request()))
1973
+ .interceptors(vigor.builder.all.interceptors()
1974
+ .result((ctx, api) => {
1975
+ api.setResult(ctx.result.flat());
1976
+ }))
1977
+ .request();
1978
+ return results.filter(u => u.id != null && u.name != null && u.displayName != null);
1979
+ }
1980
+ catch (cause) {
1981
+ throw wrapVigorError(cause);
1982
+ }
1983
+ },
1984
+ });
1985
+ }
1986
+ async function users(userIds) {
1987
+ return withCache({
1988
+ type: 'users',
1989
+ keys: userIds.map(String),
1990
+ ttlMs: 60 * 60 * 1000,
1991
+ getKey: item => String(item.id),
1992
+ fallback: {},
1993
+ fetchMissing: async (missing) => {
1994
+ try {
1995
+ const results = await vigor.all(...missing.map(id => () => usersApi.path('users', id).request()))
1996
+ .settings(s => s.concurrency(2))
1997
+ .request();
1998
+ return results.filter(u => u.id != null && u.name != null && u.displayName != null && u.description != null);
1999
+ }
2000
+ catch (cause) {
2001
+ throw wrapVigorError(cause);
2002
+ }
2003
+ },
2004
+ });
2005
+ }
2006
+ async function usersByName(usernames) {
2007
+ return withCache({
2008
+ type: 'usernames',
2009
+ keys: usernames,
2010
+ ttlMs: 30 * 60 * 1000,
2011
+ getKey: item => item.requestedUsername ?? item.name,
2012
+ fallback: {},
2013
+ fetchMissing: async (missing) => {
2014
+ try {
2015
+ const results = await vigor.all(...chunk(missing, 100).map(group => () => usersApi
2016
+ .path('usernames', 'users')
2017
+ .body({ usernames: group, excludeBannedUsers: false })
2018
+ .interceptors(dataInterceptor)
2019
+ .request()))
2020
+ .interceptors(vigor.builder.all.interceptors()
2021
+ .result((ctx, api) => {
2022
+ api.setResult(ctx.result.flat());
2023
+ }))
2024
+ .request();
2025
+ return results.filter(u => u.id != null && u.name != null && u.displayName != null);
2026
+ }
2027
+ catch (cause) {
2028
+ throw wrapVigorError(cause);
2029
+ }
2030
+ },
2031
+ });
2032
+ }
2033
+ async function presence(userIds) {
2034
+ try {
2035
+ return await vigor.all(...chunk(userIds, 50).map(group => () => presenceApi
2036
+ .path('presence', 'users')
2037
+ .body({ userIds: group })
2038
+ .interceptors(pickKey('userPresences'))
2039
+ .request()))
2040
+ .interceptors(vigor.builder.all.interceptors()
2041
+ .result((ctx, api) => {
2042
+ api.setResult(ctx.result.flat());
2043
+ }))
2044
+ .request();
2045
+ }
2046
+ catch (cause) {
2047
+ throw wrapVigorError(cause);
2048
+ }
2049
+ }
2050
+ async function thumbnailAssets(opts) {
2051
+ const { assetIds, size = '150x150', format = 'Png' } = opts;
2052
+ try {
2053
+ const results = await vigor.all(...chunk(assetIds, 100).map(group => () => thumbnailsApi
2054
+ .path('assets')
2055
+ .query({ assetIds: group.join(','), size, format })
2056
+ .interceptors(dataInterceptor)
2057
+ .request()))
2058
+ .interceptors(vigor.builder.all.interceptors()
2059
+ .result((ctx, api) => {
2060
+ api.setResult(ctx.result.flat());
2061
+ }))
2062
+ .request();
2063
+ return results.map(t => ({ ...t, url: t.state === 'Completed' ? t.url : null }));
2064
+ }
2065
+ catch (cause) {
2066
+ throw wrapVigorError(cause);
2067
+ }
2068
+ }
2069
+ async function thumbnailsBatch(targets, formatDefaults = {}) {
2070
+ const defaults = {
2071
+ type: 'AvatarHeadShot',
2072
+ size: '150x150',
2073
+ format: 'Png',
2074
+ isCircular: false,
2075
+ ...formatDefaults,
2076
+ };
2077
+ const batch = targets.map((t, i) => ({ ...defaults, ...t, requestId: String(i) }));
2078
+ const batchMap = new Map(batch.map(t => [t.requestId, t]));
2079
+ try {
2080
+ const results = await vigor.all(...chunk(batch, 100).map(group => () => thumbnailsApi
2081
+ .path('batch')
2082
+ .body(group)
2083
+ .interceptors(dataInterceptor)
2084
+ .request()))
2085
+ .interceptors(vigor.builder.all.interceptors()
2086
+ .result((ctx, api) => {
2087
+ api.setResult(ctx.result.flat());
2088
+ }))
2089
+ .request();
2090
+ return results.map(item => {
2091
+ const original = batchMap.get(item.requestId) ?? {};
2092
+ const { requestId: _rid, ...rest } = item;
2093
+ return { ...original, ...rest, url: rest.state === 'Completed' ? rest.url : null };
2094
+ });
2095
+ }
2096
+ catch (cause) {
2097
+ throw wrapVigorError(cause);
2098
+ }
2099
+ }
2100
+ async function serversSimple(opts) {
2101
+ const { placeId, count = 1, serverType = 'Public', cursor, thumbnailFormat } = opts;
2102
+ let nextCursor = cursor ?? null;
2103
+ let prevCursor = null;
2104
+ const rawData = [];
2105
+ try {
2106
+ for (let i = 0; i < count; i++) {
2107
+ const page = await gamesApi
2108
+ .path('games', placeId, 'servers', serverType)
2109
+ .query({ limit: 100, ...(nextCursor ? { cursor: nextCursor } : {}) })
2110
+ .request();
2111
+ if (i === 0)
2112
+ prevCursor = page.previousPageCursor;
2113
+ nextCursor = page.nextPageCursor;
2114
+ rawData.push(...page.data);
2115
+ if (!nextCursor)
2116
+ break;
2117
+ }
2118
+ }
2119
+ catch (cause) {
2120
+ throw wrapVigorError(cause);
2121
+ }
2122
+ const thumbTargets = rawData
2123
+ .flatMap(s => s.playerTokens.map(token => ({ token, type: 'AvatarHeadShot', size: '150x150', format: 'Png', ...thumbnailFormat })));
2124
+ const thumbResults = await thumbnailsBatch(thumbTargets, thumbnailFormat);
2125
+ const thumbMap = new Map(thumbResults.map(t => [t.token, t.url]));
2126
+ return {
2127
+ previousPageCursor: prevCursor,
2128
+ nextPageCursor: nextCursor,
2129
+ data: rawData.map(s => ({
2130
+ jobId: s.id,
2131
+ maxPlayers: s.maxPlayers,
2132
+ playing: s.playing,
2133
+ fps: s.fps,
2134
+ ping: s.ping,
2135
+ playerImgs: s.playerTokens.map(tok => thumbMap.get(tok)).filter((url) => url != null),
2136
+ })),
2137
+ };
2138
+ }
2139
+ async function servers(opts) {
2140
+ const result = await serversSimple(opts);
2141
+ const jobIds = result.data.map(s => s.jobId);
2142
+ const locationList = await serversRegion({ placeId: opts.placeId, jobIds }).catch(() => []);
2143
+ const locationMap = new Map(locationList.map(l => [l.jobId, l]));
2144
+ return {
2145
+ ...result,
2146
+ data: result.data.map(s => ({
2147
+ ...s,
2148
+ location: locationMap.get(s.jobId) ?? null,
2149
+ })),
2150
+ };
2151
+ }
2152
+ async function placeInfo(placeIds) {
2153
+ return withCache({
2154
+ type: 'placeInfo',
2155
+ keys: placeIds.map(String),
2156
+ ttlMs: 60 * 60 * 1000,
2157
+ getKey: item => String(item.placeId),
2158
+ fallback: {},
2159
+ fetchMissing: async (missing) => {
2160
+ try {
2161
+ const universeEntries = await vigor.all(...missing.map(placeId => () => apisRoblox
2162
+ .path('universes', 'v1', 'places', placeId, 'universe')
2163
+ .interceptors(vigor.builder.fetch.interceptors()
2164
+ .result((ctx, api) => {
2165
+ const r = ctx.result;
2166
+ api.setResult({ placeId: Number(placeId), universeId: r?.universeId ?? null });
2167
+ }))
2168
+ .request())).request();
2169
+ const metaList = await vigor.all(...universeEntries.map(({ placeId, universeId }) => async () => {
2170
+ if (!universeId)
2171
+ return { placeId, universeId: null, info: null, assetIds: [] };
2172
+ const [details, media] = await Promise.all([
2173
+ gamesApi.path('games').query({ universeIds: universeId }).interceptors(dataInterceptor).request(),
2174
+ gamesApi.path('games', universeId, 'media').interceptors(dataInterceptor).request(),
2175
+ ]);
2176
+ return {
2177
+ placeId,
2178
+ universeId,
2179
+ info: details?.[0] ?? null,
2180
+ assetIds: (media ?? []).map(m => m.imageId).filter((id) => id != null),
2181
+ };
2182
+ })).request();
2183
+ const allAssetIds = [...new Set(metaList.flatMap(m => m.assetIds))];
2184
+ const assetUrlMap = new Map();
2185
+ if (allAssetIds.length > 0) {
2186
+ const thumbs = await thumbnailAssets({ assetIds: allAssetIds, size: '768x432', format: 'Png' });
2187
+ thumbs.forEach(t => { if (t.targetId != null && t.url)
2188
+ assetUrlMap.set(t.targetId, t.url); });
2189
+ }
2190
+ return metaList.map(({ placeId, universeId, info, assetIds }) => ({
2191
+ ...(info ?? {}),
2192
+ placeId,
2193
+ universeId,
2194
+ logos: assetIds.map(id => assetUrlMap.get(id)).filter((u) => u != null),
2195
+ }));
2196
+ }
2197
+ catch (cause) {
2198
+ throw wrapVigorError(cause);
2199
+ }
2200
+ },
2201
+ });
2202
+ }
2203
+ async function usersSimpleWithImg(userIds) {
2204
+ const [userList, thumbs] = await Promise.all([
2205
+ usersSimple(userIds),
2206
+ thumbnailsBatch(userIds.map(id => ({ targetId: id }))),
2207
+ ]);
2208
+ const imgMap = new Map(thumbs.map(t => [t.targetId, t.url]));
2209
+ return userList.map(u => ({ ...u, img: imgMap.get(u.id) ?? null }));
2210
+ }
2211
+ async function usersWithImg(userIds) {
2212
+ const [userList, thumbs] = await Promise.all([
2213
+ users(userIds),
2214
+ thumbnailsBatch(userIds.map(id => ({ targetId: id }))),
2215
+ ]);
2216
+ const imgMap = new Map(thumbs.map(t => [t.targetId, t.url]));
2217
+ return userList.map(u => ({ ...u, img: imgMap.get(u.id) ?? null }));
2218
+ }
2219
+ async function track(opts) {
2220
+ const { placeId, targets } = opts;
2221
+ const { pass: rawIds, fail: names } = partition(targets, t => !Number.isNaN(Number(t)));
2222
+ const resolvedIds = (await usersByName(names)).map(u => u.id);
2223
+ const idList = [...rawIds.map(Number), ...resolvedIds];
2224
+ const [userList, serverResult, thumbs] = await Promise.all([
2225
+ usersSimple(idList),
2226
+ serversSimple({ placeId, count: 20 }),
2227
+ thumbnailsBatch(idList.map(id => ({ targetId: id }))),
2228
+ ]);
2229
+ const thumbnailsMap = new Map(thumbs.map(t => [t.targetId, t.url]));
2230
+ const defaultHashes = new Set([
2231
+ '5816BB6B457A7A2FD8F0299D6F79DADF', 'D517857E5CC51E2FF93E63E20241169E',
2232
+ '56DFC0F87BABBE49C6D1BE708AE9A66A', 'C16BE31B5A403C45279B3FF5533980E9',
2233
+ '51E47F0C53DA3A617158586DF73B1236', 'ACCF91F734E311F4A0EF23C3EDA54284',
2234
+ 'CF083BB49C3304C593C43617FF06418E', '3259891600987E41060EC3A43511F2F9',
2235
+ '19F6EB627A565DF5ABC0B82925B2C760', '5CB6042A80C64D34BA98721C96F5D6A3',
2236
+ 'E592BA2BBFA44C9021643D25BC014BD5', '661AD135B4409FF51BC4A6D80E6AC0C7',
2237
+ '8E0E19FD517F46AD46A8A322377CA89B', '1E8FFEC57F042949AEFAC69FECC72D38',
2238
+ '64D3D8C3021F7E8442CCA2825051A87A',
2239
+ ]);
2240
+ const getHash = (url) => {
2241
+ if (!url)
2242
+ return null;
2243
+ try {
2244
+ const segments = new URL(url).pathname.split('/');
2245
+ const segment = segments.find(s => s.includes('-'));
2246
+ if (!segment)
2247
+ return null;
2248
+ const parts = segment.split('-');
2249
+ return parts.length >= 3 ? parts.slice(1, -1).join('-') : null;
2250
+ }
2251
+ catch {
2252
+ return null;
2253
+ }
2254
+ };
2255
+ const serverHashMap = new Map();
2256
+ serverResult.data.forEach(s => s.playerImgs.forEach(img => {
2257
+ const h = getHash(img);
2258
+ if (h)
2259
+ serverHashMap.set(h, s);
2260
+ }));
2261
+ const matchedJobIds = new Set();
2262
+ const userServerMap = new Map();
2263
+ for (const user of userList) {
2264
+ const img = thumbnailsMap.get(user.id) ?? null;
2265
+ const hash = getHash(img);
2266
+ const server = hash && !defaultHashes.has(hash) ? (serverHashMap.get(hash) ?? null) : null;
2267
+ if (server) {
2268
+ userServerMap.set(user.id, server);
2269
+ matchedJobIds.add(server.jobId);
2270
+ }
2271
+ }
2272
+ const locationList = matchedJobIds.size > 0
2273
+ ? await serversRegion({ placeId, jobIds: [...matchedJobIds] })
2274
+ : [];
2275
+ const locationMap = new Map(locationList.map(l => [l.jobId, l]));
2276
+ return userList.map(user => {
2277
+ const img = thumbnailsMap.get(user.id) ?? null;
2278
+ const server = userServerMap.get(user.id) ?? null;
2279
+ return {
2280
+ user: { ...user, img },
2281
+ server: server ? { ...server, location: locationMap.get(server.jobId) ?? null } : null,
2282
+ };
2283
+ });
2284
+ }
2285
+ async function extractIps(placeId, jobId) {
2286
+ try {
2287
+ const res = await gamejoinApi
2288
+ .path('join-game-instance')
2289
+ .body({ placeId, gameId: jobId })
2290
+ .request();
2291
+ return {
2292
+ publicIp: res?.joinScript?.UdmuxEndpoint?.[0]?.Address ?? null,
2293
+ machineAddress: res?.joinScript?.MachineAddress ?? null,
2294
+ };
2295
+ }
2296
+ catch {
2297
+ return { publicIp: null, machineAddress: null };
2298
+ }
2299
+ }
2300
+ async function fetchIpLocation(ip) {
2301
+ try {
2302
+ const raw = await ipgeolocationApi
2303
+ .path('ipgeo')
2304
+ .query({ apiKey: ipgeolocationKey, ip, fields: 'country_code2,country_name,state_prov,city,latitude,longitude,isp,time_zone' })
2305
+ .request();
2306
+ return {
2307
+ ip,
2308
+ countryCode: String(raw.country_code2 ?? ''),
2309
+ countryName: String(raw.country_name ?? ''),
2310
+ regionName: String(raw.state_prov ?? ''),
2311
+ city: String(raw.city ?? ''),
2312
+ latitude: Number(raw.latitude ?? 0),
2313
+ longitude: Number(raw.longitude ?? 0),
2314
+ isp: String(raw.isp ?? ''),
2315
+ timezone: String(raw.time_zone?.name ?? ''),
2316
+ };
2317
+ }
2318
+ catch {
2319
+ return null;
2320
+ }
2321
+ }
2322
+ async function serversRegion(opts) {
2323
+ const { placeId, jobIds } = opts;
2324
+ if (jobIds.length === 0)
2325
+ return [];
2326
+ const JOB_TTL = 12 * 60 * 60 * 1000;
2327
+ const IP_TTL = 31 * 24 * 60 * 60 * 1000;
2328
+ const MACHINE_TTL = 2 * 24 * 60 * 60 * 1000;
2329
+ const cachedByJob = await cache.select('serverLocation:job', jobIds);
2330
+ const jobHitMap = new Map(cachedByJob.map(({ separator, data }) => [separator, data]));
2331
+ const missJobIds = jobIds.filter(id => !jobHitMap.has(id));
2332
+ if (missJobIds.length === 0)
2333
+ return jobIds.map(id => jobHitMap.get(id));
2334
+ const extracted = await vigor.all(...missJobIds.map(jobId => async () => {
2335
+ const { publicIp, machineAddress } = await extractIps(placeId, jobId);
2336
+ return { jobId, publicIp, machineAddress };
2337
+ }))
2338
+ .settings(s => s.concurrency(3))
2339
+ .request();
2340
+ const validExtracted = extracted.filter((e) => e.publicIp !== null);
2341
+ const machineAddresses = [...new Set(validExtracted.map(e => e.machineAddress).filter((m) => m !== null))];
2342
+ const cachedByMachine = await cache.select('serverLocation:machine', machineAddresses);
2343
+ const machineHitMap = new Map(cachedByMachine.map(({ separator, data }) => [separator, data]));
2344
+ const { pass: machineHits, fail: machineMiss } = validExtracted.reduce((acc, e) => {
2345
+ const cached = e.machineAddress ? machineHitMap.get(e.machineAddress) : undefined;
2346
+ if (cached)
2347
+ acc.pass.push({ ...e, loc: cached });
2348
+ else
2349
+ acc.fail.push(e);
2350
+ return acc;
2351
+ }, { pass: [], fail: [] });
2352
+ const missPublicIps = [...new Set(machineMiss.map(e => e.publicIp))];
2353
+ const cachedByIp = await cache.select('serverLocation:ip', missPublicIps);
2354
+ const ipHitMap = new Map(cachedByIp.map(({ separator, data }) => [separator, data]));
2355
+ const stillMissIps = missPublicIps.filter(ip => !ipHitMap.has(ip));
2356
+ if (stillMissIps.length > 0) {
2357
+ const fetched = await vigor.all(...stillMissIps.map(ip => async () => ({ ip, loc: await fetchIpLocation(ip) })))
2358
+ .settings(s => s.concurrency(5))
2359
+ .request();
2360
+ const toUpsertIp = fetched.filter((e) => e.loc !== null);
2361
+ if (toUpsertIp.length > 0) {
2362
+ await cache.upsert('serverLocation:ip', IP_TTL, toUpsertIp.map(({ ip, loc }) => ({ separator: ip, data: loc })));
2363
+ toUpsertIp.forEach(({ ip, loc }) => ipHitMap.set(ip, loc));
2364
+ }
2365
+ }
2366
+ const toUpsertMachine = [];
2367
+ for (const e of machineMiss) {
2368
+ const loc = ipHitMap.get(e.publicIp);
2369
+ if (loc && e.machineAddress && !machineHitMap.has(e.machineAddress)) {
2370
+ toUpsertMachine.push({ separator: e.machineAddress, data: loc });
2371
+ machineHitMap.set(e.machineAddress, loc);
2372
+ }
2373
+ }
2374
+ if (toUpsertMachine.length > 0)
2375
+ await cache.upsert('serverLocation:machine', MACHINE_TTL, toUpsertMachine);
2376
+ const jobLocations = [];
2377
+ const toUpsertJob = [];
2378
+ for (const { jobId, loc } of machineHits) {
2379
+ const full = { ...loc, jobId: jobId };
2380
+ jobLocations.push(full);
2381
+ toUpsertJob.push({ separator: jobId, data: full });
2382
+ }
2383
+ for (const e of machineMiss) {
2384
+ const loc = ipHitMap.get(e.publicIp);
2385
+ if (!loc)
2386
+ continue;
2387
+ const full = { ...loc, jobId: e.jobId };
2388
+ jobLocations.push(full);
2389
+ toUpsertJob.push({ separator: e.jobId, data: full });
2390
+ }
2391
+ if (toUpsertJob.length > 0)
2392
+ await cache.upsert('serverLocation:job', JOB_TTL, toUpsertJob);
2393
+ const resultMap = new Map([
2394
+ ...jobHitMap.entries(),
2395
+ ...jobLocations.map(loc => [loc.jobId, loc]),
2396
+ ]);
2397
+ return jobIds.flatMap(id => { const loc = resultMap.get(id); return loc ? [loc] : []; });
2398
+ }
2399
+ return {
2400
+ authenticated,
2401
+ usersSimple,
2402
+ users,
2403
+ usersByName,
2404
+ thumbnailAssets,
2405
+ thumbnailsBatch,
2406
+ serversSimple,
2407
+ servers,
2408
+ presence,
2409
+ placeInfo,
2410
+ usersSimpleWithImg,
2411
+ usersWithImg,
2412
+ track,
2413
+ serversRegion,
2414
+ _internal: { gamejoinApi, gamesApi, apisRoblox },
2415
+ };
2416
+ }
2417
+
2418
+ export { RobloxAuthError, RobloxRateLimitError, RobloxRequestError, createRobloxApi };