summaryrefslogtreecommitdiffhomepage
path: root/html
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-26 20:46:30 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-26 20:46:30 +0100
commit789e5555ab4c44a1ae779eccf6ccf8340602cf22 (patch)
treefd1c15ac38ec4d43965d8e12a149ae52a0808a73 /html
parent004db5e7e4e9ab6ac5b4730873c6b8f58da92930 (diff)
Websockets: Notify other clients of changes
Diffstat (limited to 'html')
-rw-r--r--html/whiteboard.js409
1 files changed, 121 insertions, 288 deletions
diff --git a/html/whiteboard.js b/html/whiteboard.js
index 7777d4e..9610468 100644
--- a/html/whiteboard.js
+++ b/html/whiteboard.js
@@ -3,145 +3,126 @@ function init() {
init_board();
}
-class AdjustingTimer {
- constructor() {
- this.update_counter = 0; // counting seconds since last counter reset
- }
+var revision;
- fast_mode() {
- if (this.update_counter < 5*60)
- return true;
- return false;
- }
+// helper for breaking feedback loop
+var caretpos = 0;
- // private method
- // returns current interval in ms
- current_update_interval()
- {
- if (this.fast_mode())
- return 2000; // 2s
- else
- return 5 * 60000; // 5min
- };
+function showQRWindow()
+{
+ document.getElementById("qrwindow").style.display = 'block';
+}
- // private
- count() {
- this.update_counter += this.current_update_interval() / 1000;
- };
+function hideQRWindow()
+{
+ document.getElementById("qrwindow").style.display = 'none';
+}
- // private
- on_timeout() {
- this.m_fn();
- this.count();
- var _this = this;
- this.update_timer = setTimeout(function(){_this.on_timeout();}, this.current_update_interval());
- };
+var websocket;
- // to be called once on startup
- start(fn) {
- this.m_fn = fn;
- var _this = this;
- this.update_timer = setTimeout(function(){_this.on_timeout();}, this.current_update_interval());
- };
+//
+// Callbacks for websocket data of different types
+//
- // to be called on activity:
- // * changes from remote
- // * changes by ourselves
- // * local activity, e.g. mouse move, or key presses
- reset() {
- if (!this.fast_mode()) {
- this.update_counter = 0;
- clearTimeout(this.update_timer);
- var _this = this;
- this.update_timer = setTimeout(function(){_this.on_timeout();}, this.current_update_interval());
- } else {
- this.update_counter = 0;
- }
- };
+function on_getfile(data, rev, pos)
+{
+ var board = document.getElementById("board");
+ if (board.value != data) {
+ board.value = data;
+ }
+ textAreaSetPos("board", pos);
+ revision = rev;
}
-var timer = new AdjustingTimer();
+function on_newid(id)
+{
+ var new_location = document.location.origin + document.location.pathname + '?id=' + id;
+ window.location.href = new_location;
+}
-function showQRWindow()
+function on_qrcode(png)
{
- document.getElementById("qrwindow").style.display = 'block';
+ var blob = new Blob([png], {type: 'image/png'});
+ var url = URL.createObjectURL(blob);
+ var img = document.getElementById("qrcode");
+ img.src = url;
+ showQRWindow();
}
-function hideQRWindow()
+function on_modify_ack(rev)
{
- document.getElementById("qrwindow").style.display = 'none';
+ revision = rev;
}
-function init_board() {
- var xhr = new XMLHttpRequest();
+function on_message(e) {
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString(e.data, "text/xml");
- const searchParams = (new URL(document.location)).searchParams;
- if (!searchParams.has('id')) {
- redirect_to_new_page();
- return;
- }
-
- // run on data received back
- xhr.onreadystatechange = function() {
- if (this.readyState == 3) {
- //set_status("Please wait while downloading " + filename + " ...");
- return;
- }
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
- return;
- }
-
- var file = new Blob([this.response]);
- reader = new FileReader();
- reader.onload = function() {
- var board = document.getElementById("board");
- var pos = reader.result.indexOf('\x01');
- if (pos == -1) { // not found
- board.value = reader.result;
- } else {
- board.value = reader.result.substr(0, pos) + reader.result.substr(pos + 1);
- }
- textAreaSetPos("board", pos);
-
- // Initialization done. Now we can start modifying.
- board.addEventListener("input", function() {on_modify(); });
- board.addEventListener("selectionchange", function() {on_modify(); });
-
- // Initialization done. Now we can start modifying.
- document.addEventListener("mousemove", function() {timer.reset(); });
-
- timer.start(checkupdate);
- }
-
- reader.readAsBinaryString(file);
-
- //set_status(""); // OK
+ var type = xmlDocument.getElementsByTagName("type")[0].textContent;
+
+ if (type == "getfile") {
+ on_getfile(xmlDocument.getElementsByTagName("data")[0].textContent,
+ parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent),
+ parseInt(xmlDocument.getElementsByTagName("cursorpos")[0].textContent));
+ } else if (type == "modify") {
+ on_modify_ack(parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent));
+ } else if (type == "newid") {
+ on_newid(xmlDocument.getElementsByTagName("id")[0].textContent);
+ } else if (type == "qrcode") {
+ on_qrcode(xmlDocument.getElementsByTagName("png")[0].textContent);
+ } else if (type == "error") {
+ alert(xmlDocument.getElementsByTagName("message")[0].textContent);
+ } else {
+ alert("Unhandled message type: " + e.data + "|" + type);
}
+}
- var parser = new DOMParser();
- var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
-
- var requestElement = xmlDocument.getElementsByTagName("request")[0];
+function handleSelection() {
+ const activeElement = document.activeElement
- var commandElement = xmlDocument.createElement("command");
- commandElement.appendChild(document.createTextNode("getfile"));
- requestElement.appendChild(commandElement);
+ if (activeElement && activeElement.id === 'board') {
+ if (caretpos != activeElement.selectionStart) {
+ on_selectionchange(activeElement.selectionStart);
+ caretpos = activeElement.selectionStart;
+ }
+ }
+}
- var idElement = xmlDocument.createElement("id");
- idElement.appendChild(document.createTextNode(get_id()));
- requestElement.appendChild(idElement);
+function init_board() {
+ var newlocation = location.origin + location.pathname;
+ newlocation = newlocation.replace(/^http/, 'ws');
+ if (newlocation.slice(-1) != "/")
+ newlocation += "/";
+ newlocation += "websocket";
+ websocket = new WebSocket(newlocation);
+
+ websocket.onmessage = function(e) { on_message(e); };
+
+ websocket.onopen = function(e) {
+ const searchParams = (new URL(document.location)).searchParams;
+ if (!searchParams.has('id')) {
+ redirect_to_new_page();
+ return;
+ }
- xhr.open("POST", "whiteboard.fcgi", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.responseType = 'blob';
- xhr.send(xmlDocument);
+ var board = document.getElementById("board");
+ board.addEventListener("input", function() {on_input(); });
+ // Need this workaround (different from direct on_selectionchange) for Chrome.
+ // Otherwise, callback will not be called on Chrome.
+ document.addEventListener("selectionchange", handleSelection);
+ //board.addEventListener("selectionchange", function() {on_selectionchange(); });
+
+ websocket.send("<request><command>getfile</command><id>" + get_id() + "</id></request>");
+ };
+
+ websocket.onclose = function(e) {
+ alert("Server connection closed.");
+ };
+
+ websocket.onerror = function(e) {
+ alert("Error: Server connection closed.");
+ };
- //set_status("Please wait while server prepares " + filename + " ...");
-
document.getElementById("qrwindow").onclick = function() {
hideQRWindow();
}
@@ -161,81 +142,20 @@ function get_id()
return searchParams.get('id');
}
+// from html
function on_new_page()
{
- redirect_to_new_page();
+ redirect_to_new_page();
}
function redirect_to_new_page()
{
- var xhr = new XMLHttpRequest();
-
- // run on data received back
- xhr.onreadystatechange = function() {
- if (this.readyState == 3) {
- //set_status("Please wait while downloading " + filename + " ...");
- return;
- }
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
- return;
- }
-
- var id = this.responseText;
- //alert("location=" + document.location.href);
- var new_location = document.location.href;
- var pos = new_location.search("\\?");
- if (pos >= 0)
- new_location = new_location.substring(0, pos);
- new_location += '?id=' + id;
-
- window.location.href = new_location;
- //set_status(""); // OK
- }
-
- var parser = new DOMParser();
- var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
-
- var requestElement = xmlDocument.getElementsByTagName("request")[0];
-
- var commandElement = xmlDocument.createElement("command");
- commandElement.appendChild(document.createTextNode("newid"));
- requestElement.appendChild(commandElement);
-
- xhr.open("POST", "whiteboard.fcgi", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.send(xmlDocument);
-
- //set_status("Please wait while server prepares " + filename + " ...");
+ websocket.send("<request><command>newid</command></request>");
}
// local change done
-function on_modify()
+function on_input()
{
- timer.reset();
-
- var xhr = new XMLHttpRequest();
-
- // run on data received back
- xhr.onreadystatechange = function() {
- if (this.readyState == 3) {
- //set_status("Please wait while downloading " + filename + " ...");
- return;
- }
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
- return;
- }
-
- //set_status(""); // OK
- }
-
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
@@ -250,131 +170,47 @@ function on_modify()
requestElement.appendChild(idElement);
var dataElement = xmlDocument.createElement("data");
- dataElement.appendChild(document.createTextNode(addPos(document.getElementById("board").value, document.getElementById("board").selectionStart)));
+ dataElement.appendChild(document.createTextNode(document.getElementById("board").value));
requestElement.appendChild(dataElement);
- xhr.open("POST", "whiteboard.fcgi", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.responseType = 'blob';
- xhr.send(xmlDocument);
-
- //set_status("Please wait while server prepares " + filename + " ...");
-}
-
-// checksum of string
-function checksum32(s) {
- var result = 0;
- for (var i = 0; i < s.length; i++) {
- result = ((((result >>> 1) | ((result & 1) << 31)) | 0) ^ (s.charCodeAt(i) & 0xFF)) | 0;
- }
- return (result & 0x7FFFFFFF) | 0;
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
-function textAreaSetPos(id, pos)
+// for cursor position
+function on_selectionchange(pos)
{
- document.getElementById(id).selectionStart = pos;
- document.getElementById(id).selectionEnd = pos;
-}
-
-function addPos(s, pos)
-{
- return s.substr(0, pos) + '\x01' + s.substr(pos);
-}
-
-// gets called by regular polling
-function checkupdate() {
- var xhr = new XMLHttpRequest();
-
- // run on data received back
- xhr.onreadystatechange = function() {
- if (this.readyState == 3) {
- //set_status("Please wait while downloading " + filename + " ...");
- return;
- }
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
- return;
- }
-
- // no change if response is text/plain
- if (this.getResponseHeader("Content-Type") == "application/octet-stream") {
- timer.reset();
- var file = new Blob([this.response]);
- reader = new FileReader();
- reader.onload = function() {
- var board = document.getElementById("board");
- var pos = reader.result.indexOf('\x01');
- if (pos == -1) { // not found
- board.value = reader.result;
- } else {
- board.value = reader.result.substr(0, pos) + reader.result.substr(pos + 1);
- }
- textAreaSetPos("board", pos);
- }
-
- reader.readAsBinaryString(file);
- }
-
- //set_status(""); // OK
- }
-
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
var requestElement = xmlDocument.getElementsByTagName("request")[0];
var commandElement = xmlDocument.createElement("command");
- commandElement.appendChild(document.createTextNode("checkupdate"));
+ commandElement.appendChild(document.createTextNode("cursorpos"));
requestElement.appendChild(commandElement);
var idElement = xmlDocument.createElement("id");
idElement.appendChild(document.createTextNode(get_id()));
requestElement.appendChild(idElement);
- var checksumElement = xmlDocument.createElement("checksum");
- checksumElement.appendChild(document.createTextNode(checksum32(addPos(document.getElementById("board").value, document.getElementById("board").selectionStart))));
- requestElement.appendChild(checksumElement);
-
- xhr.open("POST", "whiteboard.fcgi", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.responseType = 'blob';
- xhr.send(xmlDocument);
+ var dataElement = xmlDocument.createElement("pos");
+ dataElement.appendChild(document.createTextNode(pos));
+ requestElement.appendChild(dataElement);
- //set_status("Please wait while server prepares " + filename + " ...");
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
-function on_qrcode()
+function textAreaSetPos(id, pos)
{
- var xhr = new XMLHttpRequest();
-
- // run on data received back
- xhr.onreadystatechange = function() {
- if (this.readyState == 3) {
- //set_status("Please wait while downloading " + filename + " ...");
- return;
- }
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- //set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
- return;
- }
-
- if (this.getResponseHeader("Content-Type") == "image/png") {
- var blob = new Blob([this.response], {type: 'image/png'});
- var url = URL.createObjectURL(blob);
- var img = document.getElementById("qrcode");
- img.src = url;
- showQRWindow();
- }
-
- //set_status(""); // OK
+ if (document.getElementById(id).selectionStart != pos) {
+ document.getElementById(id).selectionStart = pos;
+ document.getElementById(id).selectionEnd = pos;
+ caretpos = pos;
}
+}
+// HTML button
+function on_qrcode()
+{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
@@ -388,9 +224,6 @@ function on_qrcode()
idElement.appendChild(document.createTextNode(document.location));
requestElement.appendChild(idElement);
- xhr.open("POST", "whiteboard.fcgi", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.responseType = 'blob';
- xhr.send(xmlDocument);
+ websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}