feat: dynamic frontend configuration

This commit is contained in:
Florent Chehab 2020-05-10 23:13:55 +02:00
parent 9fda0a2c4b
commit b0337d9f5b
No known key found for this signature in database
GPG Key ID: 9A0CE018889EA246
10 changed files with 180 additions and 83 deletions

View File

@ -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

View File

@ -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
}
}
}
}
}

View File

@ -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)) {

View File

@ -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", () => {

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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() {

View File

@ -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;
}

View File

@ -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;