mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-29 13:30:12 +03:00
925 lines
23 KiB
JavaScript
925 lines
23 KiB
JavaScript
/*
|
|
* This file is part of Xpra.
|
|
* Copyright (C) 2016 Antoine Martin <antoine@xpra.org>
|
|
* Copyright (c) 2016 Spikes, Inc.
|
|
* Licensed under MPL 2.0, see:
|
|
* http://www.mozilla.org/MPL/2.0/
|
|
*
|
|
*/
|
|
|
|
const Utilities = {
|
|
VERSION : "19",
|
|
REVISION : 0,
|
|
LOCAL_MODIFICATIONS: 0,
|
|
BRANCH: "v16.x",
|
|
|
|
exc: console_error_safe,
|
|
error: console_error_safe,
|
|
warn: console_warn_safe,
|
|
log: console_log_safe,
|
|
debug: console_debug_safe,
|
|
|
|
//these versions should not be redirected:
|
|
cexc: console_error_safe,
|
|
cerror: console_error_safe,
|
|
cwarn: console_warn_safe,
|
|
clog: console_log_safe,
|
|
cdebug: console_debug_safe,
|
|
|
|
stristrue(v, default_value) {
|
|
if (!v) {
|
|
return default_value;
|
|
}
|
|
return ["true", "on", "1", "yes", "enabled"].includes(
|
|
String(v).toLowerCase()
|
|
);
|
|
},
|
|
|
|
removeChars(validChars, inputString) {
|
|
const regex = new RegExp('[^' + validChars + ']', 'g');
|
|
return inputString.replace(regex, '');
|
|
},
|
|
|
|
getHexUUID() {
|
|
const s = [];
|
|
const hexDigits = "0123456789abcdef";
|
|
for (let index = 0; index < 36; index++) {
|
|
if (index === 8 || index === 13 || index === 18 || index === 23) {
|
|
s[index] = "-";
|
|
} else {
|
|
s[index] = hexDigits.slice(
|
|
Math.floor(Math.random() * 0x10),
|
|
Math.floor(Math.random() * 0x10) + 1
|
|
);
|
|
}
|
|
}
|
|
return s.join("");
|
|
},
|
|
|
|
getSecureRandomBytes(len) {
|
|
const u = new Uint8Array(len);
|
|
crypto.getRandomValues(u);
|
|
return u;
|
|
},
|
|
|
|
getSecureRandomString(len) {
|
|
const u = Utilities.getSecureRandomBytes(len);
|
|
return String.fromCharCode.apply(null, u);
|
|
},
|
|
|
|
xorString(string1, string2) {
|
|
if (string1.length !== string2.length) {
|
|
throw "strings must be equal length";
|
|
}
|
|
let result = "";
|
|
for (let index = 0; index < string1.length; index++) {
|
|
const character1 = string1[index];
|
|
const character2 = string2[index];
|
|
result += String.fromCharCode(character1.charCodeAt(0) ^ character2.charCodeAt(0));
|
|
}
|
|
return result;
|
|
},
|
|
|
|
u8(value) {
|
|
const type = typeof value;
|
|
if (type === 'object' && value.constructor === Uint8Array) {
|
|
return value;
|
|
}
|
|
if (type === "string") {
|
|
return Uint8Array.from(value.split("").map(x => x.charCodeAt()));
|
|
}
|
|
return new Uint8Array(value);
|
|
},
|
|
|
|
xor(str1, str2) {
|
|
const trimmed_str2 = str2.slice(0, str1.length);
|
|
return Utilities.xorString(str1, trimmed_str2);
|
|
},
|
|
|
|
gendigest(digest, password, salt) {
|
|
// Utilities.clog("gendigest(", digest, ", ", password, ", ", salt, ")");
|
|
// Utilities.clog("gendigest(", digest, ", ", Utilities.convertToHex(password), ", ", Utilities.convertToHex(salt), ")");
|
|
if (digest === "xor") {
|
|
const trimmed_salt = salt.slice(0, password.length);
|
|
// Utilities.debug("xoring with trimmed salt:", Utilities.convertToHex(trimmed_salt));
|
|
return new Promise(function(resolve, reject) {
|
|
const xored = Utilities.xorString(trimmed_salt, password);
|
|
resolve(xored);
|
|
});
|
|
}
|
|
if (!digest.startsWith("hmac")) {
|
|
return new Promise(function(resolve, reject) {
|
|
reject(new Error("unsupported digest '" + digest + "'"));
|
|
});
|
|
}
|
|
let hash = "SHA-1";
|
|
if (digest.indexOf("+") > 0) {
|
|
// "hmac+sha512" -> "sha512"
|
|
hash = digest.split("+")[1];
|
|
}
|
|
hash = hash.toUpperCase();
|
|
if (hash.startsWith("SHA") && !hash.startsWith("SHA-")) {
|
|
hash = "SHA-" + hash.substring(3);
|
|
}
|
|
|
|
if (typeof crypto.subtle === "undefined") {
|
|
// use hmac.js
|
|
return new Promise(function(resolve, reject) {
|
|
if (hash !== "SHA-256") {
|
|
reject(new Error("crypto.subtle API is not available in this context"));
|
|
}
|
|
else {
|
|
value = hmac(Utilities.u8(password), Utilities.u8(salt));
|
|
resolve(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
Utilities.clog("crypto.subtle=", crypto.subtle);
|
|
Utilities.clog("crypto.subtle.importKey=", crypto.subtle.importKey);
|
|
crypto.subtle.importKey("raw", Utilities.u8(password), {
|
|
name: "HMAC",
|
|
hash: hash
|
|
}, false, ["sign", "verify"])
|
|
.then(
|
|
(key) => {
|
|
Utilities.cdebug("imported hmac key: ", key);
|
|
const u8salt = Utilities.u8(salt);
|
|
crypto.subtle.sign("HMAC", key, u8salt).then(
|
|
(result) => {
|
|
const u8digest = new Uint8Array(result);
|
|
Utilities.clog("hmac result=", u8digest);
|
|
resolve(Utilities.ArrayBufferToString(u8digest));
|
|
},
|
|
(error) => {
|
|
Utilities.clog("hmac signing failed:", error);
|
|
reject(error);
|
|
}
|
|
);
|
|
},
|
|
(error) => {
|
|
Utilities.clog("hmac key import failed:", error);
|
|
reject(error);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
trimString(string_, trimLength) {
|
|
if (!string_) {
|
|
return "";
|
|
}
|
|
return string_.length > trimLength ?
|
|
`${string_.slice(0, Math.max(0, trimLength - 3))}...` :
|
|
string_;
|
|
},
|
|
|
|
convertToHex(value) {
|
|
if (value instanceof Uint8Array) {
|
|
return Utilities.arrayhex(value);
|
|
}
|
|
let hex = "";
|
|
for (let index = 0; index < value.length; index++) {
|
|
hex += `${value.charCodeAt(index).toString(16).padStart(2, "0")}`;
|
|
}
|
|
return hex;
|
|
},
|
|
|
|
hexToString(hexval) {
|
|
const hex = hexval.toString();
|
|
let str = '';
|
|
for (let i = 0; i < hex.length; i += 2) {
|
|
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
|
}
|
|
return str;
|
|
},
|
|
|
|
arrayhex(arr) {
|
|
return Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
},
|
|
|
|
hexarray(hex) {
|
|
const bytes = new Uint8Array(Math.ceil(hex.length / 2));
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
}
|
|
return bytes;
|
|
},
|
|
|
|
getPlatformProcessor() {
|
|
//mozilla property:
|
|
if (navigator.oscpu) {
|
|
return navigator.oscpu;
|
|
}
|
|
//ie:
|
|
if (Object.hasOwn(navigator, "cpuClass")) {
|
|
return navigator.cpuClass;
|
|
}
|
|
return "unknown";
|
|
},
|
|
|
|
getPlatformName() {
|
|
if (navigator.appVersion.includes("Win")) {
|
|
return "Microsoft Windows";
|
|
}
|
|
if (navigator.appVersion.includes("Mac")) {
|
|
return "Mac OSX";
|
|
}
|
|
if (navigator.appVersion.includes("Linux")) {
|
|
return "Linux";
|
|
}
|
|
if (navigator.appVersion.includes("X11")) {
|
|
return "Posix";
|
|
}
|
|
return "unknown";
|
|
},
|
|
|
|
getFirstBrowserLanguage() {
|
|
const nav = window.navigator;
|
|
const browserLanguagePropertyKeys = [
|
|
"language",
|
|
"browserLanguage",
|
|
"systemLanguage",
|
|
"userLanguage",
|
|
];
|
|
let language;
|
|
// support for HTML 5.1 "navigator.languages"
|
|
if (Array.isArray(nav.languages)) {
|
|
for (let language of nav.languages) {
|
|
if (language && language.length > 0) {
|
|
return language;
|
|
}
|
|
}
|
|
}
|
|
// support for other well known properties in browsers
|
|
for (const property of browserLanguagePropertyKeys) {
|
|
language = nav[property];
|
|
if (language && language.length > 0) {
|
|
return language;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
getKeyboardLayout() {
|
|
let v = Utilities.getFirstBrowserLanguage();
|
|
if (v === null) {
|
|
return "us";
|
|
}
|
|
let layout = LANGUAGE_TO_LAYOUT[v];
|
|
if (!layout) {
|
|
//ie: v="en_GB";
|
|
v = v.split(",")[0];
|
|
let l = v.split("-", 2);
|
|
if (l.length === 1) {
|
|
l = v.split("_", 2);
|
|
}
|
|
//ie: "en"
|
|
layout = l[0].toLowerCase();
|
|
const temporary = LANGUAGE_TO_LAYOUT[layout];
|
|
if (temporary) {
|
|
layout = temporary;
|
|
}
|
|
}
|
|
return layout;
|
|
},
|
|
|
|
isMacOS() {
|
|
return navigator.platform.includes("Mac");
|
|
},
|
|
|
|
isWindows() {
|
|
return navigator.platform.includes("Win");
|
|
},
|
|
|
|
isFirefox() {
|
|
const ua = navigator.userAgent.toLowerCase();
|
|
return ua.includes("firefox");
|
|
},
|
|
isOpera() {
|
|
const ua = navigator.userAgent.toLowerCase();
|
|
return ua.includes("opera");
|
|
},
|
|
isSafari() {
|
|
const ua = navigator.userAgent.toLowerCase();
|
|
return ua.includes("safari") && !ua.includes("chrome");
|
|
},
|
|
isWebkit() {
|
|
const ua = navigator.userAgent.toLowerCase();
|
|
return ua.includes("webkit");
|
|
},
|
|
isEdge() {
|
|
return navigator.userAgent.includes("Edge");
|
|
},
|
|
isChrome() {
|
|
const isChromium = Object.hasOwn(window, "chrome");
|
|
const winNav = window.navigator;
|
|
const vendorName = winNav.vendor;
|
|
const isOpera = winNav.userAgent.includes("OPR");
|
|
const isIEedge = winNav.userAgent.includes("Edge");
|
|
const isIOSChrome = winNav.userAgent.match("CriOS");
|
|
if (isIOSChrome) {
|
|
return true;
|
|
} else return isChromium !== null &&
|
|
isChromium !== undefined &&
|
|
vendorName === "Google Inc." &&
|
|
isOpera === false &&
|
|
isIEedge === false;
|
|
},
|
|
isIE() {
|
|
return (
|
|
navigator.userAgent.includes("MSIE") ||
|
|
navigator.userAgent.includes("Trident/")
|
|
);
|
|
},
|
|
|
|
getBrowserName() {
|
|
if (this.isFirefox()) {
|
|
return "Firefox";
|
|
}
|
|
if (this.isOpera()) {
|
|
return "Opera";
|
|
}
|
|
if (this.isChrome()) {
|
|
return "Chrome";
|
|
}
|
|
if (this.isEdge()) {
|
|
return "Edge";
|
|
}
|
|
if (this.isSafari()) {
|
|
return "Safari";
|
|
}
|
|
return "Unknown";
|
|
},
|
|
|
|
getOS() {
|
|
const userAgent = navigator.userAgent;
|
|
let os = "Unknown";
|
|
if (userAgent.includes("Windows NT")) {
|
|
os = "MS Windows";
|
|
} else if (userAgent.includes("Macintosh") || userAgent.includes("Mac OS X")) {
|
|
os = "macOS";
|
|
} else if (userAgent.includes("Android")) {
|
|
os = "Android";
|
|
} else if (userAgent.includes("iPhone") || userAgent.includes("iPad")) {
|
|
os = "iOS/iPadOS";
|
|
} else if (userAgent.includes("Linux")) {
|
|
os = "Linux";
|
|
}
|
|
return os;
|
|
},
|
|
|
|
is_64bit() {
|
|
const _to_check = [];
|
|
if (Object.hasOwn(window.navigator, "cpuClass"))
|
|
_to_check.push(`${window.navigator.cpuClass}`.toLowerCase());
|
|
if (window.navigator.platform)
|
|
_to_check.push(`${window.navigator.platform}`.toLowerCase());
|
|
if (navigator.userAgent)
|
|
_to_check.push(`${navigator.userAgent}`.toLowerCase());
|
|
const _64bits_signatures = [
|
|
"x86_64",
|
|
"x86-64",
|
|
"Win64",
|
|
"x64;",
|
|
"amd64",
|
|
"AMD64",
|
|
"WOW64",
|
|
"x64_64",
|
|
"ia64",
|
|
"sparc64",
|
|
"ppc64",
|
|
"IRIX64",
|
|
];
|
|
for (const a of _to_check) {
|
|
for (const b of _64bits_signatures) {
|
|
if (a.includes(b.toLowerCase())) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
isMobile() {
|
|
return /iphone|ipad|ipod|android/i.test(navigator.userAgent);
|
|
},
|
|
|
|
getSimpleUserAgentString() {
|
|
if (Utilities.isFirefox()) {
|
|
return "Firefox";
|
|
} else if (Utilities.isOpera()) {
|
|
return "Opera";
|
|
} else if (Utilities.isSafari()) {
|
|
return "Safari";
|
|
} else if (Utilities.isChrome()) {
|
|
return "Chrome";
|
|
} else if (Utilities.isIE()) {
|
|
return "MSIE";
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
|
|
getColorGamut() {
|
|
if (!window.matchMedia) {
|
|
//unknown
|
|
return "";
|
|
} else if (window.matchMedia("(color-gamut: rec2020)").matches) {
|
|
return "rec2020";
|
|
} else if (window.matchMedia("(color-gamut: p3)").matches) {
|
|
return "P3";
|
|
} else if (window.matchMedia("(color-gamut: srgb)").matches) {
|
|
return "srgb";
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
|
|
isEventSupported(event) {
|
|
let testElement = document.createElement("div");
|
|
let isSupported;
|
|
|
|
event = `on${event}`;
|
|
isSupported = event in testElement;
|
|
|
|
if (!isSupported) {
|
|
testElement.setAttribute(event, "return;");
|
|
isSupported = typeof testElement[event] === "function";
|
|
}
|
|
testElement = null;
|
|
return isSupported;
|
|
},
|
|
|
|
//https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
|
|
//BSD license
|
|
normalizeWheel( /*object*/ event) /*object*/ {
|
|
// Reasonable defaults
|
|
const PIXEL_STEP = 10;
|
|
const LINE_HEIGHT = 40;
|
|
const PAGE_HEIGHT = 800;
|
|
|
|
let sX = 0;
|
|
let sY = 0; // spinX, spinY
|
|
|
|
// Legacy
|
|
if ("detail" in event) {
|
|
sY = event.detail;
|
|
}
|
|
if ("wheelDelta" in event) {
|
|
sY = -event.wheelDelta / 120;
|
|
}
|
|
if ("wheelDeltaY" in event) {
|
|
sY = -event.wheelDeltaY / 120;
|
|
}
|
|
if ("wheelDeltaX" in event) {
|
|
sX = -event.wheelDeltaX / 120;
|
|
}
|
|
|
|
// side scrolling on FF with DOMMouseScroll
|
|
if ("axis" in event && event.axis === event.HORIZONTAL_AXIS) {
|
|
sX = sY;
|
|
sY = 0;
|
|
}
|
|
|
|
let pX = sX * PIXEL_STEP; // pixelX
|
|
let pY = sY * PIXEL_STEP; // pixelY
|
|
|
|
if ("deltaY" in event) {
|
|
pY = event.deltaY;
|
|
}
|
|
if ("deltaX" in event) {
|
|
pX = event.deltaX;
|
|
}
|
|
|
|
if ((pX || pY) && event.deltaMode) {
|
|
if (event.deltaMode === 1) {
|
|
// delta in LINE units
|
|
pX *= LINE_HEIGHT;
|
|
pY *= LINE_HEIGHT;
|
|
} else {
|
|
// delta in PAGE units
|
|
pX *= PAGE_HEIGHT;
|
|
pY *= PAGE_HEIGHT;
|
|
}
|
|
}
|
|
|
|
// Fall-back if spin cannot be determined
|
|
if (pX && !sX) {
|
|
sX = pX < 1 ? -1 : 1;
|
|
}
|
|
if (pY && !sY) {
|
|
sY = pY < 1 ? -1 : 1;
|
|
}
|
|
|
|
return {
|
|
spinX: sX,
|
|
spinY: sY,
|
|
pixelX: pX,
|
|
pixelY: pY,
|
|
deltaMode: event.deltaMode || 0,
|
|
};
|
|
},
|
|
|
|
saveFile(filename, data, mimetype) {
|
|
const a = document.createElement("a");
|
|
a.setAttribute("style", "display: none");
|
|
document.body.append(a);
|
|
const blob = new Blob([data], mimetype);
|
|
const url = window.URL.createObjectURL(blob);
|
|
if (navigator.msSaveOrOpenBlob) {
|
|
navigator.msSaveOrOpenBlob(blob, filename);
|
|
} else {
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
}
|
|
},
|
|
|
|
StringToUint8(value) {
|
|
return new TextEncoder().encode(value);
|
|
},
|
|
|
|
Uint8ToString(value) {
|
|
return new TextDecoder().decode(value);
|
|
},
|
|
|
|
s(v) {
|
|
if (v === undefined) {
|
|
return "";
|
|
}
|
|
const type = typeof v;
|
|
if (type === "object" && v.constructor === Uint8Array) {
|
|
return Utilities.Uint8ToString(v);
|
|
}
|
|
return v.toString();
|
|
},
|
|
|
|
u(v) {
|
|
if (v === undefined) {
|
|
return new Uint8Array(0);
|
|
}
|
|
const type = typeof v;
|
|
if (type === 'object' && v.constructor === Uint8Array) {
|
|
return v;
|
|
}
|
|
return Utilities.StringToUint8(v.toString());
|
|
},
|
|
|
|
ArrayBufferToString(uintArray) {
|
|
// apply in chunks of 10400 to avoid call stack overflow
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
|
|
let s = "";
|
|
const skip = 10_400;
|
|
if (uintArray.subarray) {
|
|
for (
|
|
let index = 0, length_ = uintArray.length; index < length_; index += skip
|
|
) {
|
|
s += String.fromCharCode.apply(
|
|
null,
|
|
uintArray.subarray(index, Math.min(index + skip, length_))
|
|
);
|
|
}
|
|
} else {
|
|
for (
|
|
let index = 0, length_ = uintArray.length; index < length_; index += skip
|
|
) {
|
|
s += String.fromCharCode.apply(
|
|
null,
|
|
uintArray.slice(index, Math.min(index + skip, length_))
|
|
);
|
|
}
|
|
}
|
|
return s;
|
|
},
|
|
|
|
ArrayBufferToBase64(uintArray) {
|
|
return window.btoa(Utilities.ArrayBufferToString(uintArray));
|
|
},
|
|
|
|
ToBase64(v) {
|
|
try {
|
|
return window.btoa(v);
|
|
} catch {
|
|
return ArrayBufferToBase64(v);
|
|
}
|
|
},
|
|
|
|
convertDataURIToBinary(dataURI) {
|
|
const BASE64_MARKER = ";base64,";
|
|
const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
|
|
const base64 = dataURI.slice(Math.max(0, base64Index));
|
|
const raw = window.atob(base64);
|
|
const rawLength = raw.length;
|
|
const array = new Uint8Array(new ArrayBuffer(rawLength));
|
|
|
|
for (let index = 0; index < rawLength; index++) {
|
|
array[index] = raw.charCodeAt(index);
|
|
}
|
|
return array;
|
|
},
|
|
|
|
parseINIString(data) {
|
|
const regex = {
|
|
section: /^\s*\[\s*([^\]]*)\s*]\s*$/,
|
|
param: /^\s*([^=]+?)\s*=\s*(.*?)\s*$/,
|
|
comment: /^\s*[#;].*$/,
|
|
};
|
|
const value = {};
|
|
const lines = data.split(/[\n\r]+/);
|
|
let section = null;
|
|
for (const line of lines) {
|
|
if (regex.comment.test(line)) {
|
|
// just continue
|
|
} else if (regex.param.test(line)) {
|
|
const match = line.match(regex.param);
|
|
if (section) {
|
|
value[section][match[1]] = match[2];
|
|
} else {
|
|
value[match[1]] = match[2];
|
|
}
|
|
} else if (regex.section.test(line)) {
|
|
const match = line.match(regex.section);
|
|
value[match[1]] = {};
|
|
section = match[1];
|
|
} else if (line.length === 0 && section) {
|
|
section = null;
|
|
}
|
|
}
|
|
return value;
|
|
},
|
|
|
|
isSafeHost(host) {
|
|
return host && ["localhost", "127.0.0.1", "::1"].indexOf(host) >= 0;
|
|
},
|
|
|
|
/**
|
|
* XmlHttpRequest's getAllResponseHeaders() method returns a string of response
|
|
* headers according to the format described here:
|
|
* http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method
|
|
* This method parses that string into a user-friendly key/value pair object.
|
|
*/
|
|
ParseResponseHeaders(headerString) {
|
|
const headers = {};
|
|
if (!headerString) {
|
|
return headers;
|
|
}
|
|
const headerPairs = headerString.split("\u000D\u000A");
|
|
for (const headerPair of headerPairs) {
|
|
// Can't use split() here because it does the wrong thing
|
|
// if the header value has the string ": " in it.
|
|
const index = headerPair.indexOf("\u003A\u0020");
|
|
if (index > 0) {
|
|
const key = headerPair.slice(0, Math.max(0, index));
|
|
headers[key] = headerPair.slice(Math.max(0, index + 2));
|
|
}
|
|
}
|
|
return headers;
|
|
},
|
|
|
|
parseParams(q) {
|
|
const parameters = {};
|
|
let e;
|
|
const a = /\+/g; // Regex for replacing addition symbol with a space
|
|
const r = /([^&=]+)=?([^&]*)/g;
|
|
const d = (s) => {
|
|
return decodeURIComponent(s.replace(a, " "));
|
|
};
|
|
while ((e = r.exec(q))) parameters[d(e[1])] = d(e[2]);
|
|
return parameters;
|
|
},
|
|
|
|
getparam(property) {
|
|
let getParameter = window.location.getParameter;
|
|
if (!getParameter) {
|
|
getParameter = function(key) {
|
|
if (!window.location.queryStringParams)
|
|
window.location.queryStringParams = Utilities.parseParams(
|
|
window.location.search.slice(1)
|
|
);
|
|
return window.location.queryStringParams[key];
|
|
};
|
|
}
|
|
|
|
let value = getParameter(property);
|
|
|
|
if (value === undefined) {
|
|
try {
|
|
value = Utilities.getSessionStorageValue(property);
|
|
} catch {}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
hasSessionStorage() {
|
|
if (typeof Storage === "undefined") {
|
|
return false;
|
|
}
|
|
try {
|
|
const key = "just for testing sessionStorage support";
|
|
sessionStorage.setItem(key, "store-whatever");
|
|
sessionStorage.removeItem(key);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
getSessionStorageValue(property) {
|
|
const params = JSON.parse(sessionStorage.getItem(Utilities.getSessionStoragePrefix()))
|
|
if (property in params) {
|
|
return String(params[property]);
|
|
}
|
|
return undefined;
|
|
},
|
|
|
|
setSessionStorageValue(property, value) {
|
|
const prefix = Utilities.getSessionStoragePrefix();
|
|
let params = JSON.parse(sessionStorage.getItem(prefix)) || {}
|
|
if (value === null || value === "undefined") {
|
|
delete params[property];
|
|
} else {
|
|
params[property] = String(value);
|
|
}
|
|
sessionStorage.setItem(prefix, JSON.stringify(params));
|
|
},
|
|
|
|
clearSessionStorage() {
|
|
sessionStorage.removeItem(Utilities.getSessionStoragePrefix());
|
|
},
|
|
|
|
getSessionStoragePrefix() {
|
|
const urlPath = new URL(window.location.href).pathname
|
|
return urlPath.substring(0, urlPath.lastIndexOf("/"));
|
|
},
|
|
|
|
getConnectionInfo() {
|
|
if (!Object.hasOwn(navigator, "connection")) {
|
|
return {};
|
|
}
|
|
const c = navigator.connection;
|
|
const index = {};
|
|
if (c.type) {
|
|
index["type"] = c.type;
|
|
}
|
|
if (Object.hasOwn(c, "effectiveType")) {
|
|
index["effective-type"] = c.effectiveType;
|
|
}
|
|
if (!isNaN(c.downlink) && c.downlink > 0 && isFinite(c.downlink)) {
|
|
index["downlink"] = Math.round(c.downlink * 1000 * 1000);
|
|
}
|
|
if (
|
|
Object.hasOwn(c, "downlinkMax") &&
|
|
!isNaN(c.downlinkMax) &&
|
|
!isNaN(c.downlinkMax) &&
|
|
c.downlinkMax > 0 &&
|
|
isFinite(c.downlinkMax)
|
|
) {
|
|
index["downlink.max"] = Math.round(c.downlinkMax * 1000 * 1000);
|
|
}
|
|
if (!isNaN(c.rtt) && c.rtt > 0) {
|
|
index["rtt"] = c.rtt;
|
|
}
|
|
return index;
|
|
},
|
|
|
|
json_action(uri, success_function, error_function, username, password) {
|
|
Utilities.log(
|
|
"json_action(",
|
|
uri,
|
|
", ",
|
|
success_function,
|
|
", ",
|
|
error_function,
|
|
")"
|
|
);
|
|
const xhr = new XMLHttpRequest();
|
|
let url = uri;
|
|
if (uri.startsWith("/")) {
|
|
//relative URI
|
|
url = document.location.href.split("/connect.html")[0] + uri;
|
|
}
|
|
xhr.open("GET", url, true);
|
|
if (username && password) {
|
|
const credentials = btoa(`${username}:${password}`);
|
|
xhr.setRequestHeader("Authorization", `Basic ${credentials}`);
|
|
}
|
|
xhr.responseType = "json";
|
|
xhr.addEventListener("load", function() {
|
|
Utilities.log("loaded", url, "status", xhr.status);
|
|
const status = xhr.status;
|
|
if (status === 200) {
|
|
success_function(xhr, xhr.response);
|
|
} else {
|
|
Utilities.log(uri, "failed:", status + xhr.response);
|
|
if (error_function) {
|
|
error_function(`failed: ${status}${xhr.response}`);
|
|
}
|
|
}
|
|
});
|
|
xhr.addEventListener("error", function(e) {
|
|
Utilities.log(uri, "error:", e);
|
|
if (error_function) {
|
|
error_function(e);
|
|
}
|
|
});
|
|
xhr.addEventListener("abort", function(e) {
|
|
Utilities.log(uri, "abort:", e);
|
|
if (error_function) {
|
|
error_function(e);
|
|
}
|
|
});
|
|
xhr.send();
|
|
return xhr;
|
|
},
|
|
};
|
|
|
|
//convert a language code into an X11 keyboard layout code:
|
|
const LANGUAGE_TO_LAYOUT = {
|
|
en_GB: "gb",
|
|
en: "us",
|
|
zh: "cn",
|
|
af: "za",
|
|
sq: "al",
|
|
ca: "ca",
|
|
"zh-TW": "tw",
|
|
"zh-CN": "cn",
|
|
cs: "cz",
|
|
da: "dk",
|
|
"nl-BE": "be",
|
|
"en-US": "us",
|
|
"en-AU": "us",
|
|
"en-GB": "gb",
|
|
"en-CA": "ca",
|
|
"en-NZ": "us",
|
|
"en-IE": "ie",
|
|
"en-ZA": "za",
|
|
"en-JM": "us",
|
|
"en-TT": "tr",
|
|
et: "ee",
|
|
fa: "ir",
|
|
"fr-BE": "be",
|
|
"fr-CA": "ca",
|
|
"fr-CH": "ch",
|
|
"fr-LU": "fr",
|
|
"gd-IE": "ie",
|
|
"de-CH": "ch",
|
|
"de-AT": "at",
|
|
"de-LU": "de",
|
|
"de-LI": "de",
|
|
he: "il",
|
|
hi: "in",
|
|
"it-CH": "ch",
|
|
ja: "jp",
|
|
ko: "kr",
|
|
"pt-BR": "br",
|
|
pt: "pt",
|
|
sr: "rs",
|
|
sl: "si",
|
|
es: "es",
|
|
sv: "se",
|
|
"sv-FI": "fi",
|
|
tr: "tr",
|
|
uk: "ua",
|
|
ur: "pk",
|
|
vi: "vn",
|
|
//Unknown conversions
|
|
//"en-BZ": ??
|
|
//"en-EG": ??
|
|
//"gd": ??
|
|
//"id": ??
|
|
//"ji": ??
|
|
//"rm": ??
|
|
//"ro-MO": ??
|
|
//"ru-MI": ??
|
|
//"sb": ??
|
|
//"sx": ??
|
|
//"sz": ??
|
|
//"ts": ??
|
|
//"tn": ??
|
|
//"ve": ??
|
|
//"xh": ??
|
|
//"zh-HK": ??
|
|
//"zh-SG": ??
|
|
//"zu": ??
|
|
};
|
|
|
|
function console_debug_safe() {
|
|
// if (console) console.debug.apply(console, arguments);
|
|
}
|
|
|
|
function console_error_safe() {
|
|
// if (console) console.error.apply(console, arguments);
|
|
}
|
|
|
|
function console_warn_safe() {
|
|
// if (console) console.warn.apply(console, arguments);
|
|
}
|
|
|
|
function console_log_safe() {
|
|
// if (console) console.log.apply(console, arguments);
|
|
}
|