var Yaht = function() { if(Yaht.socket !== undefined) return; var loading = document.getElementById('loading'); loading.textContent += '.'; Yaht.socket = new WebSocket(Yaht.ENDPOINT, 'fwahtzee'); Yaht.socket.binaryType = 'arraybuffer'; Yaht.socket.onopen = Yaht.onOpen; Yaht.socket.onclose = Yaht.onClose; Yaht.socket.onerror = Yaht.onError; Yaht.socket.onmessage = Yaht.onMessage; }; Yaht.ENDPOINT = 'ws://127.0.0.1:30566'; Yaht.socket = undefined; Yaht.pingInterval = undefined; Yaht.OP_PING = 0x01; Yaht.OP_ERROR = 0x02; Yaht.OP_RANDOM = 0x03; Yaht.OP_CHAT_MSG = 0x21; Yaht.OP_CHAT_DEL = 0x22; Yaht.onOpen = function(ev) { console.log('[+] Connected!'); if(Yaht.pingInterval) clearInterval(Yaht.pingInterval); Yaht.pingInterval = setInterval(Yaht.sendPing, 4.9 * 60 * 1000); Yaht.sendPing(); }; Yaht.onClose = function(ev) { if(Yaht.pingInterval) clearInterval(Yaht.pingInterval); console.log('[-] Disconnected: %s %s %s', ev.code, ev.reason, ev.wasClean.toString()); }; Yaht.onError = function(ev) { console.error('[!]', ev); }; Yaht.onMessage = function(ev) { if(!ev.data) return; var data = new Uint8Array(ev.data), packet = Yaht.decodePacket(data); if(packet !== null) Yaht.handlePacket(packet); }; Yaht.send = function(data) { Yaht.socket.send(data); }; Yaht.writeInt = function(arr, val, width, off) { val = parseInt(val); off += width - 1; for(var i = 0; i < width; ++i) arr[off - i] = val / Math.pow(2, 8 * i); }; Yaht.writeI16 = function(arr, val, off) { Yaht.writeInt(arr, val, 2, off); }; Yaht.writeI32 = function(arr, val, off) { Yaht.writeInt(arr, val, 4, off); }; Yaht.writeI48 = function(arr, val, off) { Yaht.writeInt(arr, val, 6, off); }; Yaht.readInt = function(arr, width, off) { var val = 0; off += width; for(var i = width; i > 0; --i) val |= arr[off - i] * Math.pow(2, 8 * (i - 1)); return val; }; Yaht.readI16 = function(arr, off) { return Yaht.readInt(arr, 2, off); }; Yaht.readI32 = function(arr, off) { return Yaht.readInt(arr, 4, off); }; Yaht.readI48 = function(arr, off) { return Yaht.readInt(arr, 6, off); }; Yaht.sendPing = function() { var now = Date.now() / 1000, pack = new Uint8Array(5); pack[0] = Yaht.OP_PING; Yaht.writeI32(pack, now, 1); Yaht.send(pack); }; Yaht.randomIdCounter = 0; Yaht.randomCallbacks = {}; Yaht.randomValues = {}; Yaht.getNextRandomId = function() { return Yaht.randomIdCounter = (Yaht.randomIdCounter + 1) % 0x7F; }; Yaht.registerRandomCallback = function(identifier, callback) { Yaht.randomCallbacks['_' + identifier] = callback; }; Yaht.doRandomCallback = function(identifier, buffer) { identifier = '_' + identifier.toString(); Yaht.randomValues[identifier] = buffer; if(Yaht.randomCallbacks[identifier]) { Yaht.randomCallbacks[identifier](buffer); delete Yaht.randomCallbacks[identifier]; } }; Yaht.getRandomValue = function(identifier) { return Yaht.randomValues['_' + identifier] || []; }; Yaht.requestRandom = function(count, callback) { var pack = new Uint8Array(3); pack[0] = Yaht.OP_RANDOM; pack[1] = Yaht.getNextRandomId(); pack[2] = parseInt(count); if(callback) Yaht.registerRandomCallback(pack[1], callback); Yaht.socket.send(pack); return pack[1]; }; Yaht.toUTF16 = function(str) { str = str || ''; var length = str.length, buffer = new ArrayBuffer(length * 2), u16arr = new Uint16Array(buffer); for(var i = 0; i < length; ++i) u16arr[i] = str.charCodeAt(i); return new Uint8Array(buffer); }; Yaht.fromUTF16 = function(arr, offset, length) { offset = typeof offset === 'undefined' ? 0 : offset; length = typeof length === 'undefined' ? (arr.length - offset) : length; var str = ''; for(var i = 0; i < length; i = i + 2) str += String.fromCharCode((arr[i + offset + 1] << 8) || arr[i + offset]); return str; }; Yaht.sendChatMessage = function(roomId, text) { if(text.length < 1 || text.length > 500) return false; var textBytes = Yaht.toUTF16(text), pack = new Uint8Array(9 + textBytes.length); pack[0] = Yaht.OP_CHAT_MSG; Yaht.writeI48(pack, roomId, 1); Yaht.writeI16(pack, textBytes.length, 7); for(var i = 0; i < textBytes.length; ++i) pack[9 + i] = textBytes[i]; Yaht.socket.send(pack); return true; }; Yaht.consoleBeep = function(freq, dur, finish) { var ctx = new AudioContext, osc = ctx.createOscillator(); osc.type = 'sine'; osc.frequency.value = freq; osc.connect(ctx.destination); osc.start(); setTimeout(function() { osc.stop(); if(finish) finish(); }, dur); }; Yaht.removeElement = function(elem) { elem.parentNode.removeChild(elem); }; Yaht.decodePacket = function(data) { var packet = null; switch(data[0]) { default: console.debug(data); break; case Yaht.OP_PING: packet = { type: 'pong', took: Yaht.readI32(data, 1), }; break; case Yaht.OP_ERROR: packet = { type: 'error', msg: Yaht.fromUTF16(data, 3, Yaht.readI16(data, 1)), }; break; case Yaht.OP_RANDOM: packet = { type: 'random', identifier: data[1], values: data.slice(3, data[2] + 3), }; break; case Yaht.OP_CHAT_MSG: packet = { type: 'chat:msg', room: Yaht.readI48(data, 1), user: Yaht.readI48(data, 7), msg: Yaht.readI48(data, 13), text: Yaht.fromUTF16(data, 21, Yaht.readI16(data, 19)), }; break; case Yaht.OP_CHAT_DEL: packet = { type: 'chat:del', msg: Yaht.readI48(data, 1), }; break; } return packet; }; Yaht.handlePacket = function(packet) { switch(packet.type || null) { default: console.debug('[>] ' + (packet.type || ''), packet); break; case 'pong': console.log('[>] ping received, took %dms.', packet.took); break; case 'error': console.error('[!]', packet.msg); alert(packet.msg); break; case 'random': Yaht.doRandomCallback(packet.identifier, packet.values); break; case 'chat:msg': console.log('[&] %d #%d <%d> %s', packet.msg, packet.room, packet.user, packet.text); break; case 'chat:del': console.log('[&] %d deleted.', packet.msg); break; } };