const areaId = "doctext"; const validEvents = [ "insertText", "insertFromPaste", "insertLineBreak", "insertCompositionText", "deleteContentBackward", "deleteContentForward", ]; let selectionStart = 0; let selectionEnd = 0; let area; let ws; const uuid = self.crypto.randomUUID(); const wsUrl = "ws://localhost:3000/ws"; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); function setup() { area = document.querySelector(`#${areaId}`); ws = new WebSocket(wsUrl); setupUi(); setupWs(); } function setupUi() { document.addEventListener("selectionchange", onSelectionChange, false); area.addEventListener("beforeinput", onInput, false); } function setupWs() { ws.onclose = function (e) { console.log(e); setTimeout(() => { ws = new WebSocket(wsUrl); }, 2000); }; ws.onmessage = function (e) { let payload = JSON.parse(e.data); if (payload.client !== uuid) { console.log(payload.doc); area.value = payload.doc; } }; } function onSelectionChange() { const activeElement = document.activeElement; if (activeElement && activeElement.id === areaId) { console.debug("onSelectionChange", area); selectionStart = area.selectionStart; selectionEnd = area.selectionEnd; } } function onInput(event) { if (!validEvents.includes(event.inputType)) return; const payload = { client: uuid, action: event.inputType, data: event.data, start: selectionStart, end: selectionEnd, }; ws.send(JSON.stringify(payload)); console.log(selectionStart, selectionEnd); // workaround for differences between firefox and chrome // chrome does not fire selectionchange events on backspace/delete events, // while firefox does if (event.inputType === "deleteContentBackward") { selectionStart = area.selectionStart - 1; selectionEnd = area.selectionEnd - 1; } } setup();