Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
gapodo | 4c5e744f62 | |
gapodo | 86351e336c | |
gapodo | 24c6a1e22e | |
gapodo | 2a1fd11e2d | |
gapodo | 063fdae679 | |
gapodo | e5551cea09 | |
gapodo | 3ec3db37e0 | |
gapodo | feb564d4a0 | |
gapodo | bde4bbf5ad | |
gapodo | ef0813dd84 | |
gapodo | 5b8b4d07c3 |
|
@ -2,6 +2,7 @@
|
||||||
.idea/
|
.idea/
|
||||||
node_modules/
|
node_modules/
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
|
.npm/
|
||||||
|
|
||||||
/auth/certs/
|
/auth/certs/
|
||||||
/auth/js/auth.js.map
|
/auth/js/auth.js.map
|
||||||
|
@ -33,4 +34,4 @@ node_modules/
|
||||||
/webpack/*.js
|
/webpack/*.js
|
||||||
/webpack/*.js.map
|
/webpack/*.js.map
|
||||||
|
|
||||||
/files_*.pem
|
/files_*.pem
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
audit=false
|
||||||
|
fund=false
|
||||||
|
update-notifier=false
|
||||||
|
package-lock=true
|
|
@ -0,0 +1,89 @@
|
||||||
|
when:
|
||||||
|
event: [push, deployment, manual, cron]
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- &node_image 'node:14-bullseye'
|
||||||
|
- &buildx_image 'woodpeckerci/plugin-docker-buildx:2.2.1'
|
||||||
|
- &platforms 'linux/amd64'
|
||||||
|
- &dockerfile 'docker/Dockerfile.ci'
|
||||||
|
|
||||||
|
|
||||||
|
steps:
|
||||||
|
prepare-npm:
|
||||||
|
image: *node_image
|
||||||
|
secrets:
|
||||||
|
- npmconf
|
||||||
|
commands:
|
||||||
|
- git config --add safe.directory '*'
|
||||||
|
- if [ "$${NPMCONF:-}" != "" ]; then echo "$${NPMCONF}" >> "$${HOME}/.npmrc"; fi
|
||||||
|
- npm ci
|
||||||
|
- npx browserslist@latest --update-db
|
||||||
|
|
||||||
|
build-npm:
|
||||||
|
image: *node_image
|
||||||
|
commands:
|
||||||
|
- bash ./scripts/build.sh web rel
|
||||||
|
|
||||||
|
build-docker-next:
|
||||||
|
image: *buildx_image
|
||||||
|
pull: true
|
||||||
|
settings:
|
||||||
|
platforms: *platforms
|
||||||
|
dockerfile: *dockerfile
|
||||||
|
context: .
|
||||||
|
registry:
|
||||||
|
from_secret: registry_domain
|
||||||
|
repo:
|
||||||
|
from_secret: target_image_name
|
||||||
|
password:
|
||||||
|
from_secret: registry_token
|
||||||
|
username:
|
||||||
|
from_secret: registry_user
|
||||||
|
auto_tag: true
|
||||||
|
tag: [next, "next-${CI_COMMIT_SHA:0:8}"]
|
||||||
|
when:
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
event: push
|
||||||
|
|
||||||
|
build-docker-branch:
|
||||||
|
image: *buildx_image
|
||||||
|
pull: true
|
||||||
|
settings:
|
||||||
|
platforms: *platforms
|
||||||
|
dockerfile: *dockerfile
|
||||||
|
context: .
|
||||||
|
registry:
|
||||||
|
from_secret: registry_domain
|
||||||
|
repo:
|
||||||
|
from_secret: target_image_name
|
||||||
|
password:
|
||||||
|
from_secret: registry_token
|
||||||
|
username:
|
||||||
|
from_secret: registry_user
|
||||||
|
auto_tag: true
|
||||||
|
tag: ["${CI_COMMIT_BRANCH}", "${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}"]
|
||||||
|
when:
|
||||||
|
event: [push, manual]
|
||||||
|
|
||||||
|
build-docker-tag:
|
||||||
|
image: *buildx_image
|
||||||
|
pull: true
|
||||||
|
settings:
|
||||||
|
platforms: *platforms
|
||||||
|
dockerfile: *dockerfile
|
||||||
|
context: .
|
||||||
|
registry:
|
||||||
|
from_secret: registry_domain
|
||||||
|
repo:
|
||||||
|
from_secret: target_image_name
|
||||||
|
password:
|
||||||
|
from_secret: registry_token
|
||||||
|
username:
|
||||||
|
from_secret: registry_user
|
||||||
|
auto_tag: true
|
||||||
|
tag: [latest, "${CI_COMMIT_TAG}", "tag-${CI_COMMIT_SHA:0:8}"]
|
||||||
|
when:
|
||||||
|
event: [tag]
|
|
@ -4,14 +4,12 @@ export default api => {
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
"corejs": {"version": 3},
|
"corejs": {"version": '3.33', "proposals": false},
|
||||||
"useBuiltIns": "usage",
|
"useBuiltIns": "usage",
|
||||||
"targets": {
|
"targets": {
|
||||||
"edge": "17",
|
"edge": "111",
|
||||||
"firefox": "60",
|
"firefox": "100",
|
||||||
"chrome": "67",
|
"chrome": "109"
|
||||||
"safari": "11.1",
|
|
||||||
"ie": "11"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
FROM nginx:mainline-alpine
|
||||||
|
|
||||||
|
COPY ./docker/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY ./docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY ./docker/entrypoint.sh /
|
||||||
|
|
||||||
|
RUN apk update --no-cache && apk upgrade --no-cache \
|
||||||
|
&& apk add --no-cache openssl tzdata \
|
||||||
|
&& mkdir -p /var/www/TeaWeb /etc/ssl/certs \
|
||||||
|
&& chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
ENV TZ="Europe/Berlin"
|
||||||
|
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM nginx:mainline-alpine
|
||||||
|
|
||||||
|
COPY ./docker/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY ./docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY ./docker/entrypoint.sh /
|
||||||
|
|
||||||
|
RUN apk update --no-cache && apk upgrade --no-cache \
|
||||||
|
&& apk add --no-cache openssl tzdata \
|
||||||
|
&& mkdir -p /var/www/TeaWeb /etc/ssl/certs \
|
||||||
|
&& chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
ENV TZ="Europe/Berlin"
|
||||||
|
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
COPY ./dist/ /var/www/TeaWeb/
|
|
@ -0,0 +1,36 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 default_server ssl http2;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:TLS-CHACHA20-POLY1305-SHA256:TLS-AES-256-GCM-SHA384:TLS-AES-128-GCM-SHA256;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||||
|
ssl_ecdh_curve secp384r1;
|
||||||
|
ssl_certificate /etc/ssl/certs/tea_bundle.crt;
|
||||||
|
ssl_certificate_key /etc/ssl/certs/tea.key;
|
||||||
|
ssl_session_cache shared:MozSSL:10m;
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /var/www/TeaWeb;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
# redirect server error pages to the static page /50x.html
|
||||||
|
#
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
gzip off;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
gen_self_signed() {
|
||||||
|
echo "[WRN] No certificates found, generating self signed cert with key"
|
||||||
|
openssl req -x509 -nodes -days 1780 -newkey rsa:4096 \
|
||||||
|
-keyout /etc/ssl/certs/tea.key \
|
||||||
|
-out /etc/ssl/certs/tea_bundle.crt \
|
||||||
|
-subj "/C=DE/ST=Berlin/L=Germany/O=TeaSpeak/OU=TeaWeb/CN=localhost/emailAddress=noreply@teaspeak.de"
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_diffie_hellman() {
|
||||||
|
echo "[INF] No Diffie-Hellman pem found, generating new with 2048 byte"
|
||||||
|
openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$1" = "nginx" ]; then
|
||||||
|
if [ ! -f /etc/ssl/certs/tea.key ] && [ ! -f /etc/ssl/certs/tea_bundle.crt ]; then
|
||||||
|
gen_self_signed
|
||||||
|
elif [ ! -f /etc/ssl/certs/tea.key ] || [ ! -f /etc/ssl/certs/tea_bundle.crt ]; then
|
||||||
|
echo "[ERR] Only found a key or crt-bundle file but both files are REQUIRED!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f /etc/ssl/certs/dhparam.pem ]; then
|
||||||
|
gen_diffie_hellman
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
|
@ -0,0 +1,32 @@
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
keepalive_timeout 75;
|
||||||
|
|
||||||
|
#gzip on;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
|
@ -2,32 +2,32 @@ import "core-js/stable";
|
||||||
import "./polifill";
|
import "./polifill";
|
||||||
import "./css";
|
import "./css";
|
||||||
|
|
||||||
import {ApplicationLoader} from "./loader/loader";
|
import { ApplicationLoader } from "./loader/loader";
|
||||||
import {getUrlParameter} from "./loader/Utils";
|
import { getUrlParameter } from "./loader/Utils";
|
||||||
|
|
||||||
/* let the loader register himself at the window first */
|
/* let the loader register himself at the window first */
|
||||||
const target = getUrlParameter("loader-target") || "app";
|
const target = getUrlParameter("loader-target") || "app";
|
||||||
console.info("Loading app with loader \"%s\"", target);
|
console.info("Loading app with loader \"%s\"", target);
|
||||||
|
|
||||||
let appLoader: ApplicationLoader;
|
let appLoader: ApplicationLoader;
|
||||||
if(target === "empty") {
|
if (target === "empty") {
|
||||||
appLoader = new (require("./targets/empty").default);
|
appLoader = new (require("./targets/empty").default);
|
||||||
} else if(target === "manifest") {
|
} else if (target === "manifest") {
|
||||||
appLoader = new (require("./targets/maifest-target").default);
|
appLoader = new (require("./targets/maifest-target").default);
|
||||||
} else {
|
} else {
|
||||||
appLoader = new (require("./targets/app").default);
|
appLoader = new (require("./targets/app").default);
|
||||||
}
|
}
|
||||||
setTimeout(() => appLoader.execute(), 0);
|
setTimeout(() => appLoader.execute(), 0);
|
||||||
|
|
||||||
export {};
|
export { };
|
||||||
|
|
||||||
if(__build.target === "client") {
|
if (__build.target === "client") {
|
||||||
/* do this so we don't get a react dev tools warning within the client */
|
/* do this so we don't get a react dev tools warning within the client */
|
||||||
if(!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window)) {
|
if (!('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window)) {
|
||||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {};
|
(window as Window).__REACT_DEVTOOLS_GLOBAL_HOOK__ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
|
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () { };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hello World message */
|
/* Hello World message */
|
||||||
|
@ -89,7 +89,7 @@ if(__build.target === "client") {
|
||||||
].join(";");
|
].join(";");
|
||||||
|
|
||||||
const display_detect = /./;
|
const display_detect = /./;
|
||||||
display_detect.toString = function() { print_security(); return ""; };
|
display_detect.toString = function () { print_security(); return ""; };
|
||||||
|
|
||||||
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
||||||
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
129
package.json
129
package.json
|
@ -17,74 +17,75 @@
|
||||||
"author": "TeaSpeak (WolverinDEV)",
|
"author": "TeaSpeak (WolverinDEV)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.4",
|
"@babel/core": "^7.23.3",
|
||||||
"@babel/plugin-transform-runtime": "^7.10.4",
|
"@babel/plugin-transform-runtime": "^7.23.3",
|
||||||
"@babel/preset-env": "^7.10.4",
|
"@babel/preset-env": "^7.23.3",
|
||||||
"@google-cloud/translate": "^5.3.0",
|
"@google-cloud/translate": "^5.3.0",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
"@types/dompurify": "^2.0.1",
|
"@types/dompurify": "^2.4.0",
|
||||||
"@types/ejs": "^3.0.2",
|
"@types/ejs": "^3.1.5",
|
||||||
"@types/emoji-mart": "^3.0.2",
|
"@types/emoji-mart": "^3.0.12",
|
||||||
"@types/emscripten": "^1.38.0",
|
"@types/emscripten": "^1.39.10",
|
||||||
"@types/fs-extra": "^8.0.1",
|
"@types/fs-extra": "^8.1.5",
|
||||||
"@types/html-minifier": "^3.5.3",
|
"@types/html-minifier": "^3.5.3",
|
||||||
"@types/jquery": "^3.3.34",
|
"@types/jquery": "^3.5.27",
|
||||||
"@types/jsrender": "^1.0.5",
|
"@types/jsrender": "^1.0.5",
|
||||||
"@types/lodash": "^4.14.149",
|
"@types/lodash": "^4.14.201",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
"@types/node": "^12.7.2",
|
"@types/node": "^12.20.55",
|
||||||
"@types/react-color": "^3.0.4",
|
"@types/react": "^16.14.51",
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-color": "^3.0.10",
|
||||||
"@types/react-grid-layout": "^1.1.1",
|
"@types/react-dom": "^16.9.22",
|
||||||
"@types/remarkable": "^1.7.4",
|
"@types/react-grid-layout": "^1.3.5",
|
||||||
"@types/sdp-transform": "^2.4.4",
|
"@types/remarkable": "^1.7.6",
|
||||||
"@types/sha256": "^0.2.0",
|
"@types/sdp-transform": "^2.4.9",
|
||||||
"@types/twemoji": "^12.1.1",
|
"@types/sha256": "^0.2.2",
|
||||||
|
"@types/twemoji": "^12.1.2",
|
||||||
"@types/websocket": "0.0.40",
|
"@types/websocket": "0.0.40",
|
||||||
"@types/xml-parser": "^1.2.29",
|
"@types/xml-parser": "^1.2.33",
|
||||||
"@wasm-tool/wasm-pack-plugin": "^1.3.1",
|
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||||
"autoprefixer": "^10.2.5",
|
"autoprefixer": "^10.4.16",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.3.0",
|
||||||
"circular-dependency-plugin": "^5.2.0",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
"clean-css": "^4.2.1",
|
"clean-css": "^4.2.4",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"copy-webpack-plugin": "^8.0.0",
|
"copy-webpack-plugin": "^8.1.1",
|
||||||
"css-loader": "^3.6.0",
|
"css-loader": "^3.6.0",
|
||||||
"css-minimizer-webpack-plugin": "^1.3.0",
|
"css-minimizer-webpack-plugin": "^1.3.0",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"fast-xml-parser": "^3.17.4",
|
"fast-xml-parser": "^3.21.1",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.2.0",
|
||||||
"fs-extra": "latest",
|
"fs-extra": "latest",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"html-loader": "^1.0.0",
|
"html-loader": "^1.3.2",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.5.3",
|
||||||
"inline-chunks-html-webpack-plugin": "^1.3.1",
|
"inline-chunks-html-webpack-plugin": "^1.3.1",
|
||||||
"mime-types": "^2.1.24",
|
"mime-types": "^2.1.35",
|
||||||
"mini-css-extract-plugin": "^1.3.9",
|
"mini-css-extract-plugin": "^1.6.2",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.6",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"postcss": "^8.3.0",
|
"postcss": "^8.4.31",
|
||||||
"postcss-loader": "^5.2.0",
|
"postcss-loader": "^5.3.0",
|
||||||
"potpack": "^1.0.1",
|
"potpack": "^1.0.2",
|
||||||
"raw-loader": "^4.0.0",
|
"raw-loader": "^4.0.2",
|
||||||
"sass": "1.22.10",
|
"sass": "1.22.10",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"sha256": "^0.2.0",
|
"sha256": "^0.2.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.3.0",
|
||||||
"svg-inline-loader": "^0.8.2",
|
"svg-inline-loader": "^0.8.2",
|
||||||
"terser": "^4.2.1",
|
"terser": "^4.8.1",
|
||||||
"terser-webpack-plugin": "4.2.3",
|
"terser-webpack-plugin": "4.2.3",
|
||||||
"ts-loader": "^6.2.2",
|
"ts-loader": "^8.4.0",
|
||||||
"tsd": "^0.13.1",
|
"tsd": "^0.13.1",
|
||||||
"typescript": "^4.2",
|
"typescript": "^4.9.5",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"wabt": "^1.0.13",
|
"wabt": "1.0.13",
|
||||||
"webpack": "^5.26.1",
|
"webpack": "^5.89.0",
|
||||||
"webpack-bundle-analyzer": "^3.6.1",
|
"webpack-bundle-analyzer": "^3.9.0",
|
||||||
"webpack-cli": "^4.5.0",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^3.11.2",
|
"webpack-dev-server": "^3.11.3",
|
||||||
"webpack-svg-sprite-generator": "^5.0.4",
|
"webpack-svg-sprite-generator": "^5.0.4",
|
||||||
"zip-webpack-plugin": "^4.0.1"
|
"zip-webpack-plugin": "^4.0.1"
|
||||||
},
|
},
|
||||||
|
@ -97,33 +98,33 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://www.teaspeak.de",
|
"homepage": "https://www.teaspeak.de",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/crypto-js": "^4.0.1",
|
"@types/crypto-js": "^4.2.1",
|
||||||
"broadcastchannel-polyfill": "^1.0.1",
|
"broadcastchannel-polyfill": "^1.0.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.2.0",
|
||||||
"detect-browser": "^5.2.0",
|
"detect-browser": "^5.3.0",
|
||||||
"dompurify": "^2.2.8",
|
"dompurify": "^2.4.7",
|
||||||
"emoji-mart": "git+https://github.com/WolverinDEV/emoji-mart.git",
|
"emoji-mart": "^3.0.1",
|
||||||
"emoji-regex": "^9.0.0",
|
"emoji-regex": "^9.2.2",
|
||||||
"highlight.js": "^10.1.1",
|
"highlight.js": "^10.7.3",
|
||||||
"ip-regex": "^4.2.0",
|
"ip-regex": "^4.3.0",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.7.1",
|
||||||
"jsrender": "^1.0.7",
|
"jsrender": "^1.0.13",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.29.4",
|
||||||
"react": "^16.13.1",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.14.0",
|
||||||
"react-grid-layout": "^1.2.2",
|
"react-grid-layout": "^1.4.3",
|
||||||
"react-player": "^2.5.0",
|
"react-player": "^2.13.0",
|
||||||
"remarkable": "^2.0.1",
|
"remarkable": "^2.0.1",
|
||||||
"resize-observer-polyfill": "git+https://github.com/albancreton/resize-observer-polyfill.git#patch-1",
|
"resize-observer-polyfill": "git+https://github.com/albancreton/resize-observer-polyfill.git#patch-1",
|
||||||
"sdp-transform": "^2.14.0",
|
"sdp-transform": "^2.14.1",
|
||||||
"simple-jsonp-promise": "^1.1.0",
|
"simple-jsonp-promise": "^1.1.0",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"twemoji": "^13.0.0",
|
"twemoji": "^13.1.1",
|
||||||
"url-knife": "^3.1.3",
|
"url-knife": "^3.1.3",
|
||||||
"webcrypto-liner": "^1.2.4",
|
"webcrypto-liner": "^1.4.2",
|
||||||
"webpack-manifest-plugin": "^3.1.0",
|
"webpack-manifest-plugin": "^3.2.0",
|
||||||
"webrtc-adapter": "^7.5.1"
|
"webrtc-adapter": "^7.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT=$(realpath "$0")
|
||||||
|
SCRIPTPATH=$(dirname "$SCRIPT")
|
||||||
|
BASEPATH="$(realpath "${SCRIPTPATH}/../")"
|
||||||
|
|
||||||
|
NPM_DIR="${BASEPATH}/.npm"
|
||||||
|
|
||||||
|
if [[ ! -d "${NPM_DIR}" ]]; then
|
||||||
|
mkdir "${NPM_DIR}" || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${BUILDINDOCKER:-}" != "yes" ]]; then
|
||||||
|
docker run --rm --workdir "/work" -v "${NPM_DIR}:/home/" -v "${BASEPATH}:/work" -e BUILDINDOCKER=yes node:14-bullseye /bin/bash -c 'chmod +x /work/scripts/build_in_docker.sh && /work/scripts/build_in_docker.sh'
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
## in docker
|
||||||
|
|
||||||
|
echo "adding npmrc"
|
||||||
|
cat >>"${HOME}/.npmrc" <<'EOF'
|
||||||
|
cache=/work/.npm
|
||||||
|
fund=false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "adding secure git dir"
|
||||||
|
git config --global --add safe.directory /work
|
||||||
|
|
||||||
|
echo "running chmods"
|
||||||
|
find "${BASEPATH}" -iname "*.sh" -exec chmod +x {} +
|
||||||
|
|
||||||
|
echo "Cleaning up old files"
|
||||||
|
"${BASEPATH}/scripts/cleanup.sh" full >/dev/null 2>&1 || exit 1
|
||||||
|
|
||||||
|
echo "Installing npm packages"
|
||||||
|
npm i || exit 1
|
||||||
|
|
||||||
|
echo "Updating browser list"
|
||||||
|
npx browserslist@latest --update-db || exit 1
|
||||||
|
|
||||||
|
echo "running build"
|
||||||
|
"${BASEPATH}/scripts/build.sh" web rel
|
||||||
|
|
||||||
|
echo "fixing perms"
|
||||||
|
chown -R 1000:1000 /work
|
|
@ -40,15 +40,15 @@ export interface KeyBoardBackend {
|
||||||
registerListener(listener: (event: KeyEvent) => void);
|
registerListener(listener: (event: KeyEvent) => void);
|
||||||
unregisterListener(listener: (event: KeyEvent) => void);
|
unregisterListener(listener: (event: KeyEvent) => void);
|
||||||
|
|
||||||
registerHook(hook: KeyHook) : () => void;
|
registerHook(hook: KeyHook): () => void;
|
||||||
unregisterHook(hook: KeyHook);
|
unregisterHook(hook: KeyHook);
|
||||||
|
|
||||||
isKeyPressed(key: string | SpecialKey) : boolean;
|
isKeyPressed(key: string | SpecialKey): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AbstractKeyBoard implements KeyBoardBackend {
|
export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
protected readonly registeredListener: ((event: KeyEvent) => void)[];
|
protected readonly registeredListener: ((event: KeyEvent) => void)[];
|
||||||
protected readonly activeSpecialKeys: { [key: number] : boolean };
|
protected readonly activeSpecialKeys: { [key: number]: boolean };
|
||||||
protected readonly activeKeys;
|
protected readonly activeKeys;
|
||||||
|
|
||||||
protected registeredKeyHooks: RegisteredKeyHook[] = [];
|
protected registeredKeyHooks: RegisteredKeyHook[] = [];
|
||||||
|
@ -59,10 +59,10 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
this.registeredListener = [];
|
this.registeredListener = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected destroy() {}
|
protected destroy() { }
|
||||||
|
|
||||||
isKeyPressed(key: string | SpecialKey): boolean {
|
isKeyPressed(key: string | SpecialKey): boolean {
|
||||||
if(typeof(key) === 'string') {
|
if (typeof (key) === 'string') {
|
||||||
return typeof this.activeKeys[key] !== "undefined";
|
return typeof this.activeKeys[key] !== "undefined";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.registeredKeyHooks.push(registeredHook);
|
this.registeredKeyHooks.push(registeredHook);
|
||||||
if(this.shouldHookBeActive(registeredHook)) {
|
if (this.shouldHookBeActive(registeredHook)) {
|
||||||
registeredHook.triggered = true;
|
registeredHook.triggered = true;
|
||||||
registeredHook.callbackPress();
|
registeredHook.callbackPress();
|
||||||
}
|
}
|
||||||
|
@ -85,11 +85,11 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterHook(hook: KeyHook) {
|
unregisterHook(hook: KeyHook) {
|
||||||
if(!("triggered" in hook)) {
|
if (!("triggered" in hook)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registeredKeyHooks.remove(hook);
|
this.registeredKeyHooks.remove(hook as RegisteredKeyHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListener(listener: (event: KeyEvent) => void) {
|
registerListener(listener: (event: KeyEvent) => void) {
|
||||||
|
@ -101,19 +101,19 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldHookBeActive(hook: KeyHook) {
|
private shouldHookBeActive(hook: KeyHook) {
|
||||||
if(typeof hook.keyAlt !== "undefined" && hook.keyAlt != this.activeSpecialKeys[SpecialKey.ALT]) {
|
if (typeof hook.keyAlt !== "undefined" && hook.keyAlt != this.activeSpecialKeys[SpecialKey.ALT]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof hook.keyCtrl !== "undefined" && hook.keyCtrl != this.activeSpecialKeys[SpecialKey.CTRL]) {
|
if (typeof hook.keyCtrl !== "undefined" && hook.keyCtrl != this.activeSpecialKeys[SpecialKey.CTRL]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof hook.keyShift !== "undefined" && hook.keyShift != this.activeSpecialKeys[SpecialKey.SHIFT]) {
|
if (typeof hook.keyShift !== "undefined" && hook.keyShift != this.activeSpecialKeys[SpecialKey.SHIFT]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof hook.keyWindows !== "undefined" && hook.keyWindows != this.activeSpecialKeys[SpecialKey.WINDOWS]) {
|
if (typeof hook.keyWindows !== "undefined" && hook.keyWindows != this.activeSpecialKeys[SpecialKey.WINDOWS]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,11 +122,11 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
|
|
||||||
protected fireKeyEvent(event: KeyEvent) {
|
protected fireKeyEvent(event: KeyEvent) {
|
||||||
//console.debug("Trigger key event %o", key_event);
|
//console.debug("Trigger key event %o", key_event);
|
||||||
for(const listener of this.registeredListener) {
|
for (const listener of this.registeredListener) {
|
||||||
listener(event);
|
listener(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type == EventType.KEY_TYPED) {
|
if (event.type == EventType.KEY_TYPED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,26 +134,26 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
this.activeSpecialKeys[SpecialKey.CTRL] = event.keyCtrl;
|
this.activeSpecialKeys[SpecialKey.CTRL] = event.keyCtrl;
|
||||||
this.activeSpecialKeys[SpecialKey.SHIFT] = event.keyShift;
|
this.activeSpecialKeys[SpecialKey.SHIFT] = event.keyShift;
|
||||||
this.activeSpecialKeys[SpecialKey.WINDOWS] = event.keyWindows;
|
this.activeSpecialKeys[SpecialKey.WINDOWS] = event.keyWindows;
|
||||||
if(event.type == EventType.KEY_PRESS) {
|
if (event.type == EventType.KEY_PRESS) {
|
||||||
this.activeKeys[event.keyCode] = event;
|
this.activeKeys[event.keyCode] = event;
|
||||||
} else {
|
} else {
|
||||||
delete this.activeKeys[event.keyCode];
|
delete this.activeKeys[event.keyCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for(const hook of this.registeredKeyHooks) {
|
for (const hook of this.registeredKeyHooks) {
|
||||||
const hookActive = this.shouldHookBeActive(hook);
|
const hookActive = this.shouldHookBeActive(hook);
|
||||||
if(hookActive === hook.triggered) {
|
if (hookActive === hook.triggered) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.triggered = hookActive;
|
hook.triggered = hookActive;
|
||||||
if(hookActive) {
|
if (hookActive) {
|
||||||
if(hook.callbackPress) {
|
if (hook.callbackPress) {
|
||||||
hook.callbackPress();
|
hook.callbackPress();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(hook.callbackRelease) {
|
if (hook.callbackRelease) {
|
||||||
hook.callbackRelease();
|
hook.callbackRelease();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,13 +166,13 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
this.activeSpecialKeys[SpecialKey.SHIFT] = false;
|
this.activeSpecialKeys[SpecialKey.SHIFT] = false;
|
||||||
this.activeSpecialKeys[SpecialKey.WINDOWS] = false;
|
this.activeSpecialKeys[SpecialKey.WINDOWS] = false;
|
||||||
|
|
||||||
for(const code of Object.keys(this.activeKeys)) {
|
for (const code of Object.keys(this.activeKeys)) {
|
||||||
delete this.activeKeys[code];
|
delete this.activeKeys[code];
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const hook of this.registeredKeyHooks) {
|
for (const hook of this.registeredKeyHooks) {
|
||||||
if(hook.triggered) {
|
if (hook.triggered) {
|
||||||
if(hook.callbackRelease) {
|
if (hook.callbackRelease) {
|
||||||
hook.callbackRelease();
|
hook.callbackRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ export class AbstractKeyBoard implements KeyBoardBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyBoardBackend: KeyBoardBackend;
|
let keyBoardBackend: KeyBoardBackend;
|
||||||
export function getKeyBoard() : KeyBoardBackend {
|
export function getKeyBoard(): KeyBoardBackend {
|
||||||
return keyBoardBackend;
|
return keyBoardBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,29 +193,29 @@ export function setKeyBoardBackend(newBackend: KeyBoardBackend) {
|
||||||
|
|
||||||
export function getKeyDescription(key: KeyDescriptor) {
|
export function getKeyDescription(key: KeyDescriptor) {
|
||||||
let result = "";
|
let result = "";
|
||||||
if(key.keyShift) {
|
if (key.keyShift) {
|
||||||
result += " + " + tr("Shift");
|
result += " + " + tr("Shift");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.keyAlt) {
|
if (key.keyAlt) {
|
||||||
result += " + " + tr("Alt");
|
result += " + " + tr("Alt");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.keyCtrl) {
|
if (key.keyCtrl) {
|
||||||
result += " + " + tr("CTRL");
|
result += " + " + tr("CTRL");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.keyWindows) {
|
if (key.keyWindows) {
|
||||||
result += " + " + tr("Win");
|
result += " + " + tr("Win");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.keyCode) {
|
if (key.keyCode) {
|
||||||
let keyName;
|
let keyName;
|
||||||
if(key.keyCode.startsWith("Key")) {
|
if (key.keyCode.startsWith("Key")) {
|
||||||
keyName = key.keyCode.substr(3);
|
keyName = key.keyCode.substr(3);
|
||||||
} else if(key.keyCode.startsWith("Digit")) {
|
} else if (key.keyCode.startsWith("Digit")) {
|
||||||
keyName = key.keyCode.substr(5);
|
keyName = key.keyCode.substr(5);
|
||||||
} else if(key.keyCode.startsWith("Numpad")) {
|
} else if (key.keyCode.startsWith("Numpad")) {
|
||||||
keyName = "Numpad " + key.keyCode.substr(6);
|
keyName = "Numpad " + key.keyCode.substr(6);
|
||||||
} else {
|
} else {
|
||||||
keyName = key.keyCode;
|
keyName = key.keyCode;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
||||||
import {Registry} from "tc-shared/events";
|
import { Registry } from "tc-shared/events";
|
||||||
import {LogCategory, logTrace, logWarn} from "tc-shared/log";
|
import { LogCategory, logTrace, logWarn } from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
import {getAudioBackend} from "tc-shared/audio/Player";
|
import { getAudioBackend } from "tc-shared/audio/Player";
|
||||||
|
|
||||||
export interface TrackClientInfo {
|
export interface TrackClientInfo {
|
||||||
media?: number,
|
media?: number,
|
||||||
|
@ -56,34 +56,34 @@ export class RemoteRTPTrack {
|
||||||
this.events.destroy();
|
this.events.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getEvents() : Registry<RemoteRTPTrackEvents> {
|
getEvents(): Registry<RemoteRTPTrackEvents> {
|
||||||
return this.events;
|
return this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
getState() : RemoteRTPTrackState {
|
getState(): RemoteRTPTrackState {
|
||||||
return this.currentState;
|
return this.currentState;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSsrc() : number {
|
getSsrc(): number {
|
||||||
return this.ssrc >>> 0;
|
return this.ssrc >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrack() : MediaStreamTrack {
|
getTrack(): MediaStreamTrack {
|
||||||
return this.transceiver.receiver.track;
|
return this.transceiver.receiver.track;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransceiver() : RTCRtpTransceiver {
|
getTransceiver(): RTCRtpTransceiver {
|
||||||
return this.transceiver;
|
return this.transceiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentAssignment() : TrackClientInfo | undefined {
|
getCurrentAssignment(): TrackClientInfo | undefined {
|
||||||
return this.currentAssignment;
|
return this.currentAssignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setState(state: RemoteRTPTrackState) {
|
protected setState(state: RemoteRTPTrackState) {
|
||||||
if(this.currentState === state) {
|
if (this.currentState === state) {
|
||||||
return;
|
return;
|
||||||
} else if(this.currentState === RemoteRTPTrackState.Destroyed) {
|
} else if (this.currentState === RemoteRTPTrackState.Destroyed) {
|
||||||
logWarn(LogCategory.WEBRTC, tr("Tried to change the track state for track %d from destroyed to %s."), this.getSsrc(), RemoteRTPTrackState[state]);
|
logWarn(LogCategory.WEBRTC, tr("Tried to change the track state for track %d from destroyed to %s."), this.getSsrc(), RemoteRTPTrackState[state]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -107,10 +107,9 @@ export class RemoteRTPVideoTrack extends RemoteRTPTrack {
|
||||||
track.onended = () => logTrace(LogCategory.VIDEO, "Track %d ended", ssrc);
|
track.onended = () => logTrace(LogCategory.VIDEO, "Track %d ended", ssrc);
|
||||||
track.onmute = () => logTrace(LogCategory.VIDEO, "Track %d muted", ssrc);
|
track.onmute = () => logTrace(LogCategory.VIDEO, "Track %d muted", ssrc);
|
||||||
track.onunmute = () => logTrace(LogCategory.VIDEO, "Track %d unmuted", ssrc);
|
track.onunmute = () => logTrace(LogCategory.VIDEO, "Track %d unmuted", ssrc);
|
||||||
track.onisolationchange = () => logTrace(LogCategory.VIDEO, "Track %d isolation changed", ssrc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMediaStream() : MediaStream {
|
getMediaStream(): MediaStream {
|
||||||
return this.mediaStream;
|
return this.mediaStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +169,7 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getAudioBackend().executeWhenInitialized(() => {
|
getAudioBackend().executeWhenInitialized(() => {
|
||||||
if(!this.mediaStream) {
|
if (!this.mediaStream) {
|
||||||
/* we've already been destroyed */
|
/* we've already been destroyed */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +199,7 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
|
||||||
this.setState(RemoteRTPTrackState.Destroyed);
|
this.setState(RemoteRTPTrackState.Destroyed);
|
||||||
}
|
}
|
||||||
|
|
||||||
getGain() : GainNode | undefined {
|
getGain(): GainNode | undefined {
|
||||||
return this.gainNode;
|
return this.gainNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,13 +212,13 @@ export class RemoteRTPAudioTrack extends RemoteRTPTrack {
|
||||||
* Mutes this track until the next setGain(..) call or a new sequence begins (state update)
|
* Mutes this track until the next setGain(..) call or a new sequence begins (state update)
|
||||||
*/
|
*/
|
||||||
abortCurrentReplay() {
|
abortCurrentReplay() {
|
||||||
if(this.gainNode) {
|
if (this.gainNode) {
|
||||||
this.gainNode.gain.value = 0;
|
this.gainNode.gain.value = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateGainNode() {
|
protected updateGainNode() {
|
||||||
if(!this.gainNode) {
|
if (!this.gainNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/* Note: This will be included into the controller and renderer process */
|
/* Note: This will be included into the controller and renderer process */
|
||||||
import {LogCategory, logError, logWarn} from "tc-shared/log";
|
import { LogCategory, logError, logWarn } from "tc-shared/log";
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import { Stage } from "tc-loader";
|
||||||
import * as crypto from "crypto-js";
|
import * as crypto from "crypto-js";
|
||||||
import {tra} from "tc-shared/i18n/localize";
|
import { tra } from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
export type LocalAvatarInfo = {
|
export type LocalAvatarInfo = {
|
||||||
fileName: string,
|
fileName: string,
|
||||||
|
@ -43,12 +43,12 @@ export type OwnAvatarMode = "uploading" | "server";
|
||||||
export class OwnAvatarStorage {
|
export class OwnAvatarStorage {
|
||||||
private openedCache: Cache | undefined;
|
private openedCache: Cache | undefined;
|
||||||
|
|
||||||
private static generateRequestUrl(serverUniqueId: string, mode: OwnAvatarMode) : string {
|
private static generateRequestUrl(serverUniqueId: string, mode: OwnAvatarMode): string {
|
||||||
return "https://_local_avatar/" + serverUniqueId + "/" + mode;
|
return "https://_local_avatar/" + serverUniqueId + "/" + mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
if(!("caches" in window)) {
|
if (!("caches" in window)) {
|
||||||
/* Not available (may unsecure context?) */
|
/* Not available (may unsecure context?) */
|
||||||
this.openedCache = undefined;
|
this.openedCache = undefined;
|
||||||
return;
|
return;
|
||||||
|
@ -62,8 +62,8 @@ export class OwnAvatarStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadAvatarRequest(serverUniqueId: string, mode: OwnAvatarMode) : Promise<LocalAvatarLoadResult<Response>> {
|
private async loadAvatarRequest(serverUniqueId: string, mode: OwnAvatarMode): Promise<LocalAvatarLoadResult<Response>> {
|
||||||
if(!this.openedCache) {
|
if (!this.openedCache) {
|
||||||
return { status: "cache-unavailable" };
|
return { status: "cache-unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ export class OwnAvatarStorage {
|
||||||
ignoreSearch: true,
|
ignoreSearch: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!response) {
|
if (!response) {
|
||||||
return { status: "empty-result" };
|
return { status: "empty-result" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +84,9 @@ export class OwnAvatarStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAvatarImage(serverUniqueId: string, mode: OwnAvatarMode) : Promise<LocalAvatarLoadResult<ArrayBuffer>> {
|
async loadAvatarImage(serverUniqueId: string, mode: OwnAvatarMode): Promise<LocalAvatarLoadResult<ArrayBuffer>> {
|
||||||
const loadResult = await this.loadAvatarRequest(serverUniqueId, mode);
|
const loadResult = await this.loadAvatarRequest(serverUniqueId, mode);
|
||||||
if(loadResult.status !== "success") {
|
if (loadResult.status !== "success") {
|
||||||
return loadResult;
|
return loadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +98,9 @@ export class OwnAvatarStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAvatar(serverUniqueId: string, mode: OwnAvatarMode, createResourceUrl: boolean) : Promise<LocalAvatarLoadResult<LocalAvatarInfo>> {
|
async loadAvatar(serverUniqueId: string, mode: OwnAvatarMode, createResourceUrl: boolean): Promise<LocalAvatarLoadResult<LocalAvatarInfo>> {
|
||||||
const loadResult = await this.loadAvatarRequest(serverUniqueId, mode);
|
const loadResult = await this.loadAvatarRequest(serverUniqueId, mode);
|
||||||
if(loadResult.status !== "success") {
|
if (loadResult.status !== "success") {
|
||||||
return loadResult;
|
return loadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,28 +112,28 @@ export class OwnAvatarStorage {
|
||||||
const avatarDateModified = parseInt(headers.get("X-File-Date-Modified"));
|
const avatarDateModified = parseInt(headers.get("X-File-Date-Modified"));
|
||||||
const avatarDateUploaded = parseInt(headers.get("X-File-Uploaded"));
|
const avatarDateUploaded = parseInt(headers.get("X-File-Uploaded"));
|
||||||
|
|
||||||
if(!avatarHash) {
|
if (!avatarHash) {
|
||||||
return { status: "error", reason: tr("missing response header file hash") };
|
return { status: "error", reason: tr("missing response header file hash") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!avatarName) {
|
if (!avatarName) {
|
||||||
return { status: "error", reason: tr("missing response header file name") };
|
return { status: "error", reason: tr("missing response header file name") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isNaN(avatarSize)) {
|
if (isNaN(avatarSize)) {
|
||||||
return { status: "error", reason: tr("missing/invalid response header file size") };
|
return { status: "error", reason: tr("missing/invalid response header file size") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isNaN(avatarDateModified)) {
|
if (isNaN(avatarDateModified)) {
|
||||||
return { status: "error", reason: tr("missing/invalid response header file modify date") };
|
return { status: "error", reason: tr("missing/invalid response header file modify date") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isNaN(avatarDateUploaded)) {
|
if (isNaN(avatarDateUploaded)) {
|
||||||
return { status: "error", reason: tr("missing/invalid response header file upload date") };
|
return { status: "error", reason: tr("missing/invalid response header file upload date") };
|
||||||
}
|
}
|
||||||
|
|
||||||
let resourceUrl;
|
let resourceUrl;
|
||||||
if(createResourceUrl) {
|
if (createResourceUrl) {
|
||||||
try {
|
try {
|
||||||
resourceUrl = URL.createObjectURL(await loadResult.result.blob());
|
resourceUrl = URL.createObjectURL(await loadResult.result.blob());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -159,12 +159,12 @@ export class OwnAvatarStorage {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAvatar(serverUniqueId: string, mode: OwnAvatarMode, target: File) : Promise<LocalAvatarUpdateResult> {
|
async updateAvatar(serverUniqueId: string, mode: OwnAvatarMode, target: File): Promise<LocalAvatarUpdateResult> {
|
||||||
if(!this.openedCache) {
|
if (!this.openedCache) {
|
||||||
return { status: "cache-unavailable" };
|
return { status: "cache-unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target.size > kMaxAvatarSize) {
|
if (target.size > kMaxAvatarSize) {
|
||||||
return { status: "error", reason: tra("Image exceeds maximum software size of {} bytes", kMaxAvatarSize) };
|
return { status: "error", reason: tra("Image exceeds maximum software size of {} bytes", kMaxAvatarSize) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ export class OwnAvatarStorage {
|
||||||
const hasher = crypto.algo.MD5.create();
|
const hasher = crypto.algo.MD5.create();
|
||||||
await target.stream().pipeTo(new WritableStream({
|
await target.stream().pipeTo(new WritableStream({
|
||||||
write(data) {
|
write(data) {
|
||||||
hasher.update(crypto.lib.WordArray.create(data));
|
hasher.update(crypto.lib.WordArray.create(Array.from(data)));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ export class OwnAvatarStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAvatar(serverUniqueId: string, mode: OwnAvatarMode) {
|
async removeAvatar(serverUniqueId: string, mode: OwnAvatarMode) {
|
||||||
if(!this.openedCache) {
|
if (!this.openedCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,13 +219,13 @@ export class OwnAvatarStorage {
|
||||||
* @param serverUniqueId
|
* @param serverUniqueId
|
||||||
*/
|
*/
|
||||||
async avatarUploadSucceeded(serverUniqueId: string) {
|
async avatarUploadSucceeded(serverUniqueId: string) {
|
||||||
if(!this.openedCache) {
|
if (!this.openedCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = await this.loadAvatarRequest(serverUniqueId, "uploading");
|
const request = await this.loadAvatarRequest(serverUniqueId, "uploading");
|
||||||
if(request.status !== "success") {
|
if (request.status !== "success") {
|
||||||
if(request.status !== "empty-result") {
|
if (request.status !== "empty-result") {
|
||||||
logError(LogCategory.GENERAL, tr("Failed to save uploaded avatar. Request failed to load: %o"), request);
|
logError(LogCategory.GENERAL, tr("Failed to save uploaded avatar. Request failed to load: %o"), request);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {InputStartError} from "tc-shared/voice/RecorderBase";
|
import { InputStartError } from "tc-shared/voice/RecorderBase";
|
||||||
import {LogCategory, logInfo, logWarn} from "tc-shared/log";
|
import { LogCategory, logInfo, logWarn } from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
|
|
||||||
export type MediaStreamType = "audio" | "video";
|
export type MediaStreamType = "audio" | "video";
|
||||||
|
|
||||||
|
@ -18,15 +18,15 @@ export interface MediaStreamEvents {
|
||||||
export const mediaStreamEvents = new Registry<MediaStreamEvents>();
|
export const mediaStreamEvents = new Registry<MediaStreamEvents>();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function requestMediaStreamWithConstraints(constraints: MediaTrackConstraints, type: MediaStreamType) : Promise<InputStartError | MediaStream> {
|
export async function requestMediaStreamWithConstraints(constraints: MediaTrackConstraints, type: MediaStreamType): Promise<InputStartError | MediaStream> {
|
||||||
const beginTimestamp = Date.now();
|
const beginTimestamp = Date.now();
|
||||||
try {
|
try {
|
||||||
logInfo(LogCategory.AUDIO, tr("Requesting a %s stream for device %s in group %s"), type, constraints.deviceId, constraints.groupId);
|
logInfo(LogCategory.AUDIO, tr("Requesting a %s stream for device %s in group %s"), type, constraints.deviceId, constraints.groupId);
|
||||||
return await navigator.mediaDevices.getUserMedia(type === "audio" ? { audio: constraints } : { video: constraints });
|
return await navigator.mediaDevices.getUserMedia(type === "audio" ? { audio: constraints } : { video: constraints });
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
if('name' in error) {
|
if ('name' in error) {
|
||||||
if(error.name === "NotAllowedError") {
|
if (error.name === "NotAllowedError") {
|
||||||
if(Date.now() - beginTimestamp < 250) {
|
if (Date.now() - beginTimestamp < 250) {
|
||||||
logWarn(LogCategory.AUDIO, tr("Media stream request failed (System denied). Browser message: %o"), error.message);
|
logWarn(LogCategory.AUDIO, tr("Media stream request failed (System denied). Browser message: %o"), error.message);
|
||||||
return InputStartError.ESYSTEMDENIED;
|
return InputStartError.ESYSTEMDENIED;
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,21 +46,21 @@ export async function requestMediaStreamWithConstraints(constraints: MediaTrackC
|
||||||
|
|
||||||
/* request permission for devices only one per time! */
|
/* request permission for devices only one per time! */
|
||||||
let currentMediaStreamRequest: Promise<MediaStream | InputStartError>;
|
let currentMediaStreamRequest: Promise<MediaStream | InputStartError>;
|
||||||
export async function requestMediaStream(deviceId: string | undefined, groupId: string | undefined, type: MediaStreamType) : Promise<MediaStream | InputStartError> {
|
export async function requestMediaStream(deviceId: string | undefined, groupId: string | undefined, type: MediaStreamType): Promise<MediaStream | InputStartError> {
|
||||||
/* wait for the current media stream requests to finish */
|
/* wait for the current media stream requests to finish */
|
||||||
while(currentMediaStreamRequest) {
|
while (currentMediaStreamRequest) {
|
||||||
try {
|
try {
|
||||||
await currentMediaStreamRequest;
|
await currentMediaStreamRequest;
|
||||||
} catch(error) { }
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
const constrains: MediaTrackConstraints = {};
|
const constrains: MediaTrackConstraints = {};
|
||||||
if(window.detectedBrowser?.name === "firefox") {
|
if (window.detectedBrowser?.name === "firefox") {
|
||||||
/*
|
/*
|
||||||
* Firefox only allows to open one mic/video as well deciding whats the input device it.
|
* Firefox only allows to open one mic/video as well deciding whats the input device it.
|
||||||
* It does not respect the deviceId nor the groupId
|
* It does not respect the deviceId nor the groupId
|
||||||
*/
|
*/
|
||||||
} else if(deviceId !== undefined) {
|
} else if (deviceId !== undefined) {
|
||||||
constrains.deviceId = deviceId;
|
constrains.deviceId = deviceId;
|
||||||
constrains.groupId = groupId;
|
constrains.groupId = groupId;
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,25 +75,28 @@ export async function requestMediaStream(deviceId: string | undefined, groupId:
|
||||||
try {
|
try {
|
||||||
return await currentMediaStreamRequest;
|
return await currentMediaStreamRequest;
|
||||||
} finally {
|
} finally {
|
||||||
if(currentMediaStreamRequest === promise) {
|
if (currentMediaStreamRequest === promise) {
|
||||||
currentMediaStreamRequest = undefined;
|
currentMediaStreamRequest = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryMediaPermissions(type: MediaStreamType, changeListener?: (value: PermissionState) => void) : Promise<PermissionState> {
|
export async function queryMediaPermissions(type: MediaStreamType, changeListener?: (value: PermissionState) => void): Promise<PermissionState> {
|
||||||
if('permissions' in navigator && 'query' in navigator.permissions) {
|
try {
|
||||||
try {
|
// @ts-ignore needed, as firefox doesn't allow microphone or camera, caught using the catch below
|
||||||
const result = await navigator.permissions.query({ name: type === "audio" ? "microphone" : "camera" });
|
const result = await navigator.permissions.query({ name: type === "audio" ? "microphone" : "camera" });
|
||||||
if(changeListener) {
|
if (changeListener) {
|
||||||
result.addEventListener("change", () => {
|
result.addEventListener("change", () => {
|
||||||
changeListener(result.state);
|
changeListener(result.state);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return result.state;
|
|
||||||
} catch (error) {
|
|
||||||
logWarn(LogCategory.GENERAL, tr("Failed to query for %s permissions: %s"), type, error);
|
|
||||||
}
|
}
|
||||||
|
return result.state;
|
||||||
|
} catch (error) {
|
||||||
|
// Firefox doesn't support querying for the camera / microphone permission, so return undetermined status
|
||||||
|
if (error instanceof TypeError) {
|
||||||
|
return "prompt";
|
||||||
|
}
|
||||||
|
logWarn(LogCategory.GENERAL, tr("Failed to query for %s permissions: %s"), type, error);
|
||||||
}
|
}
|
||||||
return "prompt";
|
return "prompt";
|
||||||
}
|
}
|
||||||
|
@ -101,7 +104,7 @@ export async function queryMediaPermissions(type: MediaStreamType, changeListene
|
||||||
export function stopMediaStream(stream: MediaStream) {
|
export function stopMediaStream(stream: MediaStream) {
|
||||||
stream.getVideoTracks().forEach(track => track.stop());
|
stream.getVideoTracks().forEach(track => track.stop());
|
||||||
stream.getAudioTracks().forEach(track => track.stop());
|
stream.getAudioTracks().forEach(track => track.stop());
|
||||||
if('stop' in stream) {
|
if ('stop' in stream) {
|
||||||
(stream as any).stop();
|
(stream as any).stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
/* setup jsrenderer */
|
/* setup jsrenderer */
|
||||||
import "jsrender";
|
import "jsrender";
|
||||||
import {tr} from "./i18n/localize";
|
import { tr } from "./i18n/localize";
|
||||||
import {LogCategory, logError, logTrace} from "tc-shared/log";
|
import { LogCategory, logError, logTrace } from "tc-shared/log";
|
||||||
|
|
||||||
(window as any).$ = require("jquery");
|
(window as any).$ = require("jquery");
|
||||||
(window as any).jQuery = $;
|
(window as any).jQuery = $;
|
||||||
|
@ -22,7 +22,7 @@ declare global {
|
||||||
* @param entry The entry to toggle
|
* @param entry The entry to toggle
|
||||||
* @returns `true` if the entry has been inserted and false if the entry has been deleted
|
* @returns `true` if the entry has been inserted and false if the entry has been deleted
|
||||||
*/
|
*/
|
||||||
toggle(entry: T) : boolean;
|
toggle(entry: T): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param entry The entry to toggle
|
* @param entry The entry to toggle
|
||||||
|
@ -33,21 +33,21 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JSON {
|
interface JSON {
|
||||||
map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : number;
|
map_to<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): number;
|
||||||
map_field_to<T>(object: T, value: any, field: string) : boolean;
|
map_field_to<T>(object: T, value: any, field: string): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type JQueryScrollType = "height" | "width";
|
type JQueryScrollType = "height" | "width";
|
||||||
interface JQuery<TElement = HTMLElement> {
|
interface JQuery<TElement = HTMLElement> {
|
||||||
renderTag(values?: any) : JQuery<TElement>;
|
renderTag(values?: any): JQuery<TElement>;
|
||||||
hasScrollBar(direction?: JQueryScrollType) : boolean;
|
hasScrollBar(direction?: JQueryScrollType): boolean;
|
||||||
|
|
||||||
|
|
||||||
visible_height() : number;
|
visible_height(): number;
|
||||||
visible_width() : number;
|
visible_width(): number;
|
||||||
|
|
||||||
/* first element which matches the selector, could be the element itself or a parent */
|
/* first element which matches the selector, could be the element itself or a parent */
|
||||||
firstParent(selector: string) : JQuery;
|
firstParent(selector: string): JQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JQueryStatic<TElement extends Node = HTMLElement> {
|
interface JQueryStatic<TElement extends Node = HTMLElement> {
|
||||||
|
@ -73,9 +73,6 @@ declare global {
|
||||||
name: string,
|
name: string,
|
||||||
version: string
|
version: string
|
||||||
};
|
};
|
||||||
|
|
||||||
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ObjectConstructor {
|
interface ObjectConstructor {
|
||||||
|
@ -83,9 +80,9 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IfEquals<X, Y, A=X, B=never> =
|
export type IfEquals<X, Y, A = X, B = never> =
|
||||||
(<T>() => T extends X ? 1 : 2) extends
|
(<T>() => T extends X ? 1 : 2) extends
|
||||||
(<T>() => T extends Y ? 1 : 2) ? A : B;
|
(<T>() => T extends Y ? 1 : 2) ? A : B;
|
||||||
|
|
||||||
export type WritableKeys<T> = {
|
export type WritableKeys<T> = {
|
||||||
[P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never>
|
[P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never>
|
||||||
|
@ -95,7 +92,7 @@ export type ReadonlyKeys<T> = {
|
||||||
[P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
|
[P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
|
||||||
}[keyof T];
|
}[keyof T];
|
||||||
|
|
||||||
if(!Object.isSimilar) {
|
if (!Object.isSimilar) {
|
||||||
Object.isSimilar = function (a, b) {
|
Object.isSimilar = function (a, b) {
|
||||||
const aType = typeof a;
|
const aType = typeof a;
|
||||||
const bType = typeof b;
|
const bType = typeof b;
|
||||||
|
@ -106,8 +103,8 @@ if(!Object.isSimilar) {
|
||||||
if (aType === "object") {
|
if (aType === "object") {
|
||||||
const aKeys = Object.keys(a);
|
const aKeys = Object.keys(a);
|
||||||
const bKeys = Object.keys(b);
|
const bKeys = Object.keys(b);
|
||||||
if(aKeys.length != bKeys.length) { return false; }
|
if (aKeys.length != bKeys.length) { return false; }
|
||||||
if(aKeys.findIndex(key => bKeys.indexOf(key) !== -1) !== -1) { return false; }
|
if (aKeys.findIndex(key => bKeys.indexOf(key) !== -1) !== -1) { return false; }
|
||||||
return aKeys.findIndex(key => !Object.isSimilar(a[key], b[key])) === -1;
|
return aKeys.findIndex(key => !Object.isSimilar(a[key], b[key])) === -1;
|
||||||
} else {
|
} else {
|
||||||
return a === b;
|
return a === b;
|
||||||
|
@ -115,7 +112,7 @@ if(!Object.isSimilar) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!JSON.map_to) {
|
if (!JSON.map_to) {
|
||||||
JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): number {
|
JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): number {
|
||||||
if (!validator)
|
if (!validator)
|
||||||
validator = () => true;
|
validator = () => true;
|
||||||
|
@ -145,29 +142,29 @@ if(!JSON.map_to) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(JSON.map_field_to(object, json[field], field))
|
if (JSON.map_field_to(object, json[field], field))
|
||||||
updates++;
|
updates++;
|
||||||
}
|
}
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!JSON.map_field_to) {
|
if (!JSON.map_field_to) {
|
||||||
JSON.map_field_to = function<T>(object: T, value: any, field: string) : boolean {
|
JSON.map_field_to = function <T>(object: T, value: any, field: string): boolean {
|
||||||
let fieldType = typeof object[field];
|
let fieldType = typeof object[field];
|
||||||
let newValue;
|
let newValue;
|
||||||
if(fieldType == "string" || fieldType == "object" || fieldType == "undefined") {
|
if (fieldType == "string" || fieldType == "object" || fieldType == "undefined") {
|
||||||
newValue = value;
|
newValue = value;
|
||||||
} else if(fieldType == "number") {
|
} else if (fieldType == "number") {
|
||||||
newValue = parseFloat(value);
|
newValue = parseFloat(value);
|
||||||
} else if(fieldType == "boolean") {
|
} else if (fieldType == "boolean") {
|
||||||
newValue = typeof value === "boolean" && value || value === "1" || value === "true";
|
newValue = typeof value === "boolean" && value || value === "1" || value === "true";
|
||||||
} else {
|
} else {
|
||||||
console.warn(tr("Invalid object type %s for entry %s"), fieldType, field);
|
console.warn(tr("Invalid object type %s for entry %s"), fieldType, field);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(newValue === object[field]) {
|
if (newValue === object[field]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +174,7 @@ if(!JSON.map_field_to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.prototype.remove) {
|
if (!Array.prototype.remove) {
|
||||||
Array.prototype.remove = function<T>(elem?: T): boolean {
|
Array.prototype.remove = function <T>(elem?: T): boolean {
|
||||||
const index = this.indexOf(elem);
|
const index = this.indexOf(elem);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.splice(index, 1);
|
this.splice(index, 1);
|
||||||
|
@ -188,18 +185,18 @@ if (!Array.prototype.remove) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.prototype.pop_front) {
|
if (!Array.prototype.pop_front) {
|
||||||
Array.prototype.pop_front = function<T>(): T {
|
Array.prototype.pop_front = function <T>(): T {
|
||||||
if(this.length == 0) return undefined;
|
if (this.length == 0) return undefined;
|
||||||
return this.splice(0, 1)[0];
|
return this.splice(0, 1)[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.prototype.toggle) {
|
if (!Array.prototype.toggle) {
|
||||||
Array.prototype.toggle = function<T>(element: T, insert?: boolean): boolean {
|
Array.prototype.toggle = function <T>(element: T, insert?: boolean): boolean {
|
||||||
const index = this.findIndex(e => e === element);
|
const index = this.findIndex(e => e === element);
|
||||||
if((index !== -1) === insert) {
|
if ((index !== -1) === insert) {
|
||||||
return false;
|
return false;
|
||||||
} else if(index === -1) {
|
} else if (index === -1) {
|
||||||
this.push(element);
|
this.push(element);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -209,25 +206,25 @@ if (!Array.prototype.toggle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.prototype.last){
|
if (!Array.prototype.last) {
|
||||||
Array.prototype.last = function(){
|
Array.prototype.last = function () {
|
||||||
if(this.length == 0) return undefined;
|
if (this.length == 0) return undefined;
|
||||||
return this[this.length - 1];
|
return this[this.length - 1];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof ($) !== "undefined") {
|
if (typeof ($) !== "undefined") {
|
||||||
if(!$.spawn) {
|
if (!$.spawn) {
|
||||||
$.spawn = function<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]> {
|
$.spawn = function <K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]> {
|
||||||
return $(document.createElement(tagName) as any);
|
return $(document.createElement(tagName) as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$.fn.renderTag) {
|
if (!$.fn.renderTag) {
|
||||||
$.fn.renderTag = function (this: JQuery, values?: any) : JQuery {
|
$.fn.renderTag = function (this: JQuery, values?: any): JQuery {
|
||||||
let result;
|
let result;
|
||||||
const template = $.views.templates[this.attr("id")];
|
const template = $.views.templates[this.attr("id")];
|
||||||
if(!template) {
|
if (!template) {
|
||||||
console.error("Tried to render template %o, but template is not available!", this.attr("id"));
|
console.error("Tried to render template %o, but template is not available!", this.attr("id"));
|
||||||
throw "missing template " + this.attr("id");
|
throw "missing template " + this.attr("id");
|
||||||
}
|
}
|
||||||
|
@ -243,30 +240,30 @@ if(typeof ($) !== "undefined") {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!$.fn.hasScrollBar)
|
if (!$.fn.hasScrollBar)
|
||||||
$.fn.hasScrollBar = function(direction?: "height" | "width") {
|
$.fn.hasScrollBar = function (direction?: "height" | "width") {
|
||||||
if(this.length <= 0)
|
if (this.length <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const scroll_height = this.get(0).scrollHeight > this.height();
|
const scroll_height = this.get(0).scrollHeight > this.height();
|
||||||
const scroll_width = this.get(0).scrollWidth > this.width();
|
const scroll_width = this.get(0).scrollWidth > this.width();
|
||||||
|
|
||||||
if(typeof(direction) === "string") {
|
if (typeof (direction) === "string") {
|
||||||
if(direction === "height")
|
if (direction === "height")
|
||||||
return scroll_height;
|
return scroll_height;
|
||||||
if(direction === "width")
|
if (direction === "width")
|
||||||
return scroll_width;
|
return scroll_width;
|
||||||
}
|
}
|
||||||
return scroll_width || scroll_height;
|
return scroll_width || scroll_height;
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!$.fn.visible_height)
|
if (!$.fn.visible_height)
|
||||||
$.fn.visible_height = function (this: JQuery<HTMLElement>) {
|
$.fn.visible_height = function (this: JQuery<HTMLElement>) {
|
||||||
const original_style = this.attr("style");
|
const original_style = this.attr("style");
|
||||||
this.css({
|
this.css({
|
||||||
position: 'absolute!important',
|
position: 'absolute!important',
|
||||||
visibility: 'hidden!important',
|
visibility: 'hidden!important',
|
||||||
display: 'block!important'
|
display: 'block!important'
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = this.height();
|
const result = this.height();
|
||||||
|
@ -274,13 +271,13 @@ if(typeof ($) !== "undefined") {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!$.fn.visible_width)
|
if (!$.fn.visible_width)
|
||||||
$.fn.visible_width = function (this: JQuery<HTMLElement>) {
|
$.fn.visible_width = function (this: JQuery<HTMLElement>) {
|
||||||
const original_style = this.attr("style");
|
const original_style = this.attr("style");
|
||||||
this.css({
|
this.css({
|
||||||
position: 'absolute!important',
|
position: 'absolute!important',
|
||||||
visibility: 'hidden!important',
|
visibility: 'hidden!important',
|
||||||
display: 'block!important'
|
display: 'block!important'
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = this.width();
|
const result = this.width();
|
||||||
|
@ -288,20 +285,20 @@ if(typeof ($) !== "undefined") {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!$.fn.firstParent)
|
if (!$.fn.firstParent)
|
||||||
$.fn.firstParent = function (this: JQuery<HTMLElement>, selector: string) {
|
$.fn.firstParent = function (this: JQuery<HTMLElement>, selector: string) {
|
||||||
if(this.is(selector))
|
if (this.is(selector))
|
||||||
return this;
|
return this;
|
||||||
return this.parent(selector);
|
return this.parent(selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Object.values) {
|
if (!Object.values) {
|
||||||
Object.values = object => Object.keys(object).map(e => object[e]);
|
Object.values = object => Object.keys(object).map(e => object[e]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function crashOnThrow<T>(promise: Promise<T> | (() => Promise<T>)) : Promise<T> {
|
export function crashOnThrow<T>(promise: Promise<T> | (() => Promise<T>)): Promise<T> {
|
||||||
if(typeof promise === "function") {
|
if (typeof promise === "function") {
|
||||||
try {
|
try {
|
||||||
promise = promise();
|
promise = promise();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -314,11 +311,11 @@ export function crashOnThrow<T>(promise: Promise<T> | (() => Promise<T>)) : Prom
|
||||||
logError(LogCategory.GENERAL, tr("Critical app error: %o"), error);
|
logError(LogCategory.GENERAL, tr("Critical app error: %o"), error);
|
||||||
|
|
||||||
/* Lets make this promise stuck for ever */
|
/* Lets make this promise stuck for ever */
|
||||||
return new Promise(() => {});
|
return new Promise(() => { });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ignorePromise<T>(_promise: Promise<T>) {}
|
export function ignorePromise<T>(_promise: Promise<T>) { }
|
||||||
|
|
||||||
export function NoThrow(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
export function NoThrow(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
||||||
const crashApp = error => {
|
const crashApp = error => {
|
||||||
|
@ -332,13 +329,13 @@ export function NoThrow(target: any, methodName: string, descriptor: PropertyDes
|
||||||
descriptor.value = function () {
|
descriptor.value = function () {
|
||||||
try {
|
try {
|
||||||
const result = originalMethod.apply(this, arguments);
|
const result = originalMethod.apply(this, arguments);
|
||||||
if(result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
promiseAccepted.value = true;
|
promiseAccepted.value = true;
|
||||||
return result.catch(error => {
|
return result.catch(error => {
|
||||||
crashApp(error);
|
crashApp(error);
|
||||||
|
|
||||||
/* Lets make this promise stuck for ever since we're in a not well defined state */
|
/* Lets make this promise stuck for ever since we're in a not well defined state */
|
||||||
return new Promise(() => {});
|
return new Promise(() => { });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,14 +343,14 @@ export function NoThrow(target: any, methodName: string, descriptor: PropertyDes
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
crashApp(error);
|
crashApp(error);
|
||||||
|
|
||||||
if(!promiseAccepted.value) {
|
if (!promiseAccepted.value) {
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* We don't know if we can return a promise or if just the object is expected.
|
* We don't know if we can return a promise or if just the object is expected.
|
||||||
* Since we don't know that, we're just rethrowing the error for now.
|
* Since we don't know that, we're just rethrowing the error for now.
|
||||||
*/
|
*/
|
||||||
return new Promise(() => {});
|
return new Promise(() => { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -365,7 +362,7 @@ export function CallOnce(target: any, methodName: string, descriptor: PropertyDe
|
||||||
|
|
||||||
const originalMethod: Function = descriptor.value;
|
const originalMethod: Function = descriptor.value;
|
||||||
descriptor.value = function () {
|
descriptor.value = function () {
|
||||||
if(callOnceData[methodName]) {
|
if (callOnceData[methodName]) {
|
||||||
debugger;
|
debugger;
|
||||||
throw "method " + methodName + " has already been called";
|
throw "method " + methodName + " has already been called";
|
||||||
}
|
}
|
||||||
|
@ -378,7 +375,7 @@ const kNonNullSymbol = Symbol("non-null-data");
|
||||||
export function NonNull(target: any, methodName: string, parameterIndex: number) {
|
export function NonNull(target: any, methodName: string, parameterIndex: number) {
|
||||||
const nonNullInfo = target[kNonNullSymbol] || (target[kNonNullSymbol] = {});
|
const nonNullInfo = target[kNonNullSymbol] || (target[kNonNullSymbol] = {});
|
||||||
const methodInfo = nonNullInfo[methodName] || (nonNullInfo[methodName] = {});
|
const methodInfo = nonNullInfo[methodName] || (nonNullInfo[methodName] = {});
|
||||||
if(!Array.isArray(methodInfo.indexes)) {
|
if (!Array.isArray(methodInfo.indexes)) {
|
||||||
/* Initialize method info */
|
/* Initialize method info */
|
||||||
methodInfo.overloaded = false;
|
methodInfo.overloaded = false;
|
||||||
methodInfo.indexes = [];
|
methodInfo.indexes = [];
|
||||||
|
@ -386,7 +383,7 @@ export function NonNull(target: any, methodName: string, parameterIndex: number)
|
||||||
|
|
||||||
methodInfo.indexes.push(parameterIndex);
|
methodInfo.indexes.push(parameterIndex);
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
if(methodInfo.overloaded || methodInfo.missingWarned) {
|
if (methodInfo.overloaded || methodInfo.missingWarned) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,21 +398,21 @@ export function NonNull(target: any, methodName: string, parameterIndex: number)
|
||||||
*/
|
*/
|
||||||
export function ParameterConstrained(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
export function ParameterConstrained(target: any, methodName: string, descriptor: PropertyDescriptor) {
|
||||||
const nonNullInfo = target[kNonNullSymbol];
|
const nonNullInfo = target[kNonNullSymbol];
|
||||||
if(!nonNullInfo) {
|
if (!nonNullInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodInfo = nonNullInfo[methodName] || (nonNullInfo[methodName] = {});
|
const methodInfo = nonNullInfo[methodName] || (nonNullInfo[methodName] = {});
|
||||||
if(!methodInfo) {
|
if (!methodInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
methodInfo.overloaded = true;
|
methodInfo.overloaded = true;
|
||||||
const originalMethod: Function = descriptor.value;
|
const originalMethod: Function = descriptor.value;
|
||||||
descriptor.value = function () {
|
descriptor.value = function () {
|
||||||
for(let index = 0; index < methodInfo.indexes.length; index++) {
|
for (let index = 0; index < methodInfo.indexes.length; index++) {
|
||||||
const argument = arguments[methodInfo.indexes[index]];
|
const argument = arguments[methodInfo.indexes[index]];
|
||||||
if(typeof argument === undefined || typeof argument === null) {
|
if (typeof argument === undefined || typeof argument === null) {
|
||||||
throw "parameter " + methodInfo.indexes[index] + " should not be null or undefined";
|
throw "parameter " + methodInfo.indexes[index] + " should not be null or undefined";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {LogCategory, logError, logInfo, logTrace} from "./log";
|
import { LogCategory, logError, logInfo, logTrace } from "./log";
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {Stage} from "tc-loader";
|
import { Stage } from "tc-loader";
|
||||||
import {Registry} from "./events";
|
import { Registry } from "./events";
|
||||||
import {tr} from "./i18n/localize";
|
import { tr } from "./i18n/localize";
|
||||||
import {CallOnce, ignorePromise} from "tc-shared/proto";
|
import { CallOnce, ignorePromise } from "tc-shared/proto";
|
||||||
import {getStorageAdapter} from "tc-shared/StorageAdapter";
|
import { getStorageAdapter } from "tc-shared/StorageAdapter";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Sync settings across renderer instances
|
* TODO: Sync settings across renderer instances
|
||||||
|
@ -14,17 +14,17 @@ export type RegistryValueType = boolean | number | string | object;
|
||||||
export type RegistryValueTypeNames = "boolean" | "number" | "string" | "object";
|
export type RegistryValueTypeNames = "boolean" | "number" | "string" | "object";
|
||||||
|
|
||||||
export type RegistryValueTypeMapping<T> = T extends boolean ? "boolean" :
|
export type RegistryValueTypeMapping<T> = T extends boolean ? "boolean" :
|
||||||
T extends number ? "number" :
|
T extends number ? "number" :
|
||||||
T extends string ? "string" :
|
T extends string ? "string" :
|
||||||
T extends object ? "object" :
|
T extends object ? "object" :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
export interface RegistryKey<ValueType extends RegistryValueType> {
|
export interface RegistryKey<ValueType extends RegistryValueType> {
|
||||||
key: string;
|
key: string;
|
||||||
valueType: RegistryValueTypeMapping<ValueType>;
|
valueType: RegistryValueTypeMapping<ValueType>;
|
||||||
|
|
||||||
fallbackKeys?: string | string[];
|
fallbackKeys?: string | string[];
|
||||||
fallbackImports?: {[key: string]:(value: string) => ValueType};
|
fallbackImports?: { [key: string]: (value: string) => ValueType };
|
||||||
|
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export interface ValuedRegistryKey<ValueType extends RegistryValueType> extends
|
||||||
|
|
||||||
const UPDATE_DIRECT: boolean = true;
|
const UPDATE_DIRECT: boolean = true;
|
||||||
|
|
||||||
function decodeValueFromString<T extends RegistryValueType>(input: string, type: RegistryValueTypeMapping<T>) : T {
|
function decodeValueFromString<T extends RegistryValueType>(input: string, type: RegistryValueTypeMapping<T>): T {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "string":
|
case "string":
|
||||||
return input as any;
|
return input as any;
|
||||||
|
@ -60,7 +60,7 @@ function decodeValueFromString<T extends RegistryValueType>(input: string, type:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeSettingValueToString<T extends RegistryValueType>(input: T) : string {
|
export function encodeSettingValueToString<T extends RegistryValueType>(input: T): string {
|
||||||
switch (typeof input) {
|
switch (typeof input) {
|
||||||
case "string":
|
case "string":
|
||||||
return input;
|
return input;
|
||||||
|
@ -83,24 +83,24 @@ export function resolveSettingKey<ValueType extends RegistryValueType, DefaultTy
|
||||||
key: RegistryKey<ValueType>,
|
key: RegistryKey<ValueType>,
|
||||||
resolver: (key: string) => string | undefined | null,
|
resolver: (key: string) => string | undefined | null,
|
||||||
defaultValue: DefaultType
|
defaultValue: DefaultType
|
||||||
) : ValueType | DefaultType {
|
): ValueType | DefaultType {
|
||||||
let value = resolver(key.key);
|
let value = resolver(key.key);
|
||||||
|
|
||||||
const keys = [key.key];
|
const keys = [key.key];
|
||||||
if(Array.isArray(key.fallbackKeys)) {
|
if (Array.isArray(key.fallbackKeys)) {
|
||||||
keys.push(...key.fallbackKeys);
|
keys.push(...key.fallbackKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const resolveKey of keys) {
|
for (const resolveKey of keys) {
|
||||||
value = resolver(resolveKey);
|
value = resolver(resolveKey);
|
||||||
if(typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key.valueType) {
|
switch (key.valueType) {
|
||||||
case "number":
|
case "number":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
if(value.length === 0) {
|
if (value.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -109,9 +109,9 @@ export function resolveSettingKey<ValueType extends RegistryValueType, DefaultTy
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key.fallbackImports) {
|
if (key.fallbackImports) {
|
||||||
const fallbackValueImporter = key.fallbackImports[resolveKey];
|
const fallbackValueImporter = key.fallbackImports[resolveKey];
|
||||||
if(fallbackValueImporter) {
|
if (fallbackValueImporter) {
|
||||||
return fallbackValueImporter(value);
|
return fallbackValueImporter(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,21 +129,21 @@ export class UrlParameterParser {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getParameter(key: string) : string | undefined {
|
private getParameter(key: string): string | undefined {
|
||||||
const value = this.url.searchParams.get(key);
|
const value = this.url.searchParams.get(key);
|
||||||
if(value === null) {
|
if (value === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodeURIComponent(value);
|
return decodeURIComponent(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV) : V | DV;
|
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
|
||||||
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V) : V;
|
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
|
||||||
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV) : V | DV {
|
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
|
||||||
if(arguments.length > 1) {
|
if (arguments.length > 1) {
|
||||||
return resolveSettingKey(key, key => this.getParameter(key), defaultValue);
|
return resolveSettingKey(key, key => this.getParameter(key), defaultValue);
|
||||||
} else if("defaultValue" in key) {
|
} else if ("defaultValue" in key) {
|
||||||
return resolveSettingKey(key, key => this.getParameter(key), key.defaultValue);
|
return resolveSettingKey(key, key => this.getParameter(key), key.defaultValue);
|
||||||
} else {
|
} else {
|
||||||
throw tr("missing value");
|
throw tr("missing value");
|
||||||
|
@ -155,14 +155,14 @@ export class UrlParameterBuilder {
|
||||||
private parameters = {};
|
private parameters = {};
|
||||||
|
|
||||||
setValue<V extends RegistryValueType>(key: RegistryKey<V>, value: V) {
|
setValue<V extends RegistryValueType>(key: RegistryKey<V>, value: V) {
|
||||||
if(value === undefined) {
|
if (value === undefined) {
|
||||||
delete this.parameters[key.key];
|
delete this.parameters[key.key];
|
||||||
} else {
|
} else {
|
||||||
this.parameters[key.key] = encodeURIComponent(encodeSettingValueToString(value));
|
this.parameters[key.key] = encodeURIComponent(encodeSettingValueToString(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build() : string {
|
build(): string {
|
||||||
return Object.keys(this.parameters).map(key => `${key}=${this.parameters[key]}`).join("&");
|
return Object.keys(this.parameters).map(key => `${key}=${this.parameters[key]}`).join("&");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,12 +174,12 @@ export class UrlParameterBuilder {
|
||||||
export namespace AppParameters {
|
export namespace AppParameters {
|
||||||
export const Instance = new UrlParameterParser(new URL(window.location.href));
|
export const Instance = new UrlParameterParser(new URL(window.location.href));
|
||||||
|
|
||||||
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV) : V | DV;
|
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
|
||||||
export function getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V) : V;
|
export function getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
|
||||||
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV) : V | DV {
|
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
|
||||||
if(arguments.length > 1) {
|
if (arguments.length > 1) {
|
||||||
return Instance.getValue(key, defaultValue);
|
return Instance.getValue(key, defaultValue);
|
||||||
} else if("defaultValue" in key) {
|
} else if ("defaultValue" in key) {
|
||||||
return Instance.getValue(key);
|
return Instance.getValue(key);
|
||||||
} else {
|
} else {
|
||||||
throw tr("missing value");
|
throw tr("missing value");
|
||||||
|
@ -408,12 +408,12 @@ export class Settings {
|
||||||
static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedRegistryKey<boolean> = {
|
static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedRegistryKey<boolean> = {
|
||||||
key: "connect_default",
|
key: "connect_default",
|
||||||
valueType: "boolean",
|
valueType: "boolean",
|
||||||
defaultValue: false
|
defaultValue: true
|
||||||
};
|
};
|
||||||
static readonly KEY_CONNECT_ADDRESS: ValuedRegistryKey<string> = {
|
static readonly KEY_CONNECT_ADDRESS: ValuedRegistryKey<string> = {
|
||||||
key: "connect_address",
|
key: "connect_address",
|
||||||
valueType: "string",
|
valueType: "string",
|
||||||
defaultValue: undefined
|
defaultValue: "tea.lp.kle.li"
|
||||||
};
|
};
|
||||||
static readonly KEY_CONNECT_PROFILE: ValuedRegistryKey<string> = {
|
static readonly KEY_CONNECT_PROFILE: ValuedRegistryKey<string> = {
|
||||||
key: "connect_profile",
|
key: "connect_profile",
|
||||||
|
@ -448,7 +448,7 @@ export class Settings {
|
||||||
|
|
||||||
static readonly KEY_CONNECT_NO_DNSPROXY: ValuedRegistryKey<boolean> = {
|
static readonly KEY_CONNECT_NO_DNSPROXY: ValuedRegistryKey<boolean> = {
|
||||||
key: "connect_no_dnsproxy",
|
key: "connect_no_dnsproxy",
|
||||||
defaultValue: false,
|
defaultValue: true,
|
||||||
valueType: "boolean",
|
valueType: "boolean",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -616,7 +616,7 @@ export class Settings {
|
||||||
/* defaultValue: <users download directory> */
|
/* defaultValue: <users download directory> */
|
||||||
};
|
};
|
||||||
|
|
||||||
static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey<string> = {
|
static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey<string> = {
|
||||||
key: "ipc-address",
|
key: "ipc-address",
|
||||||
valueType: "string"
|
valueType: "string"
|
||||||
};
|
};
|
||||||
|
@ -913,8 +913,8 @@ export class Settings {
|
||||||
static readonly KEYS = (() => {
|
static readonly KEYS = (() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
for(const key of Object.keys(Settings)) {
|
for (const key of Object.keys(Settings)) {
|
||||||
if(!key.toUpperCase().startsWith("KEY_")) {
|
if (!key.toUpperCase().startsWith("KEY_")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -943,13 +943,13 @@ export class Settings {
|
||||||
const json = await getStorageAdapter().get("settings.global");
|
const json = await getStorageAdapter().get("settings.global");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(json === null) {
|
if (json === null) {
|
||||||
logInfo(LogCategory.GENERAL, tr("Found no settings. Creating new client settings."));
|
logInfo(LogCategory.GENERAL, tr("Found no settings. Creating new client settings."));
|
||||||
this.settingsCache = {};
|
this.settingsCache = {};
|
||||||
} else {
|
} else {
|
||||||
this.settingsCache = JSON.parse(json);
|
this.settingsCache = JSON.parse(json);
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
this.settingsCache = {};
|
this.settingsCache = {};
|
||||||
logError(LogCategory.GENERAL, tr("Failed to load global settings!\nJson: %s\nError: %o"), json, error);
|
logError(LogCategory.GENERAL, tr("Failed to load global settings!\nJson: %s\nError: %o"), json, error);
|
||||||
|
|
||||||
|
@ -957,7 +957,7 @@ export class Settings {
|
||||||
//FIXME: Readd this
|
//FIXME: Readd this
|
||||||
//createErrorModal(tr("Failed to load global settings"), tr("Failed to load global client settings!\nLookup console for more information.")).open();
|
//createErrorModal(tr("Failed to load global settings"), tr("Failed to load global client settings!\nLookup console for more information.")).open();
|
||||||
};
|
};
|
||||||
if(!loader.finished()) {
|
if (!loader.finished()) {
|
||||||
loader.register_task(loader.Stage.LOADED, {
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
name: "Settings error",
|
name: "Settings error",
|
||||||
|
@ -969,18 +969,18 @@ export class Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saveWorker = setInterval(() => {
|
this.saveWorker = setInterval(() => {
|
||||||
if(this.updated) {
|
if (this.updated) {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV) : V | DV;
|
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
|
||||||
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V) : V;
|
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
|
||||||
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV) : V | DV {
|
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
|
||||||
if(arguments.length > 1) {
|
if (arguments.length > 1) {
|
||||||
return resolveSettingKey(key, key => this.settingsCache[key], defaultValue);
|
return resolveSettingKey(key, key => this.settingsCache[key], defaultValue);
|
||||||
} else if("defaultValue" in key) {
|
} else if ("defaultValue" in key) {
|
||||||
return resolveSettingKey(key, key => this.settingsCache[key], key.defaultValue);
|
return resolveSettingKey(key, key => this.settingsCache[key], key.defaultValue);
|
||||||
} else {
|
} else {
|
||||||
debugger;
|
debugger;
|
||||||
|
@ -988,17 +988,17 @@ export class Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue<T extends RegistryValueType>(key: RegistryKey<T>, value?: T){
|
setValue<T extends RegistryValueType>(key: RegistryKey<T>, value?: T) {
|
||||||
if(value === null) {
|
if (value === null) {
|
||||||
value = undefined;
|
value = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.settingsCache[key.key] === value) {
|
if (this.settingsCache[key.key] === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldValue = this.settingsCache[key.key];
|
const oldValue = this.settingsCache[key.key];
|
||||||
if(value === undefined) {
|
if (value === undefined) {
|
||||||
delete this.settingsCache[key.key];
|
delete this.settingsCache[key.key];
|
||||||
} else {
|
} else {
|
||||||
this.settingsCache[key.key] = encodeSettingValueToString(value);
|
this.settingsCache[key.key] = encodeSettingValueToString(value);
|
||||||
|
@ -1014,21 +1014,21 @@ export class Settings {
|
||||||
});
|
});
|
||||||
logTrace(LogCategory.GENERAL, tr("Changing global setting %s to %o"), key.key, value);
|
logTrace(LogCategory.GENERAL, tr("Changing global setting %s to %o"), key.key, value);
|
||||||
|
|
||||||
if(UPDATE_DIRECT) {
|
if (UPDATE_DIRECT) {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globalChangeListener<T extends RegistryValueType>(key: RegistryKey<T>, listener: (newValue: T) => void) : () => void {
|
globalChangeListener<T extends RegistryValueType>(key: RegistryKey<T>, listener: (newValue: T) => void): () => void {
|
||||||
return this.events.on("notify_setting_changed", event => {
|
return this.events.on("notify_setting_changed", event => {
|
||||||
if(event.setting === key.key && event.mode === "global") {
|
if (event.setting === key.key && event.mode === "global") {
|
||||||
listener(event.newCastedValue);
|
listener(event.newCastedValue);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doSave() {
|
private async doSave() {
|
||||||
if(this.saveState === "none") {
|
if (this.saveState === "none") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1040,7 +1040,7 @@ export class Settings {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(LogCategory.GENERAL, tr("Failed to save global settings: %o"), error);
|
logError(LogCategory.GENERAL, tr("Failed to save global settings: %o"), error);
|
||||||
}
|
}
|
||||||
} while(this.saveState !== "saving");
|
} while (this.saveState !== "saving");
|
||||||
|
|
||||||
this.saveState = "none";
|
this.saveState = "none";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {IpcInviteInfo, IpcInviteInfoLoaded} from "tc-shared/text/bbcode/InviteDefinitions";
|
import { IpcInviteInfo, IpcInviteInfoLoaded } from "tc-shared/text/bbcode/InviteDefinitions";
|
||||||
import {ChannelMessage, getIpcInstance, IPCChannel} from "tc-shared/ipc/BrowserIPC";
|
import { ChannelMessage, getIpcInstance, IPCChannel } from "tc-shared/ipc/BrowserIPC";
|
||||||
import * as loader from "tc-loader";
|
import * as loader from "tc-loader";
|
||||||
import {useEffect, useState} from "react";
|
import { useEffect, useState } from "react";
|
||||||
import _ = require("lodash");
|
import _ = require("lodash");
|
||||||
import {Translatable} from "tc-shared/ui/react-elements/i18n";
|
import { Translatable } from "tc-shared/ui/react-elements/i18n";
|
||||||
import {Button} from "tc-shared/ui/react-elements/Button";
|
import { Button } from "tc-shared/ui/react-elements/Button";
|
||||||
import {SimpleUrlRenderer} from "tc-shared/text/bbcode/url";
|
import { SimpleUrlRenderer } from "tc-shared/text/bbcode/url";
|
||||||
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
|
import { LoadingDots } from "tc-shared/ui/react-elements/LoadingDots";
|
||||||
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
|
import { ClientIconRenderer } from "tc-shared/ui/react-elements/Icons";
|
||||||
import {ClientIcon} from "svg-sprites/client-icons";
|
import { ClientIcon } from "svg-sprites/client-icons";
|
||||||
|
|
||||||
const cssStyle = require("./InviteRenderer.scss");
|
const cssStyle = require("./InviteRenderer.scss");
|
||||||
const kInviteUrlRegex = /^(https:\/\/)?(teaspeak.de\/|join.teaspeak.de\/(invite\/)?)([a-zA-Z0-9]{4})$/gm;
|
const kInviteUrlRegex = /^(https:\/\/)?(teaspeak.de\/|join.teaspeak.de\/(invite\/)?)([a-zA-Z0-9]{4})$/gm;
|
||||||
|
|
||||||
export function isInviteLink(url: string) : boolean {
|
export function isInviteLink(url: string): boolean {
|
||||||
kInviteUrlRegex.lastIndex = 0;
|
kInviteUrlRegex.lastIndex = 0;
|
||||||
return !!url.match(kInviteUrlRegex);
|
return !!url.match(kInviteUrlRegex);
|
||||||
}
|
}
|
||||||
|
@ -26,25 +26,25 @@ const localInviteCache: { [key: string]: InviteCacheEntry } = {};
|
||||||
const localInviteCallbacks: { [key: string]: (() => void)[] } = {};
|
const localInviteCallbacks: { [key: string]: (() => void)[] } = {};
|
||||||
|
|
||||||
const useInviteLink = (linkId: string): LocalInviteInfo => {
|
const useInviteLink = (linkId: string): LocalInviteInfo => {
|
||||||
if(!localInviteCache[linkId]) {
|
if (!localInviteCache[linkId]) {
|
||||||
localInviteCache[linkId] = { status: { status: "loading" }, timeout: setTimeout(() => delete localInviteCache[linkId], 60 * 1000) };
|
localInviteCache[linkId] = { status: { status: "loading" }, timeout: setTimeout(() => delete localInviteCache[linkId], 60 * 1000) };
|
||||||
ipcChannel?.sendMessage("query", { linkId });
|
ipcChannel?.sendMessage("query", { linkId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ value, setValue ] = useState(localInviteCache[linkId].status);
|
const [value, setValue] = useState(localInviteCache[linkId].status);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(typeof localInviteCache[linkId]?.status === "undefined") {
|
if (typeof localInviteCache[linkId]?.status === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isEqual(value, localInviteCache[linkId].status)) {
|
if (!_.isEqual(value, localInviteCache[linkId].status)) {
|
||||||
setValue(localInviteCache[linkId].status);
|
setValue(localInviteCache[linkId].status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const callback = () => setValue(localInviteCache[linkId].status);
|
const callback = () => setValue(localInviteCache[linkId].status);
|
||||||
(localInviteCallbacks[linkId] || (localInviteCallbacks[linkId] = [])).push(callback);
|
(localInviteCallbacks[linkId] || (localInviteCallbacks[linkId] = [])).push(callback);
|
||||||
return () => localInviteCallbacks[linkId]?.remove(callback);
|
return () => { localInviteCallbacks[linkId]?.remove(callback); }
|
||||||
}, [linkId]);
|
}, [linkId]);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -69,14 +69,14 @@ const LoadedInviteRenderer = React.memo((props: { info: IpcInviteInfoLoaded }) =
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const [, setRevision ] = useState(0);
|
const [, setRevision] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.info.expireTimestamp === 0) {
|
if (props.info.expireTimestamp === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeout = props.info.expireTimestamp - (Date.now() / 1000);
|
const timeout = props.info.expireTimestamp - (Date.now() / 1000);
|
||||||
if(timeout <= 0) {
|
if (timeout <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ const LoadedInviteRenderer = React.memo((props: { info: IpcInviteInfoLoaded }) =
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(props.info.expireTimestamp > 0 && Date.now() / 1000 >= props.info.expireTimestamp) {
|
if (props.info.expireTimestamp > 0 && Date.now() / 1000 >= props.info.expireTimestamp) {
|
||||||
return (
|
return (
|
||||||
<InviteErrorRenderer noTitle={true} key={"expired"}>
|
<InviteErrorRenderer noTitle={true} key={"expired"}>
|
||||||
<Translatable>Link expired</Translatable>
|
<Translatable>Link expired</Translatable>
|
||||||
|
@ -92,7 +92,7 @@ const LoadedInviteRenderer = React.memo((props: { info: IpcInviteInfoLoaded }) =
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(props.info.channelName) {
|
if (props.info.channelName) {
|
||||||
return (
|
return (
|
||||||
<div className={cssStyle.container + " " + cssStyle.info} key={"with-channel"}>
|
<div className={cssStyle.container + " " + cssStyle.info} key={"with-channel"}>
|
||||||
<div className={cssStyle.left}>
|
<div className={cssStyle.left}>
|
||||||
|
@ -215,8 +215,8 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
|
||||||
|
|
||||||
function handleIpcMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
function handleIpcMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
||||||
if(message.type === "query-result") {
|
if (message.type === "query-result") {
|
||||||
if(!localInviteCache[message.data.linkId]) {
|
if (!localInviteCache[message.data.linkId]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -8,14 +8,27 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"baseUrl": "../../",
|
"baseUrl": "../../",
|
||||||
"paths": {
|
"paths": {
|
||||||
"tc-shared/*": ["shared/js/*"],
|
"tc-shared/*": [
|
||||||
"tc-loader": ["loader/exports/loader.d.ts"],
|
"shared/js/*"
|
||||||
"svg-sprites/*": ["shared/svg-sprites/*"],
|
],
|
||||||
"vendor/xbbcode/*": ["vendor/xbbcode/src/*"],
|
"tc-loader": [
|
||||||
"tc-events": ["vendor/TeaEventBus/src/index.ts"],
|
"loader/exports/loader.d.ts"
|
||||||
"tc-services": ["vendor/TeaClientServices/src/index.ts"]
|
],
|
||||||
|
"svg-sprites/*": [
|
||||||
|
"shared/svg-sprites/*"
|
||||||
|
],
|
||||||
|
"vendor/xbbcode/*": [
|
||||||
|
"vendor/xbbcode/src/*"
|
||||||
|
],
|
||||||
|
"tc-events": [
|
||||||
|
"vendor/TeaEventBus/src/index.ts"
|
||||||
|
],
|
||||||
|
"tc-services": [
|
||||||
|
"vendor/TeaClientServices/src/index.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"plugins": [ /* ttypescript */
|
"plugins": [ /* ttypescript */
|
||||||
{
|
{
|
||||||
"transform": "../../tools/trgen/ttsc_transformer.js",
|
"transform": "../../tools/trgen/ttsc_transformer.js",
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": ["es6"],
|
"lib": [
|
||||||
|
"es6"
|
||||||
|
],
|
||||||
"typeRoots": [],
|
"typeRoots": [],
|
||||||
"types": [],
|
"types": [],
|
||||||
|
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
@ -4,20 +4,22 @@
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"webpack.config.ts",
|
"webpack.config.ts",
|
||||||
"webpack-client.config.ts",
|
"webpack-client.config.ts",
|
||||||
"webpack-web.config.ts",
|
"webpack-web.config.ts",
|
||||||
|
|
||||||
"webpack/build-definitions.d.ts",
|
"webpack/build-definitions.d.ts",
|
||||||
"webpack/HtmlWebpackInlineSource.ts",
|
"webpack/HtmlWebpackInlineSource.ts",
|
||||||
"webpack/WatLoader.ts",
|
"webpack/WatLoader.ts",
|
||||||
"webpack/ManifestPlugin.ts",
|
"webpack/ManifestPlugin.ts",
|
||||||
|
|
||||||
"babel.config.ts",
|
"babel.config.ts",
|
||||||
"postcss.config.ts",
|
"postcss.config.ts",
|
||||||
"file.ts"
|
"file.ts"
|
||||||
|
|
|
@ -5,19 +5,35 @@
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": ["ES7", "dom", "dom.iterable"],
|
"lib": [
|
||||||
|
"ES7",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
|
],
|
||||||
"removeComments": true, /* we dont really need them within the target files */
|
"removeComments": true, /* we dont really need them within the target files */
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"tc-shared/*": ["shared/js/*"],
|
"tc-shared/*": [
|
||||||
"tc-loader": ["loader/exports/loader.d.ts"],
|
"shared/js/*"
|
||||||
"tc-events": ["vendor/TeaEventBus/src/index.ts"],
|
],
|
||||||
"tc-services": ["vendor/TeaClientServices/src/index.ts"],
|
"tc-loader": [
|
||||||
|
"loader/exports/loader.d.ts"
|
||||||
"svg-sprites/*": ["shared/svg-sprites/*"],
|
],
|
||||||
"vendor/xbbcode/*": ["vendor/xbbcode/src/*"]
|
"tc-events": [
|
||||||
|
"vendor/TeaEventBus/src/index.ts"
|
||||||
|
],
|
||||||
|
"tc-services": [
|
||||||
|
"vendor/TeaClientServices/src/index.ts"
|
||||||
|
],
|
||||||
|
"svg-sprites/*": [
|
||||||
|
"shared/svg-sprites/*"
|
||||||
|
],
|
||||||
|
"vendor/xbbcode/*": [
|
||||||
|
"vendor/xbbcode/src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import {LogCategory, logError, logTrace, logWarn} from "tc-shared/log";
|
import { LogCategory, logError, logTrace, logWarn } from "tc-shared/log";
|
||||||
import {tr} from "tc-shared/i18n/localize";
|
import { tr } from "tc-shared/i18n/localize";
|
||||||
import {default_options, DNSAddress, DNSResolveResult, ResolveOptions} from "tc-shared/dns";
|
import { default_options, DNSAddress, DNSResolveResult, ResolveOptions } from "tc-shared/dns";
|
||||||
import {executeDnsRequest, RRType} from "./api";
|
import { executeDnsRequest, RRType } from "./api";
|
||||||
|
|
||||||
interface DNSResolveMethod {
|
interface DNSResolveMethod {
|
||||||
name() : string;
|
name(): string;
|
||||||
resolve(address: DNSAddress) : Promise<DNSAddress | undefined>;
|
resolve(address: DNSAddress): Promise<DNSAddress | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalhostResolver implements DNSResolveMethod {
|
class LocalhostResolver implements DNSResolveMethod {
|
||||||
|
@ -14,7 +14,7 @@ class LocalhostResolver implements DNSResolveMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
if(address.hostname === "localhost") {
|
if (address.hostname === "localhost") {
|
||||||
return {
|
return {
|
||||||
hostname: "127.0.0.1",
|
hostname: "127.0.0.1",
|
||||||
port: address.port
|
port: address.port
|
||||||
|
@ -26,6 +26,20 @@ class LocalhostResolver implements DNSResolveMethod {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FakeResolver implements DNSResolveMethod {
|
||||||
|
name(): string {
|
||||||
|
return "fake resolver";
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
|
return {
|
||||||
|
hostname: "tea.lp.kle.li",
|
||||||
|
port: address.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class IPResolveMethod implements DNSResolveMethod {
|
class IPResolveMethod implements DNSResolveMethod {
|
||||||
readonly v6: boolean;
|
readonly v6: boolean;
|
||||||
|
|
||||||
|
@ -40,7 +54,7 @@ class IPResolveMethod implements DNSResolveMethod {
|
||||||
|
|
||||||
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
const answer = await executeDnsRequest(address.hostname, this.v6 ? RRType.AAAA : RRType.A);
|
const answer = await executeDnsRequest(address.hostname, this.v6 ? RRType.AAAA : RRType.A);
|
||||||
if(!answer.length) {
|
if (!answer.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +86,10 @@ class SRVResolveMethod implements DNSResolveMethod {
|
||||||
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
const answer = await executeDnsRequest((this.application ? this.application + "." : "") + address.hostname, RRType.SRV);
|
const answer = await executeDnsRequest((this.application ? this.application + "." : "") + address.hostname, RRType.SRV);
|
||||||
|
|
||||||
const records: {[key: number]: ParsedSVRRecord[]} = {};
|
const records: { [key: number]: ParsedSVRRecord[] } = {};
|
||||||
for(const record of answer) {
|
for (const record of answer) {
|
||||||
const parts = record.data.split(" ");
|
const parts = record.data.split(" ");
|
||||||
if(parts.length !== 4) {
|
if (parts.length !== 4) {
|
||||||
logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Invalid split length."), record);
|
logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Invalid split length."), record);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +98,7 @@ class SRVResolveMethod implements DNSResolveMethod {
|
||||||
const weight = parseInt(parts[1]);
|
const weight = parseInt(parts[1]);
|
||||||
const port = parseInt(parts[2]);
|
const port = parseInt(parts[2]);
|
||||||
|
|
||||||
if((priority < 0 || priority > 65535) || (weight < 0 || weight > 65535) || (port < 0 || port > 65535)) {
|
if ((priority < 0 || priority > 65535) || (weight < 0 || weight > 65535) || (port < 0 || port > 65535)) {
|
||||||
logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Malformed data."), record);
|
logWarn(LogCategory.DNS, tr("Failed to parse SRV record %s. Malformed data."), record);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -99,35 +113,35 @@ class SRVResolveMethod implements DNSResolveMethod {
|
||||||
|
|
||||||
/* get the record with the highest priority */
|
/* get the record with the highest priority */
|
||||||
const priority_strings = Object.keys(records);
|
const priority_strings = Object.keys(records);
|
||||||
if(!priority_strings.length) {
|
if (!priority_strings.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let highestPriority: ParsedSVRRecord[];
|
let highestPriority: ParsedSVRRecord[];
|
||||||
for(const priority_str of priority_strings) {
|
for (const priority_str of priority_strings) {
|
||||||
if(!highestPriority || !highestPriority.length) {
|
if (!highestPriority || !highestPriority.length) {
|
||||||
highestPriority = records[priority_str];
|
highestPriority = records[priority_str];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(highestPriority[0].priority < parseInt(priority_str)) {
|
if (highestPriority[0].priority < parseInt(priority_str)) {
|
||||||
highestPriority = records[priority_str];
|
highestPriority = records[priority_str];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!highestPriority.length) {
|
if (!highestPriority.length) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* select randomly one record */
|
/* select randomly one record */
|
||||||
let record: ParsedSVRRecord;
|
let record: ParsedSVRRecord;
|
||||||
const max_weight = highestPriority.map(e => e.weight).reduce((a, b) => a + b, 0);
|
const max_weight = highestPriority.map(e => e.weight).reduce((a, b) => a + b, 0);
|
||||||
if(max_weight == 0) {
|
if (max_weight == 0) {
|
||||||
record = highestPriority[Math.floor(Math.random() * highestPriority.length)];
|
record = highestPriority[Math.floor(Math.random() * highestPriority.length)];
|
||||||
} else {
|
} else {
|
||||||
let rnd = Math.random() * max_weight;
|
let rnd = Math.random() * max_weight;
|
||||||
for(let i = 0; i < highestPriority.length; i++) {
|
for (let i = 0; i < highestPriority.length; i++) {
|
||||||
rnd -= highestPriority[i].weight;
|
rnd -= highestPriority[i].weight;
|
||||||
if(rnd > 0) {
|
if (rnd > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +150,7 @@ class SRVResolveMethod implements DNSResolveMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!record) {
|
if (!record) {
|
||||||
/* shall never happen */
|
/* shall never happen */
|
||||||
record = highestPriority[0];
|
record = highestPriority[0];
|
||||||
}
|
}
|
||||||
|
@ -165,7 +179,7 @@ class SRV_IPResolveMethod implements DNSResolveMethod {
|
||||||
|
|
||||||
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
const srvAddress = await this.srvResolver.resolve(address);
|
const srvAddress = await this.srvResolver.resolve(address);
|
||||||
if(!srvAddress) {
|
if (!srvAddress) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +204,7 @@ class DomainRootResolveMethod implements DNSResolveMethod {
|
||||||
|
|
||||||
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
async resolve(address: DNSAddress): Promise<DNSAddress | undefined> {
|
||||||
const parts = address.hostname.split(".");
|
const parts = address.hostname.split(".");
|
||||||
if(parts.length < 3) {
|
if (parts.length < 3) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +217,7 @@ class DomainRootResolveMethod implements DNSResolveMethod {
|
||||||
|
|
||||||
class TeaSpeakDNSResolve {
|
class TeaSpeakDNSResolve {
|
||||||
readonly address: DNSAddress;
|
readonly address: DNSAddress;
|
||||||
private resolvers: {[key: string]:{ resolver: DNSResolveMethod, after: string[] }} = {};
|
private resolvers: { [key: string]: { resolver: DNSResolveMethod, after: string[] } } = {};
|
||||||
private resolving = false;
|
private resolving = false;
|
||||||
private timeout;
|
private timeout;
|
||||||
|
|
||||||
|
@ -218,15 +232,15 @@ class TeaSpeakDNSResolve {
|
||||||
}
|
}
|
||||||
|
|
||||||
registerResolver(resolver: DNSResolveMethod, ...after: (string | DNSResolveMethod)[]) {
|
registerResolver(resolver: DNSResolveMethod, ...after: (string | DNSResolveMethod)[]) {
|
||||||
if(this.resolving) {
|
if (this.resolving) {
|
||||||
throw tr("resolver is already resolving");
|
throw tr("resolver is already resolving");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolvers[resolver.name()] = { resolver: resolver, after: after.map(e => typeof e === "string" ? e : e.name()) };
|
this.resolvers[resolver.name()] = { resolver: resolver, after: after.map(e => typeof e === "string" ? e : e.name()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(timeout: number) : Promise<DNSAddress> {
|
resolve(timeout: number): Promise<DNSAddress> {
|
||||||
if(this.resolving) {
|
if (this.resolving) {
|
||||||
throw tr("already resolving");
|
throw tr("already resolving");
|
||||||
}
|
}
|
||||||
this.resolving = true;
|
this.resolving = true;
|
||||||
|
@ -263,52 +277,53 @@ class TeaSpeakDNSResolve {
|
||||||
let invoke_count = 0;
|
let invoke_count = 0;
|
||||||
|
|
||||||
_main_loop:
|
_main_loop:
|
||||||
for(const resolver_name of Object.keys(this.resolvers)) {
|
for (const resolver_name of Object.keys(this.resolvers)) {
|
||||||
if(this.resolving_resolvers.findIndex(e => e === resolver_name) !== -1) continue;
|
if (this.resolving_resolvers.findIndex(e => e === resolver_name) !== -1) continue;
|
||||||
if(this.finished_resolvers.findIndex(e => e === resolver_name) !== -1) continue;
|
if (this.finished_resolvers.findIndex(e => e === resolver_name) !== -1) continue;
|
||||||
|
|
||||||
const resolver = this.resolvers[resolver_name];
|
const resolver = this.resolvers[resolver_name];
|
||||||
for(const after of resolver.after)
|
for (const after of resolver.after)
|
||||||
if(this.finished_resolvers.findIndex(e => e === after) === -1) continue _main_loop;
|
if (this.finished_resolvers.findIndex(e => e === after) === -1) continue _main_loop;
|
||||||
|
|
||||||
invoke_count++;
|
invoke_count++;
|
||||||
logTrace(LogCategory.DNS, tr(" Executing resolver %s"), resolver_name);
|
logTrace(LogCategory.DNS, tr(" Executing resolver %s"), resolver_name);
|
||||||
|
|
||||||
this.resolving_resolvers.push(resolver_name);
|
this.resolving_resolvers.push(resolver_name);
|
||||||
resolver.resolver.resolve(this.address).then(result => {
|
resolver.resolver.resolve(this.address).then(result => {
|
||||||
if(!this.resolving || !this.callback_success) return; /* resolve has been finished already */
|
if (!this.resolving || !this.callback_success) return; /* resolve has been finished already */
|
||||||
this.finished_resolvers.push(resolver_name);
|
this.finished_resolvers.push(resolver_name);
|
||||||
|
|
||||||
if(!result) {
|
if (!result) {
|
||||||
logTrace(LogCategory.DNS, tr(" Resolver %s returned an empty response."), resolver_name);
|
logTrace(LogCategory.DNS, tr(" Resolver %s returned an empty response."), resolver_name);
|
||||||
this.invoke_resolvers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logTrace(LogCategory.DNS, tr(" Successfully resolved address %s:%d to %s:%d via resolver %s"),
|
|
||||||
this.address.hostname, this.address.port,
|
|
||||||
result.hostname, result.port,
|
|
||||||
resolver_name);
|
|
||||||
this.callback_success(result);
|
|
||||||
}).catch(error => {
|
|
||||||
if(!this.resolving || !this.callback_success) return; /* resolve has been finished already */
|
|
||||||
this.finished_resolvers.push(resolver_name);
|
|
||||||
|
|
||||||
logTrace(LogCategory.DNS, tr(" Resolver %s ran into an error: %o"), resolver_name, error);
|
|
||||||
this.invoke_resolvers();
|
this.invoke_resolvers();
|
||||||
}).then(() => {
|
return;
|
||||||
this.resolving_resolvers.remove(resolver_name);
|
}
|
||||||
if(!this.resolving_resolvers.length && this.resolving)
|
|
||||||
this.invoke_resolvers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(invoke_count === 0 && !this.resolving_resolvers.length && this.resolving) {
|
logTrace(LogCategory.DNS, tr(" Successfully resolved address %s:%d to %s:%d via resolver %s"),
|
||||||
|
this.address.hostname, this.address.port,
|
||||||
|
result.hostname, result.port,
|
||||||
|
resolver_name);
|
||||||
|
this.callback_success(result);
|
||||||
|
}).catch(error => {
|
||||||
|
if (!this.resolving || !this.callback_success) return; /* resolve has been finished already */
|
||||||
|
this.finished_resolvers.push(resolver_name);
|
||||||
|
|
||||||
|
logTrace(LogCategory.DNS, tr(" Resolver %s ran into an error: %o"), resolver_name, error);
|
||||||
|
this.invoke_resolvers();
|
||||||
|
}).then(() => {
|
||||||
|
this.resolving_resolvers.remove(resolver_name);
|
||||||
|
if (!this.resolving_resolvers.length && this.resolving)
|
||||||
|
this.invoke_resolvers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invoke_count === 0 && !this.resolving_resolvers.length && this.resolving) {
|
||||||
this.callback_fail("no response");
|
this.callback_fail("no response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kResolverFake = new FakeResolver();
|
||||||
const kResolverLocalhost = new LocalhostResolver();
|
const kResolverLocalhost = new LocalhostResolver();
|
||||||
|
|
||||||
const kResolverIpV4 = new IPResolveMethod(false);
|
const kResolverIpV4 = new IPResolveMethod(false);
|
||||||
|
@ -320,14 +335,16 @@ const resolverSrvTS3 = new SRV_IPResolveMethod(new SRVResolveMethod("_ts3._udp")
|
||||||
const resolverDrSrvTS = new DomainRootResolveMethod(resolverSrvTS);
|
const resolverDrSrvTS = new DomainRootResolveMethod(resolverSrvTS);
|
||||||
const resolverDrSrvTS3 = new DomainRootResolveMethod(resolverSrvTS3);
|
const resolverDrSrvTS3 = new DomainRootResolveMethod(resolverSrvTS3);
|
||||||
|
|
||||||
export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options?: ResolveOptions) : Promise<DNSResolveResult> {
|
export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options?: ResolveOptions): Promise<DNSResolveResult> {
|
||||||
try {
|
try {
|
||||||
const options = Object.assign({}, default_options);
|
const options = Object.assign({}, default_options);
|
||||||
Object.assign(options, _options);
|
Object.assign(options, _options);
|
||||||
|
|
||||||
const resolver = new TeaSpeakDNSResolve(address);
|
const resolver = new TeaSpeakDNSResolve(address);
|
||||||
|
|
||||||
resolver.registerResolver(kResolverLocalhost);
|
resolver.registerResolver(kResolverFake);
|
||||||
|
|
||||||
|
resolver.registerResolver(kResolverLocalhost, kResolverFake);
|
||||||
|
|
||||||
resolver.registerResolver(resolverSrvTS, kResolverLocalhost);
|
resolver.registerResolver(resolverSrvTS, kResolverLocalhost);
|
||||||
resolver.registerResolver(resolverSrvTS3, kResolverLocalhost);
|
resolver.registerResolver(resolverSrvTS3, kResolverLocalhost);
|
||||||
|
@ -340,7 +357,7 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options
|
||||||
resolver.registerResolver(kResolverIpV6, kResolverIpV4);
|
resolver.registerResolver(kResolverIpV6, kResolverIpV4);
|
||||||
|
|
||||||
const response = await resolver.resolve(options.timeout || 5000);
|
const response = await resolver.resolve(options.timeout || 5000);
|
||||||
if(!response) {
|
if (!response) {
|
||||||
return {
|
return {
|
||||||
status: "empty-result"
|
status: "empty-result"
|
||||||
};
|
};
|
||||||
|
@ -352,7 +369,7 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options
|
||||||
resolvedAddress: response
|
resolvedAddress: response
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if(typeof error !== "string") {
|
if (typeof error !== "string") {
|
||||||
logError(LogCategory.DNS, tr("Failed to resolve %o: %o"), address, error);
|
logError(LogCategory.DNS, tr("Failed to resolve %o: %o"), address, error);
|
||||||
error = tr("lookup the console");
|
error = tr("lookup the console");
|
||||||
}
|
}
|
||||||
|
@ -364,9 +381,9 @@ export async function resolveTeaSpeakServerAddress(address: DNSAddress, _options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveAddressIpV4(address: string) : Promise<string> {
|
export async function resolveAddressIpV4(address: string): Promise<string> {
|
||||||
const result = await executeDnsRequest(address, RRType.A);
|
const result = await executeDnsRequest(address, RRType.A);
|
||||||
if(!result.length) return undefined;
|
if (!result.length) return undefined;
|
||||||
|
|
||||||
return result[0].data;
|
return result[0].data;
|
||||||
}
|
}
|
|
@ -46,9 +46,9 @@ const generateLocalBuildInfo = async (target: string): Promise<LocalBuildInfo> =
|
||||||
{
|
{
|
||||||
const gitRevision = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString();
|
const gitRevision = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString();
|
||||||
if(gitRevision.indexOf("/") === -1) {
|
if(gitRevision.indexOf("/") === -1) {
|
||||||
info.gitVersion = (gitRevision || "00000000").substr(0, 8);
|
info.gitVersion = (gitRevision || "00000000").substring(0, 8);
|
||||||
} else {
|
} else {
|
||||||
info.gitVersion = fs.readFileSync(path.join(__dirname, ".git", gitRevision.substr(5).trim())).toString().substr(0, 8);
|
info.gitVersion = fs.readFileSync(path.join(__dirname, ".git", gitRevision.substring(5).trim())).toString().substring(0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue