/** * Entire pâro client code. * Most of it is logging and ways for you to hook into pâro. * * All you need to do is to overwrite window.__PARO__.websocketUrl * to use the same port as your tauri app, have an element with id * 'paro-content' (can be configured and can be on body) * and then call * `window.__PARO__.initialize();` * * After importing paro.js: * * * * */ (function() { PARO = { websocketUrl: "ws://127.0.0.1:8080", initialize: null, onMessageHandler: undefined, // (event) => {} onOpenHandler: undefined, // (event) => {} onCloseHandler: undefined, // (event) => {} onErrorHandler: undefined, // (event) => {} onEmitEventHandler: undefined, // (event_id) => {} emitEvent: undefined, // (event_id, event) => {} baseElementId: "paro-application", pingInterval: 60000, logging: true, logger: { info: console.info.bind(console), error: console.info.bind(console) }, websocket: null }; window.__PARO__ = PARO; function getCssPath(el) { if (!(el instanceof Element)) return; var break_on_id = true; var path = []; while (el.nodeType === Node.ELEMENT_NODE) { //while (el) { var selector = el.nodeName.toLowerCase(); if (break_on_id && el.id) { selector += '#' + el.id; path.unshift(selector); break; } else { var sib = el, nth = 1; while (sib = sib.previousElementSibling) { if (sib.nodeName.toLowerCase() == selector) nth++; } if (nth != 1) selector += ":nth-of-type("+nth+")"; } path.unshift(selector); el = el.parentNode; } return path.join(" > "); } // gives you a chance to change pâro settings. You have to call initialize once you configured pâro PARO.initialize = () => { PARO = window.__PARO__; if (PARO.logging) PARO.logger.info("[paro init] connecting to websocket via " + PARO.websocketUrl); let socket = new WebSocket(PARO.websocketUrl); socket.onopen = function(event) { if (PARO.logging) PARO.logger.info("[paro open] Connection established", event); PARO.websocket = socket; if (PARO.onOpenHandler) PARO.onOpenHandler(event); socket.send("ping"); }; // keep connection alive setInterval(() => socket.send("ping"), PARO.pingInterval); socket.onmessage = function(event) { if (PARO.logging) PARO.logger.info("[paro websocket message] Data received from server:", event); if (PARO.onMessageHandler) PARO.onMessageHandler(event); if (event.data == "pong") return; var paroElement = document.getElementById(PARO.baseElementId); if (paroElement) paroElement.innerHTML = event.data; else if (PARO.logging) PARO.logger.error("[paro websocket message] could not find paro element '#" + PARO.baseElementId + "'. Html will not be rendered!", event); }; socket.onclose = function(event) { if (event.wasClean) { if (PARO.logging) PARO.logger.info("[paro websocket close] Connection closed cleanly code=" + event.code + " reason=" + event.reason, event); } else { // e.g. server process killed or network down // event.code is usually 1006 in this case if (PARO.logging) PARO.logger.info("[paro websocket close] Connection died", event); } if (PARO.onCloseHandler) PARO.onCloseHandler(event); }; socket.onerror = function(error) { if (PARO.logging) PARO.logger.error("[paro websocket error]", event); if (PARO.onErrorHandler) PARO.onErrorHandler(event); }; } /** * Calls to this function are generated by the event! macro */ PARO.emitEvent = (event_id, event) => { PARO = window.__PARO__; if (PARO.websocket) { if (PARO.logging) PARO.logger.info("[paro emit event] emitting event '" + event_id + "'", event); function get_value_and_emit() { var value = undefined; var target = event.target; var cssPath = getCssPath(target); var startPosition = target.selectionStart; var endPosition = target.selectionEnd; if (target) { value = target.value; if (target.hasAttribute("contenteditable")) value = field.innerHTML; else if (target.matches('[type="checkbox"]')) value = target.checked; } if (PARO.onEmitEventHandler) PARO.onEmitEventHandler(event_id, event); PARO.websocket.send(event_id + "__PARO__" + value); setTimeout(() => { // once the server replied with new html, go back to the // event emitting element, set focus and cursor position var element = document.querySelector(cssPath); if (element) { element.focus(); if (startPosition) { element.selectionStart = startPosition; element.endPosition = endPosition; } } }, 10); } if (event.type == "input") get_value_and_emit(); else // we often want to react to each key press for example for validation. // oninput is triggered before value is updated, so we push a callback // to the end of the executions stack, to be called, once value is set. setTimeout(get_value_and_emit, 0); } else if (PARO.logging) { PARO.logger.error("[paro emit event] trying to emit an event while websocket is not yet connected", event_id); } }; })();