diff --git a/src/mami.js/conman.js b/src/mami.js/conman.js
new file mode 100644
index 0000000..c9d2249
--- /dev/null
+++ b/src/mami.js/conman.js
@@ -0,0 +1,110 @@
+#include eventtarget.js
+#include utility.js
+
+const MamiConnectionManager = function(urls) {
+ if(!Array.isArray(urls))
+ throw 'urls must be an array';
+
+ const eventTarget = new MamiEventTarget('mami:conn');
+ const delays = [0, 2000, 2000, 2000, 5000, 5000, 5000, 5000, 5000, 10000, 10000, 10000, 10000, 10000, 15000, 30000, 45000, 60000, 120000, 300000];
+
+ let timeout;
+ let attempts, started, delay, url;
+ let attemptConnect;
+ let startResolve;
+
+ const resetTimeout = () => {
+ if(timeout !== undefined) {
+ clearTimeout(timeout);
+ timeout = undefined;
+ }
+ };
+
+ const clear = () => {
+ resetTimeout();
+ attempts = started = delay = 0;
+ url = undefined;
+ };
+
+ $as(urls);
+
+ const onFailure = ex => {
+ ++attempts;
+ delay = attempts < delays.length ? delays[attempts] : delays[delays.length - 1];
+
+ eventTarget.dispatch('fail', {
+ url: url,
+ started: started,
+ attempt: attempts,
+ delay: delay,
+ error: ex,
+ });
+
+ attempt();
+ };
+
+ const attempt = () => {
+ started = Date.now();
+ url = urls[attempts % urls.length];
+
+ const attempt = attempts + 1;
+
+ timeout = setTimeout(() => {
+ resetTimeout();
+
+ eventTarget.dispatch('attempt', {
+ url: url,
+ started: started,
+ attempt: attempt,
+ });
+
+ attemptConnect(url).then(result => {
+ if(typeof result === 'boolean' && !result) {
+ onFailure();
+ return;
+ }
+
+ eventTarget.dispatch('success', {
+ url: url,
+ started: started,
+ attempt: attempt,
+ });
+
+ startResolve();
+ startResolve = undefined;
+ attemptConnect = undefined;
+ }).catch(ex => onFailure(ex));
+ }, delay);
+ };
+
+ const isActive = () => timeout !== undefined || startResolve !== undefined;
+
+ return {
+ isActive: isActive,
+ start: body => {
+ return new Promise(resolve => {
+ if(typeof body !== 'function')
+ throw 'body must be a function';
+ if(isActive())
+ throw 'already attempting to connect';
+
+ attemptConnect = body;
+ startResolve = resolve;
+
+ clear();
+ attempt();
+ });
+ },
+ force: () => {
+ if(!isActive())
+ return;
+
+ resetTimeout();
+ delay = 0;
+ attempt();
+ },
+ clear: clear,
+ watch: eventTarget.watch,
+ unwatch: eventTarget.unwatch,
+ };
+};
diff --git a/src/mami.js/main.js b/src/mami.js/main.js
index bb4499b..cfc99fa 100644
--- a/src/mami.js/main.js
+++ b/src/mami.js/main.js
@@ -5,6 +5,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
#include channels.js
#include common.js
#include compat.js
+#include conman.js
#include context.js
#include emotes.js
#include message.js
@@ -151,7 +152,6 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
}
settings.watch('soundEnable', ev => {
- console.log(ev);
if(ev.detail.value) {
if(!soundCtx.ready)
soundCtx.reset();
@@ -221,14 +221,16 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
loadingOverlay.setMessage('Preparing UI...');
const textTriggers = new MamiTextTriggers;
+ const conMan = new MamiConnectionManager(futami.get('servers'));
- // define this as late as possible'
+ // define this as late as possible
const ctx = new MamiContext({
settings: settings,
views: views,
sound: soundCtx,
textTriggers: textTriggers,
eeprom: eeprom,
+ conMan: conMan,
});
Object.defineProperty(window, 'mami', { enumerable: true, value: ctx });
@@ -535,11 +537,15 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.UI.InputMenus.Add('markup', 'BB Code');
Umi.UI.InputMenus.Add('emotes', 'Emoticons');
+ let isUnloading = false;
+
window.addEventListener('beforeunload', function(ev) {
if(settings.get('closeTabConfirm')) {
ev.preventDefault();
return ev.returnValue = 'Are you sure you want to close the tab?';
}
+
+ isUnloading = true;
});
@@ -573,7 +579,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
'_1008': 'Your client did something the server did not agree with.',
'_1009': 'Your client sent too much data to the server at once.',
'_1011': 'Something went wrong on the server side.',
- '_1012': 'The server is restarting, reconnecting soon...',
+ '_1012': 'The chat server is restarting.',
'_1013': 'You cannot connect to the server right now, try again later.',
'_1015': 'Your client and the server could not establish a secure connection.',
};
@@ -597,30 +603,54 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
MamiCompat('Umi.Protocol.SockChat.Protocol.Instance.SendMessage', { value: text => sockChat.sendMessage(text), configurable: true });
MamiCompat('Umi.Protocol.SockLegacy.Protocol.Instance.SendMessage', { value: text => sockChat.sendMessage(text), configurable: true });
- sockChat.watch('conn:init', ev => {
- if(dumpEvents) console.log('conn:init', ev);
-
- let message = 'Connecting to server...';
- if(ev.detail.attempt > 2)
- message += ` (Attempt ${connectAttempts})`;
-
- getLoadingOverlay('spinner', 'Loading...', message);
- });
- sockChat.watch('conn:ready', ev => {
- if(dumpEvents) console.log('conn:ready', ev);
-
- getLoadingOverlay('spinner', 'Loading...', 'Authenticating...');
-
- const authInfo = MamiMisuzuAuth.getInfo();
- sockChat.sendAuth(authInfo.method, authInfo.token);
- });
sockChat.watch('conn:lost', ev => {
if(dumpEvents) console.log('conn:lost', ev);
- getLoadingOverlay(
- 'unlink', 'Disconnected!',
- wsCloseReasons[`_${ev.detail.code}`] ?? `Something caused an unexpected connection loss. (${ev.detail.code})`
- );
+ if(conMan.isActive() || isUnloading)
+ return;
+
+ const errorCode = ev.detail.code;
+ const isRestarting = ev.detail.isRestarting;
+
+ pingToggle.title = '∞ms';
+ pingIndicator.setStrength(-1);
+
+ const conManAttempt = ev => {
+ if(isRestarting || ev.detail.attempt > 1) {
+ let message = ev.detail.attempt > 2 ? `Attempt ${ev.detail.attempt}...` : 'Connecting to server...';
+ getLoadingOverlay('spinner', 'Connecting...', message);
+ }
+ };
+ const conManFail = ev => {
+ if(isRestarting || ev.detail.attempt > 1) {
+ let header = isRestarting ? 'Restarting...' : 'Disconnected';
+ let message = wsCloseReasons[`_${errorCode}`] ?? `Something caused an unexpected connection loss. (${errorCode})`;
+
+ if(ev.detail.delay > 0)
+ message += ` Retrying in ${ev.detail.delay / 1000} seconds...`;
+
+ // this is absolutely disgusting but i really don't care right now sorry
+ message += ' Retry now';
+
+ getLoadingOverlay('unlink', header, message);
+ }
+ };
+
+ conMan.watch('attempt', conManAttempt);
+ conMan.watch('fail', conManFail);
+
+ conMan.start(async url => {
+ await sockChat.open(url);
+ }).then(() => {
+ conMan.unwatch('attempt', conManAttempt);
+ conMan.unwatch('fail', conManFail);
+
+ pingToggle.title = 'Ready~';
+ pingIndicator.setStrength(3);
+
+ const authInfo = MamiMisuzuAuth.getInfo();
+ sockChat.sendAuth(authInfo.method, authInfo.token);
+ });
});
sockChat.watch('ping:send', ev => {
@@ -643,7 +673,6 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
pingIndicator.setStrength(strength);
});
-
sockChat.watch('session:start', ev => {
if(dumpEvents) console.log('session:start', ev);
@@ -655,23 +684,24 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.UI.Emoticons.Init();
Umi.Parsing.Init();
- views.pop(ctx => MamiAnimate({
- async: true,
- duration: 120,
- easing: 'inOutSine',
- start: () => {
- ctx.toElem.style.zIndex = '100';
- ctx.fromElem.style.pointerEvents = 'none';
- ctx.fromElem.style.zIndex = '200';
- },
- update: t => {
- ctx.fromElem.style.transform = `scale(${1 + (.25 * t)})`;
- ctx.fromElem.style.opacity = 1 - (1 * t).toString();
- },
- end: () => {
- ctx.toElem.style.zIndex = null;
- },
- }));
+ if(views.count() > 1)
+ views.pop(ctx => MamiAnimate({
+ async: true,
+ duration: 120,
+ easing: 'inOutSine',
+ start: () => {
+ ctx.toElem.style.zIndex = '100';
+ ctx.fromElem.style.pointerEvents = 'none';
+ ctx.fromElem.style.zIndex = '200';
+ },
+ update: t => {
+ ctx.fromElem.style.transform = `scale(${1 + (.25 * t)})`;
+ ctx.fromElem.style.opacity = 1 - (1 * t).toString();
+ },
+ end: () => {
+ ctx.toElem.style.zIndex = null;
+ },
+ }));
});
sockChat.watch('session:fail', ev => {
if(dumpEvents) console.log('session:fail', ev);
@@ -880,7 +910,28 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.UI.Messages.RemoveAll();
});
- sockChat.open();
+ const conManAttempt = ev => {
+ let message = ev.detail.attempt > 2 ? `Attempt ${ev.detail.attempt}...` : 'Connecting to server...';
+ getLoadingOverlay('spinner', 'Connecting...', message);
+ };
+ const conManFail = ev => {
+ getLoadingOverlay('cross', 'Failed to connect', `Retrying in ${ev.detail.delay / 1000} seconds...`);
+ };
+
+ conMan.watch('attempt', conManAttempt);
+ conMan.watch('fail', conManFail);
+
+ await conMan.start(async url => {
+ await sockChat.open(url);
+ });
+
+ conMan.unwatch('attempt', conManAttempt);
+ conMan.unwatch('fail', conManFail);
+
+ getLoadingOverlay('spinner', 'Connecting...', 'Authenticating...');
+
+ const authInfo = MamiMisuzuAuth.getInfo();
+ sockChat.sendAuth(authInfo.method, authInfo.token);
if(window.dispatchEvent)
window.dispatchEvent(new Event('umi:connect'));
diff --git a/src/mami.js/servers.js b/src/mami.js/servers.js
deleted file mode 100644
index 315359e..0000000
--- a/src/mami.js/servers.js
+++ /dev/null
@@ -1,27 +0,0 @@
-#include common.js
-#include utility.js
-
-const UmiServers = (function() {
- let servers = undefined,
- index = Number.MAX_SAFE_INTEGER - 1;
-
- return {
- getServer: function(callback) {
- // FutamiCommon is delayed load
- if(servers === undefined) {
- const futamiServers = futami.get('servers');
- $as(futamiServers);
- servers = futamiServers;
- }
-
- if(++index >= servers.length)
- index = 0;
-
- let server = servers[index];
- if(server.includes('//'))
- server = location.protocol.replace('http', 'ws') + server;
-
- callback(server);
- },
- };
-})();
diff --git a/src/mami.js/sockchat_old.js b/src/mami.js/sockchat_old.js
index 509cb1d..6886db0 100644
--- a/src/mami.js/sockchat_old.js
+++ b/src/mami.js/sockchat_old.js
@@ -1,5 +1,4 @@
#include eventtarget.js
-#include servers.js
#include websock.js
Umi.Protocol.SockChat.Protocol = function(pingDuration) {
@@ -31,8 +30,6 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
};
let wasConnected = false;
- let noReconnect = false;
- let connectAttempts = 0;
let wasKicked = false;
let isRestarting = false;
let dumpPackets = false;
@@ -40,6 +37,7 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
let sock;
let selfUserId, selfChannelName, selfPseudoChannelName;
let lastPing, lastPong, pingTimer, pingWatcher;
+ let openResolve, openReject;
const handlers = {};
@@ -104,7 +102,10 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
isRestarting = false;
- eventTarget.dispatch('conn:ready', { wasConnected: wasConnected });
+ if(typeof openResolve === 'function') {
+ openResolve();
+ openResolve = undefined;
+ }
};
const onClose = ev => {
@@ -126,15 +127,20 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
} else if(code === 1012)
isRestarting = true;
+ if(typeof openReject === 'function') {
+ openReject({
+ code: code,
+ wasConnected: wasConnected,
+ isRestarting: isRestarting,
+ });
+ openReject = undefined;
+ }
+
eventTarget.dispatch('conn:lost', {
wasConnected: wasConnected,
isRestarting: isRestarting,
code: code,
});
-
- connectAttempts = 0;
-
- setTimeout(() => beginConnecting(), 5000);
};
const unfuckText = text => text.replace(/
/g, "\n");
@@ -501,7 +507,6 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
// baka (ban/kick)
handlers['9'] = (type, expiry) => {
- noReconnect = true;
wasKicked = true;
const bakaInfo = {
@@ -534,49 +539,43 @@ Umi.Protocol.SockChat.Protocol = function(pingDuration) {
};
- const beginConnecting = () => {
- sock?.close();
-
- if(noReconnect)
- return;
-
- UmiServers.getServer(server => {
- eventTarget.dispatch('conn:init', {
- server: server,
- wasConnected: wasConnected,
- attempt: ++connectAttempts,
- });
-
- sock = new UmiWebSocket(server);
- sock.watch('open', onOpen);
- sock.watch('close', onClose);
- sock.watch('message', onMessage);
- sock.watch('create_interval', ev => {
- pingTimer = ev.detail.id;
- });
- sock.watch('call_interval', ev => {
- if(ev.detail.id === pingTimer)
- onSendPing();
- });
- sock.watch('create_intervals', ev => {
- pingTimer = undefined;
- });
- });
- };
-
return {
sendAuth: sendAuth,
sendMessage: sendMessage,
- open: () => {
- noReconnect = false;
- beginConnecting();
+ open: url => {
+ return new Promise((resolve, reject) => {
+ if(typeof url !== 'string')
+ throw 'url must be a string';
+ if(url.startsWith('//'))
+ url = location.protocol.replace('http', 'ws') + url;
+
+ openResolve = resolve;
+ openReject = reject;
+
+ sock?.close();
+
+ sock = new UmiWebSocket(url);
+ sock.watch('open', onOpen);
+ sock.watch('close', onClose);
+ sock.watch('message', onMessage);
+ sock.watch('create_interval', ev => {
+ pingTimer = ev.detail.id;
+ });
+ sock.watch('call_interval', ev => {
+ if(ev.detail.id === pingTimer)
+ onSendPing();
+ });
+ sock.watch('create_intervals', ev => {
+ pingTimer = undefined;
+ });
+ });
},
close: () => {
- noReconnect = true;
sock?.close();
+ sock = undefined;
},
- watch: (name, handler) => eventTarget.watch(name, handler),
- unwatch: (name, handler) => eventTarget.unwatch(name, handler),
+ watch: eventTarget.watch,
+ unwatch: eventTarget.unwatch,
setDumpPackets: state => dumpPackets = !!state,
switchChannel: channelInfo => {
if(selfUserId === undefined)
diff --git a/src/mami.js/ui/loading-overlay.jsx b/src/mami.js/ui/loading-overlay.jsx
index 2b8eb9d..bc8eb79 100644
--- a/src/mami.js/ui/loading-overlay.jsx
+++ b/src/mami.js/ui/loading-overlay.jsx
@@ -31,7 +31,7 @@ Umi.UI.LoadingOverlay = function(icon, header, message) {
};
const setHeader = text => headerElem.textContent = (text || '').toString();
- const setMessage = text => messageElem.textContent = (text || '').toString();
+ const setMessage = text => messageElem.innerHTML = (text || '').toString();
setIcon(icon);
setHeader(header);
@@ -44,8 +44,5 @@ Umi.UI.LoadingOverlay = function(icon, header, message) {
getElement: function() {
return html;
},
- implode: function() {
- html.parentNode.removeChild(html);
- },
};
};
diff --git a/src/mami.js/ui/ping.jsx b/src/mami.js/ui/ping.jsx
index b7c6fca..6270b2e 100644
--- a/src/mami.js/ui/ping.jsx
+++ b/src/mami.js/ui/ping.jsx
@@ -13,15 +13,41 @@ const MamiPingIndicator = function(initialStrength) {
for(let i = 1; i <= 3; ++i)
bars.push(html.appendChild(