|
|
|
import * as math from './math';
|
|
|
|
import * as ser from './serializer';
|
|
|
|
import * as storage from './storage';
|
|
|
|
import * as config from './config';
|
|
|
|
|
|
|
|
import { MESSAGE, SESSION, EVENT } from './enums';
|
|
|
|
import { sessions, desks } from './storage';
|
|
|
|
|
|
|
|
function event_size(event) {
|
|
|
|
let size = 1 + 4; // type + user_id
|
|
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
case EVENT.PREDRAW: {
|
|
|
|
size += 4 * 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.STROKE: {
|
|
|
|
size += 4 + 2 + 2 + 4; // stroke id + point count + width + color
|
|
|
|
size += event.points.byteLength;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE:
|
|
|
|
case EVENT.IMAGE_MOVE: {
|
|
|
|
size += 4 + 4 + 4; // file id + x + y
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.UNDO:
|
|
|
|
case EVENT.REDO: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.ERASER: {
|
|
|
|
size += 4; // stroke id
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
console.error('fuck');
|
|
|
|
console.trace();
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
function create_session(ws, desk_id) {
|
|
|
|
const user = {
|
|
|
|
id: math.crypto_random32(),
|
|
|
|
login: 'unnamed',
|
|
|
|
};
|
|
|
|
|
|
|
|
const session = {
|
|
|
|
id: math.crypto_random32(),
|
|
|
|
user_id: user.id,
|
|
|
|
desk_id: desk_id,
|
|
|
|
state: SESSION.OPENED,
|
|
|
|
sn: 0,
|
|
|
|
lsn: 0,
|
|
|
|
ws: ws,
|
|
|
|
};
|
|
|
|
|
|
|
|
storage.create_user(user);
|
|
|
|
storage.create_session(session);
|
|
|
|
|
|
|
|
sessions[session.id] = session;
|
|
|
|
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function send_init(ws) {
|
|
|
|
if (!ws) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const session_id = ws.data.session_id;
|
|
|
|
const desk_id = ws.data.desk_id;
|
|
|
|
const desk = desks[desk_id];
|
|
|
|
|
|
|
|
let opcode = MESSAGE.INIT;
|
|
|
|
let size = 1 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count
|
|
|
|
let session = null;
|
|
|
|
|
|
|
|
if (session_id in sessions && sessions[session_id].desk_id == desk_id) {
|
|
|
|
session = sessions[session_id];
|
|
|
|
} else {
|
|
|
|
size += 4; // session id
|
|
|
|
opcode = MESSAGE.JOIN;
|
|
|
|
session = create_session(ws, desk_id);
|
|
|
|
ws.data.session_id = session.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
session.desk_id = desk_id;
|
|
|
|
session.ws = ws;
|
|
|
|
session.sn = 0; // Always re-send everything on reconnect
|
|
|
|
session.state = SESSION.OPENED;
|
|
|
|
|
|
|
|
console.log(`session ${session.id} opened`);
|
|
|
|
|
|
|
|
for (const event of desk.events) {
|
|
|
|
size += event_size(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
const s = ser.create(size);
|
|
|
|
|
|
|
|
ser.u8(s, opcode);
|
|
|
|
ser.u32(s, session.user_id);
|
|
|
|
ser.u32(s, session.lsn);
|
|
|
|
|
|
|
|
if (opcode === MESSAGE.JOIN) {
|
|
|
|
ser.u32(s, session.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
ser.u32(s, desk.events.length);
|
|
|
|
|
|
|
|
for (const event of desk.events) {
|
|
|
|
ser.event(s, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
await ws.send(s.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function send_ack(ws, lsn) {
|
|
|
|
if (!ws) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size = 1 + 4; // opcode + lsn
|
|
|
|
const s = ser.create(size);
|
|
|
|
|
|
|
|
ser.u8(s, MESSAGE.ACK);
|
|
|
|
ser.u32(s, lsn);
|
|
|
|
|
|
|
|
console.log(`ack ${lsn} out`);
|
|
|
|
|
|
|
|
ws.send(s.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function send_fire(ws, user_id, event) {
|
|
|
|
if (!ws) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const s = ser.create(1 + 4 + event_size(event));
|
|
|
|
|
|
|
|
ser.u8(s, MESSAGE.FIRE);
|
|
|
|
ser.u32(s, user_id);
|
|
|
|
ser.event(s, event);
|
|
|
|
|
|
|
|
ws.send(s.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function sync_session(session_id) {
|
|
|
|
if (!(session_id in sessions)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const session = sessions[session_id];
|
|
|
|
const desk = desks[session.desk_id];
|
|
|
|
|
|
|
|
if (!session.ws) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (session.state !== SESSION.READY) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = 1 + 4 + 4; // opcode + sn + event count
|
|
|
|
let count = desk.sn - session.sn;
|
|
|
|
|
|
|
|
if (count === 0) {
|
|
|
|
console.log('client ACKed all events');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = count - 1; i >= 0; --i) {
|
|
|
|
const event = desk.events[desk.events.length - 1 - i];
|
|
|
|
size += event_size(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
const s = ser.create(size);
|
|
|
|
|
|
|
|
ser.u8(s, MESSAGE.SYN);
|
|
|
|
ser.u32(s, desk.sn);
|
|
|
|
ser.u32(s, count);
|
|
|
|
|
|
|
|
for (let i = count - 1; i >= 0; --i) {
|
|
|
|
const event = desk.events[desk.events.length - 1 - i];
|
|
|
|
ser.event(s, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.debug(`syn ${desk.sn} out`);
|
|
|
|
|
|
|
|
await session.ws.send(s.buffer);
|
|
|
|
|
|
|
|
session.sync_timer = setTimeout(() => sync_session(session_id), config.SYNC_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function sync_desk(desk_id) {
|
|
|
|
for (const sid in sessions) {
|
|
|
|
const session = sessions[sid];
|
|
|
|
if (session.state === SESSION.READY && session.desk_id == desk_id) { // NOT ===, because might be string or int IDK
|
|
|
|
if (session.sync_timer) {
|
|
|
|
clearTimeout(session.sync_timer);
|
|
|
|
}
|
|
|
|
sync_session(sid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|