mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-17 13:53:52 +02:00
Refactor 1
This commit is contained in:
372
html/src/classes/apiRequestHandler.js
Normal file
372
html/src/classes/apiRequestHandler.js
Normal file
@@ -0,0 +1,372 @@
|
||||
import Noty from 'noty';
|
||||
import { baseClass, $app, API, $t } from './baseClass.js';
|
||||
/* eslint-disable no-unused-vars */
|
||||
let webApiService = {};
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
export default class extends baseClass {
|
||||
constructor(_app, _API, _t, _webApiService) {
|
||||
super(_app, _API, _t);
|
||||
webApiService = _webApiService;
|
||||
}
|
||||
|
||||
init() {
|
||||
API.cachedConfig = {};
|
||||
API.pendingGetRequests = new Map();
|
||||
API.failedGetRequests = new Map();
|
||||
API.endpointDomainVrchat = 'https://api.vrchat.cloud/api/1';
|
||||
API.websocketDomainVrchat = 'wss://pipeline.vrchat.cloud';
|
||||
API.endpointDomain = 'https://api.vrchat.cloud/api/1';
|
||||
API.websocketDomain = 'wss://pipeline.vrchat.cloud';
|
||||
|
||||
API.call = function (endpoint, options) {
|
||||
var init = {
|
||||
url: `${API.endpointDomain}/${endpoint}`,
|
||||
method: 'GET',
|
||||
...options
|
||||
};
|
||||
var { params } = init;
|
||||
if (init.method === 'GET') {
|
||||
// don't retry recent 404/403
|
||||
if (this.failedGetRequests.has(endpoint)) {
|
||||
var lastRun = this.failedGetRequests.get(endpoint);
|
||||
if (lastRun >= Date.now() - 900000) {
|
||||
// 15mins
|
||||
throw new Error(
|
||||
`Bailing request due to recent 404/403, ${endpoint}`
|
||||
);
|
||||
}
|
||||
this.failedGetRequests.delete(endpoint);
|
||||
}
|
||||
// transform body to url
|
||||
if (params === Object(params)) {
|
||||
var url = new URL(init.url);
|
||||
var { searchParams } = url;
|
||||
for (var key in params) {
|
||||
searchParams.set(key, params[key]);
|
||||
}
|
||||
init.url = url.toString();
|
||||
}
|
||||
// merge requests
|
||||
var req = this.pendingGetRequests.get(init.url);
|
||||
if (typeof req !== 'undefined') {
|
||||
if (req.time >= Date.now() - 10000) {
|
||||
// 10s
|
||||
return req.req;
|
||||
}
|
||||
this.pendingGetRequests.delete(init.url);
|
||||
}
|
||||
} else if (
|
||||
init.uploadImage ||
|
||||
init.uploadFilePUT ||
|
||||
init.uploadImageLegacy
|
||||
) {
|
||||
// nothing
|
||||
} else {
|
||||
init.headers = {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
...init.headers
|
||||
};
|
||||
init.body =
|
||||
params === Object(params) ? JSON.stringify(params) : '{}';
|
||||
}
|
||||
var req = webApiService
|
||||
.execute(init)
|
||||
.catch((err) => {
|
||||
this.$throw(0, err, endpoint);
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.data) {
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
response.data = JSON.parse(response.data);
|
||||
if ($app.debugWebRequests) {
|
||||
console.log(init, response.data);
|
||||
}
|
||||
return response;
|
||||
} catch (e) {}
|
||||
if (response.status === 200) {
|
||||
this.$throw(0, 'Invalid JSON response', endpoint);
|
||||
}
|
||||
if (
|
||||
response.status === 429 &&
|
||||
init.url.endsWith('/instances/groups')
|
||||
) {
|
||||
$app.nextGroupInstanceRefresh = 120; // 1min
|
||||
throw new Error(
|
||||
`${response.status}: rate limited ${endpoint}`
|
||||
);
|
||||
}
|
||||
if (response.status === 504 || response.status === 502) {
|
||||
// ignore expected API errors
|
||||
throw new Error(
|
||||
`${response.status}: ${response.data} ${endpoint}`
|
||||
);
|
||||
}
|
||||
this.$throw(response.status, endpoint);
|
||||
return {};
|
||||
})
|
||||
.then(({ data, status }) => {
|
||||
if (status === 200) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
var text = '';
|
||||
if (data.success === Object(data.success)) {
|
||||
text = data.success.message;
|
||||
} else if (data.OK === String(data.OK)) {
|
||||
text = data.OK;
|
||||
}
|
||||
if (text) {
|
||||
new Noty({
|
||||
type: 'success',
|
||||
text: $app.escapeTag(text)
|
||||
}).show();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
if (
|
||||
status === 401 &&
|
||||
data.error.message === '"Missing Credentials"'
|
||||
) {
|
||||
this.$emit('AUTOLOGIN');
|
||||
throw new Error('401: Missing Credentials');
|
||||
}
|
||||
if (
|
||||
status === 401 &&
|
||||
data.error.message === '"Unauthorized"' &&
|
||||
endpoint !== 'auth/user'
|
||||
) {
|
||||
// trigger 2FA dialog
|
||||
if (!$app.twoFactorAuthDialogVisible) {
|
||||
$app.API.getCurrentUser();
|
||||
}
|
||||
throw new Error('401: Unauthorized');
|
||||
}
|
||||
if (status === 403 && endpoint === 'config') {
|
||||
$app.$alert(
|
||||
'VRChat currently blocks most VPNs. Please disable any connected VPNs and try again.',
|
||||
'Login Error 403'
|
||||
);
|
||||
this.logout();
|
||||
throw new Error(`403: ${endpoint}`);
|
||||
}
|
||||
if (
|
||||
init.method === 'GET' &&
|
||||
status === 404 &&
|
||||
endpoint.startsWith('avatars/')
|
||||
) {
|
||||
$app.$message({
|
||||
message: 'Avatar private or deleted',
|
||||
type: 'error'
|
||||
});
|
||||
$app.avatarDialog.visible = false;
|
||||
throw new Error(
|
||||
`404: ${data.error.message} ${endpoint}`
|
||||
);
|
||||
}
|
||||
if (
|
||||
status === 404 &&
|
||||
endpoint.endsWith('/persist/exists')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
init.method === 'GET' &&
|
||||
(status === 404 || status === 403) &&
|
||||
!endpoint.startsWith('auth/user')
|
||||
) {
|
||||
this.failedGetRequests.set(endpoint, Date.now());
|
||||
}
|
||||
if (
|
||||
init.method === 'GET' &&
|
||||
status === 404 &&
|
||||
endpoint.startsWith('users/') &&
|
||||
endpoint.split('/').length - 1 === 1
|
||||
) {
|
||||
throw new Error(
|
||||
`404: ${data.error.message} ${endpoint}`
|
||||
);
|
||||
}
|
||||
if (
|
||||
status === 404 &&
|
||||
endpoint.startsWith('invite/') &&
|
||||
init.inviteId
|
||||
) {
|
||||
this.expireNotification(init.inviteId);
|
||||
}
|
||||
if (
|
||||
status === 403 &&
|
||||
endpoint.startsWith('invite/myself/to/')
|
||||
) {
|
||||
throw new Error(
|
||||
`403: ${data.error.message} ${endpoint}`
|
||||
);
|
||||
}
|
||||
if (data && data.error === Object(data.error)) {
|
||||
this.$throw(
|
||||
data.error.status_code || status,
|
||||
data.error.message,
|
||||
endpoint
|
||||
);
|
||||
} else if (data && typeof data.error === 'string') {
|
||||
this.$throw(
|
||||
data.status_code || status,
|
||||
data.error,
|
||||
endpoint
|
||||
);
|
||||
}
|
||||
this.$throw(status, data, endpoint);
|
||||
return data;
|
||||
});
|
||||
if (init.method === 'GET') {
|
||||
req.finally(() => {
|
||||
this.pendingGetRequests.delete(init.url);
|
||||
});
|
||||
this.pendingGetRequests.set(init.url, {
|
||||
req,
|
||||
time: Date.now()
|
||||
});
|
||||
}
|
||||
return req;
|
||||
};
|
||||
|
||||
// FIXME : extra를 없애줘
|
||||
API.$throw = function (code, error, endpoint) {
|
||||
var text = [];
|
||||
if (code > 0) {
|
||||
var status = this.statusCodes[code];
|
||||
if (typeof status === 'undefined') {
|
||||
text.push(`${code}`);
|
||||
} else {
|
||||
text.push(`${code} ${status}`);
|
||||
}
|
||||
}
|
||||
if (typeof error !== 'undefined') {
|
||||
text.push(JSON.stringify(error));
|
||||
}
|
||||
if (typeof endpoint !== 'undefined') {
|
||||
text.push(JSON.stringify(endpoint));
|
||||
}
|
||||
text = text.map((s) => $app.escapeTag(s)).join('<br>');
|
||||
if (text.length) {
|
||||
if (this.errorNoty) {
|
||||
this.errorNoty.close();
|
||||
}
|
||||
this.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text
|
||||
}).show();
|
||||
}
|
||||
throw new Error(text);
|
||||
};
|
||||
|
||||
API.$bulk = function (options, args) {
|
||||
if ('handle' in options) {
|
||||
options.handle.call(this, args, options);
|
||||
}
|
||||
if (
|
||||
args.json.length > 0 &&
|
||||
((options.params.offset += args.json.length),
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
options.N > 0
|
||||
? options.N > options.params.offset
|
||||
: options.N < 0
|
||||
? args.json.length
|
||||
: options.params.n === args.json.length)
|
||||
) {
|
||||
this.bulk(options);
|
||||
} else if ('done' in options) {
|
||||
options.done.call(this, true, options);
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
API.bulk = function (options) {
|
||||
this[options.fn](options.params)
|
||||
.catch((err) => {
|
||||
if ('done' in options) {
|
||||
options.done.call(this, false, options);
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
.then((args) => this.$bulk(options, args));
|
||||
};
|
||||
|
||||
API.statusCodes = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
102: 'Processing',
|
||||
103: 'Early Hints',
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
207: 'Multi-Status',
|
||||
208: 'Already Reported',
|
||||
226: 'IM Used',
|
||||
300: 'Multiple Choices',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
306: 'Switch Proxy',
|
||||
307: 'Temporary Redirect',
|
||||
308: 'Permanent Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Timeout',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Payload Too Large',
|
||||
414: 'URI Too Long',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: "I'm a teapot",
|
||||
421: 'Misdirected Request',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
424: 'Failed Dependency',
|
||||
425: 'Too Early',
|
||||
426: 'Upgrade Required',
|
||||
428: 'Precondition Required',
|
||||
429: 'Too Many Requests',
|
||||
431: 'Request Header Fields Too Large',
|
||||
451: 'Unavailable For Legal Reasons',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported',
|
||||
506: 'Variant Also Negotiates',
|
||||
507: 'Insufficient Storage',
|
||||
508: 'Loop Detected',
|
||||
510: 'Not Extended',
|
||||
511: 'Network Authentication Required',
|
||||
// CloudFlare Error
|
||||
520: 'Web server returns an unknown error',
|
||||
521: 'Web server is down',
|
||||
522: 'Connection timed out',
|
||||
523: 'Origin is unreachable',
|
||||
524: 'A timeout occurred',
|
||||
525: 'SSL handshake failed',
|
||||
526: 'Invalid SSL certificate',
|
||||
527: 'Railgun Listener to origin error'
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user