Compare commits

...

5 commits

12 changed files with 530 additions and 492 deletions

110
src/mami.js/conman.js Normal file
View file

@ -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,
};
};

View file

@ -0,0 +1,22 @@
const MamiEventTarget = function(prefix) {
prefix = typeof prefix === 'string' ? `${prefix}:` : '';
const eventTarget = new EventTarget;
const createEvent = (name, detail) => new CustomEvent(prefix + name, (typeof detail === 'object' && detail !== null && 'detail' in detail ? detail : { detail: detail }));
return {
create: createEvent,
addEventListener: eventTarget.addEventListener.bind(eventTarget),
removeEventListener: eventTarget.removeEventListener.bind(eventTarget),
dispatchEvent: eventTarget.dispatchEvent.bind(eventTarget),
watch: (name, ...args) => {
eventTarget.addEventListener(prefix + name, ...args);
},
unwatch: (name, ...args) => {
eventTarget.removeEventListener(prefix + name, ...args);
},
dispatch: (...args) => eventTarget.dispatchEvent(createEvent(...args)),
};
};

View file

@ -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
@ -150,8 +151,8 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.virtualise('soundEnable');
}
settings.watch('soundEnable', (v, n, i) => {
if(v) {
settings.watch('soundEnable', ev => {
if(ev.detail.value) {
if(!soundCtx.ready)
soundCtx.reset();
@ -161,22 +162,22 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
soundCtx.library.play(soundCtx.pack.getEventSound('server'));
}
soundCtx.muted = !v;
soundCtx.muted = !ev.detail.value;
});
settings.watch('soundPack', (v, n, i) => {
settings.watch('soundPack', ev => {
const packs = soundCtx.packs;
if(!packs.has(v)) {
settings.delete(n);
if(!packs.has(ev.detail.value)) {
settings.delete(ev.detail.name);
return;
}
soundCtx.pack = packs.get(v);
if(!i) soundCtx.library.play(soundCtx.pack.getEventSound('server'));
soundCtx.pack = packs.get(ev.detail.value);
if(!ev.detail.initial) soundCtx.library.play(soundCtx.pack.getEventSound('server'));
});
settings.watch('soundVolume', v => {
soundCtx.volume = v / 100;
settings.watch('soundVolume', ev => {
soundCtx.volume = ev.detail.value / 100;
})
@ -220,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 });
@ -238,28 +241,28 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.UI.View.AccentReload();
Umi.UI.Hooks.AddHooks();
settings.watch('style', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); });
settings.watch('compactView', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); });
settings.watch('preventOverflow', v => document.body.classList.toggle('prevent-overflow', v));
settings.watch('tmpDisableOldThemeSys', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); });
settings.watch('style', ev => { if(!ev.detail.initial) Umi.UI.View.AccentReload(); });
settings.watch('compactView', ev => { if(!ev.detail.initial) Umi.UI.View.AccentReload(); });
settings.watch('preventOverflow', ev => document.body.classList.toggle('prevent-overflow', ev.detail.value));
settings.watch('tmpDisableOldThemeSys', ev => { if(!ev.detail.initial) Umi.UI.View.AccentReload(); });
settings.watch('minecraft', (v, n, i) => {
if(i && v === 'no')
settings.watch('minecraft', ev => {
if(ev.detail.initial && ev.detail.value === 'no')
return;
soundCtx.library.play((() => {
if(i)
return 'minecraft:nether:enter';
if(v === 'yes')
if(ev.detail.value === 'yes')
return 'minecraft:door:open';
if(v === 'old')
if(ev.detail.value === 'old')
return 'minecraft:door:open-old';
return soundCtx.pack.getEventSound('join');
})());
});
settings.watch('enableNotifications', v => {
if(!v || !('Notification' in window)
settings.watch('enableNotifications', ev => {
if(!ev.detail.value || !('Notification' in window)
|| (Notification.permission === 'granted' && Notification.permission !== 'denied'))
return;
@ -270,20 +273,20 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
});
});
settings.watch('playJokeSounds', v => {
if(!v) return;
settings.watch('playJokeSounds', ev => {
if(!ev.detail.value) return;
if(!textTriggers.hasTriggers())
futami.getJson('texttriggers').then(trigInfos => textTriggers.addTriggers(trigInfos));
});
settings.watch('weeaboo', v => {
if(v) Weeaboo.init();
settings.watch('weeaboo', ev => {
if(ev.detail.value) Weeaboo.init();
});
settings.watch('osuKeysV2', (v, n, i) => {
settings.watch('osuKeysV2', ev => {
// migrate old value
if(i) {
if(ev.detail.initial) {
if(settings.has('osuKeys')) {
settings.set('osuKeysV2', settings.get('osuKeys') ? 'yes' : 'no');
settings.delete('osuKeys');
@ -291,8 +294,8 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
}
}
OsuKeys.setEnable(v !== 'no');
OsuKeys.setRandomRate(v === 'rng');
OsuKeys.setEnable(ev.detail.value !== 'no');
OsuKeys.setRandomRate(ev.detail.value === 'rng');
});
@ -356,8 +359,8 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.toggle('autoScroll');
}
}, 'Autoscroll');
settings.watch('autoScroll', function(value) {
Umi.UI.Toggles.Get('scroll').classList[value ? 'remove' : 'add']('sidebar__selector-mode--scroll-off');
settings.watch('autoScroll', ev => {
Umi.UI.Toggles.Get('scroll').classList.toggle('sidebar__selector-mode--scroll-off', !ev.detail.value);
});
if(window.innerWidth < 768)
@ -368,8 +371,8 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.toggle('soundEnable');
}
}, 'Sounds');
settings.watch('soundEnable', function(value) {
Umi.UI.Toggles.Get('audio').classList[value ? 'remove' : 'add']('sidebar__selector-mode--audio-off');
settings.watch('soundEnable', ev => {
Umi.UI.Toggles.Get('audio').classList.toggle('sidebar__selector-mode--audio-off', !ev.detail.value);
});
Umi.UI.Toggles.Add('unembed', {
@ -534,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;
});
@ -572,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.',
};
@ -583,12 +590,12 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
'joinfail': 'You are banned.',
};
const sockChat = new Umi.Protocol.SockChat.Protocol;
const sockChat = new Umi.Protocol.SockChat.Protocol(futami.get('ping') * 1000);
MamiCompat('Umi.Server', { get: () => sockChat, configurable: true });
let dumpEvents = false;
settings.watch('dumpEvents', value => dumpEvents = value);
settings.watch('dumpPackets', value => sockChat.setDumpPackets(value));
settings.watch('dumpEvents', ev => dumpEvents = ev.detail.value);
settings.watch('dumpPackets', ev => sockChat.setDumpPackets(ev.detail.value));
Umi.UI.Hooks.SetCallbacks(sockChat.sendMessage, sockChat.switchChannel);
@ -596,60 +603,80 @@ 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', init => {
if(dumpEvents) console.log('conn:init', init);
sockChat.watch('conn:lost', ev => {
if(dumpEvents) console.log('conn:lost', ev);
let message = 'Connecting to server...';
if(init.attempt > 2)
message += ` (Attempt ${connectAttempts})`;
if(conMan.isActive() || isUnloading)
return;
getLoadingOverlay('spinner', 'Loading...', message);
});
sockChat.watch('conn:ready', ready => {
if(dumpEvents) console.log('conn:ready', ready);
const errorCode = ev.detail.code;
const isRestarting = ev.detail.isRestarting;
getLoadingOverlay('spinner', 'Loading...', 'Authenticating...');
pingToggle.title = '∞ms';
pingIndicator.setStrength(-1);
const authInfo = MamiMisuzuAuth.getInfo();
sockChat.sendAuth(authInfo.method, authInfo.token);
});
sockChat.watch('conn:lost', lost => {
if(dumpEvents) console.log('conn:lost', lost);
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})`;
getLoadingOverlay(
'unlink', 'Disconnected!',
wsCloseReasons[`_${lost.code}`] ?? `Something caused an unexpected connection loss. (${lost.code})`
);
});
sockChat.watch('conn:error', error => {
console.error('conn:error', error);
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 += ' <a href="javascript:void(0)" onclick="mami.conMan.force()">Retry now</a>';
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', send => {
if(dumpEvents) console.log('ping:send', send);
sockChat.watch('ping:send', ev => {
if(dumpEvents) console.log('ping:send', ev);
});
sockChat.watch('ping:long', long => {
if(dumpEvents) console.log('ping:long', long);
sockChat.watch('ping:long', ev => {
if(dumpEvents) console.log('ping:long', ev);
pingToggle.title = '+2000ms';
pingIndicator.setStrength(0);
});
sockChat.watch('ping:recv', recv => {
if(dumpEvents) console.log('ping:recv', recv);
sockChat.watch('ping:recv', ev => {
if(dumpEvents) console.log('ping:recv', ev);
let strength = 3;
if(recv.diff >= 1000) --strength;
if(recv.diff >= 250) --strength;
if(ev.detail.diff >= 1000) --strength;
if(ev.detail.diff >= 250) --strength;
pingToggle.title = `${recv.diff.toLocaleString()}ms`;
pingToggle.title = `${ev.detail.diff.toLocaleString()}ms`;
pingIndicator.setStrength(strength);
});
sockChat.watch('session:start', ev => {
if(dumpEvents) console.log('session:start', ev);
sockChat.watch('session:start', start => {
if(dumpEvents) console.log('session:start', start);
const userInfo = new Umi.User(start.user.id, start.user.name, start.user.colour, start.user.permsRaw);
const userInfo = new Umi.User(ev.detail.user.id, ev.detail.user.name, ev.detail.user.colour, ev.detail.user.permsRaw);
Umi.User.setCurrentUser(userInfo);
Umi.Users.Add(userInfo);
@ -657,102 +684,103 @@ 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', fail => {
if(dumpEvents) console.log('session:fail', fail);
sockChat.watch('session:fail', ev => {
if(dumpEvents) console.log('session:fail', ev);
if(fail.baka !== undefined) {
new MamiForceDisconnectNotice(fail.baka).pushOn(views);
if(ev.detail.baka !== undefined) {
new MamiForceDisconnectNotice(ev.detail.baka).pushOn(views);
return;
}
getLoadingOverlay(
'cross', 'Failed!',
sessFailReasons[fail.session.reason] ?? `Unknown reason: ${fail.session.reason}`
sessFailReasons[ev.detail.session.reason] ?? `Unknown reason: ${ev.detail.session.reason}`
);
if(fail.session.needsAuth)
if(ev.detail.session.needsAuth)
setTimeout(() => location.assign(futami.get('login')), 1000);
});
sockChat.watch('session:term', term => {
if(dumpEvents) console.log('session:term', term);
sockChat.watch('session:term', ev => {
if(dumpEvents) console.log('session:term', ev);
new MamiForceDisconnectNotice(term.baka).pushOn(views);
new MamiForceDisconnectNotice(ev.detail.baka).pushOn(views);
});
sockChat.watch('user:add', add => {
if(dumpEvents) console.log('user:add', add);
sockChat.watch('user:add', ev => {
if(dumpEvents) console.log('user:add', ev);
if(add.user.self)
if(ev.detail.user.self)
return;
const userInfo = new Umi.User(add.user.id, add.user.name, add.user.colour, add.user.permsRaw);
const userInfo = new Umi.User(ev.detail.user.id, ev.detail.user.name, ev.detail.user.colour, ev.detail.user.permsRaw);
Umi.Users.Add(userInfo);
if(add.msg !== undefined)
if(ev.detail.msg !== undefined)
Umi.Messages.Add(new Umi.Message(
add.msg.id, add.msg.time, undefined, '', add.msg.channel, false,
ev.detail.msg.id, ev.detail.msg.time, undefined, '', ev.detail.msg.channel, false,
{
isError: false,
type: add.msg.botInfo.type,
args: add.msg.botInfo.args,
type: ev.detail.msg.botInfo.type,
args: ev.detail.msg.botInfo.args,
target: userInfo,
}
));
});
sockChat.watch('user:remove', remove => {
if(dumpEvents) console.log('user:remove', remove);
sockChat.watch('user:remove', ev => {
if(dumpEvents) console.log('user:remove', ev);
const userInfo = Umi.Users.Get(remove.user.id);
const userInfo = Umi.Users.Get(ev.detail.user.id);
if(userInfo === null)
return;
if(remove.msg !== undefined)
if(ev.detail.msg !== undefined)
Umi.Messages.Add(new Umi.Message(
remove.msg.id,
remove.msg.time,
ev.detail.msg.id,
ev.detail.msg.time,
undefined,
'',
remove.msg.channel,
ev.detail.msg.channel,
false,
{
isError: false,
type: remove.msg.botInfo.type,
args: remove.msg.botInfo.args,
type: ev.detail.msg.botInfo.type,
args: ev.detail.msg.botInfo.args,
target: userInfo,
},
));
Umi.Users.Remove(userInfo);
});
sockChat.watch('user:update', update => {
if(dumpEvents) console.log('user:update', update);
sockChat.watch('user:update', ev => {
if(dumpEvents) console.log('user:update', ev);
const userInfo = Umi.Users.Get(update.user.id);
userInfo.setName(update.user.name);
userInfo.setColour(update.user.colour);
userInfo.setPermissions(update.user.permsRaw);
const userInfo = Umi.Users.Get(ev.detail.user.id);
userInfo.setName(ev.detail.user.name);
userInfo.setColour(ev.detail.user.colour);
userInfo.setPermissions(ev.detail.user.permsRaw);
Umi.Users.Update(userInfo.getId(), userInfo);
});
sockChat.watch('user:clear', () => {
if(dumpEvents) console.log('user:clear');
sockChat.watch('user:clear', ev => {
if(dumpEvents) console.log('user:clear', ev);
const self = Umi.User.currentUser;
Umi.Users.Clear();
@ -760,48 +788,48 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.Users.Add(self);
});
sockChat.watch('chan:add', add => {
if(dumpEvents) console.log('chan:add', add);
sockChat.watch('chan:add', ev => {
if(dumpEvents) console.log('chan:add', ev);
Umi.Channels.Add(new Umi.Channel(
add.channel.name,
add.channel.hasPassword,
add.channel.isTemporary,
ev.detail.channel.name,
ev.detail.channel.hasPassword,
ev.detail.channel.isTemporary,
));
});
sockChat.watch('chan:remove', remove => {
if(dumpEvents) console.log('chan:remove', remove);
sockChat.watch('chan:remove', ev => {
if(dumpEvents) console.log('chan:remove', ev);
Umi.Channels.Remove(Umi.Channels.Get(remove.channel.name));
Umi.Channels.Remove(Umi.Channels.Get(ev.detail.channel.name));
});
sockChat.watch('chan:update', update => {
if(dumpEvents) console.log('chan:update', update);
sockChat.watch('chan:update', ev => {
if(dumpEvents) console.log('chan:update', ev);
const chanInfo = Umi.Channels.Get(update.channel.previousName);
chanInfo.setName(update.channel.name);
chanInfo.setHasPassword(update.channel.hasPassword);
chanInfo.setTemporary(update.channel.isTemporary);
Umi.Channels.Update(update.channel.previousName, chanInfo);
const chanInfo = Umi.Channels.Get(ev.detail.channel.previousName);
chanInfo.setName(ev.detail.channel.name);
chanInfo.setHasPassword(ev.detail.channel.hasPassword);
chanInfo.setTemporary(ev.detail.channel.isTemporary);
Umi.Channels.Update(ev.detail.channel.previousName, chanInfo);
});
sockChat.watch('chan:clear', () => {
if(dumpEvents) console.log('chan:clear');
sockChat.watch('chan:clear', ev => {
if(dumpEvents) console.log('chan:clear', ev);
Umi.Channels.Clear();
});
sockChat.watch('chan:focus', focus => {
if(dumpEvents) console.log('chan:focus', focus);
sockChat.watch('chan:focus', ev => {
if(dumpEvents) console.log('chan:focus', ev);
Umi.Channels.Switch(Umi.Channels.Get(focus.channel.name));
Umi.Channels.Switch(Umi.Channels.Get(ev.detail.channel.name));
});
sockChat.watch('chan:join', join => {
if(dumpEvents) console.log('chan:join', join);
sockChat.watch('chan:join', ev => {
if(dumpEvents) console.log('chan:join', ev);
const userInfo = new Umi.User(join.user.id, join.user.name, join.user.colour, join.user.permsRaw);
const userInfo = new Umi.User(ev.detail.user.id, ev.detail.user.name, ev.detail.user.colour, ev.detail.user.permsRaw);
Umi.Users.Add(userInfo);
if(join.msg !== undefined)
if(ev.detail.msg !== undefined)
Umi.Messages.Add(new Umi.Message(
join.msg.id, null, undefined, '', join.msg.channel, false,
ev.detail.msg.id, null, undefined, '', ev.detail.msg.channel, false,
{
isError: false,
type: leave.msg.botInfo.type,
@ -810,22 +838,22 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
},
));
});
sockChat.watch('chan:leave', leave => {
if(dumpEvents) console.log('chan:leave', leave);
sockChat.watch('chan:leave', ev => {
if(dumpEvents) console.log('chan:leave', ev);
if(leave.user.self)
if(ev.detail.user.self)
return;
const userInfo = Umi.Users.Get(leave.user.id);
const userInfo = Umi.Users.Get(ev.detail.user.id);
if(userInfo === null)
return;
if(leave.msg !== undefined)
if(ev.detail.msg !== undefined)
Umi.Messages.Add(new Umi.Message(
leave.msg.id, null, undefined, '', leave.msg.channel, false,
ev.detail.msg.id, null, undefined, '', ev.detail.msg.channel, false,
{
isError: false,
type: leave.msg.botInfo.type,
type: ev.detail.msg.botInfo.type,
args: [ userInfo.getName() ],
target: userInfo,
},
@ -834,16 +862,16 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
Umi.Users.Remove(userInfo);
});
sockChat.watch('msg:add', add => {
if(dumpEvents) console.log('msg:add', add);
sockChat.watch('msg:add', ev => {
if(dumpEvents) console.log('msg:add', ev);
const senderInfo = add.msg.sender;
const senderInfo = ev.detail.msg.sender;
const userInfo = senderInfo.name === undefined
? Umi.Users.Get(senderInfo.id)
: new Umi.User(senderInfo.id, senderInfo.name, senderInfo.colour, senderInfo.permsRaw);
// hack
let channelName = add.msg.channel;
let channelName = ev.detail.msg.channel;
if(channelName !== undefined && channelName.startsWith('@~')) {
const chanUserInfo = Umi.Users.Get(channelName.substring(2));
if(chanUserInfo !== null)
@ -851,7 +879,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
}
// also hack
if(add.msg.flags.isPM) {
if(ev.detail.msg.flags.isPM) {
if(Umi.Channels.Get(channelName) === null)
Umi.Channels.Add(new Umi.Channel(channelName, false, true, true));
@ -860,29 +888,50 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
}
Umi.Messages.Add(new Umi.Message(
add.msg.id,
add.msg.time,
ev.detail.msg.id,
ev.detail.msg.time,
userInfo,
add.msg.text,
ev.detail.msg.text,
channelName,
false,
add.msg.botInfo,
add.msg.flags.isAction,
add.msg.silent,
ev.detail.msg.botInfo,
ev.detail.msg.flags.isAction,
ev.detail.msg.silent,
));
});
sockChat.watch('msg:remove', remove => {
if(dumpEvents) console.log('msg:remove', remove);
sockChat.watch('msg:remove', ev => {
if(dumpEvents) console.log('msg:remove', ev);
Umi.Messages.Remove(Umi.Messages.Get(remove.msg.id));
Umi.Messages.Remove(Umi.Messages.Get(ev.detail.msg.id));
});
sockChat.watch('msg:clear', () => {
if(dumpEvents) console.log('msg:clear');
sockChat.watch('msg:clear', ev => {
if(dumpEvents) console.log('msg:clear', ev);
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'));

View file

@ -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);
},
};
})();

View file

@ -1,4 +1,4 @@
#include watcher.js
#include eventtarget.js
#include settings/scoped.js
#include settings/virtual.js
#include settings/webstorage.js
@ -15,9 +15,16 @@ const MamiSettings = function(storageOrPrefix) {
throw 'required methods do not exist in storageOrPrefix object';
const storage = new MamiSettingsVirtualStorage(storageOrPrefix);
const watchers = new MamiWatchers;
const settings = new Map;
const eventTarget = new MamiEventTarget('mami:setting');
const createUpdateEvent = (name, value, initial) => eventTarget.create(name, {
name: name,
value: value,
initial: !!initial,
});
const dispatchUpdate = (name, value) => eventTarget.dispatchEvent(createUpdateEvent(name, value));
const broadcast = new BroadcastChannel(`${MAMI_JS}:settings:${storage.name()}`);
const broadcastUpdate = (name, value) => {
setTimeout(() => broadcast.postMessage({ act: 'update', name: name, value: value }), 0);
@ -43,7 +50,7 @@ const MamiSettings = function(storageOrPrefix) {
return;
storage.delete(setting.name);
watchers.call(setting.name, setting.fallback, setting.name);
dispatchUpdate(setting.name, setting.fallback);
broadcastUpdate(setting.name, setting.fallback);
};
@ -89,7 +96,7 @@ const MamiSettings = function(storageOrPrefix) {
} else
storage.set(setting.name, value);
watchers.call(setting.name, value, setting.name);
dispatchUpdate(setting.name, value);
broadcastUpdate(setting.name, value);
};
@ -98,7 +105,7 @@ const MamiSettings = function(storageOrPrefix) {
return;
if(ev.data.act === 'update' && typeof ev.data.name === 'string') {
watchers.call(ev.data.name, ev.data.value, ev.data.name);
dispatchUpdate(ev.data.name, ev.data.value);
return;
}
};
@ -122,8 +129,6 @@ const MamiSettings = function(storageOrPrefix) {
if(virtual === true)
storage.virtualise(name);
watchers.define(name);
},
info: name => getSetting(name),
names: () => Array.from(settings.keys()),
@ -143,7 +148,7 @@ const MamiSettings = function(storageOrPrefix) {
},
touch: name => {
const setting = getSetting(name);
watchers.call(setting.name, getValue(setting), setting.name);
dispatchUpdate(setting.name, getValue(setting));
},
clear: (criticalOnly, prefix) => {
for(const setting of settings.values())
@ -152,10 +157,11 @@ const MamiSettings = function(storageOrPrefix) {
},
watch: (name, handler) => {
const setting = getSetting(name);
watchers.watch(setting.name, handler, getValue(setting), setting.name);
eventTarget.watch(setting.name, handler);
handler(createUpdateEvent(setting.name, getValue(setting), true));
},
unwatch: (name, handler) => {
watchers.unwatch(getSetting(name).name, handler);
eventTarget.unwatch(name, handler);
},
virtualise: name => storage.virtualise(getSetting(name).name),
scope: name => new MamiSettingsScoped(pub, name),

View file

@ -1,18 +1,11 @@
#include common.js
#include servers.js
#include watcher.js
#include eventtarget.js
#include websock.js
Umi.Protocol.SockChat.Protocol = function() {
const watchers = new MamiWatchers(false);
watchers.define([
'conn:init', 'conn:ready', 'conn:lost', 'conn:error',
'ping:send', 'ping:long', 'ping:recv',
'session:start', 'session:fail', 'session:term',
'user:add', 'user:remove', 'user:update', 'user:clear',
'chan:add', 'chan:remove', 'chan:update', 'chan:clear', 'chan:focus', 'chan:join', 'chan:leave',
'msg:add', 'msg:remove', 'msg:clear',
]);
Umi.Protocol.SockChat.Protocol = function(pingDuration) {
if(typeof pingDuration !== 'number')
throw 'pingDuration must be a number';
const eventTarget = new MamiEventTarget('mami:proto');
const parseUserColour = str => {
// todo
@ -37,8 +30,6 @@ Umi.Protocol.SockChat.Protocol = function() {
};
let wasConnected = false;
let noReconnect = false;
let connectAttempts = 0;
let wasKicked = false;
let isRestarting = false;
let dumpPackets = false;
@ -46,6 +37,7 @@ Umi.Protocol.SockChat.Protocol = function() {
let sock;
let selfUserId, selfChannelName, selfPseudoChannelName;
let lastPing, lastPong, pingTimer, pingWatcher;
let openResolve, openReject;
const handlers = {};
@ -61,7 +53,7 @@ Umi.Protocol.SockChat.Protocol = function() {
stopPingWatcher();
if(lastPong === undefined)
watchers.call('ping:long');
eventTarget.dispatch('ping:long');
}, 2000);
};
@ -81,7 +73,7 @@ Umi.Protocol.SockChat.Protocol = function() {
if(selfUserId === undefined)
return;
watchers.call('ping:send');
eventTarget.dispatch('ping:send');
startPingWatcher();
lastPong = undefined;
@ -101,7 +93,7 @@ Umi.Protocol.SockChat.Protocol = function() {
send('2', selfUserId, text);
};
const startKeepAlive = () => sock?.sendInterval(`0\t${selfUserId}`, futami.get('ping') * 1000);
const startKeepAlive = () => sock?.sendInterval(`0\t${selfUserId}`, pingDuration);
const stopKeepAlive = () => sock?.clearIntervals();
const onOpen = ev => {
@ -110,11 +102,10 @@ Umi.Protocol.SockChat.Protocol = function() {
isRestarting = false;
// see if these are neccesary
watchers.call('user:clear');
watchers.call('chan:clear');
watchers.call('conn:ready', { wasConnected: wasConnected });
if(typeof openResolve === 'function') {
openResolve();
openResolve = undefined;
}
};
const onClose = ev => {
@ -130,36 +121,32 @@ Umi.Protocol.SockChat.Protocol = function() {
if(wasKicked)
return;
let code = ev.code;
let code = ev.detail.code;
if(isRestarting && code === 1006) {
code = 1012;
} else if(code === 1012)
isRestarting = true;
watchers.call('conn:lost', {
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 onError = ex => {
watchers.call('conn:error', ex);
};
const unfuckText = text => {
const elem = document.createElement('div');
elem.innerHTML = text.replace(/ <br\/> /g, "\n");
text = elem.innerText;
return text;
};
const unfuckText = text => text.replace(/ <br\/> /g, "\n");
const onMessage = ev => {
const args = ev.data.split("\t");
const args = ev.detail.data.split("\t");
let handler = handlers;
if(dumpPackets)
@ -181,7 +168,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// pong handler
handlers['0'] = () => {
lastPong = Date.now();
watchers.call('ping:recv', {
eventTarget.dispatch('ping:recv', {
ping: lastPing,
pong: lastPong,
diff: lastPong - lastPing,
@ -194,7 +181,7 @@ Umi.Protocol.SockChat.Protocol = function() {
selfUserId = userIdOrReason;
selfChannelName = chanNameOrMsgId;
watchers.call('session:start', {
eventTarget.dispatch('session:start', {
wasConnected: wasConnected,
session: { success: true },
ctx: {
@ -235,11 +222,11 @@ Umi.Protocol.SockChat.Protocol = function() {
until: userNameOrExpiry === '-1' ? undefined : new Date(parseInt(userNameOrExpiry) * 1000),
};
watchers.call('session:fail', failInfo);
eventTarget.dispatch('session:fail', failInfo);
return;
}
watchers.call('user:add', {
eventTarget.dispatch('user:add', {
msg: {
id: chanNameOrMsgId,
time: new Date(parseInt(successOrTimeStamp) * 1000),
@ -300,12 +287,12 @@ Umi.Protocol.SockChat.Protocol = function() {
};
}
watchers.call('msg:add', msgInfo);
eventTarget.dispatch('msg:add', msgInfo);
};
// user leave
handlers['3'] = (userId, userName, reason, timeStamp, msgId) => {
watchers.call('user:remove', {
eventTarget.dispatch('user:remove', {
leave: { type: reason },
msg: {
id: msgId,
@ -329,7 +316,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// channel add
handlers['4']['0'] = (name, hasPass, isTemp) => {
watchers.call('chan:add', {
eventTarget.dispatch('chan:add', {
channel: {
name: name,
hasPassword: hasPass !== '0',
@ -340,7 +327,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// channel update
handlers['4']['1'] = (prevName, name, hasPass, isTemp) => {
watchers.call('chan:update', {
eventTarget.dispatch('chan:update', {
channel: {
previousName: prevName,
name: name,
@ -352,7 +339,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// channel remove
handlers['4']['2'] = name => {
watchers.call('chan:remove', {
eventTarget.dispatch('chan:remove', {
channel: { name: name },
});
};
@ -362,7 +349,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// user join channel
handlers['5']['0'] = (userId, userName, userColour, userPerms, msgId) => {
watchers.call('chan:join', {
eventTarget.dispatch('chan:join', {
user: {
id: userId,
self: userId === selfUserId,
@ -384,7 +371,7 @@ Umi.Protocol.SockChat.Protocol = function() {
// user leave channel
handlers['5']['1'] = (userId, msgId) => {
watchers.call('chan:leave', {
eventTarget.dispatch('chan:leave', {
user: {
id: userId,
self: userId === selfUserId,
@ -404,14 +391,14 @@ Umi.Protocol.SockChat.Protocol = function() {
handlers['5']['2'] = name => {
selfChannelName = name;
watchers.call('chan:focus', {
eventTarget.dispatch('chan:focus', {
channel: { name: selfChannelName },
});
};
// message delete
handlers['6'] = msgId => {
watchers.call('msg:remove', {
eventTarget.dispatch('msg:remove', {
msg: {
id: msgId,
channel: selfChannelName,
@ -425,11 +412,12 @@ Umi.Protocol.SockChat.Protocol = function() {
// existing users
handlers['7']['0'] = (count, ...args) => {
count = parseInt(count);
eventTarget.dispatch('user:clear');
for(let i = 0; i < count; ++i) {
const offset = 5 * i;
watchers.call('user:add', {
eventTarget.dispatch('user:add', {
user: {
id: args[offset],
self: args[offset] === selfUserId,
@ -479,17 +467,18 @@ Umi.Protocol.SockChat.Protocol = function() {
};
}
watchers.call('msg:add', info);
eventTarget.dispatch('msg:add', info);
};
// existing channels
handlers['7']['2'] = (count, ...args) => {
count = parseInt(count);
eventTarget.dispatch('chan:clear');
for(let i = 0; i < count; ++i) {
const offset = 3 * i;
watchers.call('chan:add', {
eventTarget.dispatch('chan:add', {
channel: {
name: args[offset],
hasPassword: args[offset + 1] !== '0',
@ -499,7 +488,7 @@ Umi.Protocol.SockChat.Protocol = function() {
});
}
watchers.call('chan:focus', {
eventTarget.dispatch('chan:focus', {
channel: { name: selfChannelName },
});
};
@ -507,18 +496,17 @@ Umi.Protocol.SockChat.Protocol = function() {
// context clear
handlers['8'] = mode => {
if(mode === '0' || mode === '3' || mode === '4')
watchers.call('msg:clear');
eventTarget.dispatch('msg:clear');
if(mode === '1' || mode === '3' || mode === '4')
watchers.call('user:clear');
eventTarget.dispatch('user:clear');
if(mode === '2' || mode === '4')
watchers.call('chan:clear');
eventTarget.dispatch('chan:clear');
};
// baka (ban/kick)
handlers['9'] = (type, expiry) => {
noReconnect = true;
wasKicked = true;
const bakaInfo = {
@ -533,12 +521,12 @@ Umi.Protocol.SockChat.Protocol = function() {
bakaInfo.baka.until = expiry === '-1' ? undefined : new Date(parseInt(expiry) * 1000);
}
watchers.call('session:term', bakaInfo);
eventTarget.dispatch('session:term', bakaInfo);
};
// user update
handlers['10'] = (userId, userName, userColour, userPerms) => {
watchers.call('user:update', {
eventTarget.dispatch('user:update', {
user: {
id: userId,
self: userId === selfUserId,
@ -551,61 +539,43 @@ Umi.Protocol.SockChat.Protocol = function() {
};
const beginConnecting = () => {
sock?.close();
if(noReconnect)
return;
UmiServers.getServer(server => {
watchers.call('conn:init', {
server: server,
wasConnected: wasConnected,
attempt: ++connectAttempts,
});
sock = new UmiWebSocket(server, ev => {
switch(ev.act) {
case 'ws:open':
onOpen(ev);
break;
case 'ws:close':
onClose(ev);
break;
case 'ws:message':
onMessage(ev);
break;
case 'ws:create_interval':
pingTimer = ev.id;
break;
case 'ws:call_interval':
if(ev.id === pingTimer)
onSendPing();
break;
case 'ws:clear_intervals':
pingTimer = undefined;
break;
default:
console.log(ev.data);
break;
}
});
});
};
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) => watchers.watch(name, handler),
unwatch: (name, handler) => watchers.unwatch(name, handler),
watch: eventTarget.watch,
unwatch: eventTarget.unwatch,
setDumpPackets: state => dumpPackets = !!state,
switchChannel: channelInfo => {
if(selfUserId === undefined)

View file

@ -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);
},
};
};

View file

@ -13,15 +13,41 @@ const MamiPingIndicator = function(initialStrength) {
for(let i = 1; i <= 3; ++i)
bars.push(html.appendChild(<div class={`ping-bar ping-bar-${i}`}/>));
let interval;
const setStrength = strength => {
if(typeof strength !== 'number')
throw 'strength must be a number';
for(const i in bars)
bars[i].classList.toggle('ping-bar-on', i < strength);
if(strength < 0) {
if(interval === undefined) {
const cyclesMax = bars.length * 2;
let cycles = -1;
const updateCycle = () => {
let curCycle = ++cycles % cyclesMax;
if(curCycle > bars.length)
curCycle = cyclesMax - curCycle;
for(const i in bars)
bars[i].classList.toggle('ping-bar-on', curCycle === (parseInt(i) + 1));
};
interval = setInterval(updateCycle, 200);
updateCycle();
}
} else {
if(interval !== undefined) {
clearInterval(interval);
interval = undefined;
}
for(const i in bars)
bars[i].classList.toggle('ping-bar-on', i < strength);
}
html.classList.toggle('ping-state-good', strength > 1);
html.classList.toggle('ping-state-warn', strength === 1);
html.classList.toggle('ping-state-warn', strength == 1);
html.classList.toggle('ping-state-poor', strength < 1);
};

View file

@ -454,7 +454,7 @@ Umi.UI.Settings = (function() {
input.disabled = true;
if(display.type === 'checkbox') {
mami.settings.watch(setting.name, v => input.checked = v);
mami.settings.watch(setting.name, ev => input.checked = ev.detail.value);
input.addEventListener('change', () => {
if(display.confirm !== undefined && input.checked !== setting.fallback && !confirm(display.confirm)) {
input.checked = setting.fallback;
@ -464,7 +464,7 @@ Umi.UI.Settings = (function() {
mami.settings.toggle(setting.name);
});
} else {
mami.settings.watch(setting.name, v => input.value = v);
mami.settings.watch(setting.name, ev => input.value = ev.detail.value);
input.addEventListener('change', () => mami.settings.set(setting.name, input.value));
}
}

View file

@ -1,77 +0,0 @@
#include utility.js
const MamiWatcher = function(initCall) {
if(typeof initCall !== 'boolean')
initCall = true;
const handlers = [];
const watch = (handler, ...args) => {
if(typeof handler !== 'function')
throw 'handler must be a function';
if(handlers.includes(handler))
throw 'handler already registered';
handlers.push(handler);
if(initCall) {
args.push(true);
handler(...args);
}
};
const unwatch = handler => {
$ari(handlers, handler);
};
return {
watch: watch,
unwatch: unwatch,
call: (...args) => {
if(initCall)
args.push(false);
for(const handler of handlers)
handler(...args);
},
};
};
const MamiWatchers = function(initCall) {
if(typeof initCall !== 'boolean')
initCall = true;
const watchers = new Map;
const getWatcher = name => {
const watcher = watchers.get(name);
if(watcher === undefined)
throw 'undefined watcher name';
return watcher;
};
const watch = (name, handler, ...args) => {
getWatcher(name).watch(handler, ...args);
};
const unwatch = (name, handler) => {
getWatcher(name).unwatch(handler);
};
return {
watch: watch,
unwatch: unwatch,
define: names => {
if(typeof names === 'string')
watchers.set(names, new MamiWatcher(initCall));
else if(Array.isArray(names))
for(const name of names)
watchers.set(name, new MamiWatcher(initCall));
else
throw 'names must be an array of names or a single name';
},
call: (name, ...args) => {
getWatcher(name).call(...args);
},
};
};

View file

@ -1,6 +1,8 @@
const UmiWebSocket = function(server, message, useWorker) {
if(typeof useWorker === 'undefined')
useWorker = (function() {
#include eventtarget.js
const UmiWebSocket = function(url, useWorker) {
if(typeof useWorker !== 'boolean')
useWorker = (() => {
// Overrides
if(mami.settings.get('neverUseWorker'))
return false;
@ -18,90 +20,68 @@ const UmiWebSocket = function(server, message, useWorker) {
return false;
})();
const eventTarget = new MamiEventTarget('ws');
let send, close, sendInterval, clearIntervals;
if(useWorker) {
const worker = new Worker(MAMI_WS);
worker.addEventListener('message', function(ev) {
message(ev.data);
worker.addEventListener('message', ev => {
if(ev.data.act.startsWith('ws:'))
eventTarget.dispatch(ev.data.act.substring(3), ev.data.detail);
});
worker.postMessage({act: 'ws:open', server: server});
send = function(text) {
worker.postMessage({act: 'ws:open', url: url});
send = text => {
worker.postMessage({act: 'ws:send', text: text});
};
close = function() {
close = () => {
worker.postMessage({act: 'ws:close'});
};
getIntervals = function() {
worker.postMessage({act: 'ws:intervals'});
};
sendInterval = function(text, interval) {
sendInterval = (text, interval) => {
worker.postMessage({act: 'ws:send_interval', text: text, interval: interval});
};
clearIntervals = function() {
clearIntervals = () => {
worker.postMessage({act: 'ws:clear_intervals'});
};
} else {
const websocket = new WebSocket(server), intervals = [];
websocket.addEventListener('open', function(ev) {
message({
act: 'ws:open',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
});
const websocket = new WebSocket(url), intervals = [];
websocket.addEventListener('open', ev => {
eventTarget.dispatch('open');
});
websocket.addEventListener('close', function(ev) {
message({
act: 'ws:close',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
websocket.addEventListener('close', ev => {
eventTarget.dispatch('close', {
code: ev.code,
reason: ev.reason,
wasClean: ev.wasClean,
});
});
websocket.addEventListener('error', function(ev) {
message({
act: 'ws:error',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
});
websocket.addEventListener('error', ev => {
eventTarget.dispatch('error');
});
websocket.addEventListener('message', function(ev) {
message({
act: 'ws:message',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
websocket.addEventListener('message', ev => {
eventTarget.dispatch('message', {
data: ev.data,
origin: ev.origin,
lastEventId: ev.lastEventId,
});
});
send = function(text) {
send = text => {
websocket.send(text);
};
close = function() {
close = () => {
websocket.close();
};
getIntervals = function() {
return intervals;
};
sendInterval = function(text, interval) {
const intervalId = setInterval(function() {
sendInterval = (text, interval) => {
const intervalId = setInterval(() => {
if(websocket) {
websocket.send(text);
message({ act: 'ws:call_interval', id: intervalId });
eventTarget.dispatch('call_interval', { id: intervalId });
}
}, interval);
intervals.push(intervalId);
message({ act: 'ws:create_interval', id: intervalId });
eventTarget.dispatch('create_interval', { id: intervalId });
};
clearIntervals = function() {
clearIntervals = () => {
for(let i = 0; i < intervals.length; ++i)
clearInterval(intervals[i]);
};
@ -111,8 +91,9 @@ const UmiWebSocket = function(server, message, useWorker) {
isUsingWorker: useWorker,
send: send,
close: close,
getIntervals: getIntervals,
sendInterval: sendInterval,
clearIntervals: clearIntervals,
watch: eventTarget.watch,
unwatch: eventTarget.unwatch,
};
};

View file

@ -6,46 +6,34 @@ addEventListener('message', function(ev) {
switch(ev.data.act) {
case 'ws:open':
if(!ev.data.server)
if(!ev.data.url)
break;
websocket = new WebSocket(ev.data.server);
websocket = new WebSocket(ev.data.url);
websocket.addEventListener('open', function(ev) {
postMessage({
act: 'ws:open',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
});
postMessage({ act: 'ws:open' });
});
websocket.addEventListener('close', function(ev) {
postMessage({
act: 'ws:close',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
code: ev.code,
reason: ev.reason,
wasClean: ev.wasClean,
detail: {
code: ev.code,
reason: ev.reason,
wasClean: ev.wasClean,
},
});
});
websocket.addEventListener('error', function(ev) {
postMessage({
act: 'ws:error',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
});
postMessage({ act: 'ws:error' });
});
websocket.addEventListener('message', function(ev) {
postMessage({
act: 'ws:message',
type: ev.type,
timeStamp: ev.timeStamp,
isTrusted: ev.isTrusted,
data: ev.data,
origin: ev.origin,
lastEventId: ev.lastEventId,
detail: {
data: ev.data,
origin: ev.origin,
lastEventId: ev.lastEventId,
},
});
});
break;
@ -66,24 +54,17 @@ addEventListener('message', function(ev) {
websocket.send(ev.data.text);
break;
case 'ws:intervals':
postMessage({
act: 'ws:intervals',
intervals: intervals,
});
break;
case 'ws:send_interval':
(function(interval, text) {
const intervalId = setInterval(function() {
if(websocket) {
websocket.send(text);
postMessage({ act: 'ws:call_interval', id: intervalId });
postMessage({ act: 'ws:call_interval', detail: { id: intervalId } });
}
}, interval);
intervals.push(intervalId);
postMessage({ act: 'ws:create_interval', id: intervalId });
postMessage({ act: 'ws:create_interval', detail: { id: intervalId } });
})(ev.data.interval, ev.data.text);
break;