From b0337d9f5bfbb1a68d3353f63e04d8b4eef1e44f Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Sun, 10 May 2020 23:13:55 +0200 Subject: [PATCH] feat: dynamic frontend configuration --- config.default.yml | 8 ++- scripts/config-schema.json | 30 ++++++++++- scripts/server-backend.js | 22 ++++---- scripts/utils.test.js | 8 +-- src/js/const.js | 3 -- src/js/main.js | 87 ++++++++++++++------------------ src/js/services/ConfigService.js | 51 +++++++++++++++++++ src/js/services/InfoService.js | 6 +-- src/js/utils.js | 28 ++++++++++ src/js/whiteboard.js | 20 ++++---- 10 files changed, 180 insertions(+), 83 deletions(-) delete mode 100644 src/js/const.js create mode 100644 src/js/services/ConfigService.js diff --git a/config.default.yml b/config.default.yml index 72ae98f..bbfd85e 100644 --- a/config.default.yml +++ b/config.default.yml @@ -11,7 +11,11 @@ backend: frontend: # When an editable whiteboard is loading in a client, # should it be started in read-only mode. - setReadOnlyOnWhiteboardLoad: false + readOnlyOnWhiteboardLoad: false # Show smallest screen indicator showSmallestScreenIndicator: true - # performance: + performance: + pointerEventsThreshold: + minDistDelta: 1 + minTimeDelta: 33 + refreshInfoFreq: 5 diff --git a/scripts/config-schema.json b/scripts/config-schema.json index 0370f35..a389e1e 100644 --- a/scripts/config-schema.json +++ b/scripts/config-schema.json @@ -30,13 +30,39 @@ "frontend": { "type": "object", "additionalProperties": false, - "required": ["setReadOnlyOnWhiteboardLoad", "showSmallestScreenIndicator"], + "required": ["readOnlyOnWhiteboardLoad", "showSmallestScreenIndicator", "performance"], "properties": { - "setReadOnlyOnWhiteboardLoad": { + "readOnlyOnWhiteboardLoad": { "type": "boolean" }, "showSmallestScreenIndicator": { "type": "boolean" + }, + "performance": { + "type": "object", + "additionalProperties": false, + "required": ["pointerEventsThreshold", "refreshInfoFreq"], + "properties": { + "pointerEventsThreshold": { + "type": "object", + "additionalProperties": false, + "required": ["minDistDelta", "minTimeDelta"], + "properties": { + "minDistDelta": { + "type": "number", + "minimum": 0 + }, + "minTimeDelta": { + "type": "number", + "minimum": 0 + } + } + }, + "refreshInfoFreq": { + "type": "number", + "minimum": 0 + } + } } } } diff --git a/scripts/server-backend.js b/scripts/server-backend.js index dec238a..d24a961 100644 --- a/scripts/server-backend.js +++ b/scripts/server-backend.js @@ -195,19 +195,19 @@ function startBackendServer(port) { var whiteboardId = null; socket.on("disconnect", function () { if (infoByWhiteboard.has(whiteboardId)) { - const whiteboardServerSideInfo = infoByWhiteboard.get(whiteboardId); + const whiteboardServerSideInfo = infoByWhiteboard.get(whiteboardId); - if (socket && socket.id) { - whiteboardServerSideInfo.deleteScreenResolutionOfClient(socket.id); - } + if (socket && socket.id) { + whiteboardServerSideInfo.deleteScreenResolutionOfClient(socket.id); + } - whiteboardServerSideInfo.decrementNbConnectedUsers(); + whiteboardServerSideInfo.decrementNbConnectedUsers(); - if (whiteboardServerSideInfo.hasConnectedUser()) { - socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges - } else { - infoByWhiteboard.delete(whiteboardId); - } + if (whiteboardServerSideInfo.hasConnectedUser()) { + socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges + } else { + infoByWhiteboard.delete(whiteboardId); + } } }); @@ -224,6 +224,8 @@ function startBackendServer(port) { socket.on("joinWhiteboard", function (content) { content = escapeAllContentStrings(content); if (accessToken === "" || accessToken == content["at"]) { + socket.emit("whiteboardConfig", { common: config.frontend }); + whiteboardId = content["wid"]; socket.join(whiteboardId); //Joins room name=wid if (!infoByWhiteboard.has(whiteboardId)) { diff --git a/scripts/utils.test.js b/scripts/utils.test.js index 308073c..d35256c 100644 --- a/scripts/utils.test.js +++ b/scripts/utils.test.js @@ -31,11 +31,11 @@ test("Complex object config override", () => { test("Override default config", () => { const defaultConfig = getDefaultConfig(); - const overrideConfig1 = { frontend: { setReadOnlyOnWhiteboardLoad: true } }; + const overrideConfig1 = { frontend: { readOnlyOnWhiteboardLoad: true } }; - expect( - deepMergeConfigs(defaultConfig, overrideConfig1).frontend.setReadOnlyOnWhiteboardLoad - ).toBe(true); + expect(deepMergeConfigs(defaultConfig, overrideConfig1).frontend.readOnlyOnWhiteboardLoad).toBe( + true + ); }); test("Dumb config is not valid", () => { diff --git a/src/js/const.js b/src/js/const.js deleted file mode 100644 index b8db9ec..0000000 --- a/src/js/const.js +++ /dev/null @@ -1,3 +0,0 @@ -export const POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA = 1; // 1px -export const POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA = 10; // 1ms -export const REFRESH_INFO_FREQUENCY = 5; // 5 times per second diff --git a/src/js/main.js b/src/js/main.js index 084c0fe..04dbbb0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -8,47 +8,48 @@ import pdfjsLib from "pdfjs-dist/webpack"; import shortcutFunctions from "./shortcutFunctions"; import ReadOnlyService from "./services/ReadOnlyService"; import InfoService from "./services/InfoService"; +import { getQueryVariable, getSubDir } from "./utils"; +import ConfigService from "./services/ConfigService"; + +let whiteboardId = getQueryVariable("whiteboardid"); +const randomid = getQueryVariable("randomid"); +if (randomid && !whiteboardId) { + //set random whiteboard on empty whiteboardid + whiteboardId = Array(2) + .fill(null) + .map(() => Math.random().toString(36).substr(2)) + .join(""); + const urlParams = new URLSearchParams(window.location.search); + urlParams.set("whiteboardid", whiteboardId); + window.location.search = urlParams; +} + +whiteboardId = whiteboardId || "myNewWhiteboard"; +whiteboardId = unescape(encodeURIComponent(whiteboardId)).replace(/[^a-zA-Z0-9 ]/g, ""); +const myUsername = getQueryVariable("username") || "unknown" + (Math.random() + "").substring(2, 6); +const accessToken = getQueryVariable("accesstoken") || ""; + +// Custom Html Title +const title = getQueryVariable("title"); +if (!title === false) { + document.title = decodeURIComponent(title); +} + +const subdir = getSubDir(); +let signaling_socket; function main() { - var whiteboardId = getQueryVariable("whiteboardid"); - var randomid = getQueryVariable("randomid"); - if (randomid && !whiteboardId) { - //set random whiteboard on empty whiteboardid - whiteboardId = Array(2) - .fill(null) - .map(() => Math.random().toString(36).substr(2)) - .join(""); - const urlParams = new URLSearchParams(window.location.search); - urlParams.set("whiteboardid", whiteboardId); - window.location.search = urlParams; - } - - whiteboardId = whiteboardId || "myNewWhiteboard"; - whiteboardId = unescape(encodeURIComponent(whiteboardId)).replace(/[^a-zA-Z0-9 ]/g, ""); - var myUsername = getQueryVariable("username"); - var accessToken = getQueryVariable("accesstoken"); - myUsername = myUsername || "unknown" + (Math.random() + "").substring(2, 6); - accessToken = accessToken || ""; - var accessDenied = false; - - // Custom Html Title - var title = getQueryVariable("title"); - if (!title === false) { - document.title = decodeURIComponent(title); - } - - var url = document.URL.substr(0, document.URL.lastIndexOf("/")); - var signaling_socket = null; - var urlSplit = url.split("/"); - var subdir = ""; - for (var i = 3; i < urlSplit.length; i++) { - subdir = subdir + "/" + urlSplit[i]; - } signaling_socket = io("", { path: subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy signaling_socket.on("connect", function () { console.log("Websocket connected!"); + signaling_socket.on("whiteboardConfig", (serverResponse) => { + ConfigService.initFromServer(serverResponse); + // Inti whiteboard only when we have the config from the server + initWhiteboard(); + }); + signaling_socket.on("whiteboardInfoUpdate", (info) => { InfoService.updateInfoFromServer(info); whiteboard.updateSmallestScreenResolution(); @@ -63,6 +64,7 @@ function main() { whiteboard.refreshUserBadges(); }); + let accessDenied = false; signaling_socket.on("wrongAccessToken", function () { if (!accessDenied) { accessDenied = true; @@ -76,7 +78,9 @@ function main() { windowWidthHeight: { w: $(window).width(), h: $(window).height() }, }); }); +} +function initWhiteboard() { $(document).ready(function () { // by default set in readOnly mode ReadOnlyService.activateReadOnlyMode(); @@ -599,7 +603,7 @@ function main() { // fix bug cursor not showing up whiteboard.refreshCursorAppearance(); - if (process.env.NODE_ENV === "production") { + if (process.env.NODE_ENV === "production" && ConfigService.readOnlyOnWhiteboardLoad) { ReadOnlyService.activateReadOnlyMode(); InfoService.hideInfo(); } else { @@ -795,19 +799,6 @@ function main() { }, 1000 * options.hideAfter); } } - - // get 'GET' parameter by variable name - function getQueryVariable(variable) { - var query = window.location.search.substring(1); - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - if (pair[0] == variable) { - return pair[1]; - } - } - return false; - } } export default main; diff --git a/src/js/services/ConfigService.js b/src/js/services/ConfigService.js new file mode 100644 index 0000000..6f8ef64 --- /dev/null +++ b/src/js/services/ConfigService.js @@ -0,0 +1,51 @@ +class ConfigService { + /** + * @readonly + * @type {boolean} + */ + readOnlyOnWhiteboardLoad = false; + + /** + * @readonly + * @type {boolean} + */ + showSmallestScreenIndicator = true; + + /** + * @readonly + * @type {number} + */ + pointerEventsThresholdMinDistDelta = 0; + + /** + * @readonly + * @type {number} + */ + pointerEventsThresholdMinTimeDelta = 0; + + /** + * @readonly + * @type {number} + */ + refreshInfoInterval = 1000; + + /** + * Init the service from the config sent by the server + * + * @param {object} serverResponse + */ + initFromServer(serverResponse) { + const { common } = serverResponse; + const { readOnlyOnWhiteboardLoad, showSmallestScreenIndicator, performance } = common; + + this.readOnlyOnWhiteboardLoad = readOnlyOnWhiteboardLoad; + this.showSmallestScreenIndicator = showSmallestScreenIndicator; + this.pointerEventsThresholdMinDistDelta = performance.pointerEventsThreshold.minDistDelta; + this.pointerEventsThresholdMinTimeDelta = performance.pointerEventsThreshold.minTimeDelta; + this.refreshInfoInterval = 1000 / performance.refreshInfoFreq; + + console.log("Whiteboard config from server:", serverResponse, "parsed:", this); + } +} + +export default new ConfigService(); diff --git a/src/js/services/InfoService.js b/src/js/services/InfoService.js index fbbe3d0..8e5d705 100644 --- a/src/js/services/InfoService.js +++ b/src/js/services/InfoService.js @@ -1,6 +1,4 @@ -import { REFRESH_INFO_FREQUENCY } from "../const"; - -const REFRESH_INTERVAL = 1000 / REFRESH_INFO_FREQUENCY; +import ConfigService from "./ConfigService"; /** * Class the handle the information about the whiteboard @@ -90,7 +88,7 @@ class InfoService { // refresh only on a specific interval to reduce // refreshing cost this.refreshDisplayedInfo(); - }, REFRESH_INTERVAL); + }, ConfigService.refreshInfoInterval); } hideInfo() { diff --git a/src/js/utils.js b/src/js/utils.js index d5329d3..1e02d15 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -14,3 +14,31 @@ export function computeDist(p1, p2) { export function getCurrentTimeMs() { return new Date().getTime(); } + +/** + * get 'GET' parameter by variable name + * @param variable + * @return {boolean|*} + */ +export function getQueryVariable(variable) { + const query = window.location.search.substring(1); + const vars = query.split("&"); + for (let i = 0; i < vars.length; i++) { + const pair = vars[i].split("="); + if (pair[0] === variable) { + return pair[1]; + } + } + return false; +} + +export function getSubDir() { + const url = document.URL.substr(0, document.URL.lastIndexOf("/")); + const urlSplit = url.split("/"); + let subdir = ""; + for (let i = 3; i < urlSplit.length; i++) { + subdir = subdir + "/" + urlSplit[i]; + } + + return subdir; +} diff --git a/src/js/whiteboard.js b/src/js/whiteboard.js index a2303b0..e27add6 100644 --- a/src/js/whiteboard.js +++ b/src/js/whiteboard.js @@ -1,12 +1,9 @@ import { dom } from "@fortawesome/fontawesome-svg-core"; import { getCurrentTimeMs } from "./utils"; import Point from "./classes/Point"; -import { - POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA, - POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA, -} from "./const"; import ReadOnlyService from "./services/ReadOnlyService"; import InfoService from "./services/InfoService"; +import ConfigService from "./services/ConfigService"; const RAD_TO_DEG = 180.0 / Math.PI; const DEG_TO_RAD = Math.PI / 180.0; @@ -215,11 +212,11 @@ const whiteboard = { const pointerSentTime = getCurrentTimeMs(); if ( pointerSentTime - _this.lastPointerSentTime > - POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA + ConfigService.pointerEventsThresholdMinTimeDelta ) { if ( _this.lastPointerPosition.distTo(currentPos) > - POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA + ConfigService.pointerEventsThresholdMinDistDelta ) { _this.lastPointerSentTime = pointerSentTime; _this.lastPointerPosition = currentPos; @@ -545,11 +542,14 @@ const whiteboard = { }); const pointerSentTime = getCurrentTimeMs(); - if (pointerSentTime - _this.lastPointerSentTime > POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA) { + if ( + pointerSentTime - _this.lastPointerSentTime > + ConfigService.pointerEventsThresholdMinTimeDelta + ) { const newPointerPosition = currentPos; if ( _this.lastPointerPosition.distTo(newPointerPosition) > - POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA + ConfigService.pointerEventsThresholdMinDistDelta ) { _this.lastPointerSentTime = pointerSentTime; _this.lastPointerPosition = newPointerPosition; @@ -882,12 +882,12 @@ const whiteboard = { // At least 100 ms between messages to reduce server load if ( pointerSentTime - _this.lastPointerSentTime > - POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA + ConfigService.pointerEventsThresholdMinTimeDelta ) { // Minimal distance between messages to reduce server load if ( _this.lastPointerPosition.distTo(newPointerPosition) > - POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA + ConfigService.pointerEventsThresholdMinDistDelta ) { _this.lastPointerSentTime = pointerSentTime; _this.lastPointerPosition = newPointerPosition;