diff --git a/html/vr.js b/html/vr.js
index a5187bbf..7f898b18 100644
--- a/html/vr.js
+++ b/html/vr.js
@@ -8,9 +8,9 @@ if (window.CefSharp) {
CefSharp.BindObjectAsync('VRCX'),
CefSharp.BindObjectAsync('VRCXStorage'),
CefSharp.BindObjectAsync('SQLite')
- ]).catch(() => {
+ ]).catch(function () {
location = 'https://github.com/pypy-vrc/vrcx';
- }).then(() => {
+ }).then(function () {
VRCXStorage.GetBool = function (key) {
return this.Get(key) === 'true';
@@ -83,6 +83,8 @@ if (window.CefSharp) {
timeout: 6000
});
+ var isObject = (any) => any === Object(any);
+
var escapeTag = (s) => String(s).replace(/["&'<>]/gu, (c) => `${c.charCodeAt(0)};`);
Vue.filter('escapeTag', escapeTag);
@@ -90,21 +92,21 @@ if (window.CefSharp) {
Vue.filter('commaNumber', commaNumber);
var formatDate = (s, format) => {
- var ctx = new Date(s);
- if (isNaN(ctx)) {
+ var dt = new Date(s);
+ if (isNaN(dt)) {
return escapeTag(s);
}
- var hours = ctx.getHours();
+ var hours = dt.getHours();
var map = {
- 'YYYY': String(10000 + ctx.getFullYear()).substr(-4),
- 'MM': String(101 + ctx.getMonth()).substr(-2),
- 'DD': String(100 + ctx.getDate()).substr(-2),
+ 'YYYY': String(10000 + dt.getFullYear()).substr(-4),
+ 'MM': String(101 + dt.getMonth()).substr(-2),
+ 'DD': String(100 + dt.getDate()).substr(-2),
'HH24': String(100 + hours).substr(-2),
'HH': String(100 + (hours > 12
? hours - 12
: hours)).substr(-2),
- 'MI': String(100 + ctx.getMinutes()).substr(-2),
- 'SS': String(100 + ctx.getSeconds()).substr(-2),
+ 'MI': String(100 + dt.getMinutes()).substr(-2),
+ 'SS': String(100 + dt.getSeconds()).substr(-2),
'AMPM': hours >= 12
? 'PM'
: 'AM'
@@ -154,47 +156,52 @@ if (window.CefSharp) {
var API = {};
- API.$handler = {};
+ API.eventHandlers = new Map();
- API.$emit = function (event, ...args) {
+ API.$emit = function (name, ...args) {
+ // console.log(name, ...args);
+ var handlers = this.eventHandlers.get(name);
+ if (handlers === undefined) {
+ return;
+ }
try {
- // console.log(event, ...args);
- var h = this.$handler[event];
- if (h) {
- h.forEach((f) => f(...args));
+ var { length } = handlers;
+ for (var i = 0; i < length; ++i) {
+ handlers[i].apply(this, args);
}
} catch (err) {
console.error(err);
}
};
- API.$on = function (event, callback) {
- var h = this.$handler[event];
- if (h) {
- h.push(callback);
- } else {
- this.$handler[event] = [callback];
+ API.$on = function (name, fx) {
+ var handlers = this.eventHandlers.get(name);
+ if (handlers === undefined) {
+ handlers = [];
+ this.eventHandlers.set(name, handlers);
}
+ handlers.push(fx);
};
- API.$off = function (event, callback) {
- var h = this.$handler[event];
- if (h) {
- h.find((val, idx, arr) => {
- if (val !== callback) {
- return false;
- }
- if (arr.length > 1) {
- arr.splice(idx, 1);
+ API.$off = function (name, fx) {
+ var handlers = this.eventHandlers.get(name);
+ if (handlers === undefined) {
+ return;
+ }
+ var { length } = handlers;
+ for (var i = 0; i < length; ++i) {
+ if (handlers[i] === fx) {
+ if (length > 1) {
+ handlers.splice(i, 1);
} else {
- delete this.$handler[event];
+ this.eventHandlers.delete(name);
}
- return true;
- });
+ break;
+ }
}
};
- API.$fetch = {};
+ API.pendingGetRequests = new Map();
API.call = function (endpoint, options) {
var input = `https://api.vrchat.cloud/api/1/${endpoint}`;
@@ -206,28 +213,33 @@ if (window.CefSharp) {
referrerPolicy: 'no-referrer',
...options
};
- if (init.method === 'GET') {
- if (init.body) {
+ var isGetRequest = init.method === 'GET';
+
+ if (isGetRequest) {
+ // transform body to url
+ if (isObject(init.body)) {
var url = new URL(input);
for (var key in init.body) {
url.searchParams.set(key, init.body[key]);
}
input = url.toString();
- init.body = null;
}
+ delete init.body;
// merge requests
- if (this.$fetch[input]) {
- return this.$fetch[input];
+ var request = this.pendingGetRequests.get(input);
+ if (request) {
+ return request;
}
} else {
init.headers = {
'Content-Type': 'application/json;charset=utf-8',
...init.headers
};
- init.body = init.body
+ init.body = isObject(init.body)
? JSON.stringify(init.body)
: '{}';
}
+
var req = fetch(input, init).catch((err) => {
this.$throw(0, err);
}).then((res) => res.json().catch(() => {
@@ -236,33 +248,41 @@ if (window.CefSharp) {
}
this.$throw(0, 'Invalid JSON');
}).then((json) => {
- if (!res.ok) {
- if (typeof json.error === 'object') {
- this.$throw(
- json.error.status_code || res.status,
- json.error.message,
- json.error.data
- );
- } else if (typeof json.error === 'string') {
- this.$throw(
- json.status_code || res.status,
- json.error
- );
- } else {
- this.$throw(res.status, json);
+ if (res.ok) {
+ if (json.success) {
+ new Noty({
+ type: 'success',
+ text: escapeTag(json.success.message)
+ }).show();
}
+ } else if (typeof json.error === 'object') {
+ this.$throw(
+ json.error.status_code || res.status,
+ json.error.message,
+ json.error.data
+ );
+ } else if (typeof json.error === 'string') {
+ this.$throw(
+ json.status_code || res.status,
+ json.error
+ );
+ } else {
+ this.$throw(res.status, json);
}
return json;
}));
- if (init.method === 'GET') {
- this.$fetch[input] = req.finally(() => {
- delete this.$fetch[input];
+
+ if (isGetRequest) {
+ req.finally(() => {
+ this.pendingGetRequests.delete(input);
});
+ this.pendingGetRequests.set(input, req);
}
+
return req;
};
- API.$status = {
+ API.statusCodes = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
@@ -338,18 +358,28 @@ if (window.CefSharp) {
};
API.$throw = function (code, error) {
- throw {
- 'status_code': code,
- error
- };
+ var text = [];
+ if (code) {
+ var status = this.statusCodes[code];
+ if (status) {
+ text.push(`${code} ${status}`);
+ } else {
+ text.push(`${code}`);
+ }
+ }
+ if (error !== undefined) {
+ text.push(error);
+ }
+ text = text.map((s) => escapeTag(s)).join('
');
+ throw new Error(text);
};
// API: Config
API.config = {};
- API.$on('CONFIG', (args) => {
- args.ref = API.updateConfig(args.json);
+ API.$on('CONFIG', function (args) {
+ args.ref = this.updateConfig(args.json);
});
API.getConfig = function () {
@@ -468,7 +498,7 @@ if (window.CefSharp) {
} else if (L.isPrivate) {
this.text = 'Private';
} else if (L.worldId) {
- var ref = API.world[L.worldId];
+ var ref = API.cachedWorlds.get(L.worldId);
if (ref) {
if (L.instanceId) {
this.text = `${ref.name} #${L.instanceName} ${L.accessType}`;
@@ -504,10 +534,10 @@ if (window.CefSharp) {
// API: World
- API.world = {};
+ API.cachedWorlds = new Map();
- API.$on('WORLD', (args) => {
- args.ref = API.updateWorld(args.json);
+ API.$on('WORLD', function (args) {
+ args.ref = this.updateWorld(args.json);
});
/*
@@ -529,7 +559,7 @@ if (window.CefSharp) {
};
API.updateWorld = function (ref) {
- var ctx = this.world[ref.id];
+ var ctx = this.cachedWorlds.get(ref.id);
if (ctx) {
Object.assign(ctx, ref);
} else {
@@ -570,7 +600,7 @@ if (window.CefSharp) {
//
...ref
};
- this.world[ctx.id] = ctx;
+ this.cachedWorlds.set(ctx.id, ctx);
}
if (ctx.tags) {
ctx.labs_ = ctx.tags.includes('system_labs');