From 4466640439adb55b8c159559b8da557b688ab734 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Tue, 21 Mar 2023 20:33:52 +0300 Subject: [PATCH] Initital commit --- Caddyfile | 18 ++++ client/cursor.js | 96 +++++++++++++++++ client/default.css | 40 +++++++ client/draw.js | 41 ++++++++ client/favicon.png | Bin 0 -> 957 bytes client/favicon2.png | Bin 0 -> 671 bytes client/index.html | 21 ++++ client/index.js | 148 ++++++++++++++++++++++++++ client/math.js | 135 ++++++++++++++++++++++++ client/recv.js | 229 +++++++++++++++++++++++++++++++++++++++++ client/send.js | 154 +++++++++++++++++++++++++++ client/websocket.js | 35 +++++++ server/app.mjs | 5 + server/bun.lockb | Bin 0 -> 6967 bytes server/config.js | 4 + server/data/db.sqlite | Bin 0 -> 192512 bytes server/deserializer.js | 67 ++++++++++++ server/enums.js | 26 +++++ server/http.js | 4 + server/math.js | 14 +++ server/recv.js | 129 +++++++++++++++++++++++ server/send.js | 186 +++++++++++++++++++++++++++++++++ server/serializer.js | 63 ++++++++++++ server/server.js | 75 ++++++++++++++ server/storage.js | 201 ++++++++++++++++++++++++++++++++++++ 25 files changed, 1691 insertions(+) create mode 100644 Caddyfile create mode 100644 client/cursor.js create mode 100644 client/default.css create mode 100644 client/draw.js create mode 100644 client/favicon.png create mode 100644 client/favicon2.png create mode 100644 client/index.html create mode 100644 client/index.js create mode 100644 client/math.js create mode 100644 client/recv.js create mode 100644 client/send.js create mode 100644 client/websocket.js create mode 100644 server/app.mjs create mode 100755 server/bun.lockb create mode 100644 server/config.js create mode 100644 server/data/db.sqlite create mode 100644 server/deserializer.js create mode 100644 server/enums.js create mode 100644 server/http.js create mode 100644 server/math.js create mode 100644 server/recv.js create mode 100644 server/send.js create mode 100644 server/serializer.js create mode 100644 server/server.js create mode 100644 server/storage.js diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..1d84dad --- /dev/null +++ b/Caddyfile @@ -0,0 +1,18 @@ +desk.local { + redir /ws /ws/ + redir /desk /desk/ + + handle /ws/* { + reverse_proxy 127.0.0.1:3003 + } + + handle /api/* { + reverse_proxy 127.0.0.1:3003 + } + + handle_path /desk/* { + root * /code/desk2/client + try_files {path} /index.html + file_server + } +} diff --git a/client/cursor.js b/client/cursor.js new file mode 100644 index 0000000..7afc77a --- /dev/null +++ b/client/cursor.js @@ -0,0 +1,96 @@ +function on_down(e) { + if (e.button === 1) { + const event = undo_event(); + queue_event(event); + return; + } + + if (e.button != 0) { + return; + } + + const x = Math.round(e.clientX + window.scrollX); + const y = Math.round(e.clientY + window.scrollY); + + storage.state.drawing = true; + + if (storage.ctx1.lineWidth !== storage.cursor.width) { + storage.ctx1.lineWidth = storage.cursor.width; + } + + storage.cursor.x = x; + storage.cursor.y = y; + + const predraw = predraw_event(x, y); + storage.current_stroke.push(predraw); + + fire_event(predraw); +} + +function on_move(e) { + const last_x = storage.cursor.x; + const last_y = storage.cursor.y; + + const x = storage.cursor.x = Math.round(e.clientX + window.scrollX); + const y = storage.cursor.y = Math.round(e.clientY + window.scrollY); + const width = storage.cursor.width; + + elements.cursor.style.transform = `translate3D(${Math.round(x - width / 2)}px, ${Math.round(y - width / 2)}px, 0)`; + + if (storage.state.drawing) { + storage.ctx1.beginPath(); + + storage.ctx1.moveTo(last_x, last_y); + storage.ctx1.lineTo(x, y); + + storage.ctx1.stroke(); + + const predraw = predraw_event(x, y); + storage.current_stroke.push(predraw); + + fire_event(predraw); + } +} + +async function on_up(e) { + if (e.button != 0) { + return; + } + + storage.state.drawing = false; + + const event = stroke_event(); + storage.current_stroke = []; + await queue_event(event); +} + +function on_keydown(e) { + if (e.code === 'Space' && !storage.state.drawing) { + elements.cursor.classList.add('dhide'); + elements.canvas0.classList.add('moving'); + storage.state.moving = true; + } +} + +function on_keyup(e) { + if (e.code === 'Space' && storage.state.moving) { + elements.cursor.classList.remove('dhide'); + elements.canvas0.classList.remove('moving'); + storage.state.moving = false; + } +} + +function on_leave(e) { + if (storage.state.drawing) { + on_up(e); + storage.state.drawing = false; + return; + } + + if (storage.state.moving) { + elements.cursor.classList.remove('dhide'); + elements.canvas0.classList.remove('moving'); + storage.state.moving = false; + return; + } +} \ No newline at end of file diff --git a/client/default.css b/client/default.css new file mode 100644 index 0000000..39069ff --- /dev/null +++ b/client/default.css @@ -0,0 +1,40 @@ +html, body { + margin: 0; + padding: 0; +} + +.dhide { + display: none !important; +} + +.canvas { + cursor: crosshair; + position: absolute; + top: 0; + left: 0; +} + +.canvas.moving { + cursor: move; +} + +.cursor { + display: none; + position: absolute; + background: white; + border-radius: 50%; + box-sizing: border-box; + border: 1px solid black; + z-index: 10; + pointer-events: none; +} + +#canvas0 { + z-index: 0; +} + +#canvas1 { + z-index: 1; + pointer-events: none; + opacity: 0.3; +} \ No newline at end of file diff --git a/client/draw.js b/client/draw.js new file mode 100644 index 0000000..759d04a --- /dev/null +++ b/client/draw.js @@ -0,0 +1,41 @@ +function draw_stroke(stroke) { + const points = stroke.points; + + if (points.length === 0) { + return; + } + + // console.debug(points) + + storage.ctx0.beginPath(); + storage.ctx0.moveTo(points[0].x, points[0].y); + + for (let i = 1; i < points.length; ++i) { + const p = points[i]; + storage.ctx0.lineTo(p.x, p.y); + } + + storage.ctx0.stroke(); +} + +function redraw_predraw() { + storage.ctx1.clearRect(0, 0, storage.ctx1.canvas.width, storage.ctx1.canvas.height); +} + +function predraw_user(user_id, event) { + if (!(user_id in storage.predraw)) { + storage.predraw[user_id] = []; + } + + storage.ctx1.beginPath(); + if (storage.predraw[user_id].length > 0) { + const last = storage.predraw[user_id][storage.predraw[user_id].length - 1]; + storage.ctx1.moveTo(last.x, last.y); + storage.ctx1.lineTo(event.x, event.y); + } else { + storage.ctx1.moveTo(event.x, event.y); + } + storage.ctx1.stroke(); + + storage.predraw[user_id].push({ 'x': event.x, 'y': event.y }); +} \ No newline at end of file diff --git a/client/favicon.png b/client/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..dab577ecd706572f09c3e0acfd25b206bae6d23e GIT binary patch literal 957 zcmV;u148_XP)EX>4Tx04R}tkv&MmKpe$iQ>8^JDs~WY$WR@`E-K;tBaGOiZLMN=c5CXT3@PWeK{ zW0mt3XRTai&3p0}hI0DKGS_L2Ab~|JL4pVcRg_SMjVP@;DHc++AMfBFcKs5$6mpfo z$gzM5G{~+W{11M2YvrdVy`)ea=zejWk5M473p8qu^L^|%jT6BC3|#3gf29u0ev)2m zX^|tKZyUI{ZfVLMaJd5vJsGkoyON)#kk13}XY@^3VDJ{`S@U{p?&I_UNK;qI8{ps& z7%NcrdWUxh+k5->OtZfqaolpIj|eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00EUrL_t(o!|j(*YQr!LhCeU5LPkoCFen6epdD>@*i$c2 z$ir?>=*UhGP{>|XFmCKRX|ghyz@%xF{__7ziUkig=ii7(p{gZN1b%~ve5>lPNfaVd z02R;xN8l8!Bd|BWO~CgCat0I_gQfO+wK~30{%%1OzyN&N75lCPcEB!2in&r&w!7XK zIGPl-tyhnkBnykA^dvaggH@n)Z4Zxjtuuh}OaCB%SSk6Lo&%ngG)2`IX(Vl=VZ|C#Hh9=nPD|d36OF0Q%#*9= z>xos(pnOh}uKHU42Y(oNpIe?=f!{38wJp27rtImC01DvQJ4bd)j_tA%8-*QOV-}q& z?g?N3I+LOVKD?83Xq~FQiOA<_!_7qbjfvF*2j^Uw9L<&lp{g%7S3D{XF%Rg;DNt_Ay0IHY>00000NkvXXu0mjf{eGe1 literal 0 HcmV?d00001 diff --git a/client/favicon2.png b/client/favicon2.png new file mode 100644 index 0000000000000000000000000000000000000000..6bc00549fa351e5d4ba0d737caa246f3b34e6eea GIT binary patch literal 671 zcmV;Q0$}}#P)EX>4Tx04R}tkv&MmKpe$iQ>8^JB6bjQ$WR5rE-E6p)G8FALZ}s5buhW~3z{?} zEiR6NYr(;f#j1mgv#t)Vf*|+-;_Bk0=prTlFDbN$@!+^0@9sVB-T^|R%rvVj255TK zOeNxCCc7#IUg1YSf*8ZB%q(M0l9KQoU-$6w^)AY@ywCkPdexl80G~)4V}@BJ-XNad ztQws6iNmZYtHkHT<0f5@_>t?1%Ws?u4huXpY^GE5#9?Bw(7{Rvv!baHPZ38{O{aVz z*5I4>^8`Tnf2L zVB}ap1sY_>5B>+gyS4I@6K+x{4s^fR_Qwbi*aez(+x|Yb?dA#Ke+I7fw!hKo454hX`2A>SslpV=WQ^@Cm_cQvYEHH2j^sKqPwf1rP0Hmp_y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j>G32rC=}@;fd7000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001XNkl;|yGDIf;oIOI2VC#6wEweD2hV1bBiO=C;Jk4Ua;t=#?Dz=zbKU>|002ovPDHLk FV1m4p8}|SJ literal 0 HcmV?d00001 diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..de6b393 --- /dev/null +++ b/client/index.html @@ -0,0 +1,21 @@ + + + + + Desk + + + + + + + + + + + +
+ + + + diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..b9ee6e3 --- /dev/null +++ b/client/index.js @@ -0,0 +1,148 @@ +let ws = null; +let ls = window.localStorage; + +document.addEventListener('DOMContentLoaded', main); + +const EVENT = Object.freeze({ + PREDRAW: 10, + STROKE: 20, + UNDO: 30, + REDO: 31, +}); +const MESSAGE = Object.freeze({ + INIT: 100, + SYN: 101, + ACK: 102, + FULL: 103, + FIRE: 104, + JOIN: 105, +}); + +const config = { + ws_url: 'wss://desk.local/ws/', + sync_timeout: 1000, + ws_reconnect_timeout: 2000, +}; + +const storage = { + 'state': { + 'drawing': false, + 'moving': false, + }, + + 'predraw': {}, + 'timers': {}, + 'me': {}, + + 'sn': 0, // what WE think SERVER SN is (we tell this to the server, it uses to decide how much stuff to SYN to us) + 'server_lsn': 0, // what SERVER said LSN is (used to decide how much stuff to SYN) + 'lsn': 0, // what actual LSN is (can't just use length of local queue because it gets cleared) + 'queue': [], // to server + 'events': [], // from server + 'current_stroke': [], + + 'desk_id': 123, + + 'canvas': { + 'width': 2000, + 'height': 2000, + 'offset_x': 0, + 'offset_y': 0, + }, + + 'cursor': { + 'width': 8, + 'x': 0, + 'y': 0, + } +}; +const elements = { + 'cursor': null, + 'canvas0': null, + 'canvas1': null, +}; + +function event_size(event) { + let size = 1 + 1; // type + padding + + switch (event.type) { + case EVENT.PREDRAW: { + size += 2 * 2; + break; + } + + case EVENT.STROKE: { + size += 2 + event.points.length * 2 * 2; // u16 (count) + count * (u16, u16) points + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + default: { + console.error('fuck'); + process.exit(1); + } + } + + return size; +} + +function predraw_event(x, y) { + return { + 'type': EVENT.PREDRAW, + 'x': x, + 'y': y + }; +} + +function stroke_event() { + return { + 'type': EVENT.STROKE, + 'points': storage.current_stroke, + }; +} + +function undo_event() { + return { 'type': EVENT.UNDO }; +} + +function redo_event() { + return { 'type': EVENT.REDO }; +} + +function main() { + const url = new URL(window.location.href); + const parts = url.pathname.split('/'); + + storage.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; + + ws_connect(); + + elements.canvas0 = document.getElementById('canvas0'); + elements.canvas1 = document.getElementById('canvas1'); + elements.cursor = document.getElementById('cursor'); + elements.cursor.style.width = storage.cursor.width + 'px'; + elements.cursor.style.height = storage.cursor.width + 'px'; + + storage.canvas.rect = elements.canvas0.getBoundingClientRect(); + + storage.ctx0 = elements.canvas0.getContext('2d'); + storage.ctx1 = elements.canvas1.getContext('2d'); + storage.ctx1.canvas.width = storage.ctx0.canvas.width = storage.canvas.width; + storage.ctx1.canvas.height = storage.ctx0.canvas.height = storage.canvas.height; + + storage.ctx1.lineJoin = storage.ctx1.lineCap = storage.ctx0.lineJoin = storage.ctx0.lineCap = 'round'; + storage.ctx1.lineWidth = storage.ctx0.lineWidth = storage.cursor.width; + + window.addEventListener('pointerdown', on_down) + window.addEventListener('pointermove', on_move) + window.addEventListener('pointerup', on_up); + window.addEventListener('pointercancel', on_up); + window.addEventListener('touchstart', (e) => e.preventDefault()); + window.addEventListener('keydown', on_keydown); + window.addEventListener('keyup', on_keyup); + // window.addEventListener('pointerleave', on_leave); +} diff --git a/client/math.js b/client/math.js new file mode 100644 index 0000000..c8af2f1 --- /dev/null +++ b/client/math.js @@ -0,0 +1,135 @@ +function rdp_find_max(points, start, end) { + const EPS = 0.5; + + let result = -1; + let max_dist = 0; + + const a = points[start]; + const b = points[end]; + + const dx = b.x - a.x; + const dy = b.y - a.y; + + const dist_ab = Math.sqrt(dx * dx + dy * dy); + const sin_theta = dy / dist_ab; + const cos_theta = dx / dist_ab; + + for (let i = start; i < end; ++i) { + const p = points[i]; + + const ox = p.x - a.x; + const oy = p.y - a.y; + + const rx = cos_theta * ox + sin_theta * oy; + const ry = -sin_theta * ox + cos_theta * oy; + + const x = rx + a.x; + const y = ry + a.y; + + const dist = Math.abs(y - a.y); + + if (dist > EPS && dist > max_dist) { + result = i; + max_dist = dist; + } + } + + return result; +} + +function process_rdp_r(points, start, end) { + let result = []; + + const max = rdp_find_max(points, start, end); + + if (max !== -1) { + const before = process_rdp_r(points, start, max); + const after = process_rdp_r(points, max, end); + result = [...before, points[max], ...after]; + } + + return result; +} + +function process_rdp(points) { + const result = process_rdp_r(points, 0, points.length - 1); + result.unshift(points[0]); + result.push(points[points.length - 1]); + return result; +} + +function process_ewmv(points, round = false) { + const result = []; + const alpha = 0.4; + + result.push(points[0]); + + for (let i = 1; i < points.length; ++i) { + const p = points[i]; + const x = Math.round(alpha * p.x + (1 - alpha) * result[result.length - 1].x); + const y = Math.round(alpha * p.y + (1 - alpha) * result[result.length - 1].y); + result.push({'x': x, 'y': y}); + } + + return result; +} + +function process_stroke(points) { + const result0 = process_ewmv(points); + const result1 = process_rdp(result0, true); + return result1; +} + +function stroke_stats(points, width) { + let length = 0; + let xmin = points[0].x, ymin = points[0].y; + let xmax = xmin, ymax = ymin; + + for (let i = 0; i < points.length; ++i) { + const point = points[i]; + if (point.x < xmin) xmin = point.x; + if (point.y < ymin) ymin = point.y; + if (point.x > xmax) xmax = point.x; + if (point.y > ymax) ymax = point.y; + + if (i > 0) { + const last = points[i - 1]; + const dx = point.x - last.x; + const dy = point.y - last.y; + length += Math.sqrt(dx * dx + dy * dy); + } + } + + xmin -= width; + ymin -= width; + xmax += width * 2; + ymax += width * 2; + + const bbox = { + 'xmin': Math.floor(xmin), + 'ymin': Math.floor(ymin), + 'xmax': Math.ceil(xmax), + 'ymax': Math.ceil(ymax) + }; + + return { + 'bbox': bbox, + 'length': length, + }; +} + +function rectangles_intersect(a, b) { + const result = ( + a.xmin <= b.xmax + && a.xmax >= b.xmin + && a.ymin <= b.ymax + && a.ymax >= b.ymin + ); + + return result; +} + +function stroke_intersects_region(points, bbox) { + const stats = stroke_stats(points, storage.cursor.width); + return rectangles_intersect(stats.bbox, bbox); +} \ No newline at end of file diff --git a/client/recv.js b/client/recv.js new file mode 100644 index 0000000..94859d9 --- /dev/null +++ b/client/recv.js @@ -0,0 +1,229 @@ +function deserializer_create(buffer, dataview) { + return { + 'offset': 0, + 'size': buffer.byteLength, + 'buffer': buffer, + 'view': dataview, + 'strview': new Uint8Array(buffer), + }; +} + +function des_u8(d) { + const value = d.view.getUint8(d.offset); + d.offset += 1; + return value; +} + +function des_u16(d) { + const value = d.view.getUint16(d.offset, true); + d.offset += 2; + return value; +} + +function des_u32(d) { + const value = d.view.getUint32(d.offset, true); + d.offset += 4; + return value; +} + +function des_u16array(d, count) { + const result = []; + + for (let i = 0; i < count; ++i) { + const item = d.view.getUint16(d.offset, true); + d.offset += 2; + result.push(item); + } + + return result; +} + +function des_event(d) { + const event = {}; + + event.type = des_u8(d); + event.user_id = des_u32(d); + + switch (event.type) { + case EVENT.PREDRAW: { + event.x = des_u16(d); + event.y = des_u16(d); + break; + } + + case EVENT.STROKE: { + const point_count = des_u16(d); + const coords = des_u16array(d, point_count * 2); + + event.points = []; + + for (let i = 0; i < point_count; ++i) { + const x = coords[2 * i + 0]; + const y = coords[2 * i + 1]; + event.points.push({'x': x, 'y': y}); + } + + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + default: { + console.error('fuck'); + } + } + + return event; +} + +function redraw_region(bbox) { + storage.ctx0.save(); + storage.ctx0.clearRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin); + + storage.ctx0.beginPath(); + storage.ctx0.rect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin); + storage.ctx0.clip(); + + for (const event of storage.events) { + if (event.type === EVENT.STROKE && !event.deleted) { + if (stroke_intersects_region(event.points, bbox)) { + draw_stroke(event); + } + } + } + + storage.ctx0.restore(); +} + +function handle_event(event) { + console.debug(`event type ${event.type} from user ${event.user_id}`); + + switch (event.type) { + case EVENT.STROKE: { + if (event.user_id in storage.predraw || event.user_id === storage.me.id) { + storage.predraw[event.user_id] = []; + redraw_predraw(); + } + + draw_stroke(event); + + break; + } + + case EVENT.UNDO: { + for (let i = storage.events.length - 1; i >=0; --i) { + const other_event = storage.events[i]; + if (other_event.type === EVENT.STROKE && other_event.user_id === event.user_id && !other_event.deleted) { + other_event.deleted = true; + const stats = stroke_stats(other_event.points, storage.cursor.width); + redraw_region(stats.bbox); + break; + } + } + + break; + } + + default: { + console.error('fuck'); + } + } +} + +async function handle_message(d) { + const message_type = des_u8(d); + + console.debug(message_type); + + switch (message_type) { + case MESSAGE.JOIN: + case MESSAGE.INIT: { + storage.me.id = des_u32(d); + storage.server_lsn = des_u32(d); + + if (storage.server_lsn > storage.lsn) { + // Server knows something that we don't + storage.lsn = storage.server_lsn; + } + + if (message_type === MESSAGE.JOIN) { + ls.setItem('sessionId', des_u32(d)); + console.debug('join in'); + } else { + console.debug('init in'); + } + + + const event_count = des_u32(d); + + console.debug(`${event_count} events in init`); + storage.ctx0.clearRect(0, 0, storage.ctx0.canvas.width, storage.ctx0.canvas.height); + + for (let i = 0; i < event_count; ++i) { + const event = des_event(d); + handle_event(event); + storage.events.push(event); + } + + send_ack(event_count); + + sync_queue(); + + break; + } + + case MESSAGE.FIRE: { + const user_id = des_u32(d); + const predraw_event = des_event(d); + + predraw_user(user_id, predraw_event); + + break; + } + + case MESSAGE.ACK: { + const lsn = des_u32(d); + + console.debug(`ack ${lsn} in`); + + if (lsn > storage.server_lsn) { + // ACKs may arrive out of order + storage.server_lsn = lsn; + } + + break; + } + + case MESSAGE.SYN: { + const sn = des_u32(d); + const count = des_u32(d); + + const we_expect = sn - storage.sn; + const first = count - we_expect; + + console.debug(`syn ${sn} in`); + + for (let i = 0; i < count; ++i) { + const event = des_event(d); + if (i >= first) { + handle_event(event); + storage.events.push(event); + } + } + + storage.sn = sn; + + await send_ack(sn); + + break; + } + + default: { + console.error('fuck'); + return; + } + } +} diff --git a/client/send.js b/client/send.js new file mode 100644 index 0000000..3960122 --- /dev/null +++ b/client/send.js @@ -0,0 +1,154 @@ +function serializer_create(size) { + const buffer = new ArrayBuffer(size); + return { + 'offset': 0, + 'size': size, + 'buffer': buffer, + 'view': new DataView(buffer), + 'strview': new Uint8Array(buffer), + }; +} + +function ser_u8(s, value) { + s.view.setUint8(s.offset, value); + s.offset += 1; +} + +function ser_u16(s, value) { + s.view.setUint16(s.offset, value, true); + s.offset += 2; +} + +function ser_u32(s, value) { + s.view.setUint32(s.offset, value, true); + s.offset += 4; +} + +function ser_event(s, event) { + ser_u8(s, event.type); + ser_u8(s, 0); // padding for 16bit alignment + + switch (event.type) { + case EVENT.PREDRAW: { + ser_u16(s, event.x); + ser_u16(s, event.y); + break; + } + + case EVENT.STROKE: { + ser_u16(s, event.points.length); + + console.debug('original', event.points); + + for (const point of event.points) { + ser_u16(s, point.x); + ser_u16(s, point.y); + } + + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + default: { + console.error('fuck'); + process.exit(1); + } + } +} + +async function send_ack(sn) { + const s = serializer_create(1 + 4); + + ser_u8(s, MESSAGE.ACK); + ser_u32(s, sn); + + console.debug(`ack ${sn} out`); + + if (ws) await ws.send(s.buffer); +} + +async function sync_queue() { + if (ws === null) { + console.debug('socket has closed, stopping SYNs'); + return; + } + + let size = 1 + 1 + 4 + 4; // opcode + lsn + event count + let count = storage.lsn - storage.server_lsn; + + if (count === 0) { + console.debug('server ACKed all events, clearing queue'); + storage.queue.length = 0; + return; + } + + for (let i = count - 1; i >= 0; --i) { + const event = storage.queue[storage.queue.length - 1 - i]; + size += event_size(event); + } + + const s = serializer_create(size); + + ser_u8(s, MESSAGE.SYN); + ser_u8(s, 0); // padding for 16bit alignment + ser_u32(s, storage.lsn); + ser_u32(s, count); + + for (let i = count - 1; i >= 0; --i) { + const event = storage.queue[storage.queue.length - 1 - i]; + ser_event(s, event); + } + + console.debug(`syn ${storage.lsn} out`); + + if (ws) await ws.send(s.buffer); + + setTimeout(sync_queue, config.sync_timeout); +} + +function push_event(event) { + storage.lsn += 1; + + switch (event.type) { + case EVENT.STROKE: { + const points = process_stroke(event.points); + storage.queue.push({ 'type': EVENT.STROKE, 'points': points }); + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + storage.queue.push(event); + break; + } + } +} + +// Queue an event and initialize repated sends until ACKed +function queue_event(event, skip = false) { + push_event(event); + + if (skip) { + return; + } + + if (storage.timers.queue_sync) { + clearTimeout(storage.timers.queue_sync); + } + + sync_queue(); +} + +// Fire and forget. Doesn't do anything if we are offline +async function fire_event(event) { + const s = serializer_create(1 + event_size(event)); + + ser_u8(s, MESSAGE.FIRE); + ser_event(s, event); + + if (ws) await ws.send(s.buffer); +} \ No newline at end of file diff --git a/client/websocket.js b/client/websocket.js new file mode 100644 index 0000000..2d8de34 --- /dev/null +++ b/client/websocket.js @@ -0,0 +1,35 @@ +function ws_connect() { + const session_id = ls.getItem('sessionId') || '0'; + const desk_id = storage.desk_id; + + ws = new WebSocket(`${config.ws_url}?deskId=${desk_id}&sessionId=${session_id}`); + + ws.addEventListener('open', on_open); + ws.addEventListener('message', on_message); + ws.addEventListener('error', on_error); + ws.addEventListener('close', on_close); +} + +function on_open() { + clearTimeout(storage.timers.ws_reconnect); + console.debug('open') +} + +async function on_message(event) { + const data = event.data; + const message_data = await data.arrayBuffer(); + const view = new DataView(message_data); + const d = deserializer_create(message_data, view); + + await handle_message(d); +} + +function on_close() { + ws = null; + console.debug('close'); + storage.timers.ws_reconnect = setTimeout(ws_connect, config.ws_reconnect_timeout); +} + +function on_error() { + ws.close(); +} \ No newline at end of file diff --git a/server/app.mjs b/server/app.mjs new file mode 100644 index 0000000..8d36a02 --- /dev/null +++ b/server/app.mjs @@ -0,0 +1,5 @@ +import * as storage from './storage'; +import * as server from './server'; + +storage.startup(); +server.startup(); diff --git a/server/bun.lockb b/server/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..eed46a087ea591a7d12153d50fe29c04c3a47b6d GIT binary patch literal 6967 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 P@8^Ylc%;kvpZoh8Cp4B$ literal 0 HcmV?d00001 diff --git a/server/config.js b/server/config.js new file mode 100644 index 0000000..92eda4c --- /dev/null +++ b/server/config.js @@ -0,0 +1,4 @@ +export const HOST = '127.0.0.1'; +export const PORT = 3003; +export const DATADIR = 'data'; +export const SYNC_TIMEOUT = 1000; \ No newline at end of file diff --git a/server/data/db.sqlite b/server/data/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..bb577991333a1a3a99c6f928119c9040c087cef3 GIT binary patch literal 192512 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 F{{dt1ll=ey literal 0 HcmV?d00001 diff --git a/server/deserializer.js b/server/deserializer.js new file mode 100644 index 0000000..606ed79 --- /dev/null +++ b/server/deserializer.js @@ -0,0 +1,67 @@ +import { EVENT } from './enums'; + +export function create(dataview) { + return { + 'offset': 0, + 'size': dataview.byteLength, + 'view': dataview, + }; +} + +export function u8(d) { + const value = d.view.getUint8(d.offset); + d.offset += 1; + return value; +} + +export function u16(d) { + const value = d.view.getUint16(d.offset, true); + d.offset += 2; + return value; +} + +export function u32(d) { + const value = d.view.getUint32(d.offset, true); + d.offset += 4; + return value; +} + +function u16array(d, count) { + const array = new Uint16Array(d.view.buffer, d.offset, count); + d.offset += count * 2; + return array; +} + +export function event(d) { + const event = {}; + + event.type = u8(d); + u8(d); // padding + + switch (event.type) { + case EVENT.PREDRAW: { + event.x = u16(d); + event.y = u16(d); + break; + } + + case EVENT.STROKE: { + const point_count = u16(d); + event.points = u16array(d, point_count * 2); + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + default: { + console.error('fuck'); + console.trace(); + process.exit(1); + } + } + + return event; +} \ No newline at end of file diff --git a/server/enums.js b/server/enums.js new file mode 100644 index 0000000..7265373 --- /dev/null +++ b/server/enums.js @@ -0,0 +1,26 @@ +export const SESSION = Object.freeze({ + OPENED: 0, // nothing sent/recved yet + READY: 1, // init complete + CLOSED: 2, // socket closed, but we might continute this same session on another socket +}); + +export const EVENT = Object.freeze({ + PREDRAW: 10, + STROKE: 20, + UNDO: 30, + REDO: 31, +}); + +export const MESSAGE = Object.freeze({ + INIT: 100, + SYN: 101, + ACK: 102, + FULL: 103, + FIRE: 104, + JOIN: 105, +}); + +export const SNS = Object.freeze({ + DESK: 1, + SESSION: 2, +}); \ No newline at end of file diff --git a/server/http.js b/server/http.js new file mode 100644 index 0000000..e0d641f --- /dev/null +++ b/server/http.js @@ -0,0 +1,4 @@ +export function route(req) { + console.log('HTTP:', req.url); + return new Response(req.url); +} \ No newline at end of file diff --git a/server/math.js b/server/math.js new file mode 100644 index 0000000..d9f27b5 --- /dev/null +++ b/server/math.js @@ -0,0 +1,14 @@ +import crypto from 'crypto'; + +export function crypto_random32() { + const arr = new Uint8Array(4); + const dataview = new DataView(arr.buffer); + + crypto.getRandomValues(arr); + + return dataview.getUint32(0); +} + +export function fast_random32() { + return Math.floor(Math.random() * 4294967296); +} \ No newline at end of file diff --git a/server/recv.js b/server/recv.js new file mode 100644 index 0000000..c84247c --- /dev/null +++ b/server/recv.js @@ -0,0 +1,129 @@ +import * as des from './deserializer'; +import * as send from './send'; +import * as math from './math'; +import * as storage from './storage'; + +import { SESSION, MESSAGE, EVENT } from './enums'; +import { sessions, desks } from './storage'; + +// Session ACKed events up to SN +function recv_ack(d, session) { + const sn = des.u32(d); + + session.state = SESSION.READY; + session.sn = sn; + + console.log(`ack ${sn} in`); +} + +function handle_event(session, event) { + switch (event.type) { + case EVENT.STROKE: { + event.stroke_id = math.fast_random32(); + storage.put_stroke(event.stroke_id, session.desk_id, event.points); + storage.put_event(event); + break; + } + + case EVENT.UNDO: { + storage.put_event(event); + break; + } + + default: { + console.error('fuck'); + console.trace(); + process.exit(1); + } + } +} + +async function recv_syn(d, session) { + const padding = des.u8(d); + const lsn = des.u32(d); + const count = des.u32(d); + + console.log(`syn ${lsn} in, total size = ${d.size}`); + + const we_expect = lsn - session.lsn; + const first = count - we_expect; + const events = []; + + console.log(`we expect ${we_expect}, count ${count}`); + + for (let i = 0; i < count; ++i) { + const event = des.event(d); + if (i >= first) { + event.desk_id = session.desk_id; + event.user_id = session.user_id; + event.stroke_id = null; + handle_event(session, event); + events.push(event); + } + } + + desks[session.desk_id].sn += we_expect; + desks[session.desk_id].events.push(...events); + session.lsn = lsn; + + storage.save_desk_sn(session.desk_id, desks[session.desk_id].sn); + storage.save_session_lsn(session.id, lsn); + + send.send_ack(session.ws, lsn); + send.sync_desk(session.desk_id); +} + +function recv_fire(d, session) { + const event = des.event(d); + + for (const sid in sessions) { + const other = sessions[sid]; + + if (other.id === session.id) { + continue; + } + + if (other.state !== SESSION.READY) { + continue; + } + + if (other.desk_id != session.desk_id) { + continue; + } + + send.send_fire(other.ws, session.user_id, event); + } +} + +export async function handle_message(ws, d) { + if (!(ws.data.session_id in sessions)) { + return; + } + + const session = sessions[ws.data.session_id]; + const desk_id = session.desk_id; + const message_type = des.u8(d); + + switch (message_type) { + case MESSAGE.FIRE: { + recv_fire(d, session); + break; + } + + case MESSAGE.SYN: { + recv_syn(d, session); + break; + } + + case MESSAGE.ACK: { + recv_ack(d, session); + break; + } + + default: { + console.error('fuck'); + console.trace(); + process.exit(1); + } + } +} diff --git a/server/send.js b/server/send.js new file mode 100644 index 0000000..ac825cd --- /dev/null +++ b/server/send.js @@ -0,0 +1,186 @@ +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 += 2 * 2; + break; + } + + case EVENT.STROKE: { + size += 2; // point count + size += event.points.byteLength; + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + 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) { + 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) { + 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) { + 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.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); + } + } +} \ No newline at end of file diff --git a/server/serializer.js b/server/serializer.js new file mode 100644 index 0000000..9fd289c --- /dev/null +++ b/server/serializer.js @@ -0,0 +1,63 @@ +import { EVENT } from './enums'; + +export function create(size) { + const buffer = new ArrayBuffer(size); + return { + 'offset': 0, + 'size': size, + 'buffer': buffer, + 'view': new DataView(buffer), + 'strview': new Uint8Array(buffer), + }; +} + +export function u8(s, value) { + s.view.setUint8(s.offset, value); + s.offset += 1; +} + +export function u16(s, value) { + s.view.setUint16(s.offset, value, true); + s.offset += 2; +} + +export function u32(s, value) { + s.view.setUint32(s.offset, value, true); + s.offset += 4; +} + +export function bytes(s, bytes) { + s.strview.set(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), s.offset); + s.offset += bytes.byteLength; +} + +export function event(s, event) { + u8(s, event.type); + u32(s, event.user_id); + + switch (event.type) { + case EVENT.PREDRAW: { + u16(s, event.x); + u16(s, event.y); + break; + } + + case EVENT.STROKE: { + const points_bytes = event.points; + u16(s, points_bytes.byteLength / 2 / 2); // each point is 2 u16s + bytes(s, points_bytes); + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + default: { + console.error('fuck'); + console.trace(); + process.exit(1); + } + } +} diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..54a6df2 --- /dev/null +++ b/server/server.js @@ -0,0 +1,75 @@ +import * as config from './config'; +import * as storage from './storage'; +import * as http_server from './http'; +import * as math from './math'; +import * as ser from './serializer'; +import * as des from './deserializer'; +import * as send from './send'; +import * as recv from './recv'; + +import { MESSAGE, EVENT, SESSION, SNS } from './enums'; +import { sessions, desks } from './storage'; + +export function startup() { + Bun.serve({ + hostname: config.HOST, + port: config.PORT, + + fetch(req, server) { + const url = new URL(req.url); + + if (url.pathname == '/ws/') { + const desk_id = url.searchParams.get('deskId') || '0'; + const session_id = url.searchParams.get('sessionId') || '0'; + + if (!(desk_id in desks)) { + const desk = { + id: desk_id, + sn: 0, + events: [], + }; + + storage.create_desk(desk_id); + desks[desk_id] = desk; + } + + server.upgrade(req, { + data: { + desk_id: desk_id, + session_id: session_id, + } + }); + + return; + } + + return http_server.route(req); + }, + + websocket: { + open(ws) { + send.send_init(ws); + }, + + async message(ws, u8array) { + const dataview = new DataView(u8array.buffer); + const d = des.create(dataview); + await recv.handle_message(ws, d); + }, + + close(ws, code, message) { + if (ws.data.session_id in sessions) { + console.log(`session ${ws.data.session_id} closed`); + sessions[ws.data.session_id].state = SESSION.CLOSED; + sessions[ws.data.session_id].ws = null; + } + }, + + error(ws, error) { + close(ws, 1000, error); // Treat error as normal close + } + } + }); + + console.log(`Running on ${config.HOST}:${config.PORT}`) +} \ No newline at end of file diff --git a/server/storage.js b/server/storage.js new file mode 100644 index 0000000..8fd1a5d --- /dev/null +++ b/server/storage.js @@ -0,0 +1,201 @@ +import * as config from './config'; +import * as sqlite from 'bun:sqlite'; + +import { EVENT, SESSION } from './enums'; + +export const sessions = {}; +export const desks = {}; + +let db = null; +const queries = {}; + +export function startup() { + const path = `${config.DATADIR}/db.sqlite`; + + db = new sqlite.Database(path, { create: true }); + + db.query(`CREATE TABLE IF NOT EXISTS desks ( + id INTEGER PRIMARY KEY, + sn INTEGER, + title TEXT + );`).run(); + + db.query(`CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + login TEXT + );`).run(); + + db.query(`CREATE TABLE IF NOT EXISTS sessions ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + desk_id INTEGER, + lsn INTEGER, + + FOREIGN KEY (user_id) + REFERENCES users (id) + ON DELETE CASCADE + ON UPDATE NO ACTION, + + FOREIGN KEY (desk_id) + REFERENCES desks (id) + ON DELETE CASCADE + ON UPDATE NO ACTION + );`).run(); + + db.query(`CREATE TABLE IF NOT EXISTS strokes ( + id INTEGER PRIMARY KEY, + desk_id INTEGER, + points BLOB, + + FOREIGN KEY (desk_id) + REFERENCES desks (id) + ON DELETE CASCADE + ON UPDATE NO ACTION + );`).run(); + + db.query(`CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY, + type INTEGER, + desk_id INTEGER, + user_id INTEGER, + stroke_id INTEGER, + x INTEGER, + y INTEGER, + + FOREIGN KEY (desk_id) + REFERENCES desks (id) + ON DELETE CASCADE + ON UPDATE NO ACTION, + + FOREIGN KEY (user_id) + REFERENCES users (id) + ON DELETE CASCADE + ON UPDATE NO ACTION + + FOREIGN KEY (stroke_id) + REFERENCES strokes (id) + ON DELETE CASCADE + ON UPDATE NO ACTION + );`).run(); + + db.query(`CREATE INDEX IF NOT EXISTS idx_events_desk_id + ON events (desk_id); + `).run(); + + db.query(`CREATE INDEX IF NOT EXISTS idx_strokes_desk_id + ON strokes (desk_id); + `).run(); + + const res1 = db.query('SELECT COUNT(id) as count FROM desks').get(); + const res2 = db.query('SELECT COUNT(id) as count FROM events').get(); + const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get(); + const res4 = db.query('SELECT COUNT(id) as count FROM users').get(); + const res5 = db.query('SELECT COUNT(id) as count FROM sessions').get(); + + queries.desks = db.query('SELECT id, sn FROM desks'); + queries.events = db.query('SELECT id, desk_id, user_id, stroke_id, type, x, y FROM events'); + queries.sessions = db.query('SELECT id, lsn, user_id, desk_id FROM sessions'); + queries.strokes = db.query('SELECT id, points FROM strokes'); + queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)'); + queries.desk_strokes = db.query('SELECT id, points FROM strokes WHERE desk_id = ?1'); + queries.put_desk_stroke = db.query('INSERT INTO strokes (id, desk_id, points) VALUES (?1, ?2, ?3)'); + queries.clear_desk_events = db.query('DELETE FROM events WHERE desk_id = ?1'); + queries.set_desk_sn = db.query('UPDATE desks SET sn = ?1 WHERE id = ?2'); + queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2'); + queries.create_session = db.query('INSERT INTO sessions (id, lsn, user_id, desk_id) VALUES (?1, 0, ?2, ?3)'); + queries.create_user = db.query('INSERT INTO users (id, login) VALUES (?1, ?2)'); + queries.put_event = db.query('INSERT INTO events (type, desk_id, user_id, stroke_id, x, y) VALUES (?1, ?2, ?3, ?4, ?5, ?6)'); + + console.log(`Storing data in ${path}`); + console.log(`Entity count at startup: + ${res1.count} desks + ${res2.count} events + ${res3.count} strokes + ${res4.count} users + ${res5.count} sessions` + ); + + const stored_desks = get_desks(); + const stored_events = get_events(); + const stored_strokes = get_strokes(); + const stored_sessions = get_sessions(); + + const stroke_dict = {}; + + for (const stroke of stored_strokes) { + stroke.points = new Uint16Array(stroke.points.buffer); + stroke_dict[stroke.id] = stroke; + } + + for (const desk of stored_desks) { + desks[desk.id] = desk; + desks[desk.id].events = []; + } + + for (const event of stored_events) { + if (event.type === EVENT.STROKE) { + event.points = stroke_dict[event.stroke_id].points; + } + + + desks[event.desk_id].events.push(event); + } + + for (const session of stored_sessions) { + session.state = SESSION.CLOSED; + session.ws = null; + sessions[session.id] = session; + } +} + +export function get_strokes() { + return queries.strokes.all(); +} + +export function get_sessions() { + return queries.sessions.all(); +} + +export function get_desks() { + return queries.desks.all(); +} + +export function get_events() { + return queries.events.all(); +} + +export function get_desk_strokes(desk_id) { + return queries.desk_strokes.all(desk_id); +} + +export function put_event(event) { + return queries.put_event.get(event.type, event.desk_id || 0, event.user_id || 0, event.stroke_id || 0, event.x || 0, event.y || 0); +} + +export function put_stroke(stroke_id, desk_id, points) { + return queries.put_desk_stroke.get(stroke_id, desk_id, new Uint8Array(points.buffer, points.byteOffset, points.byteLength)); +} + +export function clear_events(desk_id) { + return queries.clear_desk_events.get(desk_id); +} + +export function create_desk(desk_id, title = 'untitled') { + return queries.empty_desk.get(desk_id, title); +} + +export function save_desk_sn(desk_id, sn) { + return queries.set_desk_sn.get(sn, desk_id); +} + +export function create_session(session) { + return queries.create_session.get(session.id, session.user_id, session.desk_id); +} + +export function create_user(user) { + return queries.create_user.get(user.id, user.login); +} + +export function save_session_lsn(session_id, lsn) { + return queries.save_session_lsn.get(lsn, session_id); +} \ No newline at end of file