diff --git a/Program.cs b/Program.cs
index 58c1f092..fabe38f1 100644
--- a/Program.cs
+++ b/Program.cs
@@ -60,11 +60,13 @@ namespace VRCX
CpuMonitor.Instance.Init();
Discord.Instance.Init();
SQLite.Instance.Init();
+ WebApi.Instance.Init();
LogWatcher.Instance.Init();
VRCXVR.Init();
Application.Run(new MainForm());
VRCXVR.Exit();
LogWatcher.Instance.Exit();
+ WebApi.Instance.Exit();
SQLite.Instance.Exit();
Discord.Instance.Exit();
CpuMonitor.Instance.Exit();
diff --git a/Util.cs b/Util.cs
index 06eb37c5..0d97cbbe 100644
--- a/Util.cs
+++ b/Util.cs
@@ -12,6 +12,7 @@ namespace VRCX
};
repository.Register("VRCX", VRCX.Instance, true, options);
repository.Register("SharedVariable", SharedVariable.Instance, false, options);
+ repository.Register("WebApi", WebApi.Instance, true, options);
repository.Register("VRCXStorage", VRCXStorage.Instance, false, options);
repository.Register("SQLite", SQLite.Instance, true, options);
repository.Register("LogWatcher", LogWatcher.Instance, true, options);
diff --git a/VRCX.csproj b/VRCX.csproj
index 4b3b18a9..a490a2bb 100644
--- a/VRCX.csproj
+++ b/VRCX.csproj
@@ -131,6 +131,7 @@
+
diff --git a/WebApi.cs b/WebApi.cs
new file mode 100644
index 00000000..32eba673
--- /dev/null
+++ b/WebApi.cs
@@ -0,0 +1,149 @@
+using CefSharp;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace VRCX
+{
+ public class WebApi
+ {
+ private readonly string COOKIE_FILE_NAME = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cookies.dat");
+ public static WebApi Instance { get; private set; }
+ private CookieContainer _cookieContainer;
+
+ static WebApi()
+ {
+ Instance = new WebApi();
+ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
+ }
+
+ public WebApi()
+ {
+ _cookieContainer = new CookieContainer();
+ }
+
+ internal void Init()
+ {
+ try
+ {
+ using (var file = File.Open(COOKIE_FILE_NAME, FileMode.Open, FileAccess.Read))
+ {
+ _cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(file);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ internal void Exit()
+ {
+ try
+ {
+ using (var file = File.Open(COOKIE_FILE_NAME, FileMode.Create, FileAccess.Write))
+ {
+ new BinaryFormatter().Serialize(file, _cookieContainer);
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ public void ClearCookies()
+ {
+ _cookieContainer = new CookieContainer();
+ }
+
+#pragma warning disable CS4014
+ public async void Execute(IDictionary options, IJavascriptCallback callback)
+ {
+ try
+ {
+ var request = WebRequest.CreateHttp((string)options["url"]);
+ request.CookieContainer = _cookieContainer;
+ request.KeepAlive = true;
+
+ if (options.TryGetValue("headers", out object headers) == true)
+ {
+ foreach (var header in (IEnumerable>)headers)
+ {
+ var key = header.Key;
+ var value = header.Value.ToString();
+
+ if (string.Compare(key, "Content-Type", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ request.ContentType = value;
+ }
+ else if (string.Compare(key, "User-Agent", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ request.UserAgent = value;
+ }
+ else
+ {
+ request.Headers.Add(key, value);
+ }
+ }
+ }
+
+ if (options.TryGetValue("method", out object method) == true)
+ {
+ var _method = (string)method;
+ request.Method = _method;
+
+ if (string.Compare(_method, "GET", StringComparison.OrdinalIgnoreCase) != 0 &&
+ options.TryGetValue("body", out object body) == true)
+ {
+ using (var stream = await request.GetRequestStreamAsync())
+ using (var streamWriter = new StreamWriter(stream))
+ {
+ await streamWriter.WriteAsync((string)body);
+ }
+ }
+ }
+
+ try
+ {
+ using (var response = await request.GetResponseAsync() as HttpWebResponse)
+ using (var stream = response.GetResponseStream())
+ using (var streamReader = new StreamReader(stream))
+ {
+ callback.ExecuteAsync(null, new
+ {
+ data = await streamReader.ReadToEndAsync(),
+ status = response.StatusCode
+ });
+ }
+ }
+ catch (WebException webException)
+ {
+ if (webException.Response is HttpWebResponse response)
+ {
+ using (var stream = response.GetResponseStream())
+ using (var streamReader = new StreamReader(stream))
+ {
+ callback.ExecuteAsync(null, new
+ {
+ data = await streamReader.ReadToEndAsync(),
+ status = response.StatusCode
+ });
+ }
+ }
+ else
+ {
+ callback.ExecuteAsync(webException.Message, null);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ callback.ExecuteAsync(e.Message, null);
+ }
+
+ callback.Dispose();
+ }
+#pragma warning restore CS4014
+ }
+}
diff --git a/html/src/app.js b/html/src/app.js
index 9fc4a36b..5f8a809e 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -13,12 +13,13 @@ import locale from 'element-ui/lib/locale/lang/en';
import sharedRepository from './repository/shared.js';
import configRepository from './repository/config.js';
-
window.sharedRepository = sharedRepository;
window.configRepository = configRepository;
+import webApiService from './service/webapi.js';
(async function () {
await CefSharp.BindObjectAsync(
+ 'WebApi',
'VRCX',
'SharedVariable', // DO NOT DIRECT ACCESS
'VRCXStorage',
@@ -330,30 +331,25 @@ window.configRepository = configRepository;
API.pendingGetRequests = new Map();
API.call = function (endpoint, options) {
- var resource = `https://api.vrchat.cloud/api/1/${endpoint}`;
var init = {
+ url: `https://api.vrchat.cloud/api/1/${endpoint}`,
method: 'GET',
- mode: 'cors',
- credentials: 'include',
- cache: 'no-cache',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
...options
};
var { params } = init;
var isGetRequest = init.method === 'GET';
- if (isGetRequest) {
+ if (isGetRequest === true) {
// transform body to url
if (params === Object(params)) {
- var url = new URL(resource);
+ var url = new URL(init.url);
var { searchParams } = url;
for (var key in params) {
searchParams.set(key, params[key]);
}
- resource = url.toString();
+ init.url = url.toString();
}
// merge requests
- var req = this.pendingGetRequests.get(resource);
+ var req = this.pendingGetRequests.get(init.url);
if (req !== undefined) {
return req;
}
@@ -366,45 +362,50 @@ window.configRepository = configRepository;
? JSON.stringify(params)
: '{}';
}
- var req = fetch(resource, init).catch((err) => {
+ var req = webApiService.execute(init).catch((err) => {
this.$throw(0, err);
- }).then((res) => res.json().catch(() => {
- if (res.ok) {
+ }).then((response) => {
+ try {
+ response.data = JSON.parse(response.data);
+ return response;
+ } catch (e) {
+ }
+ if (response.status === 200) {
this.$throw(0, 'Invalid JSON response');
}
this.$throw(res.status);
- }).then((json) => {
- if (res.ok) {
- if (json.success === Object(json.success)) {
- new Noty({
- type: 'success',
- text: escapeTag(json.success.message)
- }).show();
+ }).then(({ data, status }) => {
+ if (data === Object(data)) {
+ if (status === 200) {
+ if (data.success === Object(data.success)) {
+ new Noty({
+ type: 'success',
+ text: escapeTag(data.success.message)
+ }).show();
+ }
+ return data;
}
- return json;
- }
- if (json === Object(json)) {
- if (json.error === Object(json.error)) {
+ if (data.error === Object(data.error)) {
this.$throw(
- json.error.status_code || res.status,
- json.error.message,
- json.error.data
+ data.error.status_code || status,
+ data.error.message,
+ data.error.data
);
- } else if (typeof json.error === 'string') {
+ } else if (typeof data.error === 'string') {
this.$throw(
- json.status_code || res.status,
- json.error
+ data.status_code || status,
+ data.error
);
}
}
- this.$throw(res.status, json);
- return json;
- }));
- if (isGetRequest) {
+ this.$throw(status, data);
+ return data;
+ });
+ if (isGetRequest === true) {
req.finally(() => {
- this.pendingGetRequests.delete(resource);
+ this.pendingGetRequests.delete(init.url);
});
- this.pendingGetRequests.set(resource, req);
+ this.pendingGetRequests.set(init.url, req);
}
return req;
};
@@ -817,7 +818,7 @@ window.configRepository = configRepository;
API.currentUser = {};
API.$on('LOGOUT', function () {
- VRCX.DeleteAllCookies();
+ webApiService.clearCookies();
this.isLoggedIn = false;
});
@@ -3341,28 +3342,33 @@ window.configRepository = configRepository;
return style;
};
- $app.methods.checkAppVersion = function () {
- var url = 'https://api.github.com/repos/pypy-vrc/VRCX/releases/latest';
- fetch(url).then((res) => res.json()).then((json) => {
- if (json === Object(json) &&
- json.name &&
- json.published_at) {
- this.latestAppVersion = `${json.name} (${formatDate(json.published_at, 'YYYY-MM-DD HH24:MI:SS')})`;
- if (json.name > this.appVersion) {
- new Noty({
- type: 'info',
- text: `Update available!!
${this.latestAppVersion}`,
- timeout: 60000,
- callbacks: {
- onClick: () => VRCX.OpenLink('https://github.com/pypy-vrc/VRCX/releases')
- }
- }).show();
- this.notifyMenu('more');
- }
- } else {
- this.latestAppVersion = 'Error occured';
+ $app.methods.checkAppVersion = async function () {
+ var response = await webApiService.execute({
+ url: 'https://api.github.com/repos/pypy-vrc/VRCX/releases/latest',
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'VRCX'
}
});
+ var json = JSON.parse(response.data);
+ if (json === Object(json) &&
+ json.name &&
+ json.published_at) {
+ this.latestAppVersion = `${json.name} (${formatDate(json.published_at, 'YYYY-MM-DD HH24:MI:SS')})`;
+ if (json.name > this.appVersion) {
+ new Noty({
+ type: 'info',
+ text: `Update available!!
${this.latestAppVersion}`,
+ timeout: 60000,
+ callbacks: {
+ onClick: () => VRCX.OpenLink('https://github.com/pypy-vrc/VRCX/releases')
+ }
+ }).show();
+ this.notifyMenu('more');
+ }
+ } else {
+ this.latestAppVersion = 'Error occured';
+ }
};
$app.methods.updateLoop = function () {
diff --git a/html/src/service/webapi.js b/html/src/service/webapi.js
new file mode 100644
index 00000000..1031f005
--- /dev/null
+++ b/html/src/service/webapi.js
@@ -0,0 +1,26 @@
+// requires binding of WebApi
+
+class WebApiService {
+ clearCookies() {
+ return WebApi.ClearCookies();
+ }
+
+ execute(options) {
+ return new Promise((resolve, reject) => {
+ WebApi.Execute(options, (err, response) => {
+ if (err !== null) {
+ reject(err);
+ return;
+ }
+ resolve(response);
+ });
+ });
+ }
+}
+
+var self = new WebApiService();
+
+export {
+ self as default,
+ WebApiService
+};