Merge pull request #83 from TeaSpeak/react-tree
Using webpack to build/bundle the project
This commit is contained in:
commit
41eac54c70
236 changed files with 79187 additions and 55441 deletions
40
.gitignore
vendored
40
.gitignore
vendored
|
@ -1,21 +1,31 @@
|
||||||
asm/generated/
|
# Global ignorings
|
||||||
node_modules/
|
|
||||||
auth/certs/
|
|
||||||
auth/js/auth.js.map
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
.sass-cache/
|
|
||||||
.idea/
|
.idea/
|
||||||
|
node_modules/
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
vendor/emoji-picker/**/*.js
|
/auth/certs/
|
||||||
vendor/emoji-picker/**/*.js.map
|
/auth/js/auth.js.map
|
||||||
|
|
||||||
TeaSpeakUI.tar.gz
|
/.idea/
|
||||||
TeaWeb-*.zip
|
|
||||||
|
|
||||||
todo.txt
|
/vendor/emoji-picker/**/*.js
|
||||||
|
/vendor/emoji-picker/**/*.js.map
|
||||||
|
!/vendor/emoji-picker/webpack.config.js
|
||||||
|
|
||||||
tmp/
|
# Some build output
|
||||||
|
/dist/
|
||||||
|
/declarations/
|
||||||
|
|
||||||
file.js
|
# Don't add the created packages to git
|
||||||
file.js.map
|
/TeaSpeakUI.tar.gz
|
||||||
|
/TeaWeb-*.zip
|
||||||
|
|
||||||
|
/todo.txt
|
||||||
|
/tmp/
|
||||||
|
|
||||||
|
# All out config files are .ts files
|
||||||
|
/*.js
|
||||||
|
/*.js.map
|
||||||
|
|
||||||
|
/webpack/*.js
|
||||||
|
/webpack/*.js.map
|
6
asm/.gitignore
vendored
6
asm/.gitignore
vendored
|
@ -1,2 +1,6 @@
|
||||||
generated/
|
generated/
|
||||||
build/
|
build_/
|
||||||
|
libraries/opus/build_
|
||||||
|
libraries/opus/out
|
||||||
|
cmake-build-*/
|
||||||
|
libraries/opus/*
|
|
@ -1,21 +1,25 @@
|
||||||
cmake_minimum_required(VERSION 3.9)
|
cmake_minimum_required(VERSION 3.9)
|
||||||
project(TeaWeb-Native)
|
project(TeaWeb-Native)
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
set (CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "") #Override some config values from the parent project
|
|
||||||
set(CMAKE_CXX_COMPILER "emcc")
|
|
||||||
set(CMAKE_C_COMPILER "emcc")
|
function(import_opus)
|
||||||
set(CMAKE_C_LINK_EXECUTABLE "emcc")
|
# Native SIMD isn't supported yet by most browsers (only experimental)
|
||||||
|
# But since opus already detects if emscripten is able to handle SIMD we have no need to disable this explicitly
|
||||||
|
|
||||||
|
# Disable the math.h warning spam:
|
||||||
|
# #warning "Don't have the functions lrint() and lrintf ()."
|
||||||
|
# #warning "Replacing these functions with a standard C cast."
|
||||||
|
set(CMAKE_C_FLAGS "-Wno-#warnings")
|
||||||
|
set(OPUS_STACK_PROTECTOR OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(libraries/opus/)
|
||||||
|
endfunction()
|
||||||
|
import_opus()
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "-O3 --llvm-lto 1 --memory-init-file 0 -s WASM=1 -s ASSERTIONS=1") # -s ALLOW_MEMORY_GROWTH=1 -O3
|
set(CMAKE_CXX_FLAGS "-O3 --llvm-lto 1 --memory-init-file 0 -s WASM=1 -s ASSERTIONS=1") # -s ALLOW_MEMORY_GROWTH=1 -O3
|
||||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
set(CMAKE_EXE_LINKER_FLAGS "-s EXPORTED_FUNCTIONS='[\"_malloc\", \"_free\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s ENVIRONMENT='worker' --pre-js ${CMAKE_SOURCE_DIR}/init.js") #
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\", \"Pointer_stringify\"]'") #
|
|
||||||
#add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/generated/")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/generated/")
|
||||||
|
|
||||||
include_directories(libraries/tommath/)
|
|
||||||
include_directories(libraries/tomcrypt/src/headers)
|
|
||||||
include_directories(libraries/opus/include/)
|
|
||||||
add_definitions(-DLTM_DESC)
|
|
||||||
|
|
||||||
add_executable(TeaWeb-Worker-Codec-Opus src/opus.cpp)
|
add_executable(TeaWeb-Worker-Codec-Opus src/opus.cpp)
|
||||||
target_link_libraries(TeaWeb-Worker-Codec-Opus ${CMAKE_CURRENT_SOURCE_DIR}/libraries/opus/.libs/libopus.a)
|
target_link_libraries(TeaWeb-Worker-Codec-Opus opus)
|
||||||
|
|
20
asm/build.sh
Normal file
20
asm/build.sh
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
[[ ! -d libraries/opus/out/ ]] && { echo "Missing opus build. Please build it before!"; exit 1; }
|
||||||
|
[[ ! -f libraries/opus/out/lib/libopus.a ]] && { echo "Missing opus static library. Please unsure your opus build was successfull."; exit 1; }
|
||||||
|
|
||||||
|
[[ -d build_ ]] && {
|
||||||
|
rm -r build_ || { echo "failed to remove old build directory"; exit 1; }
|
||||||
|
}
|
||||||
|
mkdir build_ || exit 1
|
||||||
|
cd build_ || exit 1
|
||||||
|
|
||||||
|
emcmake cmake .. || {
|
||||||
|
echo "Failed to execute cmake"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
emmake make -j"$(nproc --all)" || {
|
||||||
|
echo "Failed to build file"
|
||||||
|
exit 1
|
||||||
|
}
|
14
asm/download_compiled_files.sh
Executable file → Normal file
14
asm/download_compiled_files.sh
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
if [[ -d generated/ ]]; then
|
if [[ -d generated/ ]]; then
|
||||||
rm -r generated
|
rm -r generated
|
||||||
|
@ -15,19 +15,19 @@ mkdir generated
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$(curl --version &> /dev/null)
|
curl --version &> /dev/null; _exit_code=$?
|
||||||
[[ $? -ne 0 ]] && {
|
[[ $_exit_code -ne 0 ]] && {
|
||||||
echo "Missing CURL. Please install it"
|
echo "Missing CURL. Please install it"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
curl https://web.teaspeak.de/wasm/TeaWeb-Worker-Codec-Opus.js --output generated/TeaWeb-Worker-Codec-Opus.js
|
curl https://web.teaspeak.de/wasm/TeaWeb-Worker-Codec-Opus.js --output generated/TeaWeb-Worker-Codec-Opus.js; _exit_code=$?
|
||||||
[[ $? -ne 0 ]] && {
|
[[ $_exit_code -ne 0 ]] && {
|
||||||
echo "Failed to download opus worker library"
|
echo "Failed to download opus worker library"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
curl https://web.teaspeak.de/wasm/TeaWeb-Worker-Codec-Opus.wasm --output generated/TeaWeb-Worker-Codec-Opus.wasm
|
curl https://web.teaspeak.de/wasm/TeaWeb-Worker-Codec-Opus.wasm --output generated/TeaWeb-Worker-Codec-Opus.wasm; _exit_code=$?
|
||||||
[[ $? -ne 0 ]] && {
|
[[ $_exit_code -ne 0 ]] && {
|
||||||
echo "Failed to download opus worker library natives"
|
echo "Failed to download opus worker library natives"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
2
asm/init.js
Normal file
2
asm/init.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
for(const callback of Array.isArray(self.__init_em_module) ? self.__init_em_module : [])
|
||||||
|
callback(Module);
|
|
@ -1 +1 @@
|
||||||
Subproject commit 655cc54c564b84ef2827f0b2152ce3811046201e
|
Subproject commit 923bebde197f42ba8e55cb055dce3ff22bbea54d
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
base_dir=`pwd`
|
|
||||||
cd "$(dirname $0)/libraries/opus/"
|
|
||||||
|
|
||||||
git checkout v1.1.2
|
|
||||||
./autogen.sh
|
|
||||||
emconfigure ./configure --disable-extra-programs --disable-doc --disable-rtcd
|
|
||||||
emmake make
|
|
||||||
|
|
||||||
cd ${base_dir}
|
|
219
asm/src/opus.cpp
219
asm/src/opus.cpp
|
@ -1,133 +1,106 @@
|
||||||
#include <opus.h>
|
#include <opus.h>
|
||||||
|
#include <array>
|
||||||
|
#include <string_view>
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
typedef std::unique_ptr<OpusEncoder, decltype(opus_encoder_destroy)*> opus_encoder_t;
|
||||||
|
typedef std::unique_ptr<OpusDecoder, decltype(opus_decoder_destroy)*> opus_decoder_t;
|
||||||
|
struct OpusHandle {
|
||||||
|
opus_encoder_t encoder{nullptr, opus_encoder_destroy};
|
||||||
|
opus_decoder_t decoder{nullptr, opus_decoder_destroy};
|
||||||
|
|
||||||
|
size_t channelCount{1};
|
||||||
|
size_t sampleRate{48000};
|
||||||
|
int opusType{OPUS_APPLICATION_AUDIO};
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<std::string_view, 7> opus_errors = {
|
||||||
|
"One or more invalid/out of range arguments", //-1 (OPUS_BAD_ARG)
|
||||||
|
"Not enough bytes allocated in the buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
|
||||||
|
"An internal error was detected", //-3 (OPUS_INTERNAL_ERROR)
|
||||||
|
"The compressed data passed is corrupted", //-4 (OPUS_INVALID_PACKET)
|
||||||
|
"Invalid/unsupported request number", //-5 (OPUS_UNIMPLEMENTED)
|
||||||
|
"An encoder or decoder structure is invalid or already freed", //-6 (OPUS_INVALID_STATE)
|
||||||
|
"Memory allocation has failed" //-7 (OPUS_ALLOC_FAIL)
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::string_view opus_error_message(int error) {
|
||||||
|
error = abs(error);
|
||||||
|
if (error > 0 && error <= 7) return opus_errors[error - 1];
|
||||||
|
return "undefined error";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool reinitialize_decoder(OpusHandle *handle) {
|
||||||
|
int error;
|
||||||
|
handle->decoder.reset(opus_decoder_create(handle->sampleRate, handle->channelCount, &error));
|
||||||
|
if(error != OPUS_OK) {
|
||||||
|
printf("Failed to create decoder (%s)\n", opus_error_message(error).data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool reinitialize_encoder(OpusHandle *handle) {
|
||||||
|
int error;
|
||||||
|
handle->encoder.reset(opus_encoder_create(handle->sampleRate, handle->channelCount, handle->opusType, &error));
|
||||||
|
if (error != OPUS_OK) {
|
||||||
|
printf("Failed to create encoder (%s)\n", opus_error_message(error).data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error = opus_encoder_ctl(&*handle->encoder, OPUS_SET_COMPLEXITY(1)); error != OPUS_OK) {
|
||||||
|
printf("Failed to setup encoder (%s)\n", opus_error_message(error).data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//TODO: May set OPUS_SET_BITRATE(4740)?
|
||||||
|
//TODO: Is the encoder event needed anymore? Or is it just overhead
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
struct OpusHandle {
|
#endif
|
||||||
OpusEncoder* encoder = nullptr;
|
EMSCRIPTEN_KEEPALIVE
|
||||||
OpusDecoder* decoder = nullptr;
|
OpusHandle *codec_opus_createNativeHandle(size_t channelCount, int type) {
|
||||||
|
auto codec = new OpusHandle{};
|
||||||
|
codec->opusType = type;
|
||||||
|
codec->channelCount = channelCount;
|
||||||
|
codec->sampleRate = 48000;
|
||||||
|
if (!reinitialize_decoder(codec)) return nullptr;
|
||||||
|
if (!reinitialize_encoder(codec)) return nullptr;
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
size_t channelCount = 1;
|
EMSCRIPTEN_KEEPALIVE
|
||||||
size_t sampleRate = 48000;
|
void codec_opus_deleteNativeHandle(OpusHandle *codec) {
|
||||||
int opusType = OPUS_APPLICATION_AUDIO;
|
if (!codec) return;
|
||||||
};
|
|
||||||
|
|
||||||
const char* opus_errors[7] = {
|
codec->decoder.reset();
|
||||||
"One or more invalid/out of range arguments", //-1 (OPUS_BAD_ARG)
|
codec->encoder.reset();
|
||||||
"Not enough bytes allocated in the buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
|
delete codec;
|
||||||
"An internal error was detected", //-3 (OPUS_INTERNAL_ERROR)
|
}
|
||||||
"The compressed data passed is corrupted", //-4 (OPUS_INVALID_PACKET)
|
|
||||||
"Invalid/unsupported request number", //-5 (OPUS_UNIMPLEMENTED)
|
|
||||||
"An encoder or decoder structure is invalid or already freed", //-6 (OPUS_INVALID_STATE)
|
|
||||||
"Memory allocation has failed" //-7 (OPUS_ALLOC_FAIL)
|
|
||||||
};
|
|
||||||
|
|
||||||
inline const char* opus_error_message(int error) {
|
EMSCRIPTEN_KEEPALIVE
|
||||||
error = abs(error);
|
int codec_opus_encode(OpusHandle *handle, uint8_t *buffer, size_t length, size_t maxLength) {
|
||||||
if(error > 0 && error <= 7) return opus_errors[error - 1];
|
auto result = opus_encode_float(&*handle->encoder, (float *) buffer, length / handle->channelCount, buffer, maxLength);
|
||||||
return "undefined error";
|
if (result < 0) return result;
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
inline int currentMillies() {
|
EMSCRIPTEN_KEEPALIVE
|
||||||
return EM_ASM_INT({ return Date.now(); });
|
int codec_opus_decode(OpusHandle *handle, uint8_t *buffer, size_t length, size_t maxLength) {
|
||||||
}
|
auto result = opus_decode_float(&*handle->decoder, buffer, length, (float *) buffer, maxLength / sizeof(float) / handle->channelCount, false);
|
||||||
|
if (result < 0) return result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#define _S(x) #x
|
EMSCRIPTEN_KEEPALIVE
|
||||||
#define INVOKE_OPUS(result, method, ...) \
|
int codec_opus_reset(OpusHandle *handle) {
|
||||||
result = method( __VA_ARGS__ ); \
|
if (!reinitialize_decoder(handle)) return 0;
|
||||||
if(error != 0){ \
|
if (!reinitialize_encoder(handle)) return 0;
|
||||||
printf("Got opus error while invoking %s. Code: %d Message: %s\n", _S(method), error, opus_error_message(error)); \
|
return 1;
|
||||||
return false; \
|
}
|
||||||
}
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
inline bool reinitialize_decoder(OpusHandle *handle) {
|
#endif
|
||||||
if (handle->decoder)
|
|
||||||
opus_decoder_destroy(handle->decoder);
|
|
||||||
|
|
||||||
int error = 0;
|
|
||||||
INVOKE_OPUS(handle->decoder, opus_decoder_create, 48000, handle->channelCount, &error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool reinitialize_encoder(OpusHandle *handle) {
|
|
||||||
if (handle->encoder)
|
|
||||||
opus_encoder_destroy(handle->encoder);
|
|
||||||
|
|
||||||
int error = 0;
|
|
||||||
INVOKE_OPUS(handle->encoder, opus_encoder_create, 48000, handle->channelCount, handle->opusType, &error);
|
|
||||||
INVOKE_OPUS(error, opus_encoder_ctl, handle->encoder, OPUS_SET_COMPLEXITY(1));
|
|
||||||
//INVOKE_OPUS(error, opus_encoder_ctl, handle->encoder, OPUS_SET_BITRATE(4740));
|
|
||||||
|
|
||||||
EM_ASM(
|
|
||||||
printMessageToServerTab('Encoder initialized!');
|
|
||||||
printMessageToServerTab(' Comprexity: 1');
|
|
||||||
printMessageToServerTab(' Bitrate: 4740');
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
OpusHandle* codec_opus_createNativeHandle(size_t channelCount, int type) {
|
|
||||||
printf("Initialize opus. (Channel count: %d Sample rate: %d Type: %d)!\n", channelCount, 48000, type);
|
|
||||||
auto codec = new OpusHandle{};
|
|
||||||
codec->opusType = type;
|
|
||||||
codec->channelCount = channelCount;
|
|
||||||
if(!reinitialize_decoder(codec)) return nullptr;
|
|
||||||
if(!reinitialize_encoder(codec)) return nullptr;
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void codec_opus_deleteNativeHandle(OpusHandle* codec) {
|
|
||||||
if(!codec) return;
|
|
||||||
|
|
||||||
if(codec->decoder) opus_decoder_destroy(codec->decoder);
|
|
||||||
codec->decoder = nullptr;
|
|
||||||
|
|
||||||
if(codec->encoder) opus_encoder_destroy(codec->encoder);
|
|
||||||
codec->encoder = nullptr;
|
|
||||||
|
|
||||||
delete codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int codec_opus_encode(OpusHandle* handle, uint8_t* buffer, size_t length, size_t maxLength) {
|
|
||||||
auto begin = currentMillies();
|
|
||||||
auto result = opus_encode_float(handle->encoder, (float*) buffer, length / handle->channelCount, buffer, maxLength);
|
|
||||||
if(result < 0) return result;
|
|
||||||
auto end = currentMillies();
|
|
||||||
EM_ASM({
|
|
||||||
printMessageToServerTab("codec_opus_encode(...) tooks " + $0 + "ms to execute!");
|
|
||||||
}, end - begin);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int codec_opus_decode(OpusHandle* handle, uint8_t* buffer, size_t length, size_t maxLength) {
|
|
||||||
auto result = opus_decode_float(handle->decoder, buffer, length, (float*) buffer, maxLength / sizeof(float) / handle->channelCount, false);
|
|
||||||
if(result < 0) return result; //Failed
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int codec_opus_changeApplication(OpusHandle* handle, int type) {
|
|
||||||
handle->opusType = type;
|
|
||||||
if(type != OPUS_APPLICATION_VOIP && type != OPUS_APPLICATION_AUDIO && type != OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
|
||||||
return 1;
|
|
||||||
return opus_encoder_ctl(handle->encoder, OPUS_SET_APPLICATION(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int codec_opus_reset(OpusHandle* handle) {
|
|
||||||
if(!reinitialize_decoder(handle)) return 0;
|
|
||||||
if(!reinitialize_encoder(handle)) return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate));
|
|
||||||
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
|
|
||||||
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type));
|
|
||||||
*/
|
|
||||||
}
|
|
0
client/generate_packed.sh
Executable file → Normal file
0
client/generate_packed.sh
Executable file → Normal file
37115
client/generated/client.js
Normal file
37115
client/generated/client.js
Normal file
File diff suppressed because one or more lines are too long
1
client/generated/client.js.map
Normal file
1
client/generated/client.js.map
Normal file
File diff suppressed because one or more lines are too long
4
client/js/index.ts
Normal file
4
client/js/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
declare const __webpack_require__;
|
||||||
|
window["shared-require"] = __webpack_require__;
|
||||||
|
/* firstly assign the shared-require */
|
||||||
|
setTimeout(() => require("tc-shared/main"), 0);
|
466
file.ts
466
file.ts
|
@ -37,60 +37,13 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
||||||
"path": "./",
|
"path": "./",
|
||||||
"local-path": "./shared/html/"
|
"local-path": "./shared/html/"
|
||||||
},
|
},
|
||||||
|
{ /* javascript files as manifest.json */
|
||||||
{ /* javascript loader */
|
|
||||||
"type": "js",
|
"type": "js",
|
||||||
"search-pattern": /.*\.js$/,
|
"search-pattern": /.*$/,
|
||||||
"build-target": "dev",
|
|
||||||
|
|
||||||
"path": "loader/",
|
|
||||||
"local-path": "./shared/loader/"
|
|
||||||
},
|
|
||||||
{ /* javascript loader for releases */
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*loader_[\S]+.min.js$/,
|
|
||||||
"build-target": "rel",
|
|
||||||
|
|
||||||
"path": "loader/",
|
|
||||||
"local-path": "./shared/generated/"
|
|
||||||
},
|
|
||||||
|
|
||||||
{ /* shared javascript files (WebRTC adapter) */
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.js$/,
|
|
||||||
"build-target": "dev|rel",
|
"build-target": "dev|rel",
|
||||||
|
|
||||||
"path": "adapter/",
|
|
||||||
"local-path": "./shared/adapter/"
|
|
||||||
},
|
|
||||||
|
|
||||||
{ /* shared javascript files (development mode only) */
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.js$/,
|
|
||||||
"search-exclude": /(.*\/)?workers\/.*/,
|
|
||||||
"build-target": "dev",
|
|
||||||
|
|
||||||
"path": "js/",
|
"path": "js/",
|
||||||
"local-path": "./shared/js/"
|
"local-path": "./dist/"
|
||||||
},
|
|
||||||
{ /* shared javascript mapping files (development mode only) */
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.(js.map|ts)$/,
|
|
||||||
"search-exclude": /(.*\/)?workers\/.*/,
|
|
||||||
"build-target": "dev",
|
|
||||||
|
|
||||||
"path": "js/",
|
|
||||||
"local-path": "./shared/js/",
|
|
||||||
"req-parm": ["--mappings"]
|
|
||||||
},
|
|
||||||
|
|
||||||
{ /* shared generated worker codec */
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /(WorkerPOW.js)$/,
|
|
||||||
"build-target": "dev|rel",
|
|
||||||
|
|
||||||
"path": "js/workers/",
|
|
||||||
"local-path": "./shared/js/workers/"
|
|
||||||
},
|
},
|
||||||
{ /* shared developer single css files */
|
{ /* shared developer single css files */
|
||||||
"type": "css",
|
"type": "css",
|
||||||
|
@ -156,14 +109,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
||||||
|
|
||||||
"path": "img/",
|
"path": "img/",
|
||||||
"local-path": "./shared/img/"
|
"local-path": "./shared/img/"
|
||||||
},
|
|
||||||
{ /* own webassembly files */
|
|
||||||
"type": "wasm",
|
|
||||||
"search-pattern": /.*\.(wasm)/,
|
|
||||||
"build-target": "dev|rel",
|
|
||||||
|
|
||||||
"path": "wat/",
|
|
||||||
"local-path": "./shared/wat/"
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -204,28 +149,7 @@ const APP_FILE_LIST_CLIENT_SOURCE: ProjectResource[] = [
|
||||||
|
|
||||||
"path": "js/",
|
"path": "js/",
|
||||||
"local-path": "./client/js/"
|
"local-path": "./client/js/"
|
||||||
},
|
}
|
||||||
|
|
||||||
/* release specific */
|
|
||||||
{ /* web merged javascript files (shared inclusive) */
|
|
||||||
"client-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.js$/,
|
|
||||||
"build-target": "rel",
|
|
||||||
|
|
||||||
"path": "js/",
|
|
||||||
"local-path": "./client/generated/"
|
|
||||||
},
|
|
||||||
{ /* Add the shared generated files. Exclude the shared file because we're including it already */
|
|
||||||
"client-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.js$/,
|
|
||||||
"search-exclude": /shared\.js(.map)?$/,
|
|
||||||
"build-target": "rel",
|
|
||||||
|
|
||||||
"path": "js/",
|
|
||||||
"local-path": "./shared/generated/"
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
|
const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
|
||||||
|
@ -238,42 +162,6 @@ const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
|
||||||
"path": "wasm/",
|
"path": "wasm/",
|
||||||
"local-path": "./asm/generated/"
|
"local-path": "./asm/generated/"
|
||||||
},
|
},
|
||||||
{ /* generated assembly javascript files */
|
|
||||||
"web-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.(js)/,
|
|
||||||
"build-target": "dev|rel",
|
|
||||||
|
|
||||||
"path": "wasm/",
|
|
||||||
"local-path": "./asm/generated/"
|
|
||||||
},
|
|
||||||
{ /* web generated worker codec */
|
|
||||||
"web-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /(WorkerCodec.js)$/,
|
|
||||||
"build-target": "dev|rel",
|
|
||||||
|
|
||||||
"path": "js/workers/",
|
|
||||||
"local-path": "./web/js/workers/"
|
|
||||||
},
|
|
||||||
{ /* web javascript files (development mode only) */
|
|
||||||
"web-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /.*\.js$/,
|
|
||||||
"build-target": "dev",
|
|
||||||
|
|
||||||
"path": "js/",
|
|
||||||
"local-path": "./web/js/"
|
|
||||||
},
|
|
||||||
{ /* web merged javascript files (shared inclusive) */
|
|
||||||
"web-only": true,
|
|
||||||
"type": "js",
|
|
||||||
"search-pattern": /client(\.min)?\.js$/,
|
|
||||||
"build-target": "rel",
|
|
||||||
|
|
||||||
"path": "js/",
|
|
||||||
"local-path": "./web/generated/"
|
|
||||||
},
|
|
||||||
{ /* web css files */
|
{ /* web css files */
|
||||||
"web-only": true,
|
"web-only": true,
|
||||||
"type": "css",
|
"type": "css",
|
||||||
|
@ -303,6 +191,7 @@ const APP_FILE_LIST_WEB_SOURCE: ProjectResource[] = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//TODO: This isn't needed anymore
|
||||||
const APP_FILE_LIST_WEB_TEASPEAK: ProjectResource[] = [
|
const APP_FILE_LIST_WEB_TEASPEAK: ProjectResource[] = [
|
||||||
/* special web.teaspeak.de only auth files */
|
/* special web.teaspeak.de only auth files */
|
||||||
{ /* login page and api */
|
{ /* login page and api */
|
||||||
|
@ -347,6 +236,7 @@ const APP_FILE_LIST_WEB_TEASPEAK: ProjectResource[] = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//FIXME: This isn't working right now
|
||||||
const CERTACCEPT_FILE_LIST: ProjectResource[] = [
|
const CERTACCEPT_FILE_LIST: ProjectResource[] = [
|
||||||
{ /* html files */
|
{ /* html files */
|
||||||
"type": "html",
|
"type": "html",
|
||||||
|
@ -452,6 +342,147 @@ const WEB_APP_FILE_LIST = [
|
||||||
...CERTACCEPT_FILE_LIST,
|
...CERTACCEPT_FILE_LIST,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
//const WEB_APP_FILE_LIST = [
|
||||||
|
// ...APP_FILE_LIST_SHARED_VENDORS,
|
||||||
|
// { /* shared html and php files */
|
||||||
|
// "type": "html",
|
||||||
|
// "search-pattern": /^.*([a-zA-Z]+)\.(html|php|json)$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "./",
|
||||||
|
// "local-path": "./shared/html/"
|
||||||
|
// },
|
||||||
|
// { /* javascript files as manifest.json */
|
||||||
|
// "type": "js",
|
||||||
|
// "search-pattern": /.*$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "js/",
|
||||||
|
// "local-path": "./dist/"
|
||||||
|
// },
|
||||||
|
// { /* loader javascript file */
|
||||||
|
// "type": "js",
|
||||||
|
// "search-pattern": /.*$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "js/",
|
||||||
|
// "local-path": "./loader/dist/"
|
||||||
|
// },
|
||||||
|
// { /* shared developer single css files */
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.css$/,
|
||||||
|
// "build-target": "dev",
|
||||||
|
//
|
||||||
|
// "path": "css/",
|
||||||
|
// "local-path": "./shared/css/"
|
||||||
|
// },
|
||||||
|
// { /* shared css mapping files (development mode only) */
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.(css.map|scss)$/,
|
||||||
|
// "build-target": "dev",
|
||||||
|
//
|
||||||
|
// "path": "css/",
|
||||||
|
// "local-path": "./shared/css/",
|
||||||
|
// "req-parm": ["--mappings"]
|
||||||
|
// },
|
||||||
|
// { /* shared release css files */
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.css$/,
|
||||||
|
// "build-target": "rel",
|
||||||
|
//
|
||||||
|
// "path": "css/",
|
||||||
|
// "local-path": "./shared/generated/"
|
||||||
|
// },
|
||||||
|
// { /* shared release css files */
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.css$/,
|
||||||
|
// "build-target": "rel",
|
||||||
|
//
|
||||||
|
// "path": "css/loader/",
|
||||||
|
// "local-path": "./shared/css/loader/"
|
||||||
|
// },
|
||||||
|
// { /* shared release css files */
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.css$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "css/theme/",
|
||||||
|
// "local-path": "./shared/css/theme/"
|
||||||
|
// },
|
||||||
|
// { /* shared sound files */
|
||||||
|
// "type": "wav",
|
||||||
|
// "search-pattern": /.*\.wav$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "audio/",
|
||||||
|
// "local-path": "./shared/audio/"
|
||||||
|
// },
|
||||||
|
// { /* shared data sound files */
|
||||||
|
// "type": "json",
|
||||||
|
// "search-pattern": /.*\.json/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "audio/",
|
||||||
|
// "local-path": "./shared/audio/"
|
||||||
|
// },
|
||||||
|
// { /* shared image files */
|
||||||
|
// "type": "img",
|
||||||
|
// "search-pattern": /.*\.(svg|png)/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "img/",
|
||||||
|
// "local-path": "./shared/img/"
|
||||||
|
// },
|
||||||
|
// { /* own webassembly files */
|
||||||
|
// "type": "wasm",
|
||||||
|
// "search-pattern": /.*\.(wasm)/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "wat/",
|
||||||
|
// "local-path": "./shared/wat/"
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// /* web specific */
|
||||||
|
// { /* generated assembly files */
|
||||||
|
// "web-only": true,
|
||||||
|
// "type": "wasm",
|
||||||
|
// "search-pattern": /.*\.(wasm)/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "wasm/",
|
||||||
|
// "local-path": "./asm/generated/"
|
||||||
|
// },
|
||||||
|
// { /* web css files */
|
||||||
|
// "web-only": true,
|
||||||
|
// "type": "css",
|
||||||
|
// "search-pattern": /.*\.css$/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "css/",
|
||||||
|
// "local-path": "./web/css/"
|
||||||
|
// },
|
||||||
|
// { /* web html files */
|
||||||
|
// "web-only": true,
|
||||||
|
// "type": "html",
|
||||||
|
// "search-pattern": /.*\.(php|html)/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "./",
|
||||||
|
// "local-path": "./web/html/"
|
||||||
|
// },
|
||||||
|
// { /* translations */
|
||||||
|
// "web-only": true, /* Only required for the web client */
|
||||||
|
// "type": "i18n",
|
||||||
|
// "search-pattern": /.*\.(translation|json)/,
|
||||||
|
// "build-target": "dev|rel",
|
||||||
|
//
|
||||||
|
// "path": "i18n/",
|
||||||
|
// "local-path": "./shared/i18n/"
|
||||||
|
// }
|
||||||
|
//] as any;
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
declare module "fs-extra" {
|
declare module "fs-extra" {
|
||||||
export function exists(path: PathLike): Promise<boolean>;
|
export function exists(path: PathLike): Promise<boolean>;
|
||||||
|
@ -492,41 +523,49 @@ namespace generator {
|
||||||
return result.digest("hex");
|
return result.digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rreaddir = async p => {
|
||||||
|
const result = [];
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(p);
|
||||||
|
for(const file of files) {
|
||||||
|
const file_path = path.join(p, file);
|
||||||
|
|
||||||
|
const info = await fs.stat(file_path);
|
||||||
|
if(info.isDirectory()) {
|
||||||
|
result.push(...await rreaddir(file_path));
|
||||||
|
} else {
|
||||||
|
result.push(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
if(error.code === "ENOENT")
|
||||||
|
return [];
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function file_matches_options(file: ProjectResource, options: SearchOptions) {
|
||||||
|
if(typeof file["web-only"] === "boolean" && file["web-only"] && options.target !== "web")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(typeof file["client-only"] === "boolean" && file["client-only"] && options.target !== "client")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(typeof file["serve-only"] === "boolean" && file["serve-only"] && !options.serving)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!file["build-target"].split("|").find(e => e === options.mode))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return !(Array.isArray(file["req-parm"]) && file["req-parm"].find(e => !options.parameter.find(p => p.toLowerCase() === e.toLowerCase())));
|
||||||
|
}
|
||||||
|
|
||||||
export async function search_files(files: ProjectResource[], options: SearchOptions) : Promise<Entry[]> {
|
export async function search_files(files: ProjectResource[], options: SearchOptions) : Promise<Entry[]> {
|
||||||
const result: Entry[] = [];
|
const result: Entry[] = [];
|
||||||
|
|
||||||
const rreaddir = async p => {
|
|
||||||
const result = [];
|
|
||||||
try {
|
|
||||||
const files = await fs.readdir(p);
|
|
||||||
for(const file of files) {
|
|
||||||
const file_path = path.join(p, file);
|
|
||||||
|
|
||||||
const info = await fs.stat(file_path);
|
|
||||||
if(info.isDirectory()) {
|
|
||||||
result.push(...await rreaddir(file_path));
|
|
||||||
} else {
|
|
||||||
result.push(file_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(error) {
|
|
||||||
if(error.code === "ENOENT")
|
|
||||||
return [];
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
for(const file of files) {
|
for(const file of files) {
|
||||||
if(typeof file["web-only"] === "boolean" && file["web-only"] && options.target !== "web")
|
if(!file_matches_options(file, options))
|
||||||
continue;
|
|
||||||
if(typeof file["client-only"] === "boolean" && file["client-only"] && options.target !== "client")
|
|
||||||
continue;
|
|
||||||
if(typeof file["serve-only"] === "boolean" && file["serve-only"] && !options.serving)
|
|
||||||
continue;
|
|
||||||
if(!file["build-target"].split("|").find(e => e === options.mode))
|
|
||||||
continue;
|
|
||||||
if(Array.isArray(file["req-parm"]) && file["req-parm"].find(e => !options.parameter.find(p => p.toLowerCase() === e.toLowerCase())))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const normal_local = path.normalize(path.join(options.source_path, file["local-path"]));
|
const normal_local = path.normalize(path.join(options.source_path, file["local-path"]));
|
||||||
|
@ -554,23 +593,52 @@ namespace generator {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function search_http_file(files: ProjectResource[], target_file: string, options: SearchOptions) : Promise<string> {
|
||||||
|
for(const file of files) {
|
||||||
|
if(!file_matches_options(file, options))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(file.path !== "./" && !target_file.startsWith("/" + file.path.replace(/\\/g, "/")))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const normal_local = path.normalize(path.join(options.source_path, file["local-path"]));
|
||||||
|
const files: string[] = await rreaddir(normal_local);
|
||||||
|
for(const f of files) {
|
||||||
|
const local_name = f.substr(normal_local.length);
|
||||||
|
if(!local_name.match(file["search-pattern"]) && !local_name.replace("\\\\", "/").match(file["search-pattern"]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(typeof(file["search-exclude"]) !== "undefined" && f.match(file["search-exclude"]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if("/" + path.join(file.path, local_name).replace(/\\/g, "/") === target_file)
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace server {
|
namespace server {
|
||||||
|
import SearchOptions = generator.SearchOptions;
|
||||||
export type Options = {
|
export type Options = {
|
||||||
port: number;
|
port: number;
|
||||||
php: string;
|
php: string;
|
||||||
|
|
||||||
|
search_options: SearchOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exec: (command: string) => Promise<{ stdout: string, stderr: string }> = util.promisify(cp.exec);
|
const exec: (command: string) => Promise<{ stdout: string, stderr: string }> = util.promisify(cp.exec);
|
||||||
|
|
||||||
let files: (generator.Entry & { http_path: string; })[] = [];
|
let files: ProjectResource[] = [];
|
||||||
let server: http.Server;
|
let server: http.Server;
|
||||||
let php: string;
|
let php: string;
|
||||||
export async function launch(_files: generator.Entry[], options: Options) {
|
let options: Options;
|
||||||
//Don't use this check anymore, because we're searching within the PATH variable
|
export async function launch(_files: ProjectResource[], options_: Options) {
|
||||||
//if(!await fs.exists(options.php) || !(await fs.stat(options.php)).isFile())
|
options = options_;
|
||||||
// throw "invalid php interpreter (not found)";
|
files = _files;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await exec(options.php + " --version");
|
const info = await exec(options.php + " --version");
|
||||||
|
@ -594,17 +662,6 @@ namespace server {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
files = _files.map(e =>{
|
|
||||||
return {
|
|
||||||
type: e.type,
|
|
||||||
name: e.name,
|
|
||||||
hash: e.hash,
|
|
||||||
local_path: e.local_path,
|
|
||||||
target_path: e.target_path,
|
|
||||||
http_path: "/" + e.target_path.replace(/\\/g, "/")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shutdown() {
|
export async function shutdown() {
|
||||||
|
@ -648,8 +705,8 @@ namespace server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function serve_file(pathname: string, query: any, response: http.ServerResponse) {
|
async function serve_file(pathname: string, query: any, response: http.ServerResponse) {
|
||||||
const file = files.find(e => e.http_path === pathname);
|
const file = await generator.search_http_file(files, pathname, options.search_options);
|
||||||
if(!file) {
|
if(!file) {
|
||||||
console.log("[SERVER] Client requested unknown file %s", pathname);
|
console.log("[SERVER] Client requested unknown file %s", pathname);
|
||||||
response.writeHead(404);
|
response.writeHead(404);
|
||||||
|
@ -658,13 +715,13 @@ namespace server {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let type = mt.lookup(path.extname(file.local_path)) || "text/html";
|
let type = mt.lookup(path.extname(file)) || "text/html";
|
||||||
console.log("[SERVER] Serving file %s (%s) (%s)", file.target_path, type, file.local_path);
|
console.log("[SERVER] Serving file %s", file, type);
|
||||||
if(path.extname(file.local_path) === ".php") {
|
if(path.extname(file) === ".php") {
|
||||||
serve_php(file.local_path, query, response);
|
serve_php(file, query, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fis = fs.createReadStream(file.local_path);
|
const fis = fs.createReadStream(file);
|
||||||
|
|
||||||
response.writeHead(200, "success", {
|
response.writeHead(200, "success", {
|
||||||
"Content-Type": type + "; charset=utf-8"
|
"Content-Type": type + "; charset=utf-8"
|
||||||
|
@ -677,23 +734,23 @@ namespace server {
|
||||||
fis.on("data", data => response.write(data));
|
fis.on("data", data => response.write(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_api_request(request: http.IncomingMessage, response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) {
|
async function handle_api_request(request: http.IncomingMessage, response: http.ServerResponse, url: url_utils.UrlWithParsedQuery) {
|
||||||
if(url.query["type"] === "files") {
|
if(url.query["type"] === "files") {
|
||||||
response.writeHead(200, { "info-version": 1 });
|
response.writeHead(200, { "info-version": 1 });
|
||||||
response.write("type\thash\tpath\tname\n");
|
response.write("type\thash\tpath\tname\n");
|
||||||
for(const file of files)
|
for(const file of await generator.search_files(files, options.search_options))
|
||||||
if(file.http_path.endsWith(".php"))
|
if(file.name.endsWith(".php"))
|
||||||
response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.http_path) + "\t" + path.basename(file.http_path, ".php") + ".html" + "\n");
|
response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.target_path) + "\t" + path.basename(file.name, ".php") + ".html" + "\n");
|
||||||
else
|
else
|
||||||
response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.http_path) + "\t" + path.basename(file.http_path) + "\n");
|
response.write(file.type + "\t" + file.hash + "\t" + path.dirname(file.target_path) + "\t" + file.name + "\n");
|
||||||
response.end();
|
response.end();
|
||||||
return;
|
return;
|
||||||
} else if(url.query["type"] === "file") {
|
} else if(url.query["type"] === "file") {
|
||||||
let p = path.join(url.query["path"] as string, url.query["name"] as string).replace(/\\/g, "/");
|
let p = path.join(url.query["path"] as string, url.query["name"] as string).replace(/\\/g, "/");
|
||||||
|
if(!p.startsWith("/")) p = "/" + p;
|
||||||
if(p.endsWith(".html")) {
|
if(p.endsWith(".html")) {
|
||||||
const np = p.substr(0, p.length - 5) + ".php";
|
const np = await generator.search_http_file(files, p.substr(0, p.length - 5) + ".php", options.search_options);
|
||||||
if(files.find(e => e.http_path == np) && !files.find(e => e.http_path == p))
|
if(np) p = p.substr(0, p.length - 5) + ".php";
|
||||||
p = np;
|
|
||||||
}
|
}
|
||||||
serve_file(p, url.query, response);
|
serve_file(p, url.query, response);
|
||||||
return;
|
return;
|
||||||
|
@ -872,17 +929,16 @@ function php_exe() : string {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: number) {
|
async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: number) {
|
||||||
const files = await generator.search_files(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
||||||
source_path: __dirname,
|
|
||||||
parameter: [],
|
|
||||||
target: target,
|
|
||||||
mode: mode,
|
|
||||||
serving: true
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.launch(files, {
|
|
||||||
port: port,
|
port: port,
|
||||||
php: php_exe(),
|
php: php_exe(),
|
||||||
|
search_options: {
|
||||||
|
source_path: __dirname,
|
||||||
|
parameter: [],
|
||||||
|
target: target,
|
||||||
|
mode: mode,
|
||||||
|
serving: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Server started on %d", port);
|
console.log("Server started on %d", port);
|
||||||
|
@ -891,14 +947,6 @@ async function main_serve(target: "client" | "web", mode: "rel" | "dev", port: n
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main_develop(node: boolean, target: "client" | "web", port: number, flags: string[]) {
|
async function main_develop(node: boolean, target: "client" | "web", port: number, flags: string[]) {
|
||||||
const files = await generator.search_files(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
|
||||||
source_path: __dirname,
|
|
||||||
parameter: [],
|
|
||||||
target: target,
|
|
||||||
mode: "dev",
|
|
||||||
serving: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const tscwatcher = new watcher.TSCWatcher();
|
const tscwatcher = new watcher.TSCWatcher();
|
||||||
try {
|
try {
|
||||||
if(flags.indexOf("--no-tsc") == -1)
|
if(flags.indexOf("--no-tsc") == -1)
|
||||||
|
@ -910,9 +958,16 @@ async function main_develop(node: boolean, target: "client" | "web", port: numbe
|
||||||
await sasswatcher.start();
|
await sasswatcher.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await server.launch(files, {
|
await server.launch(target === "client" ? CLIENT_APP_FILE_LIST : WEB_APP_FILE_LIST, {
|
||||||
port: port,
|
port: port,
|
||||||
php: php_exe(),
|
php: php_exe(),
|
||||||
|
search_options: {
|
||||||
|
source_path: __dirname,
|
||||||
|
parameter: [],
|
||||||
|
target: target,
|
||||||
|
mode: "dev",
|
||||||
|
serving: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.error("Failed to start server: %o", error instanceof Error ? error.message : error);
|
console.error("Failed to start server: %o", error instanceof Error ? error.message : error);
|
||||||
|
@ -951,18 +1006,12 @@ async function main_develop(node: boolean, target: "client" | "web", port: numbe
|
||||||
}
|
}
|
||||||
|
|
||||||
async function git_tag() {
|
async function git_tag() {
|
||||||
const exec = util.promisify(cp.exec);
|
const git_rev = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString();
|
||||||
|
let version;
|
||||||
/* check if we've any uncommited changes */
|
if(git_rev.indexOf("/") === -1)
|
||||||
{
|
return git_rev.substr(0, 7);
|
||||||
let { stdout, stderr } = await exec("git diff-index HEAD -- . ':!asm/libraries/' ':!package-lock.json' ':!vendor/'");
|
else
|
||||||
if(stderr) throw stderr;
|
return fs.readFileSync(path.join(__dirname, ".git", git_rev.substr(5).trim())).toString().substr(0, 7);
|
||||||
if(stdout) return "0000000";
|
|
||||||
}
|
|
||||||
|
|
||||||
let { stdout, stderr } = await exec("git rev-parse --short HEAD");
|
|
||||||
if(stderr) throw stderr;
|
|
||||||
return stdout.substr(0, 7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main_generate(target: "client" | "web", mode: "rel" | "dev", dest_path: string, args: any[]) {
|
async function main_generate(target: "client" | "web", mode: "rel" | "dev", dest_path: string, args: any[]) {
|
||||||
|
@ -1163,4 +1212,5 @@ main(process.argv.slice(2)).then(ignore_exit => {
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error("Failed to execute application. Exception reached execution root!");
|
console.error("Failed to execute application. Exception reached execution root!");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
0
tools/dtsgen/.gitignore → loader/.gitignore
vendored
0
tools/dtsgen/.gitignore → loader/.gitignore
vendored
8
loader/app/index.ts
Normal file
8
loader/app/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import * as loader from "./targets/app";
|
||||||
|
import * as loader_base from "./loader/loader";
|
||||||
|
|
||||||
|
window["loader"] = loader_base;
|
||||||
|
/* let the loader register himself at the window first */
|
||||||
|
setTimeout(loader.run, 0);
|
||||||
|
|
||||||
|
export {};
|
393
loader/app/loader/loader.ts
Normal file
393
loader/app/loader/loader.ts
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
import * as script_loader from "./script_loader";
|
||||||
|
import * as style_loader from "./style_loader";
|
||||||
|
import * as template_loader from "./template_loader";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
tr(message: string) : string;
|
||||||
|
tra(message: string, ...args: any[]);
|
||||||
|
|
||||||
|
log: any;
|
||||||
|
StaticSettings: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
loader_groups: boolean;
|
||||||
|
verbose: boolean;
|
||||||
|
error: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let config: Config = {
|
||||||
|
loader_groups: false,
|
||||||
|
verbose: false,
|
||||||
|
error: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Task = {
|
||||||
|
name: string,
|
||||||
|
priority: number, /* tasks with the same priority will be executed in sync */
|
||||||
|
function: () => Promise<void>
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum Stage {
|
||||||
|
/*
|
||||||
|
loading loader required files (incl this)
|
||||||
|
*/
|
||||||
|
INITIALIZING,
|
||||||
|
/*
|
||||||
|
setting up the loading process
|
||||||
|
*/
|
||||||
|
SETUP,
|
||||||
|
/*
|
||||||
|
loading all style sheet files
|
||||||
|
*/
|
||||||
|
STYLE,
|
||||||
|
/*
|
||||||
|
loading all javascript files
|
||||||
|
*/
|
||||||
|
JAVASCRIPT,
|
||||||
|
/*
|
||||||
|
loading all template files
|
||||||
|
*/
|
||||||
|
TEMPLATES,
|
||||||
|
/*
|
||||||
|
initializing static/global stuff
|
||||||
|
*/
|
||||||
|
JAVASCRIPT_INITIALIZING,
|
||||||
|
/*
|
||||||
|
finalizing load process
|
||||||
|
*/
|
||||||
|
FINALIZING,
|
||||||
|
/*
|
||||||
|
invoking main task
|
||||||
|
*/
|
||||||
|
LOADED,
|
||||||
|
|
||||||
|
DONE
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_tag: string | undefined;
|
||||||
|
let current_stage: Stage = undefined;
|
||||||
|
const tasks: {[key:number]:Task[]} = {};
|
||||||
|
|
||||||
|
/* test if all files shall be load from cache or fetch again */
|
||||||
|
function loader_cache_tag() {
|
||||||
|
if(__build.mode === "debug") {
|
||||||
|
cache_tag = "?_ts=" + Date.now();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached_version = localStorage.getItem("cached_version");
|
||||||
|
if(!cached_version || cached_version !== __build.version) {
|
||||||
|
register_task(Stage.LOADED, {
|
||||||
|
priority: 0,
|
||||||
|
name: "cached version updater",
|
||||||
|
function: async () => {
|
||||||
|
localStorage.setItem("cached_version", __build.version);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cache_tag = "?_version=" + __build.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModuleMapping = {
|
||||||
|
application: string,
|
||||||
|
modules: {
|
||||||
|
"id": string,
|
||||||
|
"context": string,
|
||||||
|
"resource": string
|
||||||
|
}[]
|
||||||
|
};
|
||||||
|
const module_mapping_: ModuleMapping[] = [];
|
||||||
|
export function module_mapping() : ModuleMapping[] { return module_mapping_; }
|
||||||
|
|
||||||
|
export function get_cache_version() { return cache_tag; }
|
||||||
|
|
||||||
|
export function finished() {
|
||||||
|
return current_stage == Stage.DONE;
|
||||||
|
}
|
||||||
|
export function running() { return typeof(current_stage) !== "undefined"; }
|
||||||
|
|
||||||
|
export function register_task(stage: Stage, task: Task) {
|
||||||
|
if(current_stage > stage) {
|
||||||
|
if(config.error)
|
||||||
|
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
|
||||||
|
|
||||||
|
const promise = task.function();
|
||||||
|
if(!promise) {
|
||||||
|
console.error("Loading task %s hasn't returned a promise!", task.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.catch(error => {
|
||||||
|
if(config.error) {
|
||||||
|
console.error("Failed to execute delayed loader task!");
|
||||||
|
console.log(" - %s: %o", task.name, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
critical_error(error);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task_array = tasks[stage] || [];
|
||||||
|
task_array.push(task);
|
||||||
|
tasks[stage] = task_array.sort((a, b) => a.priority - b.priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function execute() {
|
||||||
|
document.getElementById("loader-overlay").classList.add("started");
|
||||||
|
loader_cache_tag();
|
||||||
|
|
||||||
|
const load_begin = Date.now();
|
||||||
|
|
||||||
|
let begin: number = 0;
|
||||||
|
let end: number = Date.now();
|
||||||
|
while(current_stage <= Stage.LOADED || typeof(current_stage) === "undefined") {
|
||||||
|
|
||||||
|
let current_tasks: Task[] = [];
|
||||||
|
while((tasks[current_stage] || []).length > 0) {
|
||||||
|
if(current_tasks.length == 0 || current_tasks[0].priority == tasks[current_stage][0].priority) {
|
||||||
|
current_tasks.push(tasks[current_stage].pop());
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors: {
|
||||||
|
error: any,
|
||||||
|
task: Task
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
for(const task of current_tasks) {
|
||||||
|
try {
|
||||||
|
if(config.verbose) console.debug("Executing loader %s (%d)", task.name, task.priority);
|
||||||
|
const promise = task.function();
|
||||||
|
if(!promise) {
|
||||||
|
console.error("Loading task %s hasn't returned a promise!", task.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
promises.push(promise.catch(error => {
|
||||||
|
errors.push({
|
||||||
|
task: task,
|
||||||
|
error: error
|
||||||
|
});
|
||||||
|
return Promise.resolve();
|
||||||
|
}));
|
||||||
|
} catch(error) {
|
||||||
|
errors.push({
|
||||||
|
task: task,
|
||||||
|
error: error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(promises.length > 0) {
|
||||||
|
await Promise.all([...promises]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(errors.length > 0) {
|
||||||
|
if(config.loader_groups) console.groupEnd();
|
||||||
|
console.error("Failed to execute loader. The following tasks failed (%d):", errors.length);
|
||||||
|
for(const error of errors)
|
||||||
|
console.error(" - %s: %o", error.task.name, error.error);
|
||||||
|
|
||||||
|
throw "failed to process step " + Stage[current_stage];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(current_tasks.length == 0) {
|
||||||
|
if(typeof(current_stage) === "undefined") {
|
||||||
|
current_stage = -1;
|
||||||
|
if(config.verbose) console.debug("[loader] Booting app");
|
||||||
|
} else if(current_stage < Stage.INITIALIZING) {
|
||||||
|
if(config.loader_groups) console.groupEnd();
|
||||||
|
if(config.verbose) console.debug("[loader] Entering next state (%s). Last state took %dms", Stage[current_stage + 1], (end = Date.now()) - begin);
|
||||||
|
} else {
|
||||||
|
if(config.loader_groups) console.groupEnd();
|
||||||
|
if(config.verbose) console.debug("[loader] Finish invoke took %dms", (end = Date.now()) - begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
begin = end;
|
||||||
|
current_stage += 1;
|
||||||
|
|
||||||
|
if(current_stage != Stage.DONE && config.loader_groups)
|
||||||
|
console.groupCollapsed("Executing loading stage %s", Stage[current_stage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.verbose) console.debug("[loader] finished loader. (Total time: %dms)", Date.now() - load_begin);
|
||||||
|
}
|
||||||
|
export function execute_managed() {
|
||||||
|
execute().then(() => {
|
||||||
|
if(config.verbose) {
|
||||||
|
let message;
|
||||||
|
if(typeof(window.tr) !== "undefined")
|
||||||
|
message = tr("App loaded successfully!");
|
||||||
|
else
|
||||||
|
message = "App loaded successfully!";
|
||||||
|
|
||||||
|
if(typeof(window.log) !== "undefined") {
|
||||||
|
/* We're having our log module */
|
||||||
|
window.log.info(window.log.LogCategory.GENERAL, message);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("App loading failed: %o", error);
|
||||||
|
critical_error("Failed to execute loader", "Lookup the console for more detail");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let _fadeout_warned;
|
||||||
|
export function hide_overlay() {
|
||||||
|
if(typeof($) === "undefined") {
|
||||||
|
if(!_fadeout_warned)
|
||||||
|
console.warn("Could not fadeout loader screen. Missing jquery functions.");
|
||||||
|
_fadeout_warned = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const animation_duration = 750;
|
||||||
|
|
||||||
|
$(".loader .bookshelf_wrapper").animate({top: 0, opacity: 0}, animation_duration);
|
||||||
|
$(".loader .half").animate({width: 0}, animation_duration, () => {
|
||||||
|
$(".loader").detach();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* critical error handler */
|
||||||
|
export type ErrorHandler = (message: string, detail: string) => void;
|
||||||
|
let _callback_critical_error: ErrorHandler;
|
||||||
|
let _callback_critical_called: boolean = false;
|
||||||
|
export function critical_error(message: string, detail?: string) {
|
||||||
|
document.getElementById("loader-overlay").classList.add("started");
|
||||||
|
|
||||||
|
if(_callback_critical_called) {
|
||||||
|
console.warn("[CRITICAL] %s", message);
|
||||||
|
if(typeof(detail) === "string")
|
||||||
|
console.warn("[CRITICAL] %s", detail);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_callback_critical_called = true;
|
||||||
|
if(_callback_critical_error) {
|
||||||
|
_callback_critical_error(message, detail);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* default handling */
|
||||||
|
let tag = document.getElementById("critical-load");
|
||||||
|
|
||||||
|
{
|
||||||
|
const error_tags = tag.getElementsByClassName("error");
|
||||||
|
error_tags[0].innerHTML = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof(detail) === "string") {
|
||||||
|
let node_detail = tag.getElementsByClassName("detail")[0];
|
||||||
|
node_detail.innerHTML = detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.classList.add("shown");
|
||||||
|
}
|
||||||
|
export function critical_error_handler(handler?: ErrorHandler, override?: boolean) : ErrorHandler {
|
||||||
|
if((typeof(handler) === "object" && handler !== _callback_critical_error) || override)
|
||||||
|
_callback_critical_error = handler;
|
||||||
|
return _callback_critical_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* loaders */
|
||||||
|
export type DependSource = {
|
||||||
|
url: string;
|
||||||
|
depends: string[];
|
||||||
|
}
|
||||||
|
export type SourcePath = string | DependSource | string[];
|
||||||
|
|
||||||
|
export const scripts = script_loader;
|
||||||
|
export const style = style_loader;
|
||||||
|
export const templates = template_loader;
|
||||||
|
|
||||||
|
/* Hello World message */
|
||||||
|
{
|
||||||
|
|
||||||
|
const hello_world = () => {
|
||||||
|
const clog = console.log;
|
||||||
|
const print_security = () => {
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 42px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"-webkit-text-stroke: 2px black",
|
||||||
|
"color: red"
|
||||||
|
].join(";");
|
||||||
|
clog("%c ", "font-size: 100px;");
|
||||||
|
clog("%cSecurity warning:", css);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 18px",
|
||||||
|
"font-weight: bold"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
clog("%cPasting anything in here could give attackers access to your data.", css);
|
||||||
|
clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css);
|
||||||
|
clog("%c ", "font-size: 100px;");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* print the hello world */
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 72px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"-webkit-text-stroke: 2px black",
|
||||||
|
"color: #18BC9C"
|
||||||
|
].join(";");
|
||||||
|
clog("%cHey, hold on!", css);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const css = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 26px",
|
||||||
|
"font-weight: bold"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
const css_2 = [
|
||||||
|
"display: block",
|
||||||
|
"text-align: center",
|
||||||
|
"font-size: 26px",
|
||||||
|
"font-weight: bold",
|
||||||
|
"color: blue"
|
||||||
|
].join(";");
|
||||||
|
|
||||||
|
const display_detect = /./;
|
||||||
|
display_detect.toString = function() { print_security(); return ""; };
|
||||||
|
|
||||||
|
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("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
||||||
|
clog("%c ", display_detect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try { /* lets try to print it as VM code :)*/
|
||||||
|
let hello_world_code = hello_world.toString();
|
||||||
|
hello_world_code = hello_world_code.substr(hello_world_code.indexOf('() => {') + 8);
|
||||||
|
hello_world_code = hello_world_code.substring(0, hello_world_code.lastIndexOf("}"));
|
||||||
|
|
||||||
|
//Look aheads are not possible with firefox
|
||||||
|
//hello_world_code = hello_world_code.replace(/(?<!const|let)(?<=^([^"'/]|"[^"]*"|'[^']*'|`[^`]*`|\/[^/]*\/)*) /gm, ""); /* replace all spaces */
|
||||||
|
hello_world_code = hello_world_code.replace(/[\n\r]/g, ""); /* replace as new lines */
|
||||||
|
|
||||||
|
eval(hello_world_code);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
hello_world();
|
||||||
|
}
|
||||||
|
}
|
121
loader/app/loader/script_loader.ts
Normal file
121
loader/app/loader/script_loader.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import {config, critical_error, SourcePath} from "./loader";
|
||||||
|
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||||
|
|
||||||
|
let _script_promises: {[key: string]: Promise<void>} = {};
|
||||||
|
|
||||||
|
function load_script_url(url: string) : Promise<void> {
|
||||||
|
if(typeof _script_promises[url] === "object")
|
||||||
|
return _script_promises[url];
|
||||||
|
|
||||||
|
return (_script_promises[url] = new Promise((resolve, reject) => {
|
||||||
|
const script_tag: HTMLScriptElement = document.createElement("script");
|
||||||
|
|
||||||
|
let error = false;
|
||||||
|
const error_handler = (event: ErrorEvent) => {
|
||||||
|
if(event.filename == script_tag.src && event.message.indexOf("Illegal constructor") == -1) { //Our tag throw an uncaught error
|
||||||
|
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
|
||||||
|
window.removeEventListener('error', error_handler as any);
|
||||||
|
|
||||||
|
reject(new LoadSyntaxError(event.error));
|
||||||
|
event.preventDefault();
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('error', error_handler as any);
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
script_tag.onerror = undefined;
|
||||||
|
script_tag.onload = undefined;
|
||||||
|
|
||||||
|
clearTimeout(timeout_handle);
|
||||||
|
window.removeEventListener('error', error_handler as any);
|
||||||
|
};
|
||||||
|
const timeout_handle = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject("timeout");
|
||||||
|
}, 5000);
|
||||||
|
script_tag.type = "application/javascript";
|
||||||
|
script_tag.async = true;
|
||||||
|
script_tag.defer = true;
|
||||||
|
script_tag.onerror = error => {
|
||||||
|
cleanup();
|
||||||
|
script_tag.remove();
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
script_tag.onload = () => {
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
if(config.verbose) console.debug("Script %o loaded", url);
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("scripts").appendChild(script_tag);
|
||||||
|
|
||||||
|
script_tag.src = url;
|
||||||
|
})).then(result => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_script_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _script_promises[url];
|
||||||
|
}).catch(error => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_script_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _script_promises[url];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
cache_tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
||||||
|
if(Array.isArray(path)) { //We have fallback scripts
|
||||||
|
return load(path[0], options).catch(error => {
|
||||||
|
if(error instanceof LoadSyntaxError)
|
||||||
|
return Promise.reject(error);
|
||||||
|
|
||||||
|
if(path.length > 1)
|
||||||
|
return load(path.slice(1), options);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||||
|
if(source.url.length == 0) return Promise.resolve();
|
||||||
|
|
||||||
|
/* await depends */
|
||||||
|
for(const depend of source.depends) {
|
||||||
|
if(!_script_promises[depend])
|
||||||
|
throw "Missing dependency " + depend;
|
||||||
|
await _script_promises[depend];
|
||||||
|
}
|
||||||
|
await load_script_url(source.url + (options.cache_tag || ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultipleOptions = Options | ParallelOptions;
|
||||||
|
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
||||||
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||||
|
if(result.failed.length > 0) {
|
||||||
|
if(config.error) {
|
||||||
|
console.error("Failed to load the following scripts:");
|
||||||
|
for(const script of result.failed) {
|
||||||
|
const sname = script_name(script.request, false);
|
||||||
|
if(script.error instanceof LoadSyntaxError) {
|
||||||
|
const source = script.error.source as Error;
|
||||||
|
if(source.name === "TypeError") {
|
||||||
|
let prefix = "";
|
||||||
|
while(prefix.length < sname.length + 7) prefix += " ";
|
||||||
|
console.log(" - %s: %s:\n%s", sname, source.message, source.stack.split("\n").map(e => prefix + e.trim()).slice(1).join("\n"));
|
||||||
|
} else {
|
||||||
|
console.log(" - %s: %o", sname, source);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(" - %s: %o", sname, script.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
critical_error("Failed to load script " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
||||||
|
throw "failed to load script " + script_name(result.failed[0].request, false);
|
||||||
|
}
|
||||||
|
}
|
142
loader/app/loader/style_loader.ts
Normal file
142
loader/app/loader/style_loader.ts
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import {config, critical_error, SourcePath} from "./loader";
|
||||||
|
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||||
|
|
||||||
|
let _style_promises: {[key: string]: Promise<void>} = {};
|
||||||
|
|
||||||
|
function load_style_url(url: string) : Promise<void> {
|
||||||
|
if(typeof _style_promises[url] === "object")
|
||||||
|
return _style_promises[url];
|
||||||
|
|
||||||
|
return (_style_promises[url] = new Promise((resolve, reject) => {
|
||||||
|
const tag: HTMLLinkElement = document.createElement("link");
|
||||||
|
|
||||||
|
let error = false;
|
||||||
|
const error_handler = (event: ErrorEvent) => {
|
||||||
|
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
|
||||||
|
if(event.filename == tag.href) { //FIXME!
|
||||||
|
window.removeEventListener('error', error_handler as any);
|
||||||
|
|
||||||
|
reject(new SyntaxError(event.error));
|
||||||
|
event.preventDefault();
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('error', error_handler as any);
|
||||||
|
|
||||||
|
tag.type = "text/css";
|
||||||
|
tag.rel = "stylesheet";
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
tag.onerror = undefined;
|
||||||
|
tag.onload = undefined;
|
||||||
|
|
||||||
|
clearTimeout(timeout_handle);
|
||||||
|
window.removeEventListener('error', error_handler as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeout_handle = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject("timeout");
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
tag.onerror = error => {
|
||||||
|
cleanup();
|
||||||
|
tag.remove();
|
||||||
|
if(config.error)
|
||||||
|
console.error("File load error for file %s: %o", url, error);
|
||||||
|
reject("failed to load file " + url);
|
||||||
|
};
|
||||||
|
tag.onload = () => {
|
||||||
|
cleanup();
|
||||||
|
{
|
||||||
|
const css: CSSStyleSheet = tag.sheet as CSSStyleSheet;
|
||||||
|
const rules = css.cssRules;
|
||||||
|
const rules_remove: number[] = [];
|
||||||
|
const rules_add: string[] = [];
|
||||||
|
|
||||||
|
for(let index = 0; index < rules.length; index++) {
|
||||||
|
const rule = rules.item(index);
|
||||||
|
let rule_text = rule.cssText;
|
||||||
|
|
||||||
|
if(rule.cssText.indexOf("%%base_path%%") != -1) {
|
||||||
|
rules_remove.push(index);
|
||||||
|
rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) {
|
||||||
|
if(css.removeRule)
|
||||||
|
css.removeRule(index);
|
||||||
|
else
|
||||||
|
css.deleteRule(index);
|
||||||
|
}
|
||||||
|
for(const rule of rules_add)
|
||||||
|
css.insertRule(rule, rules_remove[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config.verbose) console.debug("Style sheet %o loaded", url);
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("style").appendChild(tag);
|
||||||
|
tag.href = url;
|
||||||
|
})).then(result => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_style_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _style_promises[url];
|
||||||
|
}).catch(error => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_style_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _style_promises[url];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
cache_tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
||||||
|
if(Array.isArray(path)) { //We have fallback scripts
|
||||||
|
return load(path[0], options).catch(error => {
|
||||||
|
if(error instanceof LoadSyntaxError)
|
||||||
|
return Promise.reject(error);
|
||||||
|
|
||||||
|
if(path.length > 1)
|
||||||
|
return load(path.slice(1), options);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||||
|
if(source.url.length == 0) return Promise.resolve();
|
||||||
|
|
||||||
|
/* await depends */
|
||||||
|
for(const depend of source.depends) {
|
||||||
|
if(!_style_promises[depend])
|
||||||
|
throw "Missing dependency " + depend;
|
||||||
|
await _style_promises[depend];
|
||||||
|
}
|
||||||
|
await load_style_url(source.url + (options.cache_tag || ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MultipleOptions = Options | ParallelOptions;
|
||||||
|
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
||||||
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||||
|
if(result.failed.length > 0) {
|
||||||
|
if(config.error) {
|
||||||
|
console.error("Failed to load the following style sheets:");
|
||||||
|
for(const style of result.failed) {
|
||||||
|
const sname = script_name(style.request, false);
|
||||||
|
if(style.error instanceof LoadSyntaxError) {
|
||||||
|
console.log(" - %s: %o", sname, style.error.source);
|
||||||
|
} else {
|
||||||
|
console.log(" - %s: %o", sname, style.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
critical_error("Failed to load style " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
||||||
|
throw "failed to load style " + script_name(result.failed[0].request, false);
|
||||||
|
}
|
||||||
|
}
|
90
loader/app/loader/template_loader.ts
Normal file
90
loader/app/loader/template_loader.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import {config, critical_error, SourcePath} from "./loader";
|
||||||
|
import {load_parallel, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||||
|
|
||||||
|
let _template_promises: {[key: string]: Promise<void>} = {};
|
||||||
|
|
||||||
|
function load_template_url(url: string) : Promise<void> {
|
||||||
|
if(typeof _template_promises[url] === "object")
|
||||||
|
return _template_promises[url];
|
||||||
|
|
||||||
|
return (_template_promises[url] = (async () => {
|
||||||
|
const response = await $.ajax(url);
|
||||||
|
|
||||||
|
let node = document.createElement("html");
|
||||||
|
node.innerHTML = response;
|
||||||
|
let tags: HTMLCollection;
|
||||||
|
if(node.getElementsByTagName("body").length > 0)
|
||||||
|
tags = node.getElementsByTagName("body")[0].children;
|
||||||
|
else
|
||||||
|
tags = node.children;
|
||||||
|
|
||||||
|
let root = document.getElementById("templates");
|
||||||
|
if(!root) {
|
||||||
|
critical_error("Failed to find template tag!");
|
||||||
|
throw "Failed to find template tag";
|
||||||
|
}
|
||||||
|
while(tags.length > 0){
|
||||||
|
let tag = tags.item(0);
|
||||||
|
root.appendChild(tag);
|
||||||
|
|
||||||
|
}
|
||||||
|
})()).then(result => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_template_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _template_promises[url];
|
||||||
|
}).catch(error => {
|
||||||
|
/* cleanup memory */
|
||||||
|
_template_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||||
|
return _template_promises[url];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
cache_tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
||||||
|
if(Array.isArray(path)) { //We have fallback scripts
|
||||||
|
return load(path[0], options).catch(error => {
|
||||||
|
if(error instanceof LoadSyntaxError)
|
||||||
|
return Promise.reject(error);
|
||||||
|
|
||||||
|
if(path.length > 1)
|
||||||
|
return load(path.slice(1), options);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||||
|
if(source.url.length == 0) return Promise.resolve();
|
||||||
|
|
||||||
|
/* await depends */
|
||||||
|
for(const depend of source.depends) {
|
||||||
|
if(!_template_promises[depend])
|
||||||
|
throw "Missing dependency " + depend;
|
||||||
|
await _template_promises[depend];
|
||||||
|
}
|
||||||
|
await load_template_url(source.url + (options.cache_tag || ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MultipleOptions = Options | ParallelOptions;
|
||||||
|
export async function load_multiple(paths: SourcePath[], options: MultipleOptions) : Promise<void> {
|
||||||
|
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options);
|
||||||
|
if(result.failed.length > 0) {
|
||||||
|
if(config.error) {
|
||||||
|
console.error("Failed to load the following template files:");
|
||||||
|
for(const style of result.failed) {
|
||||||
|
const sname = script_name(style.request, false);
|
||||||
|
if(style.error instanceof LoadSyntaxError) {
|
||||||
|
console.log(" - %s: %o", sname, style.error.source);
|
||||||
|
} else {
|
||||||
|
console.log(" - %s: %o", sname, style.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
critical_error("Failed to load template file " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
||||||
|
throw "failed to load template file " + script_name(result.failed[0].request, false);
|
||||||
|
}
|
||||||
|
}
|
65
loader/app/loader/utils.ts
Normal file
65
loader/app/loader/utils.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import {SourcePath} from "./loader";
|
||||||
|
import {Options} from "./script_loader";
|
||||||
|
|
||||||
|
export class LoadSyntaxError {
|
||||||
|
readonly source: any;
|
||||||
|
constructor(source: any) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function script_name(path: SourcePath, html: boolean) {
|
||||||
|
if(Array.isArray(path)) {
|
||||||
|
let buffer = "";
|
||||||
|
let _or = " or ";
|
||||||
|
for(let entry of path)
|
||||||
|
buffer += _or + script_name(entry, html);
|
||||||
|
return buffer.slice(_or.length);
|
||||||
|
} else if(typeof(path) === "string")
|
||||||
|
return html ? "<code>" + path + "</code>" : path;
|
||||||
|
else
|
||||||
|
return html ? "<code>" + path.url + "</code>" : path.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParallelOptions extends Options {
|
||||||
|
max_parallel_requests?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParallelResult<T> {
|
||||||
|
succeeded: T[];
|
||||||
|
failed: {
|
||||||
|
request: T,
|
||||||
|
error: T
|
||||||
|
}[],
|
||||||
|
|
||||||
|
skipped: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load_parallel<T>(requests: T[], executor: (_: T) => Promise<void>, stringify: (_: T) => string, options: ParallelOptions) : Promise<ParallelResult<T>> {
|
||||||
|
const result: ParallelResult<T> = { failed: [], succeeded: [], skipped: [] };
|
||||||
|
const pending_requests = requests.slice(0).reverse(); /* we're only able to pop from the back */
|
||||||
|
const current_requests = {};
|
||||||
|
|
||||||
|
while (pending_requests.length > 0) {
|
||||||
|
while(typeof options.max_parallel_requests !== "number" || options.max_parallel_requests <= 0 || Object.keys(current_requests).length < options.max_parallel_requests) {
|
||||||
|
const script = pending_requests.pop();
|
||||||
|
const name = stringify(script);
|
||||||
|
|
||||||
|
current_requests[name] = executor(script).catch(e => result.failed.push({ request: script, error: e })).then(() => {
|
||||||
|
delete current_requests[name];
|
||||||
|
});
|
||||||
|
if(pending_requests.length == 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait 'till a new "slot" for downloading is free.
|
||||||
|
* This should also not throw because any errors will be caught before.
|
||||||
|
*/
|
||||||
|
await Promise.race(Object.keys(current_requests).map(e => current_requests[e]));
|
||||||
|
if(result.failed.length > 0)
|
||||||
|
break; /* finish loading the other requests and than show the error */
|
||||||
|
}
|
||||||
|
await Promise.all(Object.keys(current_requests).map(e => current_requests[e]));
|
||||||
|
result.skipped.push(...pending_requests);
|
||||||
|
return result;
|
||||||
|
}
|
415
loader/app/targets/app.ts
Normal file
415
loader/app/targets/app.ts
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
import * as loader from "../loader/loader";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
native_client: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const node_require: typeof require = window.require;
|
||||||
|
|
||||||
|
function cache_tag() {
|
||||||
|
const ui = ui_version();
|
||||||
|
return "?_ts=" + (!!ui && ui !== "unknown" ? ui : Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ui_version;
|
||||||
|
export function ui_version() {
|
||||||
|
if(typeof(_ui_version) !== "string") {
|
||||||
|
const version_node = document.getElementById("app_version");
|
||||||
|
if(!version_node) return undefined;
|
||||||
|
|
||||||
|
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
|
||||||
|
if(!version) return undefined;
|
||||||
|
|
||||||
|
return (_ui_version = version);
|
||||||
|
}
|
||||||
|
return _ui_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Manifest {
|
||||||
|
version: number;
|
||||||
|
|
||||||
|
chunks: {[key: string]: {
|
||||||
|
files: {
|
||||||
|
hash: string,
|
||||||
|
file: string
|
||||||
|
}[],
|
||||||
|
modules: {
|
||||||
|
id: string,
|
||||||
|
context: string,
|
||||||
|
resource: string
|
||||||
|
}[]
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all javascript loaders */
|
||||||
|
const loader_javascript = {
|
||||||
|
load_scripts: async () => {
|
||||||
|
if(!window.require) {
|
||||||
|
await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() });
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "forum sync",
|
||||||
|
priority: 10,
|
||||||
|
function: async () => {
|
||||||
|
forum.sync_main();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
await loader.scripts.load_multiple([
|
||||||
|
["vendor/jsrender/jsrender.min.js"],
|
||||||
|
["vendor/xbbcode/src/parser.js"],
|
||||||
|
["vendor/emoji-picker/src/jquery.lsxemojipicker.js"],
|
||||||
|
["vendor/twemoji/twemoji.min.js", ""], /* empty string means not required */
|
||||||
|
["vendor/highlight/highlight.pack.js", ""], /* empty string means not required */
|
||||||
|
["vendor/remarkable/remarkable.min.js", ""], /* empty string means not required */
|
||||||
|
], {
|
||||||
|
cache_tag: cache_tag(),
|
||||||
|
max_parallel_requests: -1
|
||||||
|
});
|
||||||
|
|
||||||
|
let manifest: Manifest;
|
||||||
|
try {
|
||||||
|
const response = await fetch("js/manifest.json");
|
||||||
|
if(!response.ok) throw response.status + " " + response.statusText;
|
||||||
|
|
||||||
|
manifest = await response.json();
|
||||||
|
} catch(error) {
|
||||||
|
console.error("Failed to load javascript manifest: %o", error);
|
||||||
|
loader.critical_error("Failed to load manifest.json", error);
|
||||||
|
throw "failed to load manifest.json";
|
||||||
|
}
|
||||||
|
if(manifest.version !== 2)
|
||||||
|
throw "invalid manifest version";
|
||||||
|
|
||||||
|
const chunk_name = __build.entry_chunk_name;
|
||||||
|
if(typeof manifest.chunks[chunk_name] !== "object") {
|
||||||
|
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunk_name + " is missing.");
|
||||||
|
throw "missing entry chunk";
|
||||||
|
}
|
||||||
|
loader.module_mapping().push({
|
||||||
|
application: chunk_name,
|
||||||
|
modules: manifest.chunks[chunk_name].modules
|
||||||
|
});
|
||||||
|
await loader.scripts.load_multiple(manifest.chunks[chunk_name].files.map(e => "js/" + e.file), {
|
||||||
|
cache_tag: undefined,
|
||||||
|
max_parallel_requests: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader_webassembly = {
|
||||||
|
test_webassembly: async () => {
|
||||||
|
/* We dont required WebAssembly anymore for fundamental functions, only for auto decoding
|
||||||
|
if(typeof (WebAssembly) === "undefined" || typeof (WebAssembly.compile) === "undefined") {
|
||||||
|
console.log(navigator.browserSpecs);
|
||||||
|
if (navigator.browserSpecs.name == 'Safari') {
|
||||||
|
if (parseInt(navigator.browserSpecs.version) < 11) {
|
||||||
|
displayCriticalError("You require Safari 11 or higher to use the web client!<br>Safari " + navigator.browserSpecs.version + " does not support WebAssambly!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Do something for all other browsers.
|
||||||
|
}
|
||||||
|
displayCriticalError("You require WebAssembly for TeaSpeak-Web!");
|
||||||
|
throw "Missing web assembly";
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader_style = {
|
||||||
|
load_style: async () => {
|
||||||
|
const options = {
|
||||||
|
cache_tag: cache_tag(),
|
||||||
|
max_parallel_requests: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
await loader.style.load_multiple([
|
||||||
|
"vendor/xbbcode/src/xbbcode.css"
|
||||||
|
], options);
|
||||||
|
await loader.style.load_multiple([
|
||||||
|
"vendor/emoji-picker/src/jquery.lsxemojipicker.css"
|
||||||
|
], options);
|
||||||
|
await loader.style.load_multiple([
|
||||||
|
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
|
||||||
|
], options);
|
||||||
|
|
||||||
|
if(__build.mode === "debug") {
|
||||||
|
await loader_style.load_style_debug();
|
||||||
|
} else {
|
||||||
|
await loader_style.load_style_release();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
load_style_debug: async () => {
|
||||||
|
await loader.style.load_multiple([
|
||||||
|
"css/static/main.css",
|
||||||
|
"css/static/main-layout.css",
|
||||||
|
"css/static/helptag.css",
|
||||||
|
"css/static/scroll.css",
|
||||||
|
"css/static/channel-tree.css",
|
||||||
|
"css/static/ts/tab.css",
|
||||||
|
"css/static/ts/chat.css",
|
||||||
|
"css/static/ts/icons.css",
|
||||||
|
"css/static/ts/icons_em.css",
|
||||||
|
"css/static/ts/country.css",
|
||||||
|
"css/static/general.css",
|
||||||
|
"css/static/modal.css",
|
||||||
|
"css/static/modals.css",
|
||||||
|
"css/static/modal-about.css",
|
||||||
|
"css/static/modal-avatar.css",
|
||||||
|
"css/static/modal-icons.css",
|
||||||
|
"css/static/modal-bookmarks.css",
|
||||||
|
"css/static/modal-connect.css",
|
||||||
|
"css/static/modal-channel.css",
|
||||||
|
"css/static/modal-query.css",
|
||||||
|
"css/static/modal-volume.css",
|
||||||
|
"css/static/modal-latency.css",
|
||||||
|
"css/static/modal-invite.css",
|
||||||
|
"css/static/modal-playlist.css",
|
||||||
|
"css/static/modal-banlist.css",
|
||||||
|
"css/static/modal-banclient.css",
|
||||||
|
"css/static/modal-channelinfo.css",
|
||||||
|
"css/static/modal-clientinfo.css",
|
||||||
|
"css/static/modal-serverinfo.css",
|
||||||
|
"css/static/modal-musicmanage.css",
|
||||||
|
"css/static/modal-serverinfobandwidth.css",
|
||||||
|
"css/static/modal-identity.css",
|
||||||
|
"css/static/modal-newcomer.css",
|
||||||
|
"css/static/modal-settings.css",
|
||||||
|
"css/static/modal-poke.css",
|
||||||
|
"css/static/modal-server.css",
|
||||||
|
"css/static/modal-keyselect.css",
|
||||||
|
"css/static/modal-permissions.css",
|
||||||
|
"css/static/modal-group-assignment.css",
|
||||||
|
"css/static/overlay-image-preview.css",
|
||||||
|
"css/static/music/info_plate.css",
|
||||||
|
"css/static/frame/SelectInfo.css",
|
||||||
|
"css/static/control_bar.css",
|
||||||
|
"css/static/context_menu.css",
|
||||||
|
"css/static/frame-chat.css",
|
||||||
|
"css/static/connection_handlers.css",
|
||||||
|
"css/static/server-log.css",
|
||||||
|
"css/static/htmltags.css",
|
||||||
|
"css/static/hostbanner.css",
|
||||||
|
"css/static/menu-bar.css"
|
||||||
|
], {
|
||||||
|
cache_tag: cache_tag(),
|
||||||
|
max_parallel_requests: -1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
load_style_release: async () => {
|
||||||
|
await loader.style.load_multiple([
|
||||||
|
"css/static/base.css",
|
||||||
|
"css/static/main.css",
|
||||||
|
], {
|
||||||
|
cache_tag: cache_tag(),
|
||||||
|
max_parallel_requests: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* register tasks */
|
||||||
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
|
name: "safari fix",
|
||||||
|
function: async () => {
|
||||||
|
/* safari remove "fix" */
|
||||||
|
if(Element.prototype.remove === undefined)
|
||||||
|
Object.defineProperty(Element.prototype, "remove", {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false,
|
||||||
|
writable: false,
|
||||||
|
value: function(){
|
||||||
|
this.parentElement.removeChild(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
priority: 50
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
|
name: "Browser detection",
|
||||||
|
function: async () => {
|
||||||
|
navigator.browserSpecs = (function(){
|
||||||
|
let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||||
|
if(/trident/i.test(M[1])){
|
||||||
|
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||||
|
return {name:'IE',version:(tem[1] || '')};
|
||||||
|
}
|
||||||
|
if(M[1]=== 'Chrome'){
|
||||||
|
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
|
||||||
|
if(tem != null) return {name:tem[1].replace('OPR', 'Opera'),version:tem[2]};
|
||||||
|
}
|
||||||
|
M = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
|
||||||
|
if((tem = ua.match(/version\/(\d+)/i))!= null)
|
||||||
|
M.splice(1, 1, tem[1]);
|
||||||
|
return {name:M[0], version:M[1]};
|
||||||
|
})();
|
||||||
|
|
||||||
|
console.log("Resolved browser manufacturer to \"%s\" version \"%s\"", navigator.browserSpecs.name, navigator.browserSpecs.version);
|
||||||
|
},
|
||||||
|
priority: 30
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
|
name: "secure tester",
|
||||||
|
function: async () => {
|
||||||
|
/* we need https or localhost to use some things like the storage API */
|
||||||
|
if(typeof isSecureContext === "undefined")
|
||||||
|
(<any>window)["isSecureContext"] = location.protocol !== 'https:' && location.hostname !== 'localhost';
|
||||||
|
|
||||||
|
if(!isSecureContext) {
|
||||||
|
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
||||||
|
throw "App requires a secure context!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
|
name: "webassembly tester",
|
||||||
|
function: loader_webassembly.test_webassembly,
|
||||||
|
priority: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||||
|
name: "javascript",
|
||||||
|
function: loader_javascript.load_scripts,
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.STYLE, {
|
||||||
|
name: "style",
|
||||||
|
function: loader_style.load_style,
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.TEMPLATES, {
|
||||||
|
name: "templates",
|
||||||
|
function: async () => {
|
||||||
|
await loader.templates.load_multiple([
|
||||||
|
"templates.html",
|
||||||
|
"templates/modal/musicmanage.html",
|
||||||
|
"templates/modal/newcomer.html",
|
||||||
|
], {
|
||||||
|
cache_tag: cache_tag(),
|
||||||
|
max_parallel_requests: -1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
|
name: "loaded handler",
|
||||||
|
function: async () => loader.hide_overlay(),
|
||||||
|
priority: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "lsx emoji picker setup",
|
||||||
|
function: async () => await (window as any).setup_lsx_emoji_picker({twemoji: typeof(window.twemoji) !== "undefined"}),
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.SETUP, {
|
||||||
|
name: "page setup",
|
||||||
|
function: async () => {
|
||||||
|
const body = document.body;
|
||||||
|
/* top menu */
|
||||||
|
{
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.setAttribute('id', "top-menu-bar");
|
||||||
|
body.append(container);
|
||||||
|
}
|
||||||
|
/* template containers */
|
||||||
|
{
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.setAttribute('id', "templates");
|
||||||
|
body.append(container);
|
||||||
|
}
|
||||||
|
/* sounds container */
|
||||||
|
{
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.setAttribute('id', "sounds");
|
||||||
|
body.append(container);
|
||||||
|
}
|
||||||
|
/* mouse move container */
|
||||||
|
{
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.setAttribute('id', "mouse-move");
|
||||||
|
|
||||||
|
const inner_container = document.createElement("div");
|
||||||
|
inner_container.classList.add("container");
|
||||||
|
container.append(inner_container);
|
||||||
|
|
||||||
|
body.append(container);
|
||||||
|
}
|
||||||
|
/* tooltip container */
|
||||||
|
{
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.setAttribute('id', "global-tooltip");
|
||||||
|
|
||||||
|
container.append(document.createElement("a"));
|
||||||
|
|
||||||
|
body.append(container);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
/* test if we're getting loaded within a TeaClient preview window */
|
||||||
|
loader.register_task(loader.Stage.SETUP, {
|
||||||
|
name: "TeaClient tester",
|
||||||
|
function: async () => {
|
||||||
|
//@ts-ignore
|
||||||
|
if(typeof __teaclient_preview_notice !== "undefined" && typeof __teaclient_preview_error !== "undefined") {
|
||||||
|
loader.critical_error("Why you're opening TeaWeb within the TeaSpeak client?!");
|
||||||
|
throw "we're already a TeaClient!";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
export function run() {
|
||||||
|
window["Module"] = (window["Module"] || {}) as any; /* Why? */
|
||||||
|
|
||||||
|
/* TeaClient */
|
||||||
|
if(node_require) {
|
||||||
|
if(__build.target !== "client") {
|
||||||
|
loader.critical_error("App seems not to be compiled for the client.", "This app has been compiled for " + __build.target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.native_client = true;
|
||||||
|
|
||||||
|
const path = node_require("path");
|
||||||
|
const remote = node_require('electron').remote;
|
||||||
|
|
||||||
|
const render_entry = path.join(remote.app.getAppPath(), "/modules/", "renderer");
|
||||||
|
const render = node_require(render_entry);
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
|
name: "teaclient initialize",
|
||||||
|
function: render.initialize,
|
||||||
|
priority: 40
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if(__build.target !== "web") {
|
||||||
|
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.native_client = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loader.running()) {
|
||||||
|
/* we know that we want to load the app */
|
||||||
|
loader.execute_managed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
/// <reference path="loader.ts" />
|
import * as loader from "../loader/loader";
|
||||||
|
|
||||||
let is_debug = false;
|
let is_debug = false;
|
||||||
|
|
||||||
|
@ -25,34 +25,8 @@ const loader_javascript = {
|
||||||
|
|
||||||
load_scripts: async () => {
|
load_scripts: async () => {
|
||||||
await loader.load_script(["vendor/jquery/jquery.min.js"]);
|
await loader.load_script(["vendor/jquery/jquery.min.js"]);
|
||||||
|
|
||||||
if(!is_debug) {
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
|
||||||
name: "scripts release",
|
|
||||||
priority: 20,
|
|
||||||
function: loader_javascript.load_release
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
|
||||||
name: "scripts debug",
|
|
||||||
priority: 20,
|
|
||||||
function: loader_javascript.load_scripts_debug
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
load_scripts_debug: async () => {
|
|
||||||
await loader.load_scripts([
|
await loader.load_scripts([
|
||||||
["js/proto.js"],
|
["dist/certificate-popup.js"],
|
||||||
["js/log.js"],
|
|
||||||
["js/BrowserIPC.js"],
|
|
||||||
["js/settings.js"],
|
|
||||||
["js/main.js"]
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
load_release: async () => {
|
|
||||||
await loader.load_scripts([
|
|
||||||
["js/certaccept.min.js", "js/certaccept.js"]
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -99,9 +73,7 @@ loader.register_task(loader.Stage.STYLE, {
|
||||||
|
|
||||||
loader.register_task(loader.Stage.LOADED, {
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
name: "loaded handler",
|
name: "loaded handler",
|
||||||
function: async () => {
|
function: async () => loader.hide_overlay(),
|
||||||
fadeoutLoader();
|
|
||||||
},
|
|
||||||
priority: 0
|
priority: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -123,25 +95,6 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
priority: 50
|
priority: 50
|
||||||
});
|
});
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "settings initialisation",
|
|
||||||
function: async () => Settings.initialize(),
|
|
||||||
priority: 200
|
|
||||||
});
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "bipc initialisation",
|
|
||||||
function: async () => bipc.setup(),
|
|
||||||
priority: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "log enabled initialisation",
|
|
||||||
function: async () => log.initialize(is_debug ? log.LogType.TRACE : log.LogType.INFO),
|
|
||||||
priority: 150
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!loader.running()) {
|
if(!loader.running()) {
|
||||||
/* we know that we want to load the app */
|
/* we know that we want to load the app */
|
||||||
loader.execute_managed();
|
loader.execute_managed();
|
87
loader/exports/loader.d.ts
vendored
Normal file
87
loader/exports/loader.d.ts
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
export interface Config {
|
||||||
|
loader_groups: boolean;
|
||||||
|
verbose: boolean;
|
||||||
|
error: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BackendType {
|
||||||
|
WEB,
|
||||||
|
NATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppVersion {
|
||||||
|
ui: string;
|
||||||
|
backend: string;
|
||||||
|
|
||||||
|
type: "web" | "native";
|
||||||
|
debug_mode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let config: Config;
|
||||||
|
|
||||||
|
export type Task = {
|
||||||
|
name: string,
|
||||||
|
priority: number, /* tasks with the same priority will be executed in sync */
|
||||||
|
function: () => Promise<void>
|
||||||
|
};
|
||||||
|
export enum Stage {
|
||||||
|
/*
|
||||||
|
loading loader required files (incl this)
|
||||||
|
*/
|
||||||
|
INITIALIZING,
|
||||||
|
/*
|
||||||
|
setting up the loading process
|
||||||
|
*/
|
||||||
|
SETUP,
|
||||||
|
/*
|
||||||
|
loading all style sheet files
|
||||||
|
*/
|
||||||
|
STYLE,
|
||||||
|
/*
|
||||||
|
loading all javascript files
|
||||||
|
*/
|
||||||
|
JAVASCRIPT,
|
||||||
|
/*
|
||||||
|
loading all template files
|
||||||
|
*/
|
||||||
|
TEMPLATES,
|
||||||
|
/*
|
||||||
|
initializing static/global stuff
|
||||||
|
*/
|
||||||
|
JAVASCRIPT_INITIALIZING,
|
||||||
|
/*
|
||||||
|
finalizing load process
|
||||||
|
*/
|
||||||
|
FINALIZING,
|
||||||
|
/*
|
||||||
|
invoking main task
|
||||||
|
*/
|
||||||
|
LOADED,
|
||||||
|
|
||||||
|
DONE
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModuleMapping = {
|
||||||
|
application: string,
|
||||||
|
modules: {
|
||||||
|
"id": string,
|
||||||
|
"context": string,
|
||||||
|
"resource": string
|
||||||
|
}[]
|
||||||
|
};
|
||||||
|
export function module_mapping() : ModuleMapping;
|
||||||
|
|
||||||
|
export function finished();
|
||||||
|
export function running();
|
||||||
|
export function register_task(stage: Stage, task: Task);
|
||||||
|
export function execute() : Promise<void>;
|
||||||
|
export function execute_managed();
|
||||||
|
export type DependSource = {
|
||||||
|
url: string;
|
||||||
|
depends: string[];
|
||||||
|
}
|
||||||
|
export type SourcePath = string | DependSource | string[];
|
||||||
|
export type ErrorHandler = (message: string, detail: string) => void;
|
||||||
|
export function critical_error(message: string, detail?: string);
|
||||||
|
export function critical_error_handler(handler?: ErrorHandler, override?: boolean);
|
||||||
|
export function hide_overlay();
|
5263
package-lock.json
generated
5263
package-lock.json
generated
File diff suppressed because it is too large
Load diff
49
package.json
49
package.json
|
@ -5,41 +5,62 @@
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"directories": {},
|
"directories": {},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile-sass": "sass --update .:.",
|
"compile-sass": "sass --update shared/css/:shared/css/ web/css/:web/css/ client/css/:client/css/",
|
||||||
"compile-file-helper": "tsc file.ts",
|
"compile-project-base": "tsc -p tsbaseconfig.json",
|
||||||
"build-worker-codec": "tsc -p web/js/workers/tsconfig_worker_codec.json",
|
|
||||||
"build-worker-pow": "tsc -p shared/js/workers/tsconfig_worker_pow.json",
|
|
||||||
"build-worker": "npm run build-worker-codec; npm run build-worker-pow;",
|
|
||||||
"dtsgen": "node tools/dtsgen/index.js",
|
"dtsgen": "node tools/dtsgen/index.js",
|
||||||
"trgen": "node tools/trgen/index.js",
|
"trgen": "node tools/trgen/index.js",
|
||||||
"ttsc": "ttsc",
|
|
||||||
"sass": "sass",
|
"sass": "sass",
|
||||||
"csso": "csso",
|
"csso": "csso",
|
||||||
"rebuild-structure-web-dev": "php files.php generate web dev",
|
"rebuild-structure-web-dev": "php files.php generate web dev",
|
||||||
"minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output",
|
"minify-web-rel-file": "terser --compress --mangle --ecma 6 --keep_classnames --keep_fnames --output",
|
||||||
"start": "npm run compile-file-helper && node file.js ndevelop"
|
"start": "npm run compile-file-helper && node file.js ndevelop",
|
||||||
|
"build-web": "webpack --config webpack-web.config.js",
|
||||||
|
"watch-web": "webpack --watch --config webpack-web.config.js",
|
||||||
|
"build-client": "webpack --config webpack-client.config.js",
|
||||||
|
"watch-client": "webpack --watch --config webpack-client.config.js"
|
||||||
},
|
},
|
||||||
"author": "TeaSpeak (WolverinDEV)",
|
"author": "TeaSpeak (WolverinDEV)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/dompurify": "^2.0.1",
|
||||||
"@types/emscripten": "^1.38.0",
|
"@types/emscripten": "^1.38.0",
|
||||||
"@types/jquery": "^3.3.31",
|
"@types/fs-extra": "^8.0.1",
|
||||||
|
"@types/jquery": "^3.3.34",
|
||||||
|
"@types/lodash": "^4.14.149",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
"@types/node": "^12.7.2",
|
"@types/node": "^12.7.2",
|
||||||
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/sha256": "^0.2.0",
|
"@types/sha256": "^0.2.0",
|
||||||
"@types/websocket": "0.0.40",
|
"@types/websocket": "0.0.40",
|
||||||
|
"chunk-manifest-webpack-plugin": "^1.1.2",
|
||||||
|
"circular-dependency-plugin": "^5.2.0",
|
||||||
"clean-css": "^4.2.1",
|
"clean-css": "^4.2.1",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
"csso-cli": "^2.0.2",
|
"csso-cli": "^2.0.2",
|
||||||
|
"exports-loader": "^0.7.0",
|
||||||
|
"fs-extra": "latest",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
|
"html-loader": "^1.0.0",
|
||||||
|
"html-webpack-plugin": "^4.0.3",
|
||||||
"mime-types": "^2.1.24",
|
"mime-types": "^2.1.24",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
|
"node-sass": "^4.13.1",
|
||||||
|
"raw-loader": "^4.0.0",
|
||||||
"sass": "1.22.10",
|
"sass": "1.22.10",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
"sha256": "^0.2.0",
|
"sha256": "^0.2.0",
|
||||||
|
"style-loader": "^1.1.3",
|
||||||
"terser": "^4.2.1",
|
"terser": "^4.2.1",
|
||||||
"ttypescript": "^1.5.10",
|
"terser-webpack-plugin": "latest",
|
||||||
|
"ts-loader": "^6.2.2",
|
||||||
"typescript": "3.6.5",
|
"typescript": "3.6.5",
|
||||||
"wat2wasm": "^1.0.2",
|
"wabt": "^1.0.13",
|
||||||
"fs-extra": "latest"
|
"webpack": "^4.42.1",
|
||||||
|
"webpack-bundle-analyzer": "^3.6.1",
|
||||||
|
"webpack-cli": "^3.3.11",
|
||||||
|
"worker-plugin": "^4.0.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -50,6 +71,10 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://www.teaspeak.de",
|
"homepage": "https://www.teaspeak.de",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/fs-extra": "^8.0.1"
|
"dompurify": "^2.0.8",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"webrtc-adapter": "^7.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
93
scripts/build.sh
Normal file
93
scripts/build.sh
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$(dirname "$0")/resolve_commands.sh"
|
||||||
|
cd "$(dirname "$0")/../" || { echo "Failed to enter the base directory"; exit 1; }
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Invalid argument count!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "client" ]]; then
|
||||||
|
build_target="client"
|
||||||
|
elif [[ "$1" == "web" ]]; then
|
||||||
|
build_target="web"
|
||||||
|
else
|
||||||
|
echo "Invalid option $2"
|
||||||
|
echo 'Available options are: "web" or "client"'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$2" == "development" ]] || [[ "$2" == "dev" ]] || [[ "$2" == "debug" ]]; then
|
||||||
|
build_type="development"
|
||||||
|
elif [[ "$2" == "release" ]] || [[ "$2" == "rel" ]]; then
|
||||||
|
build_type="release"
|
||||||
|
else
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Invalid argument count!"
|
||||||
|
else
|
||||||
|
echo "Invalid option $2"
|
||||||
|
fi
|
||||||
|
echo 'Available options are: "development" or "dev", "release" or "rel"'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating required project build files"
|
||||||
|
npm run compile-project-base; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to generate project build files"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating required build tooks"
|
||||||
|
./tools/build_trgen.sh; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to build build_typescript translation generator"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating style files"
|
||||||
|
npm run compile-sass; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to generate style files"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Compile vendor XBBCode"
|
||||||
|
execute_ttsc -p ./vendor/xbbcode/tsconfig.json; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to build the XBBCode vendor"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$build_type" == "release" ]]; then # Compile everything for release mode
|
||||||
|
echo "Packing generated css files"
|
||||||
|
./shared/css/generate_packed.sh; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to package generated css files"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_ENV=production npm run build-$build_target; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to build the $build_target applcation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [[ "$build_type" == "development" ]]; then
|
||||||
|
NODE_ENV=development npm run build-$build_target; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to build the $build_target applcation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating environment"
|
||||||
|
node file.js generate $build_target ${build_type}; _exit_code=$?
|
||||||
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to generate environment"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$build_target builded successfully!"
|
22
scripts/build_declarations.sh
Executable file → Normal file
22
scripts/build_declarations.sh
Executable file → Normal file
|
@ -1,8 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
BASEDIR=$(dirname "$0")
|
||||||
|
# shellcheck disable=SC1090
|
||||||
source "${BASEDIR}/resolve_commands.sh"
|
source "${BASEDIR}/resolve_commands.sh"
|
||||||
cd "$BASEDIR/../"
|
cd "$BASEDIR/../" || { echo "Failed to enter parent directory!"; exit 1; }
|
||||||
|
|
||||||
function generate_link() {
|
function generate_link() {
|
||||||
if [[ ! -L $2 ]] || [[ "${BASH_ARGV[0]}" == "force" ]]; then
|
if [[ ! -L $2 ]] || [[ "${BASH_ARGV[0]}" == "force" ]]; then
|
||||||
|
@ -20,12 +21,19 @@ function replace_tribble() {
|
||||||
|
|
||||||
|
|
||||||
#Building the generator
|
#Building the generator
|
||||||
./tools/build_dtsgen.sh
|
./tools/build_dtsgen.sh; _exit_code=$?
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
echo "Failed to build typescript declaration generator"
|
echo "Failed to build typescript declaration generator ($_exit_code)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
#Shared
|
||||||
|
./shared/generate_declarations.sh; _exit_code=$?
|
||||||
|
[[ $_exit_code -ne 0 ]] && {
|
||||||
|
echo "Failed to generate shared ($_exit_code)"
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
#Easy going: Each "module" has it's exports and imports
|
#Easy going: Each "module" has it's exports and imports
|
||||||
#So lets first build the exports and ignore any errors
|
#So lets first build the exports and ignore any errors
|
||||||
#Note: For the client we have to use the given file
|
#Note: For the client we have to use the given file
|
||||||
|
@ -40,12 +48,6 @@ npm run dtsgen -- --config client/tsconfig/dtsconfig.json -v
|
||||||
replace_tribble client/declarations/exports.d.ts
|
replace_tribble client/declarations/exports.d.ts
|
||||||
echo "Generated client declarations"
|
echo "Generated client declarations"
|
||||||
|
|
||||||
#Shared
|
|
||||||
./shared/generate_declarations.sh
|
|
||||||
[[ $? -ne 0 ]] && {
|
|
||||||
echo "Failed to generate shared"
|
|
||||||
}
|
|
||||||
|
|
||||||
#replace_tribble shared/declarations/exports.d.ts
|
#replace_tribble shared/declarations/exports.d.ts
|
||||||
echo "Generated shared declarations"
|
echo "Generated shared declarations"
|
||||||
|
|
||||||
|
|
0
scripts/cleanup.sh
Executable file → Normal file
0
scripts/cleanup.sh
Executable file → Normal file
|
@ -1,103 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
source `dirname $0`/resolve_commands.sh
|
|
||||||
BASEDIR=$(dirname "$0")
|
|
||||||
cd "$BASEDIR/../"
|
|
||||||
|
|
||||||
source_path="client-api/environment/ui-files/raw"
|
|
||||||
if [[ "$1" == "development" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "debug" ]]; then
|
|
||||||
type="development"
|
|
||||||
elif [[ "$1" == "release" ]] || [[ "$1" == "rel" ]]; then
|
|
||||||
type="release"
|
|
||||||
else
|
|
||||||
if [[ $# -lt 1 ]]; then
|
|
||||||
echo "Invalid argument count!"
|
|
||||||
else
|
|
||||||
echo "Invalid option $1"
|
|
||||||
fi
|
|
||||||
echo 'Available options are: "development" or "dev", "release" or "rel"'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating file helper script"
|
|
||||||
npm run compile-file-helper
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate file helper"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating style files"
|
|
||||||
npm run compile-sass
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate style files"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating web workers"
|
|
||||||
npm run build-worker-codec
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web worker codec"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
npm run build-worker-pow
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web worker pow"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Lets build some tools
|
|
||||||
#dtsgen should be already build by build_declarations.sh
|
|
||||||
./tools/build_trgen.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build typescript translation generator"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Now lets build the declarations
|
|
||||||
echo "Building declarations"
|
|
||||||
./scripts/build_declarations.sh force
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate declarations"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$type" == "release" ]]; then #Compile everything for release mode
|
|
||||||
#Compile the shared source first
|
|
||||||
echo "Building shared source"
|
|
||||||
./shared/generate_packed.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build shared source"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Now compile the web client itself
|
|
||||||
echo "Building client UI"
|
|
||||||
./client/generate_packed.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web client"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "$type" == "development" ]]; then
|
|
||||||
echo "Building shared source"
|
|
||||||
execute_ttsc -p ./shared/tsconfig/tsconfig.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to compile shared sources"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building client UI source"
|
|
||||||
execute_ttsc -p ./client/tsconfig/tsconfig.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to compile web sources"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating environment"
|
|
||||||
node file.js generate client ${type}
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate environment"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Successfully build!"
|
|
53
scripts/deploy_ui_files.sh
Executable file → Normal file
53
scripts/deploy_ui_files.sh
Executable file → Normal file
|
@ -1,12 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# ./scripts/deploy_ui_files.sh http://dev.clientapi.teaspeak.de/api.php test 1.1.0
|
# Example usage: ./scripts/deploy_ui_files.sh http://dev.clientapi.teaspeak.de/api.php test 1.1.0
|
||||||
|
|
||||||
TMP_FILE_NAME="TeaSpeakUI.tar.gz"
|
TMP_FILE_NAME="TeaSpeakUI.tar.gz"
|
||||||
TMP_DIR_NAME="tmp"
|
TMP_DIR_NAME="tmp"
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
cd "$(dirname "$0")/../" || { echo "failed to enter base directory"; exit 1; }
|
||||||
cd "$BASEDIR/../"
|
|
||||||
|
|
||||||
if [[ "$#" -ne 3 ]]; then
|
if [[ "$#" -ne 3 ]]; then
|
||||||
echo "Illegal number of parameters (url | channel | required version)"
|
echo "Illegal number of parameters (url | channel | required version)"
|
||||||
|
@ -18,6 +17,7 @@ if [[ ! -d client-api/environment/ui-files/ ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
if [[ "${teaclient_deploy_secret}" == "" ]]; then
|
if [[ "${teaclient_deploy_secret}" == "" ]]; then
|
||||||
echo "Missing deploy secret!"
|
echo "Missing deploy secret!"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -26,33 +26,36 @@ fi
|
||||||
if [[ -e "${TMP_FILE_NAME}" ]]; then
|
if [[ -e "${TMP_FILE_NAME}" ]]; then
|
||||||
echo "Temp file already exists!"
|
echo "Temp file already exists!"
|
||||||
echo "Deleting it!"
|
echo "Deleting it!"
|
||||||
rm ${TMP_FILE_NAME}
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if ! rm ${TMP_FILE_NAME}; then
|
||||||
echo "Failed to delete file"
|
echo "Failed to delete file"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
GIT_HASH=$(git rev-parse --verify --short HEAD)
|
GIT_HASH=$(git rev-parse --verify --short HEAD)
|
||||||
APPLICATION_VERSION=$(cat package.json | python -c "import sys, json; print(json.load(sys.stdin)['version'])")
|
APPLICATION_VERSION=$(< package.json python -c "import sys, json; print(json.load(sys.stdin)['version'])")
|
||||||
echo "Git hash ${GIT_HASH} on version ${APPLICATION_VERSION} on channel $2"
|
echo "Git hash ${GIT_HASH} on version ${APPLICATION_VERSION} on channel $2"
|
||||||
|
|
||||||
#Packaging the app
|
#Packaging the app
|
||||||
cd client-api/environment/ui-files/
|
cd client-api/environment/ui-files/ || {
|
||||||
|
echo "Missing UI files directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
if [[ -e ${TMP_DIR_NAME} ]]; then
|
if [[ -e ${TMP_DIR_NAME} ]]; then
|
||||||
rm -r ${TMP_DIR_NAME}
|
if ! rm -r ${TMP_DIR_NAME}; then
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to remove temporary directory!"
|
echo "Failed to remove temporary directory!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
cp -rL raw ${TMP_DIR_NAME}
|
cp -rL raw ${TMP_DIR_NAME}
|
||||||
|
|
||||||
for file in $(find ${TMP_DIR_NAME} -name '*.php'); do
|
while IFS= read -r -d '' file
|
||||||
|
do
|
||||||
echo "Evaluating php file $file"
|
echo "Evaluating php file $file"
|
||||||
__cur_dir=$(pwd)
|
__cur_dir=$(pwd)
|
||||||
cd $(dirname ${file})
|
cd "$(dirname "${file}")" || { echo "Failed to enter php file directory"; exit 1; }
|
||||||
RESULT=$(php "$(basename ${file})" 2> /dev/null)
|
php_result=$(php "$(basename "${file}")" 2> /dev/null)
|
||||||
CODE=$?
|
CODE=$?
|
||||||
if [[ ${CODE} -ne 0 ]]; then
|
if [[ ${CODE} -ne 0 ]]; then
|
||||||
echo "Failed to evaluate php file $file!"
|
echo "Failed to evaluate php file $file!"
|
||||||
|
@ -60,14 +63,14 @@ for file in $(find ${TMP_DIR_NAME} -name '*.php'); do
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ${__cur_dir}
|
cd "${__cur_dir}" || { echo "failed to enter original dir"; exit 1; }
|
||||||
echo "${RESULT}" > "${file::-4}.html"
|
echo "${php_result}" > "${file::-4}.html"
|
||||||
done
|
done < <(find "${TMP_DIR_NAME}" -name '*.php' -print0)
|
||||||
|
|
||||||
cd ${TMP_DIR_NAME}
|
cd ${TMP_DIR_NAME} || { echo "failed to enter the temp dir"; exit 1; }
|
||||||
tar chvzf ${TMP_FILE_NAME} *
|
tar chvzf ${TMP_FILE_NAME} ./*; _exit_code=$?
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
echo "Failed to pack file"
|
echo "Failed to pack file ($_exit_code)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
mv ${TMP_FILE_NAME} ../../../../
|
mv ${TMP_FILE_NAME} ../../../../
|
||||||
|
@ -84,16 +87,16 @@ RESP=$(curl \
|
||||||
-F "version=$APPLICATION_VERSION" \
|
-F "version=$APPLICATION_VERSION" \
|
||||||
-F "git_ref=$GIT_HASH" \
|
-F "git_ref=$GIT_HASH" \
|
||||||
-F "secret=${teaclient_deploy_secret}" \
|
-F "secret=${teaclient_deploy_secret}" \
|
||||||
-F "file=@`pwd`/TeaSpeakUI.tar.gz" \
|
-F "file=@$(pwd)/TeaSpeakUI.tar.gz" \
|
||||||
$1
|
"$1"
|
||||||
)
|
)
|
||||||
echo "$RESP"
|
echo "$RESP"
|
||||||
SUCCESS=$(echo ${RESP} | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
|
SUCCESS=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['success'])")
|
||||||
|
|
||||||
if [[ ! "${SUCCESS}" == "True" ]]; then
|
if [[ ! "${SUCCESS}" == "True" ]]; then
|
||||||
ERROR=$(echo ${RESP} | python -c "import sys, json; print(json.load(sys.stdin)['error'])" 2>/dev/null)
|
ERROR=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['error'])" 2>/dev/null); _exit_code=$?
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
ERROR=$(echo ${RESP} | python -c "import sys, json; print(json.load(sys.stdin)['msg'])" 2>/dev/null)
|
ERROR=$(echo "${RESP}" | python -c "import sys, json; print(json.load(sys.stdin)['msg'])" 2>/dev/null)
|
||||||
fi
|
fi
|
||||||
echo "Failed to deploy build!"
|
echo "Failed to deploy build!"
|
||||||
echo "${ERROR}"
|
echo "${ERROR}"
|
||||||
|
|
6
scripts/git_index.sh
Executable file → Normal file
6
scripts/git_index.sh
Executable file → Normal file
|
@ -14,13 +14,13 @@ if [[ "$response" != "" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
if [[ "$1" == "sort-tag" ]]; then
|
if [[ "$1" == "sort-tag" ]]; then
|
||||||
echo "$(git rev-parse --short HEAD)"
|
git rev-parse --short HEAD
|
||||||
fi
|
fi
|
||||||
if [[ "$1" == "name" ]]; then
|
if [[ "$1" == "name" ]]; then
|
||||||
echo "$(git rev-parse --short HEAD)"
|
git rev-parse --short HEAD
|
||||||
fi
|
fi
|
||||||
if [[ "$1" == "file-name" ]]; then
|
if [[ "$1" == "file-name" ]]; then
|
||||||
echo "$(git rev-parse --short HEAD)"
|
git rev-parse --short HEAD
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
0
scripts/resolve_commands.sh
Executable file → Normal file
0
scripts/resolve_commands.sh
Executable file → Normal file
0
scripts/travis_deploy.sh
Executable file → Normal file
0
scripts/travis_deploy.sh
Executable file → Normal file
|
@ -1,117 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
source `dirname $0`/resolve_commands.sh
|
|
||||||
BASEDIR=$(dirname "$0")
|
|
||||||
cd "$BASEDIR/../"
|
|
||||||
|
|
||||||
if [[ "$1" == "development" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "debug" ]]; then
|
|
||||||
source_path="web/environment/development"
|
|
||||||
type="development"
|
|
||||||
elif [[ "$1" == "release" ]] || [[ "$1" == "rel" ]]; then
|
|
||||||
source_path="web/environment/release"
|
|
||||||
type="release"
|
|
||||||
else
|
|
||||||
if [[ $# -lt 1 ]]; then
|
|
||||||
echo "Invalid argument count!"
|
|
||||||
else
|
|
||||||
echo "Invalid option $1"
|
|
||||||
fi
|
|
||||||
echo 'Available options are: "development" or "dev", "release" or "rel"'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating file helper script"
|
|
||||||
npm run compile-file-helper
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate file helper"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Compile vendor XBBCode"
|
|
||||||
execute_ttsc -p ./vendor/xbbcode/tsconfig.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build the XBBCode vendor"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
execute_ttsc ./vendor/emoji-picker/src/jquery.lsxemojipicker.ts
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build the lsxemojipicker vendor"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating style files"
|
|
||||||
npm run compile-sass
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate style files"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating web workers"
|
|
||||||
npm run build-worker-codec
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web worker codec"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
npm run build-worker-pow
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web worker pow"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Lets build some tools
|
|
||||||
#dtsgen should be already build by build_declarations.sh
|
|
||||||
./tools/build_trgen.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build typescript translation generator"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Now lets build the declarations
|
|
||||||
echo "Building declarations"
|
|
||||||
./scripts/build_declarations.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate declarations"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$type" == "release" ]]; then #Compile everything for release mode
|
|
||||||
#Compile the shared source first
|
|
||||||
echo "Building shared source"
|
|
||||||
./shared/generate_packed.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build shared source"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Now compile the web client itself
|
|
||||||
echo "Building web client"
|
|
||||||
./web/generate_packed.sh
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to build web client"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "$type" == "development" ]]; then
|
|
||||||
echo "Building shared source"
|
|
||||||
execute_ttsc -p ./shared/tsconfig/tsconfig.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to compile shared sources"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building web client source"
|
|
||||||
execute_ttsc -p ./web/tsconfig/tsconfig.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to compile web sources"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating environment"
|
|
||||||
node file.js generate web ${type}
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate environment"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Successfully build!"
|
|
12
scripts/web_package.sh
Executable file → Normal file
12
scripts/web_package.sh
Executable file → Normal file
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
cd "$(dirname "$0")/../" || { echo "Failed to enter base directory"; exit 1; }
|
||||||
cd "$BASEDIR/../"
|
|
||||||
|
|
||||||
if [[ "$1" == "development" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "dev" ]]; then
|
if [[ "$1" == "development" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "dev" ]]; then
|
||||||
source_path="web/environment/development"
|
source_path="web/environment/development"
|
||||||
|
@ -36,19 +35,18 @@ fi
|
||||||
|
|
||||||
if [[ -e ${NAME} ]]; then
|
if [[ -e ${NAME} ]]; then
|
||||||
echo "Found old file. Deleting it."
|
echo "Found old file. Deleting it."
|
||||||
rm -r ${NAME}
|
rm -r "${NAME}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
current_path=$(pwd)
|
current_path=$(pwd)
|
||||||
cd "$source_path"
|
cd "$source_path" || { echo "Failed to enter source path"; exit 1; }
|
||||||
zip -9 -r ${NAME} *
|
|
||||||
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if zip -9 -r "${NAME}" ./*; then
|
||||||
echo "Failed to package environment!"
|
echo "Failed to package environment!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$current_path"
|
cd "$current_path" || { echo "Failed to reenter source path"; exit 1; }
|
||||||
|
|
||||||
mv "${source_path}/${NAME}" .
|
mv "${source_path}/${NAME}" .
|
||||||
echo "Release package successfully packaged!"
|
echo "Release package successfully packaged!"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
15
shared/backend.d/audio/player.d.ts
vendored
Normal file
15
shared/backend.d/audio/player.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {Device} from "tc-shared/audio/player";
|
||||||
|
|
||||||
|
export function initialize() : boolean;
|
||||||
|
export function initialized() : boolean;
|
||||||
|
|
||||||
|
export function get_master_volume() : number;
|
||||||
|
export function set_master_volume(volume: number);
|
||||||
|
|
||||||
|
export function on_ready(cb: () => any);
|
||||||
|
|
||||||
|
export function available_devices() : Promise<Device[]>;
|
||||||
|
export function set_device(device_id: string) : Promise<void>;
|
||||||
|
export function current_device() : Device;
|
||||||
|
|
||||||
|
export function initializeFromGesture();
|
9
shared/backend.d/audio/recorder.d.ts
vendored
Normal file
9
shared/backend.d/audio/recorder.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {AbstractInput, InputDevice, LevelMeter} from "tc-shared/voice/RecorderBase";
|
||||||
|
|
||||||
|
export function devices() : InputDevice[];
|
||||||
|
|
||||||
|
export function device_refresh_available() : boolean;
|
||||||
|
export function refresh_devices() : Promise<void>;
|
||||||
|
|
||||||
|
export function create_input() : AbstractInput;
|
||||||
|
export function create_levelmeter(device: InputDevice) : Promise<LevelMeter>;
|
3
shared/backend.d/audio/sounds.d.ts
vendored
Normal file
3
shared/backend.d/audio/sounds.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import {SoundFile} from "tc-shared/sound/Sounds";
|
||||||
|
|
||||||
|
export function play_sound(file: SoundFile) : Promise<void>;
|
5
shared/backend.d/connection.d.ts
vendored
Normal file
5
shared/backend.d/connection.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
|
|
||||||
|
export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection;
|
||||||
|
export function destroy_server_connection(handle: AbstractServerConnection);
|
5
shared/backend.d/dns.d.ts
vendored
Normal file
5
shared/backend.d/dns.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import {AddressTarget, ResolveOptions} from "tc-shared/dns";
|
||||||
|
import {ServerAddress} from "tc-shared/ui/server";
|
||||||
|
|
||||||
|
export function supported();
|
||||||
|
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;
|
12
shared/backend.d/ppt.d.ts
vendored
Normal file
12
shared/backend.d/ppt.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {KeyEvent, KeyHook, SpecialKey} from "tc-shared/PPTListener";
|
||||||
|
|
||||||
|
export function initialize() : Promise<void>;
|
||||||
|
export function finalize(); // most the times not really required
|
||||||
|
|
||||||
|
export function register_key_listener(listener: (_: KeyEvent) => any);
|
||||||
|
export function unregister_key_listener(listener: (_: KeyEvent) => any);
|
||||||
|
|
||||||
|
export function register_key_hook(hook: KeyHook);
|
||||||
|
export function unregister_key_hook(hook: KeyHook);
|
||||||
|
|
||||||
|
export function key_pressed(code: string | SpecialKey) : boolean;
|
35
shared/backend/audio.d.ts
vendored
35
shared/backend/audio.d.ts
vendored
|
@ -1,35 +0,0 @@
|
||||||
declare namespace audio {
|
|
||||||
export namespace player {
|
|
||||||
export function initialize() : boolean;
|
|
||||||
export function initialized() : boolean;
|
|
||||||
|
|
||||||
export function context() : AudioContext;
|
|
||||||
export function get_master_volume() : number;
|
|
||||||
export function set_master_volume(volume: number);
|
|
||||||
|
|
||||||
export function destination() : AudioNode;
|
|
||||||
|
|
||||||
export function on_ready(cb: () => any);
|
|
||||||
|
|
||||||
export function available_devices() : Promise<Device[]>;
|
|
||||||
export function set_device(device_id: string) : Promise<void>;
|
|
||||||
|
|
||||||
export function current_device() : Device;
|
|
||||||
|
|
||||||
export function initializeFromGesture();
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace recorder {
|
|
||||||
export function devices() : InputDevice[];
|
|
||||||
|
|
||||||
export function device_refresh_available() : boolean;
|
|
||||||
export function refresh_devices() : Promise<void>;
|
|
||||||
|
|
||||||
export function create_input() : AbstractInput;
|
|
||||||
export function create_levelmeter(device: InputDevice) : Promise<LevelMeter>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace sounds {
|
|
||||||
export function play_sound(file: sound.SoundFile) : Promise<void>;
|
|
||||||
}
|
|
||||||
}
|
|
4
shared/backend/connection.d.ts
vendored
4
shared/backend/connection.d.ts
vendored
|
@ -1,4 +0,0 @@
|
||||||
declare namespace connection {
|
|
||||||
export function spawn_server_connection(handle: ConnectionHandler) : AbstractServerConnection;
|
|
||||||
export function destroy_server_connection(handle: AbstractServerConnection);
|
|
||||||
}
|
|
4
shared/backend/dns.d.ts
vendored
4
shared/backend/dns.d.ts
vendored
|
@ -1,4 +0,0 @@
|
||||||
declare namespace dns {
|
|
||||||
export function supported();
|
|
||||||
export function resolve_address(address: ServerAddress, options?: ResolveOptions) : Promise<AddressTarget>;
|
|
||||||
}
|
|
12
shared/backend/ppt.d.ts
vendored
12
shared/backend/ppt.d.ts
vendored
|
@ -1,12 +0,0 @@
|
||||||
declare namespace ppt {
|
|
||||||
export function initialize() : Promise<void>;
|
|
||||||
export function finalize(); // most the times not really required
|
|
||||||
|
|
||||||
export function register_key_listener(listener: (_: KeyEvent) => any);
|
|
||||||
export function unregister_key_listener(listener: (_: KeyEvent) => any);
|
|
||||||
|
|
||||||
export function register_key_hook(hook: KeyHook);
|
|
||||||
export function unregister_key_hook(hook: KeyHook);
|
|
||||||
|
|
||||||
export function key_pressed(code: string | SpecialKey) : boolean;
|
|
||||||
}
|
|
12
shared/css/generate_packed.sh
Executable file → Normal file
12
shared/css/generate_packed.sh
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd "$(dirname "$0")" || exit 1
|
||||||
#find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
|
#find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,20 +62,20 @@ files=(
|
||||||
target_file=`pwd`/../generated/static/base.css
|
target_file=`pwd`/../generated/static/base.css
|
||||||
|
|
||||||
if [[ ! -d $(dirname ${target_file}) ]]; then
|
if [[ ! -d $(dirname ${target_file}) ]]; then
|
||||||
echo "Creating target path ($(dirname ${target_file}))"
|
echo "Creating target path ($(dirname "${target_file}"))"
|
||||||
mkdir -p $(dirname ${target_file})
|
mkdir -p $(dirname "${target_file}")
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo "Failed to create target path!"
|
echo "Failed to create target path!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "/* Auto generated merged CSS file */" > ${target_file}
|
echo "/* Auto generated merged CSS file */" > "${target_file}"
|
||||||
for file in "${files[@]}"; do
|
for file in "${files[@]}"; do
|
||||||
if [[ ${file} =~ css/* ]]; then
|
if [[ ${file} =~ css/* ]]; then
|
||||||
file="./${file:4}"
|
file="./${file:4}"
|
||||||
fi
|
fi
|
||||||
cat ${file} >> ${target_file}
|
cat "${file}" >> "${target_file}"
|
||||||
done
|
done
|
||||||
|
|
||||||
cat ${target_file} | npm run csso -- --output `pwd`/../generated/static/base.css
|
cat "${target_file}" | npm run csso -- --output "$(pwd)/../generated/static/base.css"
|
|
@ -123,7 +123,6 @@ fieldset {
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: lightgray;
|
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
shared/generate_declarations.sh
Executable file → Normal file
20
shared/generate_declarations.sh
Executable file → Normal file
|
@ -1,21 +1,21 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
BASEDIR=$(dirname "$0")
|
||||||
cd "$BASEDIR"
|
cd "$BASEDIR" || { echo "Failed to enter script base dir"; exit 1; }
|
||||||
source ../scripts/resolve_commands.sh
|
source ../scripts/resolve_commands.sh
|
||||||
|
|
||||||
function generate_declaration() {
|
function generate_declaration() {
|
||||||
echo "Generating declarations for project $1 ($2)"
|
echo "Generating declarations for project $1 ($2)"
|
||||||
|
|
||||||
if [[ -e ${2} ]]; then
|
if [[ -d "${2}" ]]; then
|
||||||
rm ${2}
|
rm -r "${2}"; _exit_code=$?
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $_exit_code -ne 0 ]]; then
|
||||||
echo "Failed to remove old declaration file ($2)!"
|
echo "Failed to remove old declaration file ($2): $_exit_code!"
|
||||||
echo "This could be critical later!"
|
echo "This could be critical later!"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run dtsgen -- --config $(pwd)/tsconfig/$1 -v
|
npm run dtsgen -- --config "$(pwd)/tsconfig/$1" -v
|
||||||
if [[ ! -e $2 ]]; then
|
if [[ ! -e $2 ]]; then
|
||||||
echo "Failed to generate definitions"
|
echo "Failed to generate definitions"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -23,12 +23,12 @@ function generate_declaration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#Generate the loader definitions first
|
#Generate the loader definitions first
|
||||||
app_declaration="declarations/exports_app.d.ts"
|
app_declaration="../declarations/shared-app/"
|
||||||
loader_declaration_app="declarations/exports_loader_app.d.ts"
|
loader_declaration_app="../declarations/loader/"
|
||||||
loader_declaration_certaccept="declarations/exports_loader_certaccept.d.ts"
|
# loader_declaration_certaccept="declarations/exports_loader_certaccept.d.ts"
|
||||||
|
|
||||||
generate_declaration dtsconfig_app.json ${app_declaration}
|
generate_declaration dtsconfig_app.json ${app_declaration}
|
||||||
generate_declaration dtsconfig_loader_app.json ${loader_declaration_app}
|
generate_declaration dtsconfig_loader_app.json ${loader_declaration_app}
|
||||||
generate_declaration dtsconfig_loader_certaccept.json ${loader_declaration_certaccept}
|
# generate_declaration dtsconfig_loader_certaccept.json ${loader_declaration_certaccept}
|
||||||
|
|
||||||
exit 0
|
exit 0
|
0
shared/generate_i18n_gtranslate.py
Executable file → Normal file
0
shared/generate_i18n_gtranslate.py
Executable file → Normal file
|
@ -1,56 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
|
||||||
cd "$BASEDIR"
|
|
||||||
source ../scripts/resolve_commands.sh
|
|
||||||
# The app loader
|
|
||||||
execute_ttsc -p tsconfig/tsconfig_packed_loader_app.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate packed loader file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
npm run minify-web-rel-file `pwd`/generated/loader_app.min.js `pwd`/generated/loader_app.js
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to minimize packed loader file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The popup certaccept loader
|
|
||||||
execute_ttsc -p tsconfig/tsconfig_packed_loader_certaccept.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate packed loader file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
npm run minify-web-rel-file `pwd`/generated/loader_certaccept.min.js `pwd`/generated/loader_certaccept.js
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to minimize packed loader file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The main shared source
|
|
||||||
execute_ttsc -p tsconfig/tsconfig_packed.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate packed file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The certaccept source
|
|
||||||
execute_ttsc -p tsconfig/tsconfig_packed_certaccept.json
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to generate packed certaccept file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
npm run minify-web-rel-file `pwd`/generated/certaccept.min.js `pwd`/generated/certaccept.js
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
echo "Failed to minimize the certaccept file!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create packed CSS file
|
|
||||||
./css/generate_packed.sh
|
|
||||||
|
|
||||||
echo "Packed file generated!"
|
|
||||||
exit 0
|
|
0
shared/generate_translations.sh
Executable file → Normal file
0
shared/generate_translations.sh
Executable file → Normal file
|
@ -145,9 +145,11 @@
|
||||||
|
|
||||||
<meta name="app-loader-target" content="app">
|
<meta name="app-loader-target" content="app">
|
||||||
<div id="scripts">
|
<div id="scripts">
|
||||||
|
<!--
|
||||||
<script type="application/javascript" src="loader/loader_app.min.js" async defer></script>
|
<script type="application/javascript" src="loader/loader_app.min.js" async defer></script>
|
||||||
<script type="application/javascript" src="loader/loader_app.js" async defer></script>
|
<script type="application/javascript" src="loader/loader_app.js" async defer></script>
|
||||||
<script type="application/javascript" src="loader/loader.js?_<?php echo time() ?>" async defer></script>
|
-->
|
||||||
|
<script type="application/javascript" src="js/loader.js?_<?php echo time() ?>" async defer></script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading screen -->
|
<!-- Loading screen -->
|
||||||
|
|
|
@ -2736,13 +2736,13 @@
|
||||||
</div>
|
</div>
|
||||||
{{else type == "default" }}
|
{{else type == "default" }}
|
||||||
<div class="entry default {{if selected}}selected{{/if}}">
|
<div class="entry default {{if selected}}selected{{/if}}">
|
||||||
<div class="country flag-gb" title="{{*:i18n.country_name('gb')}}"></div>
|
<div class="country flag-gb" title="{{>fallback_country_name}}"></div>
|
||||||
<div class="name">{{tr "English (Default / Fallback)" /}}</div>
|
<div class="name">{{tr "English (Default / Fallback)" /}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="entry translation {{if selected}}selected{{/if}}" parent-repository="{{:id}}">
|
<div class="entry translation {{if selected}}selected{{/if}}" parent-repository="{{:id}}">
|
||||||
<div class="country flag-{{:country_code}}"
|
<div class="country flag-{{:country_code}}"
|
||||||
title="{{*:i18n.country_name(data.country_code || 'XX')}}"></div>
|
title="{{>country_name}}"></div>
|
||||||
<div class="name">{{> name}}</div>
|
<div class="name">{{> name}}</div>
|
||||||
<div class="button button-info">
|
<div class="button button-info">
|
||||||
<div class="icon client-about"></div>
|
<div class="icon client-about"></div>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,38 @@
|
||||||
/// <reference path="log.ts" />
|
import {ChannelTree} from "tc-shared/ui/view";
|
||||||
/// <reference path="proto.ts" />
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
/// <reference path="ui/view.ts" />
|
import {PermissionManager} from "tc-shared/permission/PermissionManager";
|
||||||
/// <reference path="settings.ts" />
|
import {GroupManager} from "tc-shared/permission/GroupManager";
|
||||||
/// <reference path="FileManager.ts" />
|
import {ServerSettings, Settings, StaticSettings} from "tc-shared/settings";
|
||||||
/// <reference path="permission/PermissionManager.ts" />
|
import {Sound, SoundManager} from "tc-shared/sound/Sounds";
|
||||||
/// <reference path="permission/GroupManager.ts" />
|
import {LocalClientEntry} from "tc-shared/ui/client";
|
||||||
/// <reference path="ui/frames/ControlBar.ts" />
|
import {ServerLog} from "tc-shared/ui/frames/server_log";
|
||||||
/// <reference path="connection/ConnectionBase.ts" />
|
import {ConnectionProfile, default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
|
||||||
|
import {ServerAddress} from "tc-shared/ui/server";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import * as server_log from "tc-shared/ui/frames/server_log";
|
||||||
|
import {createErrorModal, createInfoModal, createInputModal, Modal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import {hashPassword} from "tc-shared/utils/helpers";
|
||||||
|
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
|
import * as htmltags from "./ui/htmltags";
|
||||||
|
import {ChannelEntry} from "tc-shared/ui/channel";
|
||||||
|
import {InputStartResult, InputState} from "tc-shared/voice/RecorderBase";
|
||||||
|
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
|
import * as bipc from "./BrowserIPC";
|
||||||
|
import {FileManager, spawn_upload_transfer, UploadKey} from "tc-shared/FileManager";
|
||||||
|
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
|
||||||
|
import {Frame} from "tc-shared/ui/frames/chat_frame";
|
||||||
|
import {Hostbanner} from "tc-shared/ui/frames/hostbanner";
|
||||||
|
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
||||||
|
import {connection_log, Regex} from "tc-shared/ui/modal/ModalConnect";
|
||||||
|
import {control_bar} from "tc-shared/ui/frames/ControlBar";
|
||||||
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
import {spawnAvatarUpload} from "tc-shared/ui/modal/ModalAvatar";
|
||||||
|
import * as connection from "tc-backend/connection";
|
||||||
|
import * as dns from "tc-backend/dns";
|
||||||
|
|
||||||
enum DisconnectReason {
|
export enum DisconnectReason {
|
||||||
HANDLER_DESTROYED,
|
HANDLER_DESTROYED,
|
||||||
REQUESTED,
|
REQUESTED,
|
||||||
DNS_FAILED,
|
DNS_FAILED,
|
||||||
|
@ -28,7 +52,7 @@ enum DisconnectReason {
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionState {
|
export enum ConnectionState {
|
||||||
UNCONNECTED,
|
UNCONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
INITIALISING,
|
INITIALISING,
|
||||||
|
@ -36,7 +60,7 @@ enum ConnectionState {
|
||||||
DISCONNECTING
|
DISCONNECTING
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ViewReasonId {
|
export enum ViewReasonId {
|
||||||
VREASON_USER_ACTION = 0,
|
VREASON_USER_ACTION = 0,
|
||||||
VREASON_MOVED = 1,
|
VREASON_MOVED = 1,
|
||||||
VREASON_SYSTEM = 2,
|
VREASON_SYSTEM = 2,
|
||||||
|
@ -51,7 +75,7 @@ enum ViewReasonId {
|
||||||
VREASON_SERVER_SHUTDOWN = 11
|
VREASON_SERVER_SHUTDOWN = 11
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VoiceStatus {
|
export interface VoiceStatus {
|
||||||
input_hardware: boolean;
|
input_hardware: boolean;
|
||||||
input_muted: boolean;
|
input_muted: boolean;
|
||||||
output_muted: boolean;
|
output_muted: boolean;
|
||||||
|
@ -68,7 +92,7 @@ interface VoiceStatus {
|
||||||
queries_visible: boolean;
|
queries_visible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectParameters {
|
export interface ConnectParameters {
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
channel?: {
|
channel?: {
|
||||||
target: string | number;
|
target: string | number;
|
||||||
|
@ -79,20 +103,21 @@ interface ConnectParameters {
|
||||||
auto_reconnect_attempt?: boolean;
|
auto_reconnect_attempt?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionHandler {
|
declare const native_client;
|
||||||
|
export class ConnectionHandler {
|
||||||
channelTree: ChannelTree;
|
channelTree: ChannelTree;
|
||||||
|
|
||||||
serverConnection: connection.AbstractServerConnection;
|
serverConnection: AbstractServerConnection;
|
||||||
|
|
||||||
fileManager: FileManager;
|
fileManager: FileManager;
|
||||||
|
|
||||||
permissions: PermissionManager;
|
permissions: PermissionManager;
|
||||||
groups: GroupManager;
|
groups: GroupManager;
|
||||||
|
|
||||||
side_bar: chat.Frame;
|
side_bar: Frame;
|
||||||
|
|
||||||
settings: ServerSettings;
|
settings: ServerSettings;
|
||||||
sound: sound.SoundManager;
|
sound: SoundManager;
|
||||||
|
|
||||||
hostbanner: Hostbanner;
|
hostbanner: Hostbanner;
|
||||||
|
|
||||||
|
@ -121,15 +146,15 @@ class ConnectionHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
invoke_resized_on_activate: boolean = false;
|
invoke_resized_on_activate: boolean = false;
|
||||||
log: log.ServerLog;
|
log: ServerLog;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settings = new ServerSettings();
|
this.settings = new ServerSettings();
|
||||||
|
|
||||||
this.log = new log.ServerLog(this);
|
this.log = new ServerLog(this);
|
||||||
this.channelTree = new ChannelTree(this);
|
this.channelTree = new ChannelTree(this);
|
||||||
this.side_bar = new chat.Frame(this);
|
this.side_bar = new Frame(this);
|
||||||
this.sound = new sound.SoundManager(this);
|
this.sound = new SoundManager(this);
|
||||||
this.hostbanner = new Hostbanner(this);
|
this.hostbanner = new Hostbanner(this);
|
||||||
|
|
||||||
this.serverConnection = connection.spawn_server_connection(this);
|
this.serverConnection = connection.spawn_server_connection(this);
|
||||||
|
@ -169,7 +194,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
setup() { }
|
setup() { }
|
||||||
|
|
||||||
async startConnection(addr: string, profile: profiles.ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
|
async startConnection(addr: string, profile: ConnectionProfile, user_action: boolean, parameters: ConnectParameters) {
|
||||||
this.tab_set_name(tr("Connecting"));
|
this.tab_set_name(tr("Connecting"));
|
||||||
this.cancel_reconnect(false);
|
this.cancel_reconnect(false);
|
||||||
this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
|
this._reconnect_attempt = parameters.auto_reconnect_attempt || false;
|
||||||
|
@ -192,7 +217,7 @@ class ConnectionHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
log.info(LogCategory.CLIENT, tr("Start connection to %s:%d"), server_address.host, server_address.port);
|
||||||
this.log.log(log.server.Type.CONNECTION_BEGIN, {
|
this.log.log(server_log.Type.CONNECTION_BEGIN, {
|
||||||
address: {
|
address: {
|
||||||
server_hostname: server_address.host,
|
server_hostname: server_address.host,
|
||||||
server_port: server_address.port
|
server_port: server_address.port
|
||||||
|
@ -203,7 +228,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
if(parameters.password && !parameters.password.hashed){
|
if(parameters.password && !parameters.password.hashed){
|
||||||
try {
|
try {
|
||||||
const password = await helpers.hashPassword(parameters.password.password);
|
const password = await hashPassword(parameters.password.password);
|
||||||
parameters.password = {
|
parameters.password = {
|
||||||
hashed: true,
|
hashed: true,
|
||||||
password: password
|
password: password
|
||||||
|
@ -221,9 +246,9 @@ class ConnectionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
const original_address = {host: server_address.host, port: server_address.port};
|
const original_address = {host: server_address.host, port: server_address.port};
|
||||||
if(dns.supported() && !server_address.host.match(Modals.Regex.IP_V4) && !server_address.host.match(Modals.Regex.IP_V6)) {
|
if(dns.supported() && !server_address.host.match(Regex.IP_V4) && !server_address.host.match(Regex.IP_V6)) {
|
||||||
const id = ++this._connect_initialize_id;
|
const id = ++this._connect_initialize_id;
|
||||||
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE, {});
|
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVE, {});
|
||||||
try {
|
try {
|
||||||
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
|
const resolved = await dns.resolve_address(server_address, { timeout: 5000 }) || {} as any;
|
||||||
if(id != this._connect_initialize_id)
|
if(id != this._connect_initialize_id)
|
||||||
|
@ -231,7 +256,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
|
server_address.host = typeof(resolved.target_ip) === "string" ? resolved.target_ip : server_address.host;
|
||||||
server_address.port = typeof(resolved.target_port) === "number" ? resolved.target_port : server_address.port;
|
server_address.port = typeof(resolved.target_port) === "number" ? resolved.target_port : server_address.port;
|
||||||
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVED, {
|
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVED, {
|
||||||
address: {
|
address: {
|
||||||
server_port: server_address.port,
|
server_port: server_address.port,
|
||||||
server_hostname: server_address.host
|
server_hostname: server_address.host
|
||||||
|
@ -245,7 +270,7 @@ class ConnectionHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.serverConnection.connect(server_address, new connection.HandshakeHandler(profile, parameters));
|
await this.serverConnection.connect(server_address, new HandshakeHandler(profile, parameters));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const connected = this.serverConnection.connected();
|
const connected = this.serverConnection.connected();
|
||||||
if(user_action && connected) {
|
if(user_action && connected) {
|
||||||
|
@ -270,7 +295,7 @@ class ConnectionHandler {
|
||||||
return this._clientId;
|
return this._clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
getServerConnection() : connection.AbstractServerConnection { return this.serverConnection; }
|
getServerConnection() : AbstractServerConnection { return this.serverConnection; }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -380,7 +405,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
popup.close(); /* no need, but nicer */
|
popup.close(); /* no need, but nicer */
|
||||||
|
|
||||||
const profile = profiles.find_profile(properties.connect_profile) || profiles.default_profile();
|
const profile = find_profile(properties.connect_profile) || default_profile();
|
||||||
const cprops = this.reconnect_properties(profile);
|
const cprops = this.reconnect_properties(profile);
|
||||||
this.startConnection(properties.connect_address, profile, true, cprops);
|
this.startConnection(properties.connect_address, profile, true, cprops);
|
||||||
});
|
});
|
||||||
|
@ -418,12 +443,12 @@ class ConnectionHandler {
|
||||||
case DisconnectReason.HANDLER_DESTROYED:
|
case DisconnectReason.HANDLER_DESTROYED:
|
||||||
if(data) {
|
if(data) {
|
||||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||||
this.log.log(log.server.Type.DISCONNECTED, {});
|
this.log.log(server_log.Type.DISCONNECTED, {});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.DNS_FAILED:
|
case DisconnectReason.DNS_FAILED:
|
||||||
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
|
log.error(LogCategory.CLIENT, tr("Failed to resolve hostname: %o"), data);
|
||||||
this.log.log(log.server.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
|
this.log.log(server_log.Type.CONNECTION_HOSTNAME_RESOLVE_ERROR, {
|
||||||
message: data as any
|
message: data as any
|
||||||
});
|
});
|
||||||
this.sound.play(Sound.CONNECTION_REFUSED);
|
this.sound.play(Sound.CONNECTION_REFUSED);
|
||||||
|
@ -431,7 +456,7 @@ class ConnectionHandler {
|
||||||
case DisconnectReason.CONNECT_FAILURE:
|
case DisconnectReason.CONNECT_FAILURE:
|
||||||
if(this._reconnect_attempt) {
|
if(this._reconnect_attempt) {
|
||||||
auto_reconnect = true;
|
auto_reconnect = true;
|
||||||
this.log.log(log.server.Type.CONNECTION_FAILED, {});
|
this.log.log(server_log.Type.CONNECTION_FAILED, {});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(data)
|
if(data)
|
||||||
|
@ -452,7 +477,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
this._certificate_modal = createErrorModal(
|
this._certificate_modal = createErrorModal(
|
||||||
tr("Could not connect"),
|
tr("Could not connect"),
|
||||||
MessageHelper.formatMessage(tr(error_message_format), this.generate_ssl_certificate_accept())
|
formatMessage(/* @tr-ignore */ tr(error_message_format), this.generate_ssl_certificate_accept())
|
||||||
);
|
);
|
||||||
this._certificate_modal.close_listener.push(() => this._certificate_modal = undefined);
|
this._certificate_modal.close_listener.push(() => this._certificate_modal = undefined);
|
||||||
this._certificate_modal.open();
|
this._certificate_modal.open();
|
||||||
|
@ -470,7 +495,7 @@ class ConnectionHandler {
|
||||||
case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED:
|
case DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED:
|
||||||
createErrorModal(
|
createErrorModal(
|
||||||
tr("Target server is a TeamSpeak server"),
|
tr("Target server is a TeamSpeak server"),
|
||||||
MessageHelper.formatMessage(tr("The target server is a TeamSpeak 3 server!{:br:}Only TeamSpeak 3 based identities are able to connect.{:br:}Please select another profile or change the identify type."))
|
formatMessage(tr("The target server is a TeamSpeak 3 server!{:br:}Only TeamSpeak 3 based identities are able to connect.{:br:}Please select another profile or change the identify type."))
|
||||||
).open();
|
).open();
|
||||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||||
auto_reconnect = false;
|
auto_reconnect = false;
|
||||||
|
@ -478,7 +503,7 @@ class ConnectionHandler {
|
||||||
case DisconnectReason.IDENTITY_TOO_LOW:
|
case DisconnectReason.IDENTITY_TOO_LOW:
|
||||||
createErrorModal(
|
createErrorModal(
|
||||||
tr("Identity level is too low"),
|
tr("Identity level is too low"),
|
||||||
MessageHelper.formatMessage(tr("You've been disconnected, because your Identity level is too low.{:br:}You need at least a level of {0}"), data["extra_message"])
|
formatMessage(tr("You've been disconnected, because your Identity level is too low.{:br:}You need at least a level of {0}"), data["extra_message"])
|
||||||
).open();
|
).open();
|
||||||
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
this.sound.play(Sound.CONNECTION_DISCONNECTED);
|
||||||
|
|
||||||
|
@ -506,7 +531,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.SERVER_CLOSED:
|
case DisconnectReason.SERVER_CLOSED:
|
||||||
this.log.log(log.server.Type.SERVER_CLOSED, {message: data.reasonmsg});
|
this.log.log(server_log.Type.SERVER_CLOSED, {message: data.reasonmsg});
|
||||||
|
|
||||||
createErrorModal(
|
createErrorModal(
|
||||||
tr("Server closed"),
|
tr("Server closed"),
|
||||||
|
@ -518,7 +543,7 @@ class ConnectionHandler {
|
||||||
auto_reconnect = true;
|
auto_reconnect = true;
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
case DisconnectReason.SERVER_REQUIRES_PASSWORD:
|
||||||
this.log.log(log.server.Type.SERVER_REQUIRES_PASSWORD, {});
|
this.log.log(server_log.Type.SERVER_REQUIRES_PASSWORD, {});
|
||||||
|
|
||||||
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
createInputModal(tr("Server password"), tr("Enter server password:"), password => password.length != 0, password => {
|
||||||
if(!(typeof password === "string")) return;
|
if(!(typeof password === "string")) return;
|
||||||
|
@ -540,7 +565,7 @@ class ConnectionHandler {
|
||||||
const have_invoker = typeof(data["invokerid"]) !== "undefined" && parseInt(data["invokerid"]) !== 0;
|
const have_invoker = typeof(data["invokerid"]) !== "undefined" && parseInt(data["invokerid"]) !== 0;
|
||||||
const modal = createErrorModal(
|
const modal = createErrorModal(
|
||||||
tr("You've been kicked"),
|
tr("You've been kicked"),
|
||||||
MessageHelper.formatMessage(
|
formatMessage(
|
||||||
have_invoker ? tr("You've been kicked from the server by {0}:{:br:}{1}") : tr("You've been kicked from the server:{:br:}{1}"),
|
have_invoker ? tr("You've been kicked from the server by {0}:{:br:}{1}") : tr("You've been kicked from the server:{:br:}{1}"),
|
||||||
have_invoker ?
|
have_invoker ?
|
||||||
htmltags.generate_client_object({ client_id: parseInt(data["invokerid"]), client_unique_id: data["invokeruid"], client_name: data["invokername"]}) :
|
htmltags.generate_client_object({ client_id: parseInt(data["invokerid"]), client_unique_id: data["invokeruid"], client_name: data["invokername"]}) :
|
||||||
|
@ -559,7 +584,7 @@ class ConnectionHandler {
|
||||||
this.sound.play(Sound.CONNECTION_BANNED);
|
this.sound.play(Sound.CONNECTION_BANNED);
|
||||||
break;
|
break;
|
||||||
case DisconnectReason.CLIENT_BANNED:
|
case DisconnectReason.CLIENT_BANNED:
|
||||||
this.log.log(log.server.Type.SERVER_BANNED, {
|
this.log.log(server_log.Type.SERVER_BANNED, {
|
||||||
invoker: {
|
invoker: {
|
||||||
client_name: data["invokername"],
|
client_name: data["invokername"],
|
||||||
client_id: parseInt(data["invokerid"]),
|
client_id: parseInt(data["invokerid"]),
|
||||||
|
@ -592,7 +617,7 @@ class ConnectionHandler {
|
||||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect but cant reconnect because we dont have any information left..."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.log.log(log.server.Type.RECONNECT_SCHEDULED, {timeout: 5000});
|
this.log.log(server_log.Type.RECONNECT_SCHEDULED, {timeout: 5000});
|
||||||
|
|
||||||
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
log.info(LogCategory.NETWORKING, tr("Allowed to auto reconnect. Reconnecting in 5000ms"));
|
||||||
const server_address = this.serverConnection.remote_address();
|
const server_address = this.serverConnection.remote_address();
|
||||||
|
@ -600,7 +625,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
this._reconnect_timer = setTimeout(() => {
|
this._reconnect_timer = setTimeout(() => {
|
||||||
this._reconnect_timer = undefined;
|
this._reconnect_timer = undefined;
|
||||||
this.log.log(log.server.Type.RECONNECT_EXECUTE, {});
|
this.log.log(server_log.Type.RECONNECT_EXECUTE, {});
|
||||||
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
|
log.info(LogCategory.NETWORKING, tr("Reconnecting..."));
|
||||||
|
|
||||||
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
|
this.startConnection(server_address.host + ":" + server_address.port, profile, false, Object.assign(this.reconnect_properties(profile), {auto_reconnect_attempt: true}));
|
||||||
|
@ -610,7 +635,7 @@ class ConnectionHandler {
|
||||||
|
|
||||||
cancel_reconnect(log_event: boolean) {
|
cancel_reconnect(log_event: boolean) {
|
||||||
if(this._reconnect_timer) {
|
if(this._reconnect_timer) {
|
||||||
if(log_event) this.log.log(log.server.Type.RECONNECT_CANCELED, {});
|
if(log_event) this.log.log(server_log.Type.RECONNECT_CANCELED, {});
|
||||||
clearTimeout(this._reconnect_timer);
|
clearTimeout(this._reconnect_timer);
|
||||||
this._reconnect_timer = undefined;
|
this._reconnect_timer = undefined;
|
||||||
}
|
}
|
||||||
|
@ -665,7 +690,7 @@ class ConnectionHandler {
|
||||||
if(Object.keys(property_update).length > 0) {
|
if(Object.keys(property_update).length > 0) {
|
||||||
this.serverConnection.send_command("clientupdate", property_update).catch(error => {
|
this.serverConnection.send_command("clientupdate", property_update).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to update client audio hardware properties. Error: %o"), error);
|
||||||
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update audio hardware properties.")});
|
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to update audio hardware properties.")});
|
||||||
|
|
||||||
/* Update these properties anyways (for case the server fails to handle the command) */
|
/* Update these properties anyways (for case the server fails to handle the command) */
|
||||||
const updates = [];
|
const updates = [];
|
||||||
|
@ -708,15 +733,15 @@ class ConnectionHandler {
|
||||||
const input = vconnection.voice_recorder().input;
|
const input = vconnection.voice_recorder().input;
|
||||||
if(input) {
|
if(input) {
|
||||||
if(active && this.serverConnection.connected()) {
|
if(active && this.serverConnection.connected()) {
|
||||||
if(input.current_state() === audio.recorder.InputState.PAUSED) {
|
if(input.current_state() === InputState.PAUSED) {
|
||||||
input.start().then(result => {
|
input.start().then(result => {
|
||||||
if(result != audio.recorder.InputStartResult.EOK)
|
if(result != InputStartResult.EOK)
|
||||||
throw result;
|
throw result;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
|
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
|
||||||
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
|
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
|
||||||
this._last_record_error_popup = Date.now();
|
this._last_record_error_popup = Date.now();
|
||||||
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
|
createErrorModal(tr("Failed to start recording"), formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -741,7 +766,7 @@ class ConnectionHandler {
|
||||||
client_output_hardware: this.client_status.sound_playback_supported
|
client_output_hardware: this.client_status.sound_playback_supported
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to sync handler state with server. Error: %o"), error);
|
||||||
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
|
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to sync handler state with server.")});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,7 +786,7 @@ class ConnectionHandler {
|
||||||
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
|
client_away_message: typeof(this.client_status.away) === "string" ? this.client_status.away : "",
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
|
log.warn(LogCategory.GENERAL, tr("Failed to update away status. Error: %o"), error);
|
||||||
this.log.log(log.server.Type.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
|
this.log.log(server_log.Type.ERROR_CUSTOM, {message: tr("Failed to update away status.")});
|
||||||
});
|
});
|
||||||
|
|
||||||
control_bar.update_button_away();
|
control_bar.update_button_away();
|
||||||
|
@ -781,10 +806,10 @@ class ConnectionHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnect_properties(profile?: profiles.ConnectionProfile) : ConnectParameters {
|
reconnect_properties(profile?: ConnectionProfile) : ConnectParameters {
|
||||||
const name = (this.getClient() ? this.getClient().clientNickName() : "") ||
|
const name = (this.getClient() ? this.getClient().clientNickName() : "") ||
|
||||||
(this.serverConnection && this.serverConnection.handshake_handler() ? this.serverConnection.handshake_handler().parameters.nickname : "") ||
|
(this.serverConnection && this.serverConnection.handshake_handler() ? this.serverConnection.handshake_handler().parameters.nickname : "") ||
|
||||||
settings.static_global(Settings.KEY_CONNECT_USERNAME, profile ? profile.default_username : undefined) ||
|
StaticSettings.instance.static(Settings.KEY_CONNECT_USERNAME, profile ? profile.default_username : undefined) ||
|
||||||
"Another TeaSpeak user";
|
"Another TeaSpeak user";
|
||||||
const channel = (this.getClient() && this.getClient().currentChannel() ? this.getClient().currentChannel().channelId : 0) ||
|
const channel = (this.getClient() && this.getClient().currentChannel() ? this.getClient().currentChannel().channelId : 0) ||
|
||||||
(this.serverConnection && this.serverConnection.handshake_handler() ? (this.serverConnection.handshake_handler().parameters.channel || {} as any).target : "");
|
(this.serverConnection && this.serverConnection.handshake_handler() ? (this.serverConnection.handshake_handler().parameters.channel || {} as any).target : "");
|
||||||
|
@ -798,7 +823,7 @@ class ConnectionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
update_avatar() {
|
update_avatar() {
|
||||||
Modals.spawnAvatarUpload(data => {
|
spawnAvatarUpload(data => {
|
||||||
if(typeof(data) === "undefined")
|
if(typeof(data) === "undefined")
|
||||||
return;
|
return;
|
||||||
if(data === null) {
|
if(data === null) {
|
||||||
|
@ -814,16 +839,16 @@ class ConnectionHandler {
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if(error instanceof CommandResult)
|
if(error instanceof CommandResult)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to delete avatar.{:br:}Error: {0}"), error.extra_message || error.message);
|
message = formatMessage(tr("Failed to delete avatar.{:br:}Error: {0}"), error.extra_message || error.message);
|
||||||
if(!message)
|
if(!message)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to delete avatar.{:br:}Lookup the console for more details"));
|
message = formatMessage(tr("Failed to delete avatar.{:br:}Lookup the console for more details"));
|
||||||
createErrorModal(tr("Failed to delete avatar"), message).open();
|
createErrorModal(tr("Failed to delete avatar"), message).open();
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info(LogCategory.CLIENT, tr("Uploading new avatar"));
|
log.info(LogCategory.CLIENT, tr("Uploading new avatar"));
|
||||||
(async () => {
|
(async () => {
|
||||||
let key: transfer.UploadKey;
|
let key: UploadKey;
|
||||||
try {
|
try {
|
||||||
key = await this.fileManager.upload_file({
|
key = await this.fileManager.upload_file({
|
||||||
size: data.byteLength,
|
size: data.byteLength,
|
||||||
|
@ -840,28 +865,28 @@ class ConnectionHandler {
|
||||||
//TODO: Resolve permission name
|
//TODO: Resolve permission name
|
||||||
//i_client_max_avatar_filesize
|
//i_client_max_avatar_filesize
|
||||||
if(error.id == ErrorID.PERMISSION_ERROR) {
|
if(error.id == ErrorID.PERMISSION_ERROR) {
|
||||||
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Missing permission {0}"), error["failed_permid"]);
|
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Missing permission {0}"), error["failed_permid"]);
|
||||||
} else {
|
} else {
|
||||||
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Error: {0}"), error.extra_message || error.message);
|
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Error: {0}"), error.extra_message || error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!message)
|
if(!message)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
|
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
|
||||||
createErrorModal(tr("Failed to upload avatar"), message).open();
|
createErrorModal(tr("Failed to upload avatar"), message).open();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await transfer.spawn_upload_transfer(key).put_data(data);
|
await spawn_upload_transfer(key).put_data(data);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error);
|
log.error(LogCategory.GENERAL, tr("Failed to upload avatar: %o"), error);
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if(typeof(error) === "string")
|
if(typeof(error) === "string")
|
||||||
message = MessageHelper.formatMessage(tr("Failed to upload avatar.{:br:}Error: {0}"), error);
|
message = formatMessage(tr("Failed to upload avatar.{:br:}Error: {0}"), error);
|
||||||
|
|
||||||
if(!message)
|
if(!message)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
|
message = formatMessage(tr("Failed to initialize avatar upload.{:br:}Lookup the console for more details"));
|
||||||
createErrorModal(tr("Failed to upload avatar"), message).open();
|
createErrorModal(tr("Failed to upload avatar"), message).open();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -874,9 +899,9 @@ class ConnectionHandler {
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if(error instanceof CommandResult)
|
if(error instanceof CommandResult)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to update avatar flag.{:br:}Error: {0}"), error.extra_message || error.message);
|
message = formatMessage(tr("Failed to update avatar flag.{:br:}Error: {0}"), error.extra_message || error.message);
|
||||||
if(!message)
|
if(!message)
|
||||||
message = MessageHelper.formatMessage(tr("Failed to update avatar flag.{:br:}Lookup the console for more details"));
|
message = formatMessage(tr("Failed to update avatar flag.{:br:}Lookup the console for more details"));
|
||||||
createErrorModal(tr("Failed to set avatar"), message).open();
|
createErrorModal(tr("Failed to set avatar"), message).open();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,81 @@
|
||||||
/// <reference path="connection/CommandHandler.ts" />
|
import * as log from "tc-shared/log";
|
||||||
/// <reference path="connection/ConnectionBase.ts" />
|
import * as hex from "tc-shared/crypto/hex";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {ChannelEntry} from "tc-shared/ui/channel";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {ClientEntry} from "tc-shared/ui/client";
|
||||||
|
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
class FileEntry {
|
export class FileEntry {
|
||||||
name: string;
|
name: string;
|
||||||
datetime: number;
|
datetime: number;
|
||||||
type: number;
|
type: number;
|
||||||
size: number;
|
size: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileListRequest {
|
export class FileListRequest {
|
||||||
path: string;
|
path: string;
|
||||||
entries: FileEntry[];
|
entries: FileEntry[];
|
||||||
|
|
||||||
callback: (entries: FileEntry[]) => void;
|
callback: (entries: FileEntry[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace transfer {
|
export interface TransferKey {
|
||||||
export interface TransferKey {
|
client_transfer_id: number;
|
||||||
client_transfer_id: number;
|
server_transfer_id: number;
|
||||||
server_transfer_id: number;
|
|
||||||
|
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
file_path: string;
|
file_path: string;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
|
|
||||||
peer: {
|
peer: {
|
||||||
hosts: string[],
|
hosts: string[],
|
||||||
port: number;
|
port: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
total_size: number;
|
total_size: number;
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadOptions {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
channel?: ChannelEntry;
|
|
||||||
channel_password?: string;
|
|
||||||
|
|
||||||
size: number;
|
|
||||||
overwrite: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadTransfer {
|
|
||||||
get_key() : DownloadKey;
|
|
||||||
|
|
||||||
request_file() : Promise<Response>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadTransfer {
|
|
||||||
get_key(): UploadKey;
|
|
||||||
|
|
||||||
put_data(data: BlobPart | File) : Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DownloadKey = TransferKey;
|
|
||||||
export type UploadKey = TransferKey;
|
|
||||||
|
|
||||||
export function spawn_download_transfer(key: DownloadKey) : DownloadTransfer {
|
|
||||||
return new RequestFileDownload(key);
|
|
||||||
}
|
|
||||||
export function spawn_upload_transfer(key: UploadKey) : UploadTransfer {
|
|
||||||
return new RequestFileUpload(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestFileDownload implements transfer.DownloadTransfer {
|
export interface UploadOptions {
|
||||||
readonly transfer_key: transfer.DownloadKey;
|
name: string;
|
||||||
|
path: string;
|
||||||
|
|
||||||
constructor(key: transfer.DownloadKey) {
|
channel?: ChannelEntry;
|
||||||
|
channel_password?: string;
|
||||||
|
|
||||||
|
size: number;
|
||||||
|
overwrite: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadTransfer {
|
||||||
|
get_key() : DownloadKey;
|
||||||
|
|
||||||
|
request_file() : Promise<Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadTransfer {
|
||||||
|
get_key(): UploadKey;
|
||||||
|
|
||||||
|
put_data(data: BlobPart | File) : Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DownloadKey = TransferKey;
|
||||||
|
export type UploadKey = TransferKey;
|
||||||
|
|
||||||
|
export function spawn_download_transfer(key: DownloadKey) : DownloadTransfer {
|
||||||
|
return new RequestFileDownload(key);
|
||||||
|
}
|
||||||
|
export function spawn_upload_transfer(key: UploadKey) : UploadTransfer {
|
||||||
|
return new RequestFileUpload(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RequestFileDownload implements DownloadTransfer {
|
||||||
|
readonly transfer_key: DownloadKey;
|
||||||
|
|
||||||
|
constructor(key: DownloadKey) {
|
||||||
this.transfer_key = key;
|
this.transfer_key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,18 +102,18 @@ class RequestFileDownload implements transfer.DownloadTransfer {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_key(): transfer.DownloadKey {
|
get_key(): DownloadKey {
|
||||||
return this.transfer_key;
|
return this.transfer_key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RequestFileUpload implements transfer.UploadTransfer {
|
export class RequestFileUpload implements UploadTransfer {
|
||||||
readonly transfer_key: transfer.UploadKey;
|
readonly transfer_key: UploadKey;
|
||||||
constructor(key: transfer.DownloadKey) {
|
constructor(key: DownloadKey) {
|
||||||
this.transfer_key = key;
|
this.transfer_key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_key(): transfer.UploadKey {
|
get_key(): UploadKey {
|
||||||
return this.transfer_key;
|
return this.transfer_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +157,14 @@ class RequestFileUpload implements transfer.UploadTransfer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileManager extends connection.AbstractCommandHandler {
|
export class FileManager extends AbstractCommandHandler {
|
||||||
handle: ConnectionHandler;
|
handle: ConnectionHandler;
|
||||||
icons: IconManager;
|
icons: IconManager;
|
||||||
avatars: AvatarManager;
|
avatars: AvatarManager;
|
||||||
|
|
||||||
private listRequests: FileListRequest[] = [];
|
private listRequests: FileListRequest[] = [];
|
||||||
private pending_download_requests: transfer.DownloadKey[] = [];
|
private pending_download_requests: DownloadKey[] = [];
|
||||||
private pending_upload_requests: transfer.UploadKey[] = [];
|
private pending_upload_requests: UploadKey[] = [];
|
||||||
|
|
||||||
private transfer_counter : number = 1;
|
private transfer_counter : number = 1;
|
||||||
|
|
||||||
|
@ -191,7 +196,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
this.avatars = undefined;
|
this.avatars = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
handle_command(command: ServerCommand): boolean {
|
||||||
switch (command.command) {
|
switch (command.command) {
|
||||||
case "notifyfilelist":
|
case "notifyfilelist":
|
||||||
this.notifyFileList(command.arguments);
|
this.notifyFileList(command.arguments);
|
||||||
|
@ -276,15 +281,15 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
|
|
||||||
|
|
||||||
/******************************** File download/upload ********************************/
|
/******************************** File download/upload ********************************/
|
||||||
download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise<transfer.DownloadKey> {
|
download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise<DownloadKey> {
|
||||||
const transfer_data: transfer.DownloadKey = {
|
const transfer_data: DownloadKey = {
|
||||||
file_name: file,
|
file_name: file,
|
||||||
file_path: path,
|
file_path: path,
|
||||||
client_transfer_id: this.transfer_counter++
|
client_transfer_id: this.transfer_counter++
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
this.pending_download_requests.push(transfer_data);
|
this.pending_download_requests.push(transfer_data);
|
||||||
return new Promise<transfer.DownloadKey>((resolve, reject) => {
|
return new Promise<DownloadKey>((resolve, reject) => {
|
||||||
transfer_data["_callback"] = resolve;
|
transfer_data["_callback"] = resolve;
|
||||||
this.handle.serverConnection.send_command("ftinitdownload", {
|
this.handle.serverConnection.send_command("ftinitdownload", {
|
||||||
"path": path,
|
"path": path,
|
||||||
|
@ -301,8 +306,8 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
upload_file(options: transfer.UploadOptions) : Promise<transfer.UploadKey> {
|
upload_file(options: UploadOptions) : Promise<UploadKey> {
|
||||||
const transfer_data: transfer.UploadKey = {
|
const transfer_data: UploadKey = {
|
||||||
file_path: options.path,
|
file_path: options.path,
|
||||||
file_name: options.name,
|
file_name: options.name,
|
||||||
client_transfer_id: this.transfer_counter++,
|
client_transfer_id: this.transfer_counter++,
|
||||||
|
@ -310,7 +315,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
this.pending_upload_requests.push(transfer_data);
|
this.pending_upload_requests.push(transfer_data);
|
||||||
return new Promise<transfer.UploadKey>((resolve, reject) => {
|
return new Promise<UploadKey>((resolve, reject) => {
|
||||||
transfer_data["_callback"] = resolve;
|
transfer_data["_callback"] = resolve;
|
||||||
this.handle.serverConnection.send_command("ftinitupload", {
|
this.handle.serverConnection.send_command("ftinitupload", {
|
||||||
"path": options.path,
|
"path": options.path,
|
||||||
|
@ -333,7 +338,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
json = json[0];
|
json = json[0];
|
||||||
|
|
||||||
let clientftfid = parseInt(json["clientftfid"]);
|
let clientftfid = parseInt(json["clientftfid"]);
|
||||||
let transfer: transfer.DownloadKey;
|
let transfer: DownloadKey;
|
||||||
for(let e of this.pending_download_requests)
|
for(let e of this.pending_download_requests)
|
||||||
if(e.client_transfer_id == clientftfid) {
|
if(e.client_transfer_id == clientftfid) {
|
||||||
transfer = e;
|
transfer = e;
|
||||||
|
@ -355,14 +360,14 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
|
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
|
||||||
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
|
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
|
||||||
|
|
||||||
(transfer["_callback"] as (val: transfer.DownloadKey) => void)(transfer);
|
(transfer["_callback"] as (val: DownloadKey) => void)(transfer);
|
||||||
this.pending_download_requests.remove(transfer);
|
this.pending_download_requests.remove(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private notifyStartUpload(json) {
|
private notifyStartUpload(json) {
|
||||||
json = json[0];
|
json = json[0];
|
||||||
|
|
||||||
let transfer: transfer.UploadKey;
|
let transfer: UploadKey;
|
||||||
let clientftfid = parseInt(json["clientftfid"]);
|
let clientftfid = parseInt(json["clientftfid"]);
|
||||||
for(let e of this.pending_upload_requests)
|
for(let e of this.pending_upload_requests)
|
||||||
if(e.client_transfer_id == clientftfid) {
|
if(e.client_transfer_id == clientftfid) {
|
||||||
|
@ -384,7 +389,7 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
|
if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0')
|
||||||
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
|
transfer.peer.hosts[0] = this.handle.serverConnection.remote_address().host;
|
||||||
|
|
||||||
(transfer["_callback"] as (val: transfer.UploadKey) => void)(transfer);
|
(transfer["_callback"] as (val: UploadKey) => void)(transfer);
|
||||||
this.pending_upload_requests.remove(transfer);
|
this.pending_upload_requests.remove(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,12 +416,12 @@ class FileManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Icon {
|
export class Icon {
|
||||||
id: number;
|
id: number;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ImageType {
|
export enum ImageType {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
BITMAP,
|
BITMAP,
|
||||||
PNG,
|
PNG,
|
||||||
|
@ -425,7 +430,7 @@ enum ImageType {
|
||||||
JPEG
|
JPEG
|
||||||
}
|
}
|
||||||
|
|
||||||
function media_image_type(type: ImageType, file?: boolean) {
|
export function media_image_type(type: ImageType, file?: boolean) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ImageType.BITMAP:
|
case ImageType.BITMAP:
|
||||||
return "bmp";
|
return "bmp";
|
||||||
|
@ -442,7 +447,7 @@ function media_image_type(type: ImageType, file?: boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean) {
|
export function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean) {
|
||||||
const ab2str10 = () => {
|
const ab2str10 = () => {
|
||||||
const buf = new Uint8Array(encoded_data as ArrayBuffer);
|
const buf = new Uint8Array(encoded_data as ArrayBuffer);
|
||||||
if(buf.byteLength < 10)
|
if(buf.byteLength < 10)
|
||||||
|
@ -472,7 +477,7 @@ function image_type(encoded_data: string | ArrayBuffer, base64_encoded?: boolean
|
||||||
return ImageType.UNKNOWN;
|
return ImageType.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CacheManager {
|
export class CacheManager {
|
||||||
readonly cache_name: string;
|
readonly cache_name: string;
|
||||||
|
|
||||||
private _cache_category: Cache;
|
private _cache_category: Cache;
|
||||||
|
@ -547,7 +552,7 @@ class CacheManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IconManager {
|
export class IconManager {
|
||||||
private static cache: CacheManager = new CacheManager("icons");
|
private static cache: CacheManager = new CacheManager("icons");
|
||||||
|
|
||||||
handle: FileManager;
|
handle: FileManager;
|
||||||
|
@ -590,7 +595,7 @@ class IconManager {
|
||||||
return this.handle.requestFileList("/icons");
|
return this.handle.requestFileList("/icons");
|
||||||
}
|
}
|
||||||
|
|
||||||
create_icon_download(id: number) : Promise<transfer.DownloadKey> {
|
create_icon_download(id: number) : Promise<DownloadKey> {
|
||||||
return this.handle.download_file("", "/icon_" + id);
|
return this.handle.download_file("", "/icon_" + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,7 +670,7 @@ class IconManager {
|
||||||
|
|
||||||
private async _load_icon(id: number) : Promise<Icon> {
|
private async _load_icon(id: number) : Promise<Icon> {
|
||||||
try {
|
try {
|
||||||
let download_key: transfer.DownloadKey;
|
let download_key: DownloadKey;
|
||||||
try {
|
try {
|
||||||
download_key = await this.create_icon_download(id);
|
download_key = await this.create_icon_download(id);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
|
@ -673,7 +678,7 @@ class IconManager {
|
||||||
throw "Failed to request icon";
|
throw "Failed to request icon";
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloader = transfer.spawn_download_transfer(download_key);
|
const downloader = spawn_download_transfer(download_key);
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await downloader.request_file();
|
response = await downloader.request_file();
|
||||||
|
@ -801,14 +806,14 @@ class IconManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Avatar {
|
export class Avatar {
|
||||||
client_avatar_id: string; /* the base64 uid thing from a-m */
|
client_avatar_id: string; /* the base64 uid thing from a-m */
|
||||||
avatar_id: string; /* client_flag_avatar */
|
avatar_id: string; /* client_flag_avatar */
|
||||||
url: string;
|
url: string;
|
||||||
type: ImageType;
|
type: ImageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AvatarManager {
|
export class AvatarManager {
|
||||||
handle: FileManager;
|
handle: FileManager;
|
||||||
|
|
||||||
private static cache: CacheManager;
|
private static cache: CacheManager;
|
||||||
|
@ -867,14 +872,14 @@ class AvatarManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
create_avatar_download(client_avatar_id: string) : Promise<transfer.DownloadKey> {
|
create_avatar_download(client_avatar_id: string) : Promise<DownloadKey> {
|
||||||
log.debug(LogCategory.GENERAL, "Requesting download for avatar %s", client_avatar_id);
|
log.debug(LogCategory.GENERAL, "Requesting download for avatar %s", client_avatar_id);
|
||||||
return this.handle.download_file("", "/avatar_" + client_avatar_id);
|
return this.handle.download_file("", "/avatar_" + client_avatar_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _load_avatar(client_avatar_id: string, avatar_version: string) {
|
private async _load_avatar(client_avatar_id: string, avatar_version: string) {
|
||||||
try {
|
try {
|
||||||
let download_key: transfer.DownloadKey;
|
let download_key: DownloadKey;
|
||||||
try {
|
try {
|
||||||
download_key = await this.create_avatar_download(client_avatar_id);
|
download_key = await this.create_avatar_download(client_avatar_id);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
|
@ -882,7 +887,7 @@ class AvatarManager {
|
||||||
throw "failed to request avatar download";
|
throw "failed to request avatar download";
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloader = transfer.spawn_download_transfer(download_key);
|
const downloader = spawn_download_transfer(download_key);
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await downloader.request_file();
|
response = await downloader.request_file();
|
||||||
|
|
|
@ -1,250 +1,281 @@
|
||||||
namespace messages.formatter {
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
export namespace bbcode {
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]";
|
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||||
const sanitizer_escaped_regex = /\[-- sescaped: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
const sanitizer_escaped_map: {[key: string]: string} = {};
|
import * as loader from "tc-loader";
|
||||||
|
import * as image_preview from "./ui/frames/image_preview"
|
||||||
|
import * as DOMPurify from "dompurify";
|
||||||
|
|
||||||
const yt_url_regex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
|
declare const xbbcode;
|
||||||
|
export namespace bbcode {
|
||||||
|
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]";
|
||||||
|
const sanitizer_escaped_regex = /\[-- sescaped: ([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}) --]/;
|
||||||
|
const sanitizer_escaped_map: {[key: string]: string} = {};
|
||||||
|
|
||||||
export interface FormatSettings {
|
const yt_url_regex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
|
||||||
is_chat_message?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function format(message: string, fsettings?: FormatSettings) : JQuery[] {
|
export interface FormatSettings {
|
||||||
fsettings = fsettings || {};
|
is_chat_message?: boolean
|
||||||
|
|
||||||
single_url_parse:
|
|
||||||
if(fsettings.is_chat_message) {
|
|
||||||
/* try if its only one url */
|
|
||||||
const raw_url = message.replace(/\[url(=\S+)?](\S+)\[\/url]/, "$2");
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL(raw_url);
|
|
||||||
} catch(error) {
|
|
||||||
break single_url_parse;
|
|
||||||
}
|
|
||||||
|
|
||||||
single_url_yt:
|
|
||||||
{
|
|
||||||
const result = raw_url.match(yt_url_regex);
|
|
||||||
if(!result) break single_url_yt;
|
|
||||||
|
|
||||||
return format("[yt]https://www.youtube.com/watch?v=" + result[5] + "[/yt]");
|
|
||||||
}
|
|
||||||
|
|
||||||
single_url_image:
|
|
||||||
{
|
|
||||||
const ext_index = url.pathname.lastIndexOf(".");
|
|
||||||
if(ext_index == -1) break single_url_image;
|
|
||||||
|
|
||||||
const ext_name = url.pathname.substr(ext_index + 1).toLowerCase();
|
|
||||||
if([
|
|
||||||
"jpeg", "jpg",
|
|
||||||
"png", "bmp", "gif",
|
|
||||||
"tiff", "pdf", "svg"
|
|
||||||
].findIndex(e => e === ext_name) == -1) break single_url_image;
|
|
||||||
|
|
||||||
return format("[img]" + message + "[/img]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = xbbcode.parse(message, {
|
|
||||||
tag_whitelist: [
|
|
||||||
"b", "big",
|
|
||||||
"i", "italic",
|
|
||||||
"u", "underlined",
|
|
||||||
"s", "strikethrough",
|
|
||||||
"color",
|
|
||||||
"url",
|
|
||||||
"code",
|
|
||||||
"i-code", "icode",
|
|
||||||
"sub", "sup",
|
|
||||||
"size",
|
|
||||||
"hr", "br",
|
|
||||||
|
|
||||||
"ul", "ol", "list",
|
|
||||||
"li",
|
|
||||||
|
|
||||||
"table",
|
|
||||||
"tr", "td", "th",
|
|
||||||
|
|
||||||
"yt", "youtube",
|
|
||||||
"img"
|
|
||||||
]
|
|
||||||
});
|
|
||||||
let html = result.build_html();
|
|
||||||
if(typeof(window.twemoji) !== "undefined" && settings.static_global(Settings.KEY_CHAT_COLORED_EMOJIES))
|
|
||||||
html = twemoji.parse(html);
|
|
||||||
|
|
||||||
const container = $.spawn("div");
|
|
||||||
let sanitized = DOMPurify.sanitize(html, {
|
|
||||||
ADD_ATTR: [
|
|
||||||
"x-highlight-type",
|
|
||||||
"x-code-type",
|
|
||||||
"x-image-url"
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
sanitized = sanitized.replace(sanitizer_escaped_regex, data => {
|
|
||||||
const uid = data.match(sanitizer_escaped_regex)[1];
|
|
||||||
const value = sanitizer_escaped_map[uid];
|
|
||||||
if(!value) return data;
|
|
||||||
delete sanitizer_escaped_map[uid];
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
container[0].innerHTML = sanitized;
|
|
||||||
|
|
||||||
|
|
||||||
container.find("a")
|
|
||||||
.attr('target', "_blank")
|
|
||||||
.on('contextmenu', event => {
|
|
||||||
if(event.isDefaultPrevented()) return;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const url = $(event.target).attr("href");
|
|
||||||
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
|
|
||||||
callback: () => {
|
|
||||||
const win = window.open(url, '_blank');
|
|
||||||
win.focus();
|
|
||||||
},
|
|
||||||
name: tr("Open URL"),
|
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
|
||||||
icon_class: "client-browse-addon-online"
|
|
||||||
}, {
|
|
||||||
callback: () => {
|
|
||||||
//TODO
|
|
||||||
},
|
|
||||||
name: tr("Open URL in Browser"),
|
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
|
||||||
visible: !app.is_web() && false // Currently not possible
|
|
||||||
}, contextmenu.Entry.HR(), {
|
|
||||||
callback: () => copy_to_clipboard(url),
|
|
||||||
name: tr("Copy URL to clipboard"),
|
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
|
||||||
icon_class: "client-copy"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return [container.contents() as JQuery];
|
|
||||||
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function load_image(entry: HTMLImageElement) {
|
|
||||||
const url = decodeURIComponent(entry.getAttribute("x-image-url") || "");
|
|
||||||
const proxy_url = "https://images.weserv.nl/?url=" + encodeURIComponent(url);
|
|
||||||
|
|
||||||
entry.onload = undefined;
|
|
||||||
entry.src = proxy_url;
|
|
||||||
|
|
||||||
const parent = $(entry.parentElement);
|
|
||||||
parent.on('contextmenu', event => {
|
|
||||||
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
|
|
||||||
callback: () => {
|
|
||||||
const win = window.open(url, '_blank');
|
|
||||||
win.focus();
|
|
||||||
},
|
|
||||||
name: tr("Open image in browser"),
|
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
|
||||||
icon_class: "client-browse-addon-online"
|
|
||||||
}, contextmenu.Entry.HR(), {
|
|
||||||
callback: () => copy_to_clipboard(url),
|
|
||||||
name: tr("Copy image URL to clipboard"),
|
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
|
||||||
icon_class: "client-copy"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
parent.css("cursor", "pointer").on('click', event => image_preview.preview_image(proxy_url, url));
|
|
||||||
}
|
|
||||||
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "XBBCode code tag init",
|
|
||||||
function: async () => {
|
|
||||||
/* override default parser */
|
|
||||||
xbbcode.register.register_parser({
|
|
||||||
tag: ["code", "icode", "i-code"],
|
|
||||||
content_tags_whitelist: [],
|
|
||||||
|
|
||||||
build_html(layer) : string {
|
|
||||||
const klass = layer.tag_normalized != 'code' ? "tag-hljs-inline-code" : "tag-hljs-code";
|
|
||||||
const language = (layer.options || "").replace("\"", "'").toLowerCase();
|
|
||||||
|
|
||||||
/* remove heading empty lines */
|
|
||||||
let text = layer.content.map(e => e.build_text())
|
|
||||||
.reduce((a, b) => a.length == 0 && b.replace(/[ \n\r\t]+/g, "").length == 0 ? "" : a + b, "")
|
|
||||||
.replace(/^([ \n\r\t]*)(?=\n)+/g, "");
|
|
||||||
if(text.startsWith("\r") || text.startsWith("\n"))
|
|
||||||
text = text.substr(1);
|
|
||||||
|
|
||||||
let result: HighlightJSResult;
|
|
||||||
if(window.hljs.getLanguage(language))
|
|
||||||
result = window.hljs.highlight(language, text, true);
|
|
||||||
else
|
|
||||||
result = window.hljs.highlightAuto(text);
|
|
||||||
|
|
||||||
let html = '<pre class="' + klass + '">';
|
|
||||||
html += '<code class="hljs" x-code-type="' + language + '" x-highlight-type="' + result.language + '">';
|
|
||||||
html += result.value;
|
|
||||||
return html + "</code></pre>";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* override the yt parser */
|
|
||||||
const original_parser = xbbcode.register.find_parser("yt");
|
|
||||||
if(original_parser)
|
|
||||||
xbbcode.register.register_parser({
|
|
||||||
tag: ["yt", "youtube"],
|
|
||||||
build_html(layer): string {
|
|
||||||
const result = original_parser.build_html(layer);
|
|
||||||
if(!result.startsWith("<iframe")) return result;
|
|
||||||
|
|
||||||
const url = result.match(/src="(\S+)" /)[1];
|
|
||||||
const uid = guid();
|
|
||||||
|
|
||||||
sanitizer_escaped_map[uid] = "<iframe class=\"xbbcode-tag xbbcode-tag-video\" src=\"" + url + "\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>";
|
|
||||||
return sanitizer_escaped(uid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* the image parse & displayer */
|
|
||||||
xbbcode.register.register_parser({
|
|
||||||
tag: ["img", "image"],
|
|
||||||
build_html(layer): string {
|
|
||||||
const uid = guid();
|
|
||||||
const fallback_value = "[img]" + layer.build_text() + "[/img]";
|
|
||||||
|
|
||||||
let target;
|
|
||||||
let content = layer.content.map(e => e.build_text()).join("");
|
|
||||||
if (!layer.options) {
|
|
||||||
target = content;
|
|
||||||
} else
|
|
||||||
target = layer.options;
|
|
||||||
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL(target);
|
|
||||||
if(!url.hostname) throw "";
|
|
||||||
} catch(error) {
|
|
||||||
return fallback_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
sanitizer_escaped_map[uid] = "<div class='xbbcode-tag-img'><img src='img/loading_image.svg' onload='messages.formatter.bbcode.load_image(this)' x-image-url='" + encodeURIComponent(target) + "' title='" + sanitize_text(target) + "' /></div>";
|
|
||||||
return sanitizer_escaped(uid);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
priority: 10
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize_text(text: string) : string {
|
export function format(message: string, fsettings?: FormatSettings) : JQuery[] {
|
||||||
return $(DOMPurify.sanitize("<a>" + text + "</a>", {
|
fsettings = fsettings || {};
|
||||||
|
|
||||||
|
single_url_parse:
|
||||||
|
if(fsettings.is_chat_message) {
|
||||||
|
/* try if its only one url */
|
||||||
|
const raw_url = message.replace(/\[url(=\S+)?](\S+)\[\/url]/, "$2");
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(raw_url);
|
||||||
|
} catch(error) {
|
||||||
|
break single_url_parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
single_url_yt:
|
||||||
|
{
|
||||||
|
const result = raw_url.match(yt_url_regex);
|
||||||
|
if(!result) break single_url_yt;
|
||||||
|
|
||||||
|
return format("[yt]https://www.youtube.com/watch?v=" + result[5] + "[/yt]");
|
||||||
|
}
|
||||||
|
|
||||||
|
single_url_image:
|
||||||
|
{
|
||||||
|
const ext_index = url.pathname.lastIndexOf(".");
|
||||||
|
if(ext_index == -1) break single_url_image;
|
||||||
|
|
||||||
|
const ext_name = url.pathname.substr(ext_index + 1).toLowerCase();
|
||||||
|
if([
|
||||||
|
"jpeg", "jpg",
|
||||||
|
"png", "bmp", "gif",
|
||||||
|
"tiff", "pdf", "svg"
|
||||||
|
].findIndex(e => e === ext_name) == -1) break single_url_image;
|
||||||
|
|
||||||
|
return format("[img]" + message + "[/img]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = xbbcode.parse(message, {
|
||||||
|
tag_whitelist: [
|
||||||
|
"b", "big",
|
||||||
|
"i", "italic",
|
||||||
|
"u", "underlined",
|
||||||
|
"s", "strikethrough",
|
||||||
|
"color",
|
||||||
|
"url",
|
||||||
|
"code",
|
||||||
|
"i-code", "icode",
|
||||||
|
"sub", "sup",
|
||||||
|
"size",
|
||||||
|
"hr", "br",
|
||||||
|
|
||||||
|
"ul", "ol", "list",
|
||||||
|
"li",
|
||||||
|
|
||||||
|
"table",
|
||||||
|
"tr", "td", "th",
|
||||||
|
|
||||||
|
"yt", "youtube",
|
||||||
|
"img"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
let html = result.build_html();
|
||||||
|
if(typeof(window.twemoji) !== "undefined" && settings.static_global(Settings.KEY_CHAT_COLORED_EMOJIES))
|
||||||
|
html = twemoji.parse(html);
|
||||||
|
|
||||||
|
const container = $.spawn("div");
|
||||||
|
let sanitized = DOMPurify.sanitize(html, {
|
||||||
ADD_ATTR: [
|
ADD_ATTR: [
|
||||||
"x-highlight-type",
|
"x-highlight-type",
|
||||||
"x-code-type",
|
"x-code-type",
|
||||||
"x-image-url"
|
"x-image-url"
|
||||||
]
|
]
|
||||||
})).text();
|
});
|
||||||
|
|
||||||
|
sanitized = sanitized.replace(sanitizer_escaped_regex, data => {
|
||||||
|
const uid = data.match(sanitizer_escaped_regex)[1];
|
||||||
|
const value = sanitizer_escaped_map[uid];
|
||||||
|
if(!value) return data;
|
||||||
|
delete sanitizer_escaped_map[uid];
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
container[0].innerHTML = sanitized;
|
||||||
|
|
||||||
|
|
||||||
|
container.find("a")
|
||||||
|
.attr('target', "_blank")
|
||||||
|
.on('contextmenu', event => {
|
||||||
|
if(event.isDefaultPrevented()) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const url = $(event.target).attr("href");
|
||||||
|
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
|
||||||
|
callback: () => {
|
||||||
|
const win = window.open(url, '_blank');
|
||||||
|
win.focus();
|
||||||
|
},
|
||||||
|
name: tr("Open URL"),
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
icon_class: "client-browse-addon-online"
|
||||||
|
}, {
|
||||||
|
callback: () => {
|
||||||
|
//TODO
|
||||||
|
},
|
||||||
|
name: tr("Open URL in Browser"),
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
visible: __build.target === "client" && false // Currently not possible
|
||||||
|
}, contextmenu.Entry.HR(), {
|
||||||
|
callback: () => copy_to_clipboard(url),
|
||||||
|
name: tr("Copy URL to clipboard"),
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
icon_class: "client-copy"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [container.contents() as JQuery];
|
||||||
|
//return result.root_tag.content.map(e => e.build_html()).map((entry, idx, array) => $.spawn("a").css("display", (idx == 0 ? "inline" : "") + "block").html(entry == "" && idx != 0 ? " " : entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function load_image(entry: HTMLImageElement) {
|
||||||
|
const url = decodeURIComponent(entry.getAttribute("x-image-url") || "");
|
||||||
|
const proxy_url = "https://images.weserv.nl/?url=" + encodeURIComponent(url);
|
||||||
|
|
||||||
|
entry.onload = undefined;
|
||||||
|
entry.src = proxy_url;
|
||||||
|
|
||||||
|
const parent = $(entry.parentElement);
|
||||||
|
parent.on('contextmenu', event => {
|
||||||
|
contextmenu.spawn_context_menu(event.pageX, event.pageY, {
|
||||||
|
callback: () => {
|
||||||
|
const win = window.open(url, '_blank');
|
||||||
|
win.focus();
|
||||||
|
},
|
||||||
|
name: tr("Open image in browser"),
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
icon_class: "client-browse-addon-online"
|
||||||
|
}, contextmenu.Entry.HR(), {
|
||||||
|
callback: () => copy_to_clipboard(url),
|
||||||
|
name: tr("Copy image URL to clipboard"),
|
||||||
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
|
icon_class: "client-copy"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
parent.css("cursor", "pointer").on('click', event => image_preview.preview_image(proxy_url, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "XBBCode code tag init",
|
||||||
|
function: async () => {
|
||||||
|
/* override default parser */
|
||||||
|
xbbcode.register.register_parser({
|
||||||
|
tag: ["code", "icode", "i-code"],
|
||||||
|
content_tags_whitelist: [],
|
||||||
|
|
||||||
|
build_html(layer) : string {
|
||||||
|
const klass = layer.tag_normalized != 'code' ? "tag-hljs-inline-code" : "tag-hljs-code";
|
||||||
|
const language = (layer.options || "").replace("\"", "'").toLowerCase();
|
||||||
|
|
||||||
|
/* remove heading empty lines */
|
||||||
|
let text = layer.content.map(e => e.build_text())
|
||||||
|
.reduce((a, b) => a.length == 0 && b.replace(/[ \n\r\t]+/g, "").length == 0 ? "" : a + b, "")
|
||||||
|
.replace(/^([ \n\r\t]*)(?=\n)+/g, "");
|
||||||
|
if(text.startsWith("\r") || text.startsWith("\n"))
|
||||||
|
text = text.substr(1);
|
||||||
|
|
||||||
|
let result: HighlightJSResult;
|
||||||
|
if(window.hljs.getLanguage(language))
|
||||||
|
result = window.hljs.highlight(language, text, true);
|
||||||
|
else
|
||||||
|
result = window.hljs.highlightAuto(text);
|
||||||
|
|
||||||
|
let html = '<pre class="' + klass + '">';
|
||||||
|
html += '<code class="hljs" x-code-type="' + language + '" x-highlight-type="' + result.language + '">';
|
||||||
|
html += result.value;
|
||||||
|
return html + "</code></pre>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* override the yt parser */
|
||||||
|
const original_parser = xbbcode.register.find_parser("yt");
|
||||||
|
if(original_parser)
|
||||||
|
xbbcode.register.register_parser({
|
||||||
|
tag: ["yt", "youtube"],
|
||||||
|
build_html(layer): string {
|
||||||
|
const result = original_parser.build_html(layer);
|
||||||
|
if(!result.startsWith("<iframe")) return result;
|
||||||
|
|
||||||
|
const url = result.match(/src="(\S+)" /)[1];
|
||||||
|
const uid = guid();
|
||||||
|
|
||||||
|
sanitizer_escaped_map[uid] = "<iframe class=\"xbbcode-tag xbbcode-tag-video\" src=\"" + url + "\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>";
|
||||||
|
return sanitizer_escaped(uid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* the image parse & displayer */
|
||||||
|
xbbcode.register.register_parser({
|
||||||
|
tag: ["img", "image"],
|
||||||
|
build_html(layer): string {
|
||||||
|
const uid = guid();
|
||||||
|
const fallback_value = "[img]" + layer.build_text() + "[/img]";
|
||||||
|
|
||||||
|
let target;
|
||||||
|
let content = layer.content.map(e => e.build_text()).join("");
|
||||||
|
if (!layer.options) {
|
||||||
|
target = content;
|
||||||
|
} else
|
||||||
|
target = layer.options;
|
||||||
|
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(target);
|
||||||
|
if(!url.hostname) throw "";
|
||||||
|
} catch(error) {
|
||||||
|
return fallback_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizer_escaped_map[uid] = "<div class='xbbcode-tag-img'><img src='img/loading_image.svg' onload='messages.formatter.bbcode.load_image(this)' x-image-url='" + encodeURIComponent(target) + "' title='" + sanitize_text(target) + "' /></div>";
|
||||||
|
return sanitizer_escaped(uid);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
priority: 10
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitize_text(text: string) : string {
|
||||||
|
return $(DOMPurify.sanitize("<a>" + text + "</a>", {
|
||||||
|
ADD_ATTR: [
|
||||||
|
"x-highlight-type",
|
||||||
|
"x-code-type",
|
||||||
|
"x-image-url"
|
||||||
|
]
|
||||||
|
})).text();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(secs: number) : string {
|
||||||
|
let years = Math.floor(secs / (60 * 60 * 24 * 365));
|
||||||
|
let days = Math.floor(secs / (60 * 60 * 24)) % 365;
|
||||||
|
let hours = Math.floor(secs / (60 * 60)) % 24;
|
||||||
|
let minutes = Math.floor(secs / 60) % 60;
|
||||||
|
let seconds = Math.floor(secs % 60);
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
if(years > 0)
|
||||||
|
result += years + " " + tr("years") + " ";
|
||||||
|
if(years > 0 || days > 0)
|
||||||
|
result += days + " " + tr("days") + " ";
|
||||||
|
if(years > 0 || days > 0 || hours > 0)
|
||||||
|
result += hours + " " + tr("hours") + " ";
|
||||||
|
if(years > 0 || days > 0 || hours > 0 || minutes > 0)
|
||||||
|
result += minutes + " " + tr("minutes") + " ";
|
||||||
|
if(years > 0 || days > 0 || hours > 0 || minutes > 0 || seconds > 0)
|
||||||
|
result += seconds + " " + tr("seconds") + " ";
|
||||||
|
else
|
||||||
|
result = tr("now") + " ";
|
||||||
|
|
||||||
|
return result.substr(0, result.length - 1);
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
enum KeyCode {
|
export enum KeyCode {
|
||||||
KEY_CANCEL = 3,
|
KEY_CANCEL = 3,
|
||||||
KEY_HELP = 6,
|
KEY_HELP = 6,
|
||||||
KEY_BACK_SPACE = 8,
|
KEY_BACK_SPACE = 8,
|
||||||
|
@ -118,59 +118,57 @@ enum KeyCode {
|
||||||
KEY_META = 224
|
KEY_META = 224
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ppt {
|
export enum EventType {
|
||||||
export enum EventType {
|
KEY_PRESS,
|
||||||
KEY_PRESS,
|
KEY_RELEASE,
|
||||||
KEY_RELEASE,
|
KEY_TYPED
|
||||||
KEY_TYPED
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export enum SpecialKey {
|
export enum SpecialKey {
|
||||||
CTRL,
|
CTRL,
|
||||||
WINDOWS,
|
WINDOWS,
|
||||||
SHIFT,
|
SHIFT,
|
||||||
ALT
|
ALT
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyDescriptor {
|
export interface KeyDescriptor {
|
||||||
key_code: string;
|
key_code: string;
|
||||||
|
|
||||||
key_ctrl: boolean;
|
key_ctrl: boolean;
|
||||||
key_windows: boolean;
|
key_windows: boolean;
|
||||||
key_shift: boolean;
|
key_shift: boolean;
|
||||||
key_alt: boolean;
|
key_alt: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyEvent extends KeyDescriptor {
|
export interface KeyEvent extends KeyDescriptor {
|
||||||
readonly type: EventType;
|
readonly type: EventType;
|
||||||
|
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyHook extends KeyDescriptor {
|
export interface KeyHook extends KeyDescriptor {
|
||||||
cancel: boolean;
|
cancel: boolean;
|
||||||
|
|
||||||
|
|
||||||
callback_press: () => any;
|
callback_press: () => any;
|
||||||
callback_release: () => any;
|
callback_release: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function key_description(key: KeyDescriptor) {
|
export function key_description(key: KeyDescriptor) {
|
||||||
let result = "";
|
let result = "";
|
||||||
if(key.key_shift)
|
if(key.key_shift)
|
||||||
result += " + " + tr("Shift");
|
result += " + " + tr("Shift");
|
||||||
if(key.key_alt)
|
if(key.key_alt)
|
||||||
result += " + " + tr("Alt");
|
result += " + " + tr("Alt");
|
||||||
if(key.key_ctrl)
|
if(key.key_ctrl)
|
||||||
result += " + " + tr("CTRL");
|
result += " + " + tr("CTRL");
|
||||||
if(key.key_windows)
|
if(key.key_windows)
|
||||||
result += " + " + tr("Win");
|
result += " + " + tr("Win");
|
||||||
|
|
||||||
if(!result && !key.key_code)
|
if(!result && !key.key_code)
|
||||||
return tr("unset");
|
return tr("unset");
|
||||||
|
|
||||||
if(key.key_code)
|
if(key.key_code)
|
||||||
result += " + " + key.key_code;
|
result += " + " + key.key_code;
|
||||||
return result.substr(3);
|
return result.substr(3);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
namespace audio {
|
|
||||||
export namespace player {
|
|
||||||
export interface Device {
|
|
||||||
device_id: string;
|
|
||||||
|
|
||||||
driver: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
6
shared/js/audio/player.ts
Normal file
6
shared/js/audio/player.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface Device {
|
||||||
|
device_id: string;
|
||||||
|
|
||||||
|
driver: string;
|
||||||
|
name: string;
|
||||||
|
}
|
|
@ -1,262 +1,260 @@
|
||||||
namespace bookmarks {
|
import * as log from "tc-shared/log";
|
||||||
function guid() {
|
import {LogCategory} from "tc-shared/log";
|
||||||
function s4() {
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
return Math
|
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
|
||||||
.floor((1 + Math.random()) * 0x10000)
|
import {default_profile, find_profile} from "tc-shared/profiles/ConnectionProfile";
|
||||||
.toString(16)
|
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
||||||
.substring(1);
|
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
|
||||||
}
|
import {control_bar} from "tc-shared/ui/frames/ControlBar";
|
||||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
import * as top_menu from "./ui/frames/MenuBar";
|
||||||
|
|
||||||
|
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
|
||||||
|
const profile = find_profile(mark.connect_profile) || default_profile();
|
||||||
|
if(profile.valid()) {
|
||||||
|
const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler();
|
||||||
|
server_connections.set_active_connection_handler(connection);
|
||||||
|
connection.startConnection(
|
||||||
|
mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||||
|
profile,
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connect_username() : mark.nickname,
|
||||||
|
password: mark.server_properties.server_password_hash ? {
|
||||||
|
password: mark.server_properties.server_password_hash,
|
||||||
|
hashed: true
|
||||||
|
} : mark.server_properties.server_password ? {
|
||||||
|
hashed: false,
|
||||||
|
password: mark.server_properties.server_password
|
||||||
|
} : undefined
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
spawnConnectModal({}, {
|
||||||
|
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
||||||
|
enforce: true
|
||||||
|
}, {
|
||||||
|
profile: profile,
|
||||||
|
enforce: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
|
export interface ServerProperties {
|
||||||
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
|
server_address: string;
|
||||||
if(profile.valid()) {
|
server_port: number;
|
||||||
const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection_handler() : server_connections.spawn_server_connection_handler();
|
server_password_hash?: string;
|
||||||
server_connections.set_active_connection_handler(connection);
|
server_password?: string;
|
||||||
connection.startConnection(
|
}
|
||||||
mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
|
||||||
profile,
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connect_username() : mark.nickname,
|
|
||||||
password: mark.server_properties.server_password_hash ? {
|
|
||||||
password: mark.server_properties.server_password_hash,
|
|
||||||
hashed: true
|
|
||||||
} : mark.server_properties.server_password ? {
|
|
||||||
hashed: false,
|
|
||||||
password: mark.server_properties.server_password
|
|
||||||
} : undefined
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Modals.spawnConnectModal({}, {
|
|
||||||
url: mark.server_properties.server_address + ":" + mark.server_properties.server_port,
|
|
||||||
enforce: true
|
|
||||||
}, {
|
|
||||||
profile: profile,
|
|
||||||
enforce: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ServerProperties {
|
export enum BookmarkType {
|
||||||
server_address: string;
|
ENTRY,
|
||||||
server_port: number;
|
DIRECTORY
|
||||||
server_password_hash?: string;
|
}
|
||||||
server_password?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BookmarkType {
|
export interface Bookmark {
|
||||||
ENTRY,
|
type: /* BookmarkType.ENTRY */ BookmarkType;
|
||||||
DIRECTORY
|
/* readonly */ parent: DirectoryBookmark;
|
||||||
}
|
|
||||||
|
|
||||||
export interface Bookmark {
|
server_properties: ServerProperties;
|
||||||
type: /* BookmarkType.ENTRY */ BookmarkType;
|
display_name: string;
|
||||||
/* readonly */ parent: DirectoryBookmark;
|
unique_id: string;
|
||||||
|
|
||||||
server_properties: ServerProperties;
|
nickname: string;
|
||||||
display_name: string;
|
default_channel?: number | string;
|
||||||
unique_id: string;
|
default_channel_password_hash?: string;
|
||||||
|
default_channel_password?: string;
|
||||||
|
|
||||||
nickname: string;
|
connect_profile: string;
|
||||||
default_channel?: number | string;
|
|
||||||
default_channel_password_hash?: string;
|
|
||||||
default_channel_password?: string;
|
|
||||||
|
|
||||||
connect_profile: string;
|
last_icon_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
last_icon_id?: number;
|
export interface DirectoryBookmark {
|
||||||
}
|
type: /* BookmarkType.DIRECTORY */ BookmarkType;
|
||||||
|
/* readonly */ parent: DirectoryBookmark;
|
||||||
|
|
||||||
export interface DirectoryBookmark {
|
readonly content: (Bookmark | DirectoryBookmark)[];
|
||||||
type: /* BookmarkType.DIRECTORY */ BookmarkType;
|
unique_id: string;
|
||||||
/* readonly */ parent: DirectoryBookmark;
|
display_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
readonly content: (Bookmark | DirectoryBookmark)[];
|
interface BookmarkConfig {
|
||||||
unique_id: string;
|
root_bookmark?: DirectoryBookmark;
|
||||||
display_name: string;
|
default_added?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BookmarkConfig {
|
let _bookmark_config: BookmarkConfig;
|
||||||
root_bookmark?: DirectoryBookmark;
|
|
||||||
default_added?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _bookmark_config: BookmarkConfig;
|
|
||||||
|
|
||||||
function bookmark_config() : BookmarkConfig {
|
|
||||||
if(_bookmark_config)
|
|
||||||
return _bookmark_config;
|
|
||||||
|
|
||||||
let bookmark_json = localStorage.getItem("bookmarks");
|
|
||||||
let bookmarks;
|
|
||||||
try {
|
|
||||||
bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
|
|
||||||
} catch(error) {
|
|
||||||
log.error(LogCategory.BOOKMARKS, tr("Failed to load bookmarks: %o"), error);
|
|
||||||
bookmarks = {} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bookmark_config = bookmarks;
|
|
||||||
_bookmark_config.root_bookmark = _bookmark_config.root_bookmark || { content: [], display_name: "root", type: BookmarkType.DIRECTORY} as DirectoryBookmark;
|
|
||||||
|
|
||||||
if(!_bookmark_config.default_added) {
|
|
||||||
_bookmark_config.default_added = true;
|
|
||||||
create_bookmark("TeaSpeak official Test-Server", _bookmark_config.root_bookmark, {
|
|
||||||
server_address: "ts.teaspeak.de",
|
|
||||||
server_port: 9987
|
|
||||||
}, undefined);
|
|
||||||
|
|
||||||
save_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
const fix_parent = (parent: DirectoryBookmark, entry: Bookmark | DirectoryBookmark) => {
|
|
||||||
entry.parent = parent;
|
|
||||||
if(entry.type === BookmarkType.DIRECTORY)
|
|
||||||
for(const child of (entry as DirectoryBookmark).content)
|
|
||||||
fix_parent(entry as DirectoryBookmark, child);
|
|
||||||
};
|
|
||||||
for(const entry of _bookmark_config.root_bookmark.content)
|
|
||||||
fix_parent(_bookmark_config.root_bookmark, entry);
|
|
||||||
|
|
||||||
|
function bookmark_config() : BookmarkConfig {
|
||||||
|
if(_bookmark_config)
|
||||||
return _bookmark_config;
|
return _bookmark_config;
|
||||||
|
|
||||||
|
let bookmark_json = localStorage.getItem("bookmarks");
|
||||||
|
let bookmarks;
|
||||||
|
try {
|
||||||
|
bookmarks = JSON.parse(bookmark_json) || {} as BookmarkConfig;
|
||||||
|
} catch(error) {
|
||||||
|
log.error(LogCategory.BOOKMARKS, tr("Failed to load bookmarks: %o"), error);
|
||||||
|
bookmarks = {} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_config() {
|
_bookmark_config = bookmarks;
|
||||||
localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => {
|
_bookmark_config.root_bookmark = _bookmark_config.root_bookmark || { content: [], display_name: "root", type: BookmarkType.DIRECTORY} as DirectoryBookmark;
|
||||||
if(key === "parent")
|
|
||||||
return undefined;
|
if(!_bookmark_config.default_added) {
|
||||||
return value;
|
_bookmark_config.default_added = true;
|
||||||
}));
|
create_bookmark("TeaSpeak official Test-Server", _bookmark_config.root_bookmark, {
|
||||||
|
server_address: "ts.teaspeak.de",
|
||||||
|
server_port: 9987
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
save_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bookmarks() : DirectoryBookmark {
|
const fix_parent = (parent: DirectoryBookmark, entry: Bookmark | DirectoryBookmark) => {
|
||||||
return bookmark_config().root_bookmark;
|
entry.parent = parent;
|
||||||
}
|
if(entry.type === BookmarkType.DIRECTORY)
|
||||||
|
for(const child of (entry as DirectoryBookmark).content)
|
||||||
|
fix_parent(entry as DirectoryBookmark, child);
|
||||||
|
};
|
||||||
|
for(const entry of _bookmark_config.root_bookmark.content)
|
||||||
|
fix_parent(_bookmark_config.root_bookmark, entry);
|
||||||
|
|
||||||
export function bookmarks_flat() : Bookmark[] {
|
return _bookmark_config;
|
||||||
const result: Bookmark[] = [];
|
}
|
||||||
const _flat = (bookmark: Bookmark | DirectoryBookmark) => {
|
|
||||||
if(bookmark.type == BookmarkType.DIRECTORY)
|
|
||||||
for(const book of (bookmark as DirectoryBookmark).content)
|
|
||||||
_flat(book);
|
|
||||||
else
|
|
||||||
result.push(bookmark as Bookmark);
|
|
||||||
};
|
|
||||||
_flat(bookmark_config().root_bookmark);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark {
|
function save_config() {
|
||||||
for(const entry of parent.content) {
|
localStorage.setItem("bookmarks", JSON.stringify(bookmark_config(), (key, value) => {
|
||||||
if(entry.unique_id == uuid)
|
if(key === "parent")
|
||||||
return entry;
|
return undefined;
|
||||||
if(entry.type == BookmarkType.DIRECTORY) {
|
return value;
|
||||||
const result = find_bookmark_recursive(entry as DirectoryBookmark, uuid);
|
}));
|
||||||
if(result) return result;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined {
|
export function bookmarks() : DirectoryBookmark {
|
||||||
return find_bookmark_recursive(bookmarks(), uuid);
|
return bookmark_config().root_bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark {
|
export function bookmarks_flat() : Bookmark[] {
|
||||||
const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()];
|
const result: Bookmark[] = [];
|
||||||
while(!books.length) {
|
const _flat = (bookmark: Bookmark | DirectoryBookmark) => {
|
||||||
const directory = books.pop_front();
|
if(bookmark.type == BookmarkType.DIRECTORY)
|
||||||
if(directory.type == BookmarkType.DIRECTORY) {
|
for(const book of (bookmark as DirectoryBookmark).content)
|
||||||
const cast = <DirectoryBookmark>directory;
|
_flat(book);
|
||||||
|
|
||||||
if(cast.content.indexOf(bookmark) != -1)
|
|
||||||
return cast;
|
|
||||||
books.push(...cast.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bookmarks();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark {
|
|
||||||
const bookmark = {
|
|
||||||
display_name: display_name,
|
|
||||||
server_properties: server_properties,
|
|
||||||
nickname: nickname,
|
|
||||||
type: BookmarkType.ENTRY,
|
|
||||||
connect_profile: "default",
|
|
||||||
unique_id: guid(),
|
|
||||||
parent: directory
|
|
||||||
} as Bookmark;
|
|
||||||
|
|
||||||
directory.content.push(bookmark);
|
|
||||||
return bookmark;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function create_bookmark_directory(parent: DirectoryBookmark, name: string) : DirectoryBookmark {
|
|
||||||
const bookmark = {
|
|
||||||
type: BookmarkType.DIRECTORY,
|
|
||||||
|
|
||||||
display_name: name,
|
|
||||||
content: [],
|
|
||||||
unique_id: guid(),
|
|
||||||
parent: parent
|
|
||||||
} as DirectoryBookmark;
|
|
||||||
|
|
||||||
parent.content.push(bookmark);
|
|
||||||
return bookmark;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO test if the new parent is within the old bookmark
|
|
||||||
export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
|
|
||||||
delete_bookmark(bookmark);
|
|
||||||
parent.content.push(bookmark)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function save_bookmark(bookmark?: Bookmark | DirectoryBookmark) {
|
|
||||||
save_config(); /* nvm we dont give a fuck... saving everything */
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_bookmark_recursive(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
|
|
||||||
const index = parent.content.indexOf(bookmark);
|
|
||||||
if(index != -1)
|
|
||||||
parent.content.remove(bookmark);
|
|
||||||
else
|
else
|
||||||
for(const entry of parent.content)
|
result.push(bookmark as Bookmark);
|
||||||
if(entry.type == BookmarkType.DIRECTORY)
|
};
|
||||||
delete_bookmark_recursive(entry as DirectoryBookmark, bookmark)
|
_flat(bookmark_config().root_bookmark);
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function delete_bookmark(bookmark: Bookmark | DirectoryBookmark) {
|
function find_bookmark_recursive(parent: DirectoryBookmark, uuid: string) : Bookmark | DirectoryBookmark {
|
||||||
delete_bookmark_recursive(bookmarks(), bookmark)
|
for(const entry of parent.content) {
|
||||||
}
|
if(entry.unique_id == uuid)
|
||||||
|
return entry;
|
||||||
export function add_current_server() {
|
if(entry.type == BookmarkType.DIRECTORY) {
|
||||||
const ch = server_connections.active_connection_handler();
|
const result = find_bookmark_recursive(entry as DirectoryBookmark, uuid);
|
||||||
if(ch && ch.connected) {
|
if(result) return result;
|
||||||
const ce = ch.getClient();
|
|
||||||
const name = ce ? ce.clientNickName() : undefined;
|
|
||||||
createInputModal(tr("Enter bookmarks name"), tr("Please enter the bookmarks name:<br>"), text => text.length > 0, result => {
|
|
||||||
if(result) {
|
|
||||||
const bookmark = create_bookmark(result as string, bookmarks(), {
|
|
||||||
server_port: ch.serverConnection.remote_address().port,
|
|
||||||
server_address: ch.serverConnection.remote_address().host,
|
|
||||||
|
|
||||||
server_password: "",
|
|
||||||
server_password_hash: ""
|
|
||||||
}, name);
|
|
||||||
save_bookmark(bookmark);
|
|
||||||
|
|
||||||
control_bar.update_bookmarks();
|
|
||||||
top_menu.rebuild_bookmarks();
|
|
||||||
|
|
||||||
createInfoModal(tr("Server added"), tr("Server has been successfully added to your bookmarks.")).open();
|
|
||||||
}
|
|
||||||
}).open();
|
|
||||||
} else {
|
|
||||||
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function find_bookmark(uuid: string) : Bookmark | DirectoryBookmark | undefined {
|
||||||
|
return find_bookmark_recursive(bookmarks(), uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parent_bookmark(bookmark: Bookmark) : DirectoryBookmark {
|
||||||
|
const books: (DirectoryBookmark | Bookmark)[] = [bookmarks()];
|
||||||
|
while(!books.length) {
|
||||||
|
const directory = books.pop_front();
|
||||||
|
if(directory.type == BookmarkType.DIRECTORY) {
|
||||||
|
const cast = <DirectoryBookmark>directory;
|
||||||
|
|
||||||
|
if(cast.content.indexOf(bookmark) != -1)
|
||||||
|
return cast;
|
||||||
|
books.push(...cast.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bookmarks();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create_bookmark(display_name: string, directory: DirectoryBookmark, server_properties: ServerProperties, nickname: string) : Bookmark {
|
||||||
|
const bookmark = {
|
||||||
|
display_name: display_name,
|
||||||
|
server_properties: server_properties,
|
||||||
|
nickname: nickname,
|
||||||
|
type: BookmarkType.ENTRY,
|
||||||
|
connect_profile: "default",
|
||||||
|
unique_id: guid(),
|
||||||
|
parent: directory
|
||||||
|
} as Bookmark;
|
||||||
|
|
||||||
|
directory.content.push(bookmark);
|
||||||
|
return bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create_bookmark_directory(parent: DirectoryBookmark, name: string) : DirectoryBookmark {
|
||||||
|
const bookmark = {
|
||||||
|
type: BookmarkType.DIRECTORY,
|
||||||
|
|
||||||
|
display_name: name,
|
||||||
|
content: [],
|
||||||
|
unique_id: guid(),
|
||||||
|
parent: parent
|
||||||
|
} as DirectoryBookmark;
|
||||||
|
|
||||||
|
parent.content.push(bookmark);
|
||||||
|
return bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO test if the new parent is within the old bookmark
|
||||||
|
export function change_directory(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
|
||||||
|
delete_bookmark(bookmark);
|
||||||
|
parent.content.push(bookmark)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save_bookmark(bookmark?: Bookmark | DirectoryBookmark) {
|
||||||
|
save_config(); /* nvm we dont give a fuck... saving everything */
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_bookmark_recursive(parent: DirectoryBookmark, bookmark: Bookmark | DirectoryBookmark) {
|
||||||
|
const index = parent.content.indexOf(bookmark);
|
||||||
|
if(index != -1)
|
||||||
|
parent.content.remove(bookmark);
|
||||||
|
else
|
||||||
|
for(const entry of parent.content)
|
||||||
|
if(entry.type == BookmarkType.DIRECTORY)
|
||||||
|
delete_bookmark_recursive(entry as DirectoryBookmark, bookmark)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delete_bookmark(bookmark: Bookmark | DirectoryBookmark) {
|
||||||
|
delete_bookmark_recursive(bookmarks(), bookmark)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function add_current_server() {
|
||||||
|
const ch = server_connections.active_connection_handler();
|
||||||
|
if(ch && ch.connected) {
|
||||||
|
const ce = ch.getClient();
|
||||||
|
const name = ce ? ce.clientNickName() : undefined;
|
||||||
|
createInputModal(tr("Enter bookmarks name"), tr("Please enter the bookmarks name:<br>"), text => text.length > 0, result => {
|
||||||
|
if(result) {
|
||||||
|
const bookmark = create_bookmark(result as string, bookmarks(), {
|
||||||
|
server_port: ch.serverConnection.remote_address().port,
|
||||||
|
server_address: ch.serverConnection.remote_address().host,
|
||||||
|
|
||||||
|
server_password: "",
|
||||||
|
server_password_hash: ""
|
||||||
|
}, name);
|
||||||
|
save_bookmark(bookmark);
|
||||||
|
|
||||||
|
control_bar.update_bookmarks();
|
||||||
|
top_menu.rebuild_bookmarks();
|
||||||
|
|
||||||
|
createInfoModal(tr("Server added"), tr("Server has been successfully added to your bookmarks.")).open();
|
||||||
|
}
|
||||||
|
}).open();
|
||||||
|
} else {
|
||||||
|
createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open();
|
||||||
|
}
|
||||||
}
|
}
|
98
shared/js/connection/AbstractCommandHandler.ts
Normal file
98
shared/js/connection/AbstractCommandHandler.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import {
|
||||||
|
AbstractServerConnection,
|
||||||
|
ServerCommand,
|
||||||
|
SingleCommandHandler
|
||||||
|
} from "tc-shared/connection/ConnectionBase";
|
||||||
|
|
||||||
|
export abstract class AbstractCommandHandler {
|
||||||
|
readonly connection: AbstractServerConnection;
|
||||||
|
|
||||||
|
handler_boss: AbstractCommandHandlerBoss | undefined;
|
||||||
|
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
|
||||||
|
|
||||||
|
ignore_consumed: boolean = false;
|
||||||
|
|
||||||
|
protected constructor(connection: AbstractServerConnection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the command should be consumed
|
||||||
|
*/
|
||||||
|
abstract handle_command(command: ServerCommand) : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractCommandHandlerBoss {
|
||||||
|
readonly connection: AbstractServerConnection;
|
||||||
|
protected command_handlers: AbstractCommandHandler[] = [];
|
||||||
|
/* TODO: Timeout */
|
||||||
|
protected single_command_handler: SingleCommandHandler[] = [];
|
||||||
|
|
||||||
|
protected constructor(connection: AbstractServerConnection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.command_handlers = undefined;
|
||||||
|
this.single_command_handler = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
register_handler(handler: AbstractCommandHandler) {
|
||||||
|
if(!handler.volatile_handler_boss && handler.handler_boss)
|
||||||
|
throw "handler already registered";
|
||||||
|
|
||||||
|
this.command_handlers.remove(handler); /* just to be sure */
|
||||||
|
this.command_handlers.push(handler);
|
||||||
|
handler.handler_boss = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister_handler(handler: AbstractCommandHandler) {
|
||||||
|
if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
|
||||||
|
console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.command_handlers.remove(handler);
|
||||||
|
handler.handler_boss = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
register_single_handler(handler: SingleCommandHandler) {
|
||||||
|
this.single_command_handler.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_single_handler(handler: SingleCommandHandler) {
|
||||||
|
this.single_command_handler.remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers() : AbstractCommandHandler[] {
|
||||||
|
return this.command_handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke_handle(command: ServerCommand) : boolean {
|
||||||
|
let flag_consumed = false;
|
||||||
|
|
||||||
|
for(const handler of this.command_handlers) {
|
||||||
|
try {
|
||||||
|
if(!flag_consumed || handler.ignore_consumed)
|
||||||
|
flag_consumed = flag_consumed || handler.handle_command(command);
|
||||||
|
} catch(error) {
|
||||||
|
console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const handler of [...this.single_command_handler]) {
|
||||||
|
if(handler.command && handler.command != command.command)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(handler.function(command))
|
||||||
|
this.single_command_handler.remove(handler);
|
||||||
|
} catch(error) {
|
||||||
|
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flag_consumed;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,448 +1,461 @@
|
||||||
namespace connection {
|
import {ServerCommand, SingleCommandHandler} from "tc-shared/connection/ConnectionBase";
|
||||||
export class CommandHelper extends AbstractCommandHandler {
|
import * as log from "tc-shared/log";
|
||||||
private _who_am_i: any;
|
import {LogCategory} from "tc-shared/log";
|
||||||
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
import {
|
||||||
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
ClientNameInfo,
|
||||||
|
CommandResult,
|
||||||
|
ErrorID, Playlist, PlaylistInfo, PlaylistSong,
|
||||||
|
QueryList,
|
||||||
|
QueryListEntry, ServerGroupClient
|
||||||
|
} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {ChannelEntry} from "tc-shared/ui/channel";
|
||||||
|
import {ClientEntry} from "tc-shared/ui/client";
|
||||||
|
import {ChatType} from "tc-shared/ui/frames/chat";
|
||||||
|
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
constructor(connection) {
|
export class CommandHelper extends AbstractCommandHandler {
|
||||||
super(connection);
|
private _who_am_i: any;
|
||||||
|
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||||
|
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||||
|
|
||||||
this.volatile_handler_boss = false;
|
constructor(connection) {
|
||||||
this.ignore_consumed = true;
|
super(connection);
|
||||||
|
|
||||||
|
this.volatile_handler_boss = false;
|
||||||
|
this.ignore_consumed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.connection.command_handler_boss().register_handler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if(this.connection) {
|
||||||
|
const hboss = this.connection.command_handler_boss();
|
||||||
|
hboss && hboss.unregister_handler(this);
|
||||||
|
}
|
||||||
|
this._awaiters_unique_ids = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_command(command: ServerCommand): boolean {
|
||||||
|
if(command.command == "notifyclientnamefromuid")
|
||||||
|
this.handle_notifyclientnamefromuid(command.arguments);
|
||||||
|
if(command.command == "notifyclientgetnamefromdbid")
|
||||||
|
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
joinChannel(channel: ChannelEntry, password?: string) : Promise<CommandResult> {
|
||||||
|
return this.connection.send_command("clientmove", {
|
||||||
|
"clid": this.connection.client.getClientId(),
|
||||||
|
"cid": channel.getChannelId(),
|
||||||
|
"cpw": password || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise<CommandResult> {
|
||||||
|
if(type == ChatType.SERVER)
|
||||||
|
return this.connection.send_command("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
|
||||||
|
else if(type == ChatType.CHANNEL)
|
||||||
|
return this.connection.send_command("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
|
||||||
|
else if(type == ChatType.CLIENT)
|
||||||
|
return this.connection.send_command("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateClient(key: string, value: string) : Promise<CommandResult> {
|
||||||
|
let data = {};
|
||||||
|
data[key] = value;
|
||||||
|
return this.connection.send_command("clientupdate", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async info_from_uid(..._unique_ids: string[]) : Promise<ClientNameInfo[]> {
|
||||||
|
const response: ClientNameInfo[] = [];
|
||||||
|
const request = [];
|
||||||
|
const unique_ids = new Set(_unique_ids);
|
||||||
|
if(!unique_ids.size) return [];
|
||||||
|
|
||||||
|
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
||||||
|
|
||||||
|
|
||||||
|
for(const unique_id of unique_ids) {
|
||||||
|
request.push({'cluid': unique_id});
|
||||||
|
(this._awaiters_unique_ids[unique_id] || (this._awaiters_unique_ids[unique_id] = []))
|
||||||
|
.push(unique_id_resolvers[unique_id] = info => response.push(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
try {
|
||||||
this.connection.command_handler_boss().register_handler(this);
|
await this.connection.send_command("clientgetnamefromuid", request);
|
||||||
}
|
} catch(error) {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
destroy() {
|
/* nothing */
|
||||||
if(this.connection) {
|
} else {
|
||||||
const hboss = this.connection.command_handler_boss();
|
throw error;
|
||||||
hboss && hboss.unregister_handler(this);
|
|
||||||
}
|
}
|
||||||
this._awaiters_unique_ids = undefined;
|
} finally {
|
||||||
|
/* cleanup */
|
||||||
|
for(const unique_id of Object.keys(unique_id_resolvers))
|
||||||
|
(this._awaiters_unique_ids[unique_id] || []).remove(unique_id_resolvers[unique_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
return response;
|
||||||
if(command.command == "notifyclientnamefromuid")
|
}
|
||||||
this.handle_notifyclientnamefromuid(command.arguments);
|
|
||||||
if(command.command == "notifyclientgetnamefromdbid")
|
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
||||||
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
for(const entry of json) {
|
||||||
else
|
const info: ClientNameInfo = {
|
||||||
return false;
|
client_unique_id: entry["cluid"],
|
||||||
return true;
|
client_nickname: entry["clname"],
|
||||||
|
client_database_id: parseInt(entry["cldbid"])
|
||||||
|
};
|
||||||
|
|
||||||
|
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
||||||
|
delete this._awaiters_unique_dbid[info.client_database_id];
|
||||||
|
|
||||||
|
for(const fn of functions)
|
||||||
|
fn(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
||||||
|
const response: ClientNameInfo[] = [];
|
||||||
|
const request = [];
|
||||||
|
const unique_cldbid = new Set(_cldbid);
|
||||||
|
if(!unique_cldbid.size) return [];
|
||||||
|
|
||||||
|
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
||||||
|
|
||||||
|
|
||||||
|
for(const cldbid of unique_cldbid) {
|
||||||
|
request.push({'cldbid': cldbid});
|
||||||
|
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
||||||
|
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
joinChannel(channel: ChannelEntry, password?: string) : Promise<CommandResult> {
|
try {
|
||||||
return this.connection.send_command("clientmove", {
|
await this.connection.send_command("clientgetnamefromdbid", request);
|
||||||
"clid": this.connection.client.getClientId(),
|
} catch(error) {
|
||||||
"cid": channel.getChannelId(),
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
"cpw": password || ""
|
/* nothing */
|
||||||
});
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
/* cleanup */
|
||||||
|
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
||||||
|
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message: string, type: ChatType, target?: ChannelEntry | ClientEntry) : Promise<CommandResult> {
|
return response;
|
||||||
if(type == ChatType.SERVER)
|
}
|
||||||
return this.connection.send_command("sendtextmessage", {"targetmode": 3, "target": 0, "msg": message});
|
|
||||||
else if(type == ChatType.CHANNEL)
|
private handle_notifyclientnamefromuid(json: any[]) {
|
||||||
return this.connection.send_command("sendtextmessage", {"targetmode": 2, "target": (target as ChannelEntry).getChannelId(), "msg": message});
|
for(const entry of json) {
|
||||||
else if(type == ChatType.CLIENT)
|
const info: ClientNameInfo = {
|
||||||
return this.connection.send_command("sendtextmessage", {"targetmode": 1, "target": (target as ClientEntry).clientId(), "msg": message});
|
client_unique_id: entry["cluid"],
|
||||||
}
|
client_nickname: entry["clname"],
|
||||||
|
client_database_id: parseInt(entry["cldbid"])
|
||||||
|
};
|
||||||
|
|
||||||
|
const functions = this._awaiters_unique_ids[entry["cluid"]] || [];
|
||||||
|
delete this._awaiters_unique_ids[entry["cluid"]];
|
||||||
|
|
||||||
|
for(const fn of functions)
|
||||||
|
fn(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request_query_list(server_id: number = undefined) : Promise<QueryList> {
|
||||||
|
return new Promise<QueryList>((resolve, reject) => {
|
||||||
|
const single_handler = {
|
||||||
|
command: "notifyquerylist",
|
||||||
|
function: command => {
|
||||||
|
const json = command.arguments;
|
||||||
|
|
||||||
|
const result = {} as QueryList;
|
||||||
|
|
||||||
|
result.flag_all = json[0]["flag_all"];
|
||||||
|
result.flag_own = json[0]["flag_own"];
|
||||||
|
result.queries = [];
|
||||||
|
|
||||||
|
for(const entry of json) {
|
||||||
|
const rentry = {} as QueryListEntry;
|
||||||
|
rentry.bounded_server = parseInt(entry["client_bound_server"]);
|
||||||
|
rentry.username = entry["client_login_name"];
|
||||||
|
rentry.unique_id = entry["client_unique_identifier"];
|
||||||
|
|
||||||
|
result.queries.push(rentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
updateClient(key: string, value: string) : Promise<CommandResult> {
|
|
||||||
let data = {};
|
let data = {};
|
||||||
data[key] = value;
|
if(server_id !== undefined)
|
||||||
return this.connection.send_command("clientupdate", data);
|
data["server_id"] = server_id;
|
||||||
}
|
|
||||||
|
|
||||||
async info_from_uid(..._unique_ids: string[]) : Promise<ClientNameInfo[]> {
|
this.connection.send_command("querylist", data).catch(error => {
|
||||||
const response: ClientNameInfo[] = [];
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
const request = [];
|
|
||||||
const unique_ids = new Set(_unique_ids);
|
|
||||||
if(!unique_ids.size) return [];
|
|
||||||
|
|
||||||
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
if(error instanceof CommandResult) {
|
||||||
|
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||||
|
resolve(undefined);
|
||||||
for(const unique_id of unique_ids) {
|
return;
|
||||||
request.push({'cluid': unique_id});
|
}
|
||||||
(this._awaiters_unique_ids[unique_id] || (this._awaiters_unique_ids[unique_id] = []))
|
|
||||||
.push(unique_id_resolvers[unique_id] = info => response.push(info));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.send_command("clientgetnamefromuid", request);
|
|
||||||
} catch(error) {
|
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
/* nothing */
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
} finally {
|
reject(error);
|
||||||
/* cleanup */
|
});
|
||||||
for(const unique_id of Object.keys(unique_id_resolvers))
|
});
|
||||||
(this._awaiters_unique_ids[unique_id] || []).remove(unique_id_resolvers[unique_id]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
request_playlist_list() : Promise<Playlist[]> {
|
||||||
}
|
return new Promise((resolve, reject) => {
|
||||||
|
const single_handler: SingleCommandHandler = {
|
||||||
|
command: "notifyplaylistlist",
|
||||||
|
function: command => {
|
||||||
|
const json = command.arguments;
|
||||||
|
const result: Playlist[] = [];
|
||||||
|
|
||||||
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
for(const entry of json) {
|
||||||
for(const entry of json) {
|
try {
|
||||||
const info: ClientNameInfo = {
|
result.push({
|
||||||
client_unique_id: entry["cluid"],
|
playlist_id: parseInt(entry["playlist_id"]),
|
||||||
client_nickname: entry["clname"],
|
playlist_bot_id: parseInt(entry["playlist_bot_id"]),
|
||||||
client_database_id: parseInt(entry["cldbid"])
|
playlist_title: entry["playlist_title"],
|
||||||
};
|
playlist_type: parseInt(entry["playlist_type"]),
|
||||||
|
playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
|
||||||
|
playlist_owner_name: entry["playlist_owner_name"],
|
||||||
|
|
||||||
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
needed_power_modify: parseInt(entry["needed_power_modify"]),
|
||||||
delete this._awaiters_unique_dbid[info.client_database_id];
|
needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
|
||||||
|
needed_power_delete: parseInt(entry["needed_power_delete"]),
|
||||||
|
needed_power_song_add: parseInt(entry["needed_power_song_add"]),
|
||||||
|
needed_power_song_move: parseInt(entry["needed_power_song_move"]),
|
||||||
|
needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(const fn of functions)
|
resolve(result);
|
||||||
fn(info);
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
|
||||||
const response: ClientNameInfo[] = [];
|
|
||||||
const request = [];
|
|
||||||
const unique_cldbid = new Set(_cldbid);
|
|
||||||
if(!unique_cldbid.size) return [];
|
|
||||||
|
|
||||||
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
|
||||||
|
|
||||||
|
|
||||||
for(const cldbid of unique_cldbid) {
|
|
||||||
request.push({'cldbid': cldbid});
|
|
||||||
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
|
||||||
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.connection.send_command("clientgetnamefromdbid", request);
|
|
||||||
} catch(error) {
|
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
/* nothing */
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
} finally {
|
};
|
||||||
/* cleanup */
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
|
||||||
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
this.connection.send_command("playlistlist").catch(error => {
|
||||||
}
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
|
||||||
private handle_notifyclientnamefromuid(json: any[]) {
|
if(error instanceof CommandResult) {
|
||||||
for(const entry of json) {
|
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||||
const info: ClientNameInfo = {
|
|
||||||
client_unique_id: entry["cluid"],
|
|
||||||
client_nickname: entry["clname"],
|
|
||||||
client_database_id: parseInt(entry["cldbid"])
|
|
||||||
};
|
|
||||||
|
|
||||||
const functions = this._awaiters_unique_ids[entry["cluid"]] || [];
|
|
||||||
delete this._awaiters_unique_ids[entry["cluid"]];
|
|
||||||
|
|
||||||
for(const fn of functions)
|
|
||||||
fn(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request_query_list(server_id: number = undefined) : Promise<QueryList> {
|
|
||||||
return new Promise<QueryList>((resolve, reject) => {
|
|
||||||
const single_handler = {
|
|
||||||
command: "notifyquerylist",
|
|
||||||
function: command => {
|
|
||||||
const json = command.arguments;
|
|
||||||
|
|
||||||
const result = {} as QueryList;
|
|
||||||
|
|
||||||
result.flag_all = json[0]["flag_all"];
|
|
||||||
result.flag_own = json[0]["flag_own"];
|
|
||||||
result.queries = [];
|
|
||||||
|
|
||||||
for(const entry of json) {
|
|
||||||
const rentry = {} as QueryListEntry;
|
|
||||||
rentry.bounded_server = parseInt(entry["client_bound_server"]);
|
|
||||||
rentry.username = entry["client_login_name"];
|
|
||||||
rentry.unique_id = entry["client_unique_identifier"];
|
|
||||||
|
|
||||||
result.queries.push(rentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
let data = {};
|
|
||||||
if(server_id !== undefined)
|
|
||||||
data["server_id"] = server_id;
|
|
||||||
|
|
||||||
this.connection.send_command("querylist", data).catch(error => {
|
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
|
||||||
|
|
||||||
if(error instanceof CommandResult) {
|
|
||||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
resolve(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request_playlist_list() : Promise<Playlist[]> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const single_handler: SingleCommandHandler = {
|
|
||||||
command: "notifyplaylistlist",
|
|
||||||
function: command => {
|
|
||||||
const json = command.arguments;
|
|
||||||
const result: Playlist[] = [];
|
|
||||||
|
|
||||||
for(const entry of json) {
|
|
||||||
try {
|
|
||||||
result.push({
|
|
||||||
playlist_id: parseInt(entry["playlist_id"]),
|
|
||||||
playlist_bot_id: parseInt(entry["playlist_bot_id"]),
|
|
||||||
playlist_title: entry["playlist_title"],
|
|
||||||
playlist_type: parseInt(entry["playlist_type"]),
|
|
||||||
playlist_owner_dbid: parseInt(entry["playlist_owner_dbid"]),
|
|
||||||
playlist_owner_name: entry["playlist_owner_name"],
|
|
||||||
|
|
||||||
needed_power_modify: parseInt(entry["needed_power_modify"]),
|
|
||||||
needed_power_permission_modify: parseInt(entry["needed_power_permission_modify"]),
|
|
||||||
needed_power_delete: parseInt(entry["needed_power_delete"]),
|
|
||||||
needed_power_song_add: parseInt(entry["needed_power_song_add"]),
|
|
||||||
needed_power_song_move: parseInt(entry["needed_power_song_move"]),
|
|
||||||
needed_power_song_remove: parseInt(entry["needed_power_song_remove"])
|
|
||||||
});
|
|
||||||
} catch(error) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist entry: %o"), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
this.connection.send_command("playlistlist").catch(error => {
|
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
|
||||||
|
|
||||||
if(error instanceof CommandResult) {
|
|
||||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
resolve([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request_playlist_songs(playlist_id: number) : Promise<PlaylistSong[]> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const single_handler: SingleCommandHandler = {
|
|
||||||
command: "notifyplaylistsonglist",
|
|
||||||
function: command => {
|
|
||||||
const json = command.arguments;
|
|
||||||
|
|
||||||
if(json[0]["playlist_id"] != playlist_id) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: PlaylistSong[] = [];
|
|
||||||
|
|
||||||
for(const entry of json) {
|
|
||||||
try {
|
|
||||||
result.push({
|
|
||||||
song_id: parseInt(entry["song_id"]),
|
|
||||||
song_invoker: entry["song_invoker"],
|
|
||||||
song_previous_song_id: parseInt(entry["song_previous_song_id"]),
|
|
||||||
song_url: entry["song_url"],
|
|
||||||
song_url_loader: entry["song_url_loader"],
|
|
||||||
|
|
||||||
song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
|
|
||||||
song_metadata: entry["song_metadata"]
|
|
||||||
});
|
|
||||||
} catch(error) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
|
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
|
||||||
if(error instanceof CommandResult) {
|
|
||||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
resolve([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const single_handler: SingleCommandHandler = {
|
|
||||||
command: "notifyplaylistclientlist",
|
|
||||||
function: command => {
|
|
||||||
const json = command.arguments;
|
|
||||||
|
|
||||||
if(json[0]["playlist_id"] != playlist_id) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: number[] = [];
|
|
||||||
|
|
||||||
for(const entry of json)
|
|
||||||
result.push(parseInt(entry["cldbid"]));
|
|
||||||
|
|
||||||
resolve(result.filter(e => !isNaN(e)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
resolve([]);
|
resolve([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reject(error);
|
}
|
||||||
})
|
reject(error);
|
||||||
});
|
})
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
request_playlist_songs(playlist_id: number) : Promise<PlaylistSong[]> {
|
||||||
//servergroupclientlist sgid=2
|
return new Promise((resolve, reject) => {
|
||||||
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
const single_handler: SingleCommandHandler = {
|
||||||
return new Promise<ServerGroupClient[]>((resolve, reject) => {
|
command: "notifyplaylistsonglist",
|
||||||
const single_handler: SingleCommandHandler = {
|
function: command => {
|
||||||
command: "notifyservergroupclientlist",
|
const json = command.arguments;
|
||||||
function: command => {
|
|
||||||
if (command.arguments[0]["sgid"] != group_id) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for server group client list"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if(json[0]["playlist_id"] != playlist_id) {
|
||||||
const result: ServerGroupClient[] = [];
|
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist songs"));
|
||||||
for(const entry of command.arguments)
|
return false;
|
||||||
result.push({
|
|
||||||
client_database_id: parseInt(entry["cldbid"]),
|
|
||||||
client_nickname: entry["client_nickname"],
|
|
||||||
client_unique_identifier: entry["client_unique_identifier"]
|
|
||||||
});
|
|
||||||
resolve(result);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Failed to parse server group client list: %o"), error);
|
|
||||||
reject("failed to parse info");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
this.connection.send_command("servergroupclientlist", {sgid: group_id}).catch(error => {
|
const result: PlaylistSong[] = [];
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
|
||||||
reject(error);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request_playlist_info(playlist_id: number) : Promise<PlaylistInfo> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const single_handler: SingleCommandHandler = {
|
|
||||||
command: "notifyplaylistinfo",
|
|
||||||
function: command => {
|
|
||||||
const json = command.arguments[0];
|
|
||||||
if (json["playlist_id"] != playlist_id) {
|
|
||||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for(const entry of json) {
|
||||||
try {
|
try {
|
||||||
//resolve
|
result.push({
|
||||||
resolve({
|
song_id: parseInt(entry["song_id"]),
|
||||||
playlist_id: parseInt(json["playlist_id"]),
|
song_invoker: entry["song_invoker"],
|
||||||
playlist_title: json["playlist_title"],
|
song_previous_song_id: parseInt(entry["song_previous_song_id"]),
|
||||||
playlist_description: json["playlist_description"],
|
song_url: entry["song_url"],
|
||||||
playlist_type: parseInt(json["playlist_type"]),
|
song_url_loader: entry["song_url_loader"],
|
||||||
|
|
||||||
playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
|
song_loaded: entry["song_loaded"] == true || entry["song_loaded"] == "1",
|
||||||
playlist_owner_name: json["playlist_owner_name"],
|
song_metadata: entry["song_metadata"]
|
||||||
|
|
||||||
playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
|
|
||||||
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
|
||||||
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
|
||||||
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
|
||||||
|
|
||||||
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch(error) {
|
||||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist song entry: %o"), error);
|
||||||
reject("failed to parse info");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
|
||||||
|
|
||||||
this.connection.send_command("playlistinfo", {playlist_id: playlist_id}).catch(error => {
|
resolve(result);
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
return true;
|
||||||
reject(error);
|
}
|
||||||
})
|
};
|
||||||
});
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
this.connection.send_command("playlistsonglist", {playlist_id: playlist_id}).catch(error => {
|
||||||
* @deprecated
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
* Its just a workaround for the query management.
|
if(error instanceof CommandResult) {
|
||||||
* There is no garante that the whoami trick will work forever
|
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||||
*/
|
resolve([]);
|
||||||
current_virtual_server_id() : Promise<number> {
|
return;
|
||||||
if(this._who_am_i)
|
|
||||||
return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
|
||||||
|
|
||||||
return new Promise<number>((resolve, reject) => {
|
|
||||||
const single_handler: SingleCommandHandler = {
|
|
||||||
function: command => {
|
|
||||||
if(command.command != "" && command.command.indexOf("=") == -1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this._who_am_i = command.arguments[0];
|
|
||||||
resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
this.handler_boss.register_single_handler(single_handler);
|
reject(error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.connection.send_command("whoami").catch(error => {
|
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
||||||
this.handler_boss.remove_single_handler(single_handler);
|
return new Promise((resolve, reject) => {
|
||||||
reject(error);
|
const single_handler: SingleCommandHandler = {
|
||||||
});
|
command: "notifyplaylistclientlist",
|
||||||
|
function: command => {
|
||||||
|
const json = command.arguments;
|
||||||
|
|
||||||
|
if(json[0]["playlist_id"] != playlist_id) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: number[] = [];
|
||||||
|
|
||||||
|
for(const entry of json)
|
||||||
|
result.push(parseInt(entry["cldbid"]));
|
||||||
|
|
||||||
|
resolve(result.filter(e => !isNaN(e)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
||||||
|
//servergroupclientlist sgid=2
|
||||||
|
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
||||||
|
return new Promise<ServerGroupClient[]>((resolve, reject) => {
|
||||||
|
const single_handler: SingleCommandHandler = {
|
||||||
|
command: "notifyservergroupclientlist",
|
||||||
|
function: command => {
|
||||||
|
if (command.arguments[0]["sgid"] != group_id) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Received invalid notification for server group client list"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: ServerGroupClient[] = [];
|
||||||
|
for(const entry of command.arguments)
|
||||||
|
result.push({
|
||||||
|
client_database_id: parseInt(entry["cldbid"]),
|
||||||
|
client_nickname: entry["client_nickname"],
|
||||||
|
client_unique_identifier: entry["client_unique_identifier"]
|
||||||
|
});
|
||||||
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Failed to parse server group client list: %o"), error);
|
||||||
|
reject("failed to parse info");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("servergroupclientlist", {sgid: group_id}).catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
request_playlist_info(playlist_id: number) : Promise<PlaylistInfo> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const single_handler: SingleCommandHandler = {
|
||||||
|
command: "notifyplaylistinfo",
|
||||||
|
function: command => {
|
||||||
|
const json = command.arguments[0];
|
||||||
|
if (json["playlist_id"] != playlist_id) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist info"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//resolve
|
||||||
|
resolve({
|
||||||
|
playlist_id: parseInt(json["playlist_id"]),
|
||||||
|
playlist_title: json["playlist_title"],
|
||||||
|
playlist_description: json["playlist_description"],
|
||||||
|
playlist_type: parseInt(json["playlist_type"]),
|
||||||
|
|
||||||
|
playlist_owner_dbid: parseInt(json["playlist_owner_dbid"]),
|
||||||
|
playlist_owner_name: json["playlist_owner_name"],
|
||||||
|
|
||||||
|
playlist_flag_delete_played: json["playlist_flag_delete_played"] == true || json["playlist_flag_delete_played"] == "1",
|
||||||
|
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
||||||
|
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
||||||
|
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
||||||
|
|
||||||
|
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
||||||
|
reject("failed to parse info");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("playlistinfo", {playlist_id: playlist_id}).catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* Its just a workaround for the query management.
|
||||||
|
* There is no garante that the whoami trick will work forever
|
||||||
|
*/
|
||||||
|
current_virtual_server_id() : Promise<number> {
|
||||||
|
if(this._who_am_i)
|
||||||
|
return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||||
|
|
||||||
|
return new Promise<number>((resolve, reject) => {
|
||||||
|
const single_handler: SingleCommandHandler = {
|
||||||
|
function: command => {
|
||||||
|
if(command.command != "" && command.command.indexOf("=") == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this._who_am_i = command.arguments[0];
|
||||||
|
resolve(parseInt(this._who_am_i["virtualserver_id"]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("whoami").catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
reject(error);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,216 +1,129 @@
|
||||||
namespace connection {
|
import {CommandHelper} from "tc-shared/connection/CommandHelper";
|
||||||
export interface CommandOptions {
|
import {HandshakeHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
flagset?: string[]; /* default: [] */
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
process_result?: boolean; /* default: true */
|
import {ServerAddress} from "tc-shared/ui/server";
|
||||||
|
import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
|
||||||
|
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||||
|
import {AbstractCommandHandlerBoss} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
timeout?: number /* default: 1000 */;
|
export interface CommandOptions {
|
||||||
|
flagset?: string[]; /* default: [] */
|
||||||
|
process_result?: boolean; /* default: true */
|
||||||
|
|
||||||
|
timeout?: number /* default: 1000 */;
|
||||||
|
}
|
||||||
|
export const CommandOptionDefaults: CommandOptions = {
|
||||||
|
flagset: [],
|
||||||
|
process_result: true,
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
|
||||||
|
export abstract class AbstractServerConnection {
|
||||||
|
readonly client: ConnectionHandler;
|
||||||
|
readonly command_helper: CommandHelper;
|
||||||
|
|
||||||
|
protected constructor(client: ConnectionHandler) {
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
this.command_helper = new CommandHelper(this);
|
||||||
}
|
}
|
||||||
export const CommandOptionDefaults: CommandOptions = {
|
|
||||||
flagset: [],
|
/* resolved as soon a connection has been established. This does not means that the authentication had yet been done! */
|
||||||
process_result: true,
|
abstract connect(address: ServerAddress, handshake: HandshakeHandler, timeout?: number) : Promise<void>;
|
||||||
timeout: 1000
|
|
||||||
|
abstract connected() : boolean;
|
||||||
|
abstract disconnect(reason?: string) : Promise<void>;
|
||||||
|
|
||||||
|
abstract support_voice() : boolean;
|
||||||
|
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
||||||
|
|
||||||
|
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||||
|
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||||
|
|
||||||
|
abstract get onconnectionstatechanged() : ConnectionStateListener;
|
||||||
|
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
|
||||||
|
|
||||||
|
abstract remote_address() : ServerAddress; /* only valid when connected */
|
||||||
|
abstract handshake_handler() : HandshakeHandler; /* only valid when connected */
|
||||||
|
|
||||||
|
abstract ping() : {
|
||||||
|
native: number,
|
||||||
|
javascript?: number
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type ConnectionStateListener = (old_state: ConnectionState, new_state: ConnectionState) => any;
|
export namespace voice {
|
||||||
export abstract class AbstractServerConnection {
|
export enum PlayerState {
|
||||||
readonly client: ConnectionHandler;
|
PREBUFFERING,
|
||||||
readonly command_helper: CommandHelper;
|
PLAYING,
|
||||||
|
BUFFERING,
|
||||||
|
STOPPING,
|
||||||
|
STOPPED
|
||||||
|
}
|
||||||
|
|
||||||
protected constructor(client: ConnectionHandler) {
|
export type LatencySettings = {
|
||||||
this.client = client;
|
min_buffer: number; /* milliseconds */
|
||||||
|
max_buffer: number; /* milliseconds */
|
||||||
|
}
|
||||||
|
|
||||||
this.command_helper = new CommandHelper(this);
|
export interface VoiceClient {
|
||||||
|
client_id: number;
|
||||||
|
|
||||||
|
callback_playback: () => any;
|
||||||
|
callback_stopped: () => any;
|
||||||
|
|
||||||
|
callback_state_changed: (new_state: PlayerState) => any;
|
||||||
|
|
||||||
|
get_state() : PlayerState;
|
||||||
|
|
||||||
|
get_volume() : number;
|
||||||
|
set_volume(volume: number) : void;
|
||||||
|
|
||||||
|
abort_replay();
|
||||||
|
|
||||||
|
support_latency_settings() : boolean;
|
||||||
|
|
||||||
|
reset_latency_settings();
|
||||||
|
latency_settings(settings?: LatencySettings) : LatencySettings;
|
||||||
|
|
||||||
|
support_flush() : boolean;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractVoiceConnection {
|
||||||
|
readonly connection: AbstractServerConnection;
|
||||||
|
|
||||||
|
protected constructor(connection: AbstractServerConnection) {
|
||||||
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* resolved as soon a connection has been established. This does not means that the authentication had yet been done! */
|
|
||||||
abstract connect(address: ServerAddress, handshake: HandshakeHandler, timeout?: number) : Promise<void>;
|
|
||||||
|
|
||||||
abstract connected() : boolean;
|
abstract connected() : boolean;
|
||||||
abstract disconnect(reason?: string) : Promise<void>;
|
abstract encoding_supported(codec: number) : boolean;
|
||||||
|
abstract decoding_supported(codec: number) : boolean;
|
||||||
|
|
||||||
abstract support_voice() : boolean;
|
abstract register_client(client_id: number) : VoiceClient;
|
||||||
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
abstract available_clients() : VoiceClient[];
|
||||||
|
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||||
|
|
||||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
abstract voice_recorder() : RecorderProfile;
|
||||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
|
||||||
|
|
||||||
abstract get onconnectionstatechanged() : ConnectionStateListener;
|
abstract get_encoder_codec() : number;
|
||||||
abstract set onconnectionstatechanged(listener: ConnectionStateListener);
|
abstract set_encoder_codec(codec: number);
|
||||||
|
|
||||||
abstract remote_address() : ServerAddress; /* only valid when connected */
|
|
||||||
abstract handshake_handler() : HandshakeHandler; /* only valid when connected */
|
|
||||||
|
|
||||||
abstract ping() : {
|
|
||||||
native: number,
|
|
||||||
javascript?: number
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export namespace voice {
|
export class ServerCommand {
|
||||||
export enum PlayerState {
|
command: string;
|
||||||
PREBUFFERING,
|
arguments: any[];
|
||||||
PLAYING,
|
}
|
||||||
BUFFERING,
|
|
||||||
STOPPING,
|
|
||||||
STOPPED
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LatencySettings = {
|
export interface SingleCommandHandler {
|
||||||
min_buffer: number; /* milliseconds */
|
name?: string;
|
||||||
max_buffer: number; /* milliseconds */
|
command?: string;
|
||||||
}
|
timeout?: number;
|
||||||
|
|
||||||
export interface VoiceClient {
|
/* if the return is true then the command handler will be removed */
|
||||||
client_id: number;
|
function: (command: ServerCommand) => boolean;
|
||||||
|
|
||||||
callback_playback: () => any;
|
|
||||||
callback_stopped: () => any;
|
|
||||||
|
|
||||||
callback_state_changed: (new_state: PlayerState) => any;
|
|
||||||
|
|
||||||
get_state() : PlayerState;
|
|
||||||
|
|
||||||
get_volume() : number;
|
|
||||||
set_volume(volume: number) : void;
|
|
||||||
|
|
||||||
abort_replay();
|
|
||||||
|
|
||||||
support_latency_settings() : boolean;
|
|
||||||
|
|
||||||
reset_latency_settings();
|
|
||||||
latency_settings(settings?: LatencySettings) : LatencySettings;
|
|
||||||
|
|
||||||
support_flush() : boolean;
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class AbstractVoiceConnection {
|
|
||||||
readonly connection: AbstractServerConnection;
|
|
||||||
|
|
||||||
protected constructor(connection: AbstractServerConnection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract connected() : boolean;
|
|
||||||
abstract encoding_supported(codec: number) : boolean;
|
|
||||||
abstract decoding_supported(codec: number) : boolean;
|
|
||||||
|
|
||||||
abstract register_client(client_id: number) : VoiceClient;
|
|
||||||
abstract available_clients() : VoiceClient[];
|
|
||||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
|
||||||
|
|
||||||
abstract voice_recorder() : RecorderProfile;
|
|
||||||
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
|
|
||||||
|
|
||||||
abstract get_encoder_codec() : number;
|
|
||||||
abstract set_encoder_codec(codec: number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerCommand {
|
|
||||||
command: string;
|
|
||||||
arguments: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class AbstractCommandHandler {
|
|
||||||
readonly connection: AbstractServerConnection;
|
|
||||||
|
|
||||||
handler_boss: AbstractCommandHandlerBoss | undefined;
|
|
||||||
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
|
|
||||||
|
|
||||||
ignore_consumed: boolean = false;
|
|
||||||
|
|
||||||
protected constructor(connection: AbstractServerConnection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return If the command should be consumed
|
|
||||||
*/
|
|
||||||
abstract handle_command(command: ServerCommand) : boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SingleCommandHandler {
|
|
||||||
name?: string;
|
|
||||||
command?: string;
|
|
||||||
timeout?: number;
|
|
||||||
|
|
||||||
/* if the return is true then the command handler will be removed */
|
|
||||||
function: (command: ServerCommand) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class AbstractCommandHandlerBoss {
|
|
||||||
readonly connection: AbstractServerConnection;
|
|
||||||
protected command_handlers: AbstractCommandHandler[] = [];
|
|
||||||
/* TODO: Timeout */
|
|
||||||
protected single_command_handler: SingleCommandHandler[] = [];
|
|
||||||
|
|
||||||
protected constructor(connection: AbstractServerConnection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.command_handlers = undefined;
|
|
||||||
this.single_command_handler = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
register_handler(handler: AbstractCommandHandler) {
|
|
||||||
if(!handler.volatile_handler_boss && handler.handler_boss)
|
|
||||||
throw "handler already registered";
|
|
||||||
|
|
||||||
this.command_handlers.remove(handler); /* just to be sure */
|
|
||||||
this.command_handlers.push(handler);
|
|
||||||
handler.handler_boss = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unregister_handler(handler: AbstractCommandHandler) {
|
|
||||||
if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
|
|
||||||
console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.command_handlers.remove(handler);
|
|
||||||
handler.handler_boss = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
register_single_handler(handler: SingleCommandHandler) {
|
|
||||||
this.single_command_handler.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_single_handler(handler: SingleCommandHandler) {
|
|
||||||
this.single_command_handler.remove(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers() : AbstractCommandHandler[] {
|
|
||||||
return this.command_handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
invoke_handle(command: ServerCommand) : boolean {
|
|
||||||
let flag_consumed = false;
|
|
||||||
|
|
||||||
for(const handler of this.command_handlers) {
|
|
||||||
try {
|
|
||||||
if(!flag_consumed || handler.ignore_consumed)
|
|
||||||
flag_consumed = flag_consumed || handler.handle_command(command);
|
|
||||||
} catch(error) {
|
|
||||||
console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const handler of [...this.single_command_handler]) {
|
|
||||||
if(handler.command && handler.command != command.command)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if(handler.function(command))
|
|
||||||
this.single_command_handler.remove(handler);
|
|
||||||
} catch(error) {
|
|
||||||
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flag_consumed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,146 +1,153 @@
|
||||||
namespace connection {
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
export interface HandshakeIdentityHandler {
|
import {IdentitifyType} from "tc-shared/profiles/Identity";
|
||||||
connection: AbstractServerConnection;
|
import {TeaSpeakIdentity} from "tc-shared/profiles/identities/TeamSpeakIdentity";
|
||||||
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {ConnectionProfile} from "tc-shared/profiles/ConnectionProfile";
|
||||||
|
import {settings} from "tc-shared/settings";
|
||||||
|
import {ConnectParameters, DisconnectReason} from "tc-shared/ConnectionHandler";
|
||||||
|
|
||||||
start_handshake();
|
export interface HandshakeIdentityHandler {
|
||||||
register_callback(callback: (success: boolean, message?: string) => any);
|
connection: AbstractServerConnection;
|
||||||
|
|
||||||
|
start_handshake();
|
||||||
|
register_callback(callback: (success: boolean, message?: string) => any);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const native_client;
|
||||||
|
export class HandshakeHandler {
|
||||||
|
private connection: AbstractServerConnection;
|
||||||
|
private handshake_handler: HandshakeIdentityHandler;
|
||||||
|
private failed = false;
|
||||||
|
|
||||||
|
readonly profile: ConnectionProfile;
|
||||||
|
readonly parameters: ConnectParameters;
|
||||||
|
|
||||||
|
constructor(profile: ConnectionProfile, parameters: ConnectParameters) {
|
||||||
|
this.profile = profile;
|
||||||
|
this.parameters = parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HandshakeHandler {
|
setConnection(con: AbstractServerConnection) {
|
||||||
private connection: AbstractServerConnection;
|
this.connection = con;
|
||||||
private handshake_handler: HandshakeIdentityHandler;
|
}
|
||||||
private failed = false;
|
|
||||||
|
|
||||||
readonly profile: profiles.ConnectionProfile;
|
initialize() {
|
||||||
readonly parameters: ConnectParameters;
|
this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
|
||||||
|
if(!this.handshake_handler) {
|
||||||
constructor(profile: profiles.ConnectionProfile, parameters: ConnectParameters) {
|
this.handshake_failed("failed to create identity handler");
|
||||||
this.profile = profile;
|
return;
|
||||||
this.parameters = parameters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConnection(con: AbstractServerConnection) {
|
this.handshake_handler.register_callback((flag, message) => {
|
||||||
this.connection = con;
|
if(flag)
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
|
|
||||||
if(!this.handshake_handler) {
|
|
||||||
this.handshake_failed("failed to create identity handler");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handshake_handler.register_callback((flag, message) => {
|
|
||||||
if(flag)
|
|
||||||
this.handshake_finished();
|
|
||||||
else
|
|
||||||
this.handshake_failed(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get_identity_handler() : HandshakeIdentityHandler {
|
|
||||||
return this.handshake_handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandshake() {
|
|
||||||
this.handshake_handler.start_handshake();
|
|
||||||
}
|
|
||||||
|
|
||||||
on_teamspeak() {
|
|
||||||
const type = this.profile.selected_type();
|
|
||||||
if(type == profiles.identities.IdentitifyType.TEAMSPEAK)
|
|
||||||
this.handshake_finished();
|
this.handshake_finished();
|
||||||
else {
|
else
|
||||||
|
this.handshake_failed(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(this.failed) return;
|
get_identity_handler() : HandshakeIdentityHandler {
|
||||||
|
return this.handshake_handler;
|
||||||
|
}
|
||||||
|
|
||||||
this.failed = true;
|
startHandshake() {
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED);
|
this.handshake_handler.start_handshake();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
on_teamspeak() {
|
||||||
|
const type = this.profile.selected_type();
|
||||||
|
if(type == IdentitifyType.TEAMSPEAK)
|
||||||
|
this.handshake_finished();
|
||||||
|
else {
|
||||||
|
|
||||||
private handshake_failed(message: string) {
|
|
||||||
if(this.failed) return;
|
if(this.failed) return;
|
||||||
|
|
||||||
this.failed = true;
|
this.failed = true;
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
|
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_TEAMSPEAK_REQUIRED);
|
||||||
}
|
|
||||||
|
|
||||||
private handshake_finished(version?: string) {
|
|
||||||
const _native = window["native"];
|
|
||||||
if(native_client && _native && _native.client_version && !version) {
|
|
||||||
_native.client_version()
|
|
||||||
.then( this.handshake_finished.bind(this))
|
|
||||||
.catch(error => {
|
|
||||||
console.error(tr("Failed to get version:"));
|
|
||||||
console.error(error);
|
|
||||||
this.handshake_finished("?.?.?");
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const git_version = settings.static_global("version", "unknown");
|
|
||||||
const browser_name = (navigator.browserSpecs || {})["name"] || " ";
|
|
||||||
let data = {
|
|
||||||
client_nickname: this.parameters.nickname || "Another TeaSpeak user",
|
|
||||||
client_platform: (browser_name ? browser_name + " " : "") + navigator.platform,
|
|
||||||
client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
|
|
||||||
client_version_sign: undefined,
|
|
||||||
|
|
||||||
client_default_channel: (this.parameters.channel || {} as any).target,
|
|
||||||
client_default_channel_password: (this.parameters.channel || {} as any).password,
|
|
||||||
client_default_token: this.parameters.token,
|
|
||||||
|
|
||||||
client_server_password: this.parameters.password ? this.parameters.password.password : undefined,
|
|
||||||
client_browser_engine: navigator.product,
|
|
||||||
|
|
||||||
client_input_hardware: this.connection.client.client_status.input_hardware,
|
|
||||||
client_output_hardware: false,
|
|
||||||
client_input_muted: this.connection.client.client_status.input_muted,
|
|
||||||
client_output_muted: this.connection.client.client_status.output_muted,
|
|
||||||
};
|
|
||||||
|
|
||||||
//0.0.1 [Build: 1549713549] Linux 7XvKmrk7uid2ixHFeERGqcC8vupeQqDypLtw2lY9slDNPojEv//F47UaDLG+TmVk4r6S0TseIKefzBpiRtLDAQ==
|
|
||||||
|
|
||||||
if(version) {
|
|
||||||
data.client_version = "TeaClient ";
|
|
||||||
data.client_version += " " + version;
|
|
||||||
|
|
||||||
const os = require("os");
|
|
||||||
const arch_mapping = {
|
|
||||||
"x32": "32bit",
|
|
||||||
"x64": "64bit"
|
|
||||||
};
|
|
||||||
|
|
||||||
data.client_version += " " + (arch_mapping[os.arch()] || os.arch());
|
|
||||||
|
|
||||||
const os_mapping = {
|
|
||||||
"win32": "Windows",
|
|
||||||
"linux": "Linux"
|
|
||||||
};
|
|
||||||
data.client_platform = (os_mapping[os.platform()] || os.platform());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* required to keep compatibility */
|
|
||||||
if(this.profile.selected_type() === profiles.identities.IdentitifyType.TEAMSPEAK) {
|
|
||||||
data["client_key_offset"] = (this.profile.selected_identity() as profiles.identities.TeaSpeakIdentity).hash_number;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.send_command("clientinit", data).catch(error => {
|
|
||||||
if(error instanceof CommandResult) {
|
|
||||||
if(error.id == 1028) {
|
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.SERVER_REQUIRES_PASSWORD);
|
|
||||||
} else if(error.id == 783 || error.id == 519) {
|
|
||||||
error.extra_message = isNaN(parseInt(error.extra_message)) ? "8" : error.extra_message;
|
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.IDENTITY_TOO_LOW, error);
|
|
||||||
} else if(error.id == 3329) {
|
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_BANNED, error);
|
|
||||||
} else {
|
|
||||||
this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, error);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
this.connection.disconnect();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handshake_failed(message: string) {
|
||||||
|
if(this.failed) return;
|
||||||
|
|
||||||
|
this.failed = true;
|
||||||
|
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_FAILED, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handshake_finished(version?: string) {
|
||||||
|
const _native = window["native"];
|
||||||
|
if(native_client && _native && _native.client_version && !version) {
|
||||||
|
_native.client_version()
|
||||||
|
.then( this.handshake_finished.bind(this))
|
||||||
|
.catch(error => {
|
||||||
|
console.error(tr("Failed to get version:"));
|
||||||
|
console.error(error);
|
||||||
|
this.handshake_finished("?.?.?");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const git_version = settings.static_global("version", "unknown");
|
||||||
|
const browser_name = (navigator.browserSpecs || {})["name"] || " ";
|
||||||
|
let data = {
|
||||||
|
client_nickname: this.parameters.nickname || "Another TeaSpeak user",
|
||||||
|
client_platform: (browser_name ? browser_name + " " : "") + navigator.platform,
|
||||||
|
client_version: "TeaWeb " + git_version + " (" + navigator.userAgent + ")",
|
||||||
|
client_version_sign: undefined,
|
||||||
|
|
||||||
|
client_default_channel: (this.parameters.channel || {} as any).target,
|
||||||
|
client_default_channel_password: (this.parameters.channel || {} as any).password,
|
||||||
|
client_default_token: this.parameters.token,
|
||||||
|
|
||||||
|
client_server_password: this.parameters.password ? this.parameters.password.password : undefined,
|
||||||
|
client_browser_engine: navigator.product,
|
||||||
|
|
||||||
|
client_input_hardware: this.connection.client.client_status.input_hardware,
|
||||||
|
client_output_hardware: false,
|
||||||
|
client_input_muted: this.connection.client.client_status.input_muted,
|
||||||
|
client_output_muted: this.connection.client.client_status.output_muted,
|
||||||
|
};
|
||||||
|
|
||||||
|
//0.0.1 [Build: 1549713549] Linux 7XvKmrk7uid2ixHFeERGqcC8vupeQqDypLtw2lY9slDNPojEv//F47UaDLG+TmVk4r6S0TseIKefzBpiRtLDAQ==
|
||||||
|
|
||||||
|
if(version) {
|
||||||
|
data.client_version = "TeaClient ";
|
||||||
|
data.client_version += " " + version;
|
||||||
|
|
||||||
|
const os = require("os");
|
||||||
|
const arch_mapping = {
|
||||||
|
"x32": "32bit",
|
||||||
|
"x64": "64bit"
|
||||||
|
};
|
||||||
|
|
||||||
|
data.client_version += " " + (arch_mapping[os.arch()] || os.arch());
|
||||||
|
|
||||||
|
const os_mapping = {
|
||||||
|
"win32": "Windows",
|
||||||
|
"linux": "Linux"
|
||||||
|
};
|
||||||
|
data.client_platform = (os_mapping[os.platform()] || os.platform());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* required to keep compatibility */
|
||||||
|
if(this.profile.selected_type() === IdentitifyType.TEAMSPEAK) {
|
||||||
|
data["client_key_offset"] = (this.profile.selected_identity() as TeaSpeakIdentity).hash_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.send_command("clientinit", data).catch(error => {
|
||||||
|
if(error instanceof CommandResult) {
|
||||||
|
if(error.id == 1028) {
|
||||||
|
this.connection.client.handleDisconnect(DisconnectReason.SERVER_REQUIRES_PASSWORD);
|
||||||
|
} else if(error.id == 783 || error.id == 519) {
|
||||||
|
error.extra_message = isNaN(parseInt(error.extra_message)) ? "8" : error.extra_message;
|
||||||
|
this.connection.client.handleDisconnect(DisconnectReason.IDENTITY_TOO_LOW, error);
|
||||||
|
} else if(error.id == 3329) {
|
||||||
|
this.connection.client.handleDisconnect(DisconnectReason.HANDSHAKE_BANNED, error);
|
||||||
|
} else {
|
||||||
|
this.connection.client.handleDisconnect(DisconnectReason.CLIENT_KICKED, error);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
this.connection.disconnect();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
enum ErrorID {
|
import {LaterPromise} from "tc-shared/utils/LaterPromise";
|
||||||
|
|
||||||
|
export enum ErrorID {
|
||||||
NOT_IMPLEMENTED = 0x2,
|
NOT_IMPLEMENTED = 0x2,
|
||||||
COMMAND_NOT_FOUND = 0x100,
|
COMMAND_NOT_FOUND = 0x100,
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ enum ErrorID {
|
||||||
CONVERSATION_IS_PRIVATE = 0x2202
|
CONVERSATION_IS_PRIVATE = 0x2202
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommandResult {
|
export class CommandResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -35,39 +37,39 @@ class CommandResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientNameInfo {
|
export interface ClientNameInfo {
|
||||||
//cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
|
//cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
|
||||||
client_unique_id: string;
|
client_unique_id: string;
|
||||||
client_nickname: string;
|
client_nickname: string;
|
||||||
client_database_id: number;
|
client_database_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientNameFromUid {
|
export interface ClientNameFromUid {
|
||||||
promise: LaterPromise<ClientNameInfo[]>,
|
promise: LaterPromise<ClientNameInfo[]>,
|
||||||
keys: string[],
|
keys: string[],
|
||||||
response: ClientNameInfo[]
|
response: ClientNameInfo[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServerGroupClient {
|
export interface ServerGroupClient {
|
||||||
client_nickname: string;
|
client_nickname: string;
|
||||||
client_unique_identifier: string;
|
client_unique_identifier: string;
|
||||||
client_database_id: number;
|
client_database_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryListEntry {
|
export interface QueryListEntry {
|
||||||
username: string;
|
username: string;
|
||||||
unique_id: string;
|
unique_id: string;
|
||||||
bounded_server: number;
|
bounded_server: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryList {
|
export interface QueryList {
|
||||||
flag_own: boolean;
|
flag_own: boolean;
|
||||||
flag_all: boolean;
|
flag_all: boolean;
|
||||||
|
|
||||||
queries: QueryListEntry[];
|
queries: QueryListEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Playlist {
|
export interface Playlist {
|
||||||
playlist_id: number;
|
playlist_id: number;
|
||||||
playlist_bot_id: number;
|
playlist_bot_id: number;
|
||||||
playlist_title: string;
|
playlist_title: string;
|
||||||
|
@ -83,7 +85,7 @@ interface Playlist {
|
||||||
needed_power_song_remove: number;
|
needed_power_song_remove: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistInfo {
|
export interface PlaylistInfo {
|
||||||
playlist_id: number,
|
playlist_id: number,
|
||||||
playlist_title: string,
|
playlist_title: string,
|
||||||
playlist_description: string,
|
playlist_description: string,
|
||||||
|
@ -100,7 +102,7 @@ interface PlaylistInfo {
|
||||||
playlist_max_songs: number
|
playlist_max_songs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistSong {
|
export interface PlaylistSong {
|
||||||
song_id: number;
|
song_id: number;
|
||||||
song_previous_song_id: number;
|
song_previous_song_id: number;
|
||||||
song_invoker: string;
|
song_invoker: string;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
class Crc32 {
|
export class Crc32 {
|
||||||
private static readonly lookup = [
|
private static readonly lookup = [
|
||||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
namespace hex {
|
export function encode(buffer) {
|
||||||
export function encode(buffer) {
|
let hexCodes = [];
|
||||||
let hexCodes = [];
|
let view = new DataView(buffer);
|
||||||
let view = new DataView(buffer);
|
for (let i = 0; i < view.byteLength % 4; i ++) {
|
||||||
for (let i = 0; i < view.byteLength % 4; i ++) {
|
let value = view.getUint32(i * 4);
|
||||||
let value = view.getUint32(i * 4);
|
let stringValue = value.toString(16);
|
||||||
let stringValue = value.toString(16);
|
let padding = '00000000';
|
||||||
let padding = '00000000';
|
let paddedValue = (padding + stringValue).slice(-padding.length);
|
||||||
let paddedValue = (padding + stringValue).slice(-padding.length);
|
hexCodes.push(paddedValue);
|
||||||
hexCodes.push(paddedValue);
|
|
||||||
}
|
|
||||||
for (let i = (view.byteLength % 4) * 4; i < view.byteLength; i++) {
|
|
||||||
let value = view.getUint8(i).toString(16);
|
|
||||||
let padding = '00';
|
|
||||||
hexCodes.push((padding + value).slice(-padding.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
return hexCodes.join("");
|
|
||||||
}
|
}
|
||||||
|
for (let i = (view.byteLength % 4) * 4; i < view.byteLength; i++) {
|
||||||
|
let value = view.getUint8(i).toString(16);
|
||||||
|
let padding = '00';
|
||||||
|
hexCodes.push((padding + value).slice(-padding.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexCodes.join("");
|
||||||
}
|
}
|
|
@ -6,405 +6,367 @@ declare class _sha1 {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
interface Window {
|
interface Window {
|
||||||
TextEncoder: any;
|
TextEncoder: any;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace sha {
|
/*
|
||||||
/*
|
* [js-sha1]{@link https://github.com/emn178/js-sha1}
|
||||||
* [js-sha1]{@link https://github.com/emn178/js-sha1}
|
*
|
||||||
*
|
* @version 0.6.0
|
||||||
* @version 0.6.0
|
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
||||||
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
* @copyright Chen, Yi-Cyuan 2014-2017
|
||||||
* @copyright Chen, Yi-Cyuan 2014-2017
|
* @license MIT
|
||||||
* @license MIT
|
*/
|
||||||
*/
|
/*jslint bitwise: true */
|
||||||
/*jslint bitwise: true */
|
(function() {
|
||||||
(function() {
|
'use strict';
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let root: any = typeof window === 'object' ? window : {};
|
let root: any = typeof window === 'object' ? window : {};
|
||||||
let NODE_JS = !root.JS_SHA1_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
|
let HEX_CHARS = '0123456789abcdef'.split('');
|
||||||
if (NODE_JS) {
|
let EXTRA = [-2147483648, 8388608, 32768, 128];
|
||||||
root = global;
|
let SHIFT = [24, 16, 8, 0];
|
||||||
|
let OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];
|
||||||
|
|
||||||
|
let blocks = [];
|
||||||
|
|
||||||
|
let createOutputMethod = function (outputType) {
|
||||||
|
return function (message) {
|
||||||
|
return new Sha1(true).update(message)[outputType]();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let createMethod = function () {
|
||||||
|
let method: any = createOutputMethod('hex');
|
||||||
|
method.create = function () {
|
||||||
|
return new (Sha1 as any)();
|
||||||
|
};
|
||||||
|
method.update = function (message) {
|
||||||
|
return method.create().update(message);
|
||||||
|
};
|
||||||
|
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
||||||
|
var type = OUTPUT_TYPES[i];
|
||||||
|
method[type] = createOutputMethod(type);
|
||||||
}
|
}
|
||||||
let COMMON_JS = !root.JS_SHA1_NO_COMMON_JS && typeof module === 'object' && module.exports;
|
return method;
|
||||||
let AMD = typeof define === 'function' && (define as any).amd;
|
};
|
||||||
let HEX_CHARS = '0123456789abcdef'.split('');
|
|
||||||
let EXTRA = [-2147483648, 8388608, 32768, 128];
|
|
||||||
let SHIFT = [24, 16, 8, 0];
|
|
||||||
let OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];
|
|
||||||
|
|
||||||
let blocks = [];
|
function Sha1(sharedMemory) {
|
||||||
|
if (sharedMemory) {
|
||||||
let createOutputMethod = function (outputType) {
|
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
||||||
return function (message) {
|
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
||||||
return new Sha1(true).update(message)[outputType]();
|
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
||||||
};
|
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
||||||
};
|
this.blocks = blocks;
|
||||||
|
} else {
|
||||||
let createMethod = function () {
|
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
let method: any = createOutputMethod('hex');
|
|
||||||
if (NODE_JS) {
|
|
||||||
method = nodeWrap(method);
|
|
||||||
}
|
|
||||||
method.create = function () {
|
|
||||||
return new (Sha1 as any)();
|
|
||||||
};
|
|
||||||
method.update = function (message) {
|
|
||||||
return method.create().update(message);
|
|
||||||
};
|
|
||||||
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
|
||||||
var type = OUTPUT_TYPES[i];
|
|
||||||
method[type] = createOutputMethod(type);
|
|
||||||
}
|
|
||||||
return method;
|
|
||||||
};
|
|
||||||
|
|
||||||
var nodeWrap = function (method) {
|
|
||||||
var crypto = eval("require('crypto')");
|
|
||||||
var Buffer = eval("require('buffer').Buffer");
|
|
||||||
var nodeMethod = function (message) {
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
return crypto.createHash('sha1').update(message, 'utf8').digest('hex');
|
|
||||||
} else if (message.constructor === ArrayBuffer) {
|
|
||||||
message = new Uint8Array(message);
|
|
||||||
} else if (message.length === undefined) {
|
|
||||||
return method(message);
|
|
||||||
}
|
|
||||||
return crypto.createHash('sha1').update(new Buffer(message)).digest('hex');
|
|
||||||
};
|
|
||||||
return nodeMethod;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Sha1(sharedMemory) {
|
|
||||||
if (sharedMemory) {
|
|
||||||
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
|
||||||
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
|
||||||
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
|
||||||
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
|
||||||
this.blocks = blocks;
|
|
||||||
} else {
|
|
||||||
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.h0 = 0x67452301;
|
|
||||||
this.h1 = 0xEFCDAB89;
|
|
||||||
this.h2 = 0x98BADCFE;
|
|
||||||
this.h3 = 0x10325476;
|
|
||||||
this.h4 = 0xC3D2E1F0;
|
|
||||||
|
|
||||||
this.block = this.start = this.bytes = this.hBytes = 0;
|
|
||||||
this.finalized = this.hashed = false;
|
|
||||||
this.first = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sha1.prototype.update = function (message) {
|
this.h0 = 0x67452301;
|
||||||
if (this.finalized) {
|
this.h1 = 0xEFCDAB89;
|
||||||
return;
|
this.h2 = 0x98BADCFE;
|
||||||
}
|
this.h3 = 0x10325476;
|
||||||
var notString = typeof(message) !== 'string';
|
this.h4 = 0xC3D2E1F0;
|
||||||
if (notString && message.constructor === root.ArrayBuffer) {
|
|
||||||
message = new Uint8Array(message);
|
|
||||||
}
|
|
||||||
var code, index = 0, i, length = message.length || 0, blocks = this.blocks;
|
|
||||||
|
|
||||||
while (index < length) {
|
this.block = this.start = this.bytes = this.hBytes = 0;
|
||||||
if (this.hashed) {
|
this.finalized = this.hashed = false;
|
||||||
this.hashed = false;
|
this.first = true;
|
||||||
blocks[0] = this.block;
|
}
|
||||||
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
|
||||||
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
|
||||||
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
|
||||||
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(notString) {
|
Sha1.prototype.update = function (message) {
|
||||||
for (i = this.start; index < length && i < 64; ++index) {
|
if (this.finalized) {
|
||||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
var notString = typeof(message) !== 'string';
|
||||||
for (i = this.start; index < length && i < 64; ++index) {
|
if (notString && message.constructor === root.ArrayBuffer) {
|
||||||
code = message.charCodeAt(index);
|
message = new Uint8Array(message);
|
||||||
if (code < 0x80) {
|
}
|
||||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
var code, index = 0, i, length = message.length || 0, blocks = this.blocks;
|
||||||
} else if (code < 0x800) {
|
|
||||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
} else if (code < 0xd800 || code >= 0xe000) {
|
|
||||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
} else {
|
|
||||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
|
||||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastByteIndex = i;
|
while (index < length) {
|
||||||
this.bytes += i - this.start;
|
if (this.hashed) {
|
||||||
if (i >= 64) {
|
this.hashed = false;
|
||||||
this.block = blocks[16];
|
|
||||||
this.start = i - 64;
|
|
||||||
this.hash();
|
|
||||||
this.hashed = true;
|
|
||||||
} else {
|
|
||||||
this.start = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.bytes > 4294967295) {
|
|
||||||
this.hBytes += this.bytes / 4294967296 << 0;
|
|
||||||
this.bytes = this.bytes % 4294967296;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sha1.prototype.finalize = function () {
|
|
||||||
if (this.finalized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.finalized = true;
|
|
||||||
var blocks = this.blocks, i = this.lastByteIndex;
|
|
||||||
blocks[16] = this.block;
|
|
||||||
blocks[i >> 2] |= EXTRA[i & 3];
|
|
||||||
this.block = blocks[16];
|
|
||||||
if (i >= 56) {
|
|
||||||
if (!this.hashed) {
|
|
||||||
this.hash();
|
|
||||||
}
|
|
||||||
blocks[0] = this.block;
|
blocks[0] = this.block;
|
||||||
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
||||||
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
||||||
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
||||||
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
||||||
}
|
}
|
||||||
blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
|
|
||||||
blocks[15] = this.bytes << 3;
|
|
||||||
this.hash();
|
|
||||||
};
|
|
||||||
|
|
||||||
Sha1.prototype.hash = function () {
|
if(notString) {
|
||||||
var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4;
|
for (i = this.start; index < length && i < 64; ++index) {
|
||||||
var f, j, t, blocks = this.blocks;
|
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||||
|
}
|
||||||
for(j = 16; j < 80; ++j) {
|
} else {
|
||||||
t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
|
for (i = this.start; index < length && i < 64; ++index) {
|
||||||
blocks[j] = (t << 1) | (t >>> 31);
|
code = message.charCodeAt(index);
|
||||||
|
if (code < 0x80) {
|
||||||
|
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||||
|
} else if (code < 0x800) {
|
||||||
|
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
} else if (code < 0xd800 || code >= 0xe000) {
|
||||||
|
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
} else {
|
||||||
|
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||||
|
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(j = 0; j < 20; j += 5) {
|
this.lastByteIndex = i;
|
||||||
f = (b & c) | ((~b) & d);
|
this.bytes += i - this.start;
|
||||||
t = (a << 5) | (a >>> 27);
|
if (i >= 64) {
|
||||||
e = t + f + e + 1518500249 + blocks[j] << 0;
|
this.block = blocks[16];
|
||||||
b = (b << 30) | (b >>> 2);
|
this.start = i - 64;
|
||||||
|
this.hash();
|
||||||
f = (a & b) | ((~a) & c);
|
this.hashed = true;
|
||||||
t = (e << 5) | (e >>> 27);
|
} else {
|
||||||
d = t + f + d + 1518500249 + blocks[j + 1] << 0;
|
this.start = i;
|
||||||
a = (a << 30) | (a >>> 2);
|
|
||||||
|
|
||||||
f = (e & a) | ((~e) & b);
|
|
||||||
t = (d << 5) | (d >>> 27);
|
|
||||||
c = t + f + c + 1518500249 + blocks[j + 2] << 0;
|
|
||||||
e = (e << 30) | (e >>> 2);
|
|
||||||
|
|
||||||
f = (d & e) | ((~d) & a);
|
|
||||||
t = (c << 5) | (c >>> 27);
|
|
||||||
b = t + f + b + 1518500249 + blocks[j + 3] << 0;
|
|
||||||
d = (d << 30) | (d >>> 2);
|
|
||||||
|
|
||||||
f = (c & d) | ((~c) & e);
|
|
||||||
t = (b << 5) | (b >>> 27);
|
|
||||||
a = t + f + a + 1518500249 + blocks[j + 4] << 0;
|
|
||||||
c = (c << 30) | (c >>> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(; j < 40; j += 5) {
|
|
||||||
f = b ^ c ^ d;
|
|
||||||
t = (a << 5) | (a >>> 27);
|
|
||||||
e = t + f + e + 1859775393 + blocks[j] << 0;
|
|
||||||
b = (b << 30) | (b >>> 2);
|
|
||||||
|
|
||||||
f = a ^ b ^ c;
|
|
||||||
t = (e << 5) | (e >>> 27);
|
|
||||||
d = t + f + d + 1859775393 + blocks[j + 1] << 0;
|
|
||||||
a = (a << 30) | (a >>> 2);
|
|
||||||
|
|
||||||
f = e ^ a ^ b;
|
|
||||||
t = (d << 5) | (d >>> 27);
|
|
||||||
c = t + f + c + 1859775393 + blocks[j + 2] << 0;
|
|
||||||
e = (e << 30) | (e >>> 2);
|
|
||||||
|
|
||||||
f = d ^ e ^ a;
|
|
||||||
t = (c << 5) | (c >>> 27);
|
|
||||||
b = t + f + b + 1859775393 + blocks[j + 3] << 0;
|
|
||||||
d = (d << 30) | (d >>> 2);
|
|
||||||
|
|
||||||
f = c ^ d ^ e;
|
|
||||||
t = (b << 5) | (b >>> 27);
|
|
||||||
a = t + f + a + 1859775393 + blocks[j + 4] << 0;
|
|
||||||
c = (c << 30) | (c >>> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(; j < 60; j += 5) {
|
|
||||||
f = (b & c) | (b & d) | (c & d);
|
|
||||||
t = (a << 5) | (a >>> 27);
|
|
||||||
e = t + f + e - 1894007588 + blocks[j] << 0;
|
|
||||||
b = (b << 30) | (b >>> 2);
|
|
||||||
|
|
||||||
f = (a & b) | (a & c) | (b & c);
|
|
||||||
t = (e << 5) | (e >>> 27);
|
|
||||||
d = t + f + d - 1894007588 + blocks[j + 1] << 0;
|
|
||||||
a = (a << 30) | (a >>> 2);
|
|
||||||
|
|
||||||
f = (e & a) | (e & b) | (a & b);
|
|
||||||
t = (d << 5) | (d >>> 27);
|
|
||||||
c = t + f + c - 1894007588 + blocks[j + 2] << 0;
|
|
||||||
e = (e << 30) | (e >>> 2);
|
|
||||||
|
|
||||||
f = (d & e) | (d & a) | (e & a);
|
|
||||||
t = (c << 5) | (c >>> 27);
|
|
||||||
b = t + f + b - 1894007588 + blocks[j + 3] << 0;
|
|
||||||
d = (d << 30) | (d >>> 2);
|
|
||||||
|
|
||||||
f = (c & d) | (c & e) | (d & e);
|
|
||||||
t = (b << 5) | (b >>> 27);
|
|
||||||
a = t + f + a - 1894007588 + blocks[j + 4] << 0;
|
|
||||||
c = (c << 30) | (c >>> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(; j < 80; j += 5) {
|
|
||||||
f = b ^ c ^ d;
|
|
||||||
t = (a << 5) | (a >>> 27);
|
|
||||||
e = t + f + e - 899497514 + blocks[j] << 0;
|
|
||||||
b = (b << 30) | (b >>> 2);
|
|
||||||
|
|
||||||
f = a ^ b ^ c;
|
|
||||||
t = (e << 5) | (e >>> 27);
|
|
||||||
d = t + f + d - 899497514 + blocks[j + 1] << 0;
|
|
||||||
a = (a << 30) | (a >>> 2);
|
|
||||||
|
|
||||||
f = e ^ a ^ b;
|
|
||||||
t = (d << 5) | (d >>> 27);
|
|
||||||
c = t + f + c - 899497514 + blocks[j + 2] << 0;
|
|
||||||
e = (e << 30) | (e >>> 2);
|
|
||||||
|
|
||||||
f = d ^ e ^ a;
|
|
||||||
t = (c << 5) | (c >>> 27);
|
|
||||||
b = t + f + b - 899497514 + blocks[j + 3] << 0;
|
|
||||||
d = (d << 30) | (d >>> 2);
|
|
||||||
|
|
||||||
f = c ^ d ^ e;
|
|
||||||
t = (b << 5) | (b >>> 27);
|
|
||||||
a = t + f + a - 899497514 + blocks[j + 4] << 0;
|
|
||||||
c = (c << 30) | (c >>> 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.h0 = this.h0 + a << 0;
|
|
||||||
this.h1 = this.h1 + b << 0;
|
|
||||||
this.h2 = this.h2 + c << 0;
|
|
||||||
this.h3 = this.h3 + d << 0;
|
|
||||||
this.h4 = this.h4 + e << 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Sha1.prototype.hex = function () {
|
|
||||||
this.finalize();
|
|
||||||
|
|
||||||
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
|
|
||||||
|
|
||||||
return HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
|
|
||||||
HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
|
|
||||||
HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
|
|
||||||
HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
|
|
||||||
HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
|
|
||||||
HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
|
|
||||||
HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
|
|
||||||
HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
|
|
||||||
HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
|
|
||||||
HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
|
|
||||||
HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
|
|
||||||
HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
|
|
||||||
HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] +
|
|
||||||
HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
|
|
||||||
HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
|
|
||||||
HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
|
|
||||||
HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] +
|
|
||||||
HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] +
|
|
||||||
HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] +
|
|
||||||
HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F];
|
|
||||||
};
|
|
||||||
|
|
||||||
Sha1.prototype.toString = Sha1.prototype.hex;
|
|
||||||
|
|
||||||
Sha1.prototype.digest = function () {
|
|
||||||
this.finalize();
|
|
||||||
|
|
||||||
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
|
|
||||||
|
|
||||||
return [
|
|
||||||
(h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF,
|
|
||||||
(h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF,
|
|
||||||
(h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF,
|
|
||||||
(h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF,
|
|
||||||
(h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
Sha1.prototype.array = Sha1.prototype.digest;
|
|
||||||
|
|
||||||
Sha1.prototype.arrayBuffer = function () {
|
|
||||||
this.finalize();
|
|
||||||
|
|
||||||
var buffer = new ArrayBuffer(20);
|
|
||||||
var dataView = new DataView(buffer);
|
|
||||||
dataView.setUint32(0, this.h0);
|
|
||||||
dataView.setUint32(4, this.h1);
|
|
||||||
dataView.setUint32(8, this.h2);
|
|
||||||
dataView.setUint32(12, this.h3);
|
|
||||||
dataView.setUint32(16, this.h4);
|
|
||||||
return buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
var exports = createMethod();
|
|
||||||
|
|
||||||
if (COMMON_JS) {
|
|
||||||
module.exports = exports;
|
|
||||||
} else {
|
|
||||||
root._sha1 = exports;
|
|
||||||
if (AMD) {
|
|
||||||
define(function () {
|
|
||||||
return exports;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
if (this.bytes > 4294967295) {
|
||||||
|
this.hBytes += this.bytes / 4294967296 << 0;
|
||||||
|
this.bytes = this.bytes % 4294967296;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
export function encode_text(buffer: string) : ArrayBuffer {
|
Sha1.prototype.finalize = function () {
|
||||||
if ((window as any).TextEncoder) {
|
if (this.finalized) {
|
||||||
return new TextEncoder().encode(buffer).buffer;
|
return;
|
||||||
}
|
}
|
||||||
let utf8 = unescape(encodeURIComponent(buffer));
|
this.finalized = true;
|
||||||
let result = new Uint8Array(utf8.length);
|
var blocks = this.blocks, i = this.lastByteIndex;
|
||||||
for (let i = 0; i < utf8.length; i++) {
|
blocks[16] = this.block;
|
||||||
result[i] = utf8.charCodeAt(i);
|
blocks[i >> 2] |= EXTRA[i & 3];
|
||||||
|
this.block = blocks[16];
|
||||||
|
if (i >= 56) {
|
||||||
|
if (!this.hashed) {
|
||||||
|
this.hash();
|
||||||
|
}
|
||||||
|
blocks[0] = this.block;
|
||||||
|
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
|
||||||
|
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
|
||||||
|
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
|
||||||
|
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
|
||||||
}
|
}
|
||||||
return result.buffer;
|
blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
|
||||||
|
blocks[15] = this.bytes << 3;
|
||||||
|
this.hash();
|
||||||
|
};
|
||||||
|
|
||||||
|
Sha1.prototype.hash = function () {
|
||||||
|
var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4;
|
||||||
|
var f, j, t, blocks = this.blocks;
|
||||||
|
|
||||||
|
for(j = 16; j < 80; ++j) {
|
||||||
|
t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
|
||||||
|
blocks[j] = (t << 1) | (t >>> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(j = 0; j < 20; j += 5) {
|
||||||
|
f = (b & c) | ((~b) & d);
|
||||||
|
t = (a << 5) | (a >>> 27);
|
||||||
|
e = t + f + e + 1518500249 + blocks[j] << 0;
|
||||||
|
b = (b << 30) | (b >>> 2);
|
||||||
|
|
||||||
|
f = (a & b) | ((~a) & c);
|
||||||
|
t = (e << 5) | (e >>> 27);
|
||||||
|
d = t + f + d + 1518500249 + blocks[j + 1] << 0;
|
||||||
|
a = (a << 30) | (a >>> 2);
|
||||||
|
|
||||||
|
f = (e & a) | ((~e) & b);
|
||||||
|
t = (d << 5) | (d >>> 27);
|
||||||
|
c = t + f + c + 1518500249 + blocks[j + 2] << 0;
|
||||||
|
e = (e << 30) | (e >>> 2);
|
||||||
|
|
||||||
|
f = (d & e) | ((~d) & a);
|
||||||
|
t = (c << 5) | (c >>> 27);
|
||||||
|
b = t + f + b + 1518500249 + blocks[j + 3] << 0;
|
||||||
|
d = (d << 30) | (d >>> 2);
|
||||||
|
|
||||||
|
f = (c & d) | ((~c) & e);
|
||||||
|
t = (b << 5) | (b >>> 27);
|
||||||
|
a = t + f + a + 1518500249 + blocks[j + 4] << 0;
|
||||||
|
c = (c << 30) | (c >>> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(; j < 40; j += 5) {
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
t = (a << 5) | (a >>> 27);
|
||||||
|
e = t + f + e + 1859775393 + blocks[j] << 0;
|
||||||
|
b = (b << 30) | (b >>> 2);
|
||||||
|
|
||||||
|
f = a ^ b ^ c;
|
||||||
|
t = (e << 5) | (e >>> 27);
|
||||||
|
d = t + f + d + 1859775393 + blocks[j + 1] << 0;
|
||||||
|
a = (a << 30) | (a >>> 2);
|
||||||
|
|
||||||
|
f = e ^ a ^ b;
|
||||||
|
t = (d << 5) | (d >>> 27);
|
||||||
|
c = t + f + c + 1859775393 + blocks[j + 2] << 0;
|
||||||
|
e = (e << 30) | (e >>> 2);
|
||||||
|
|
||||||
|
f = d ^ e ^ a;
|
||||||
|
t = (c << 5) | (c >>> 27);
|
||||||
|
b = t + f + b + 1859775393 + blocks[j + 3] << 0;
|
||||||
|
d = (d << 30) | (d >>> 2);
|
||||||
|
|
||||||
|
f = c ^ d ^ e;
|
||||||
|
t = (b << 5) | (b >>> 27);
|
||||||
|
a = t + f + a + 1859775393 + blocks[j + 4] << 0;
|
||||||
|
c = (c << 30) | (c >>> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(; j < 60; j += 5) {
|
||||||
|
f = (b & c) | (b & d) | (c & d);
|
||||||
|
t = (a << 5) | (a >>> 27);
|
||||||
|
e = t + f + e - 1894007588 + blocks[j] << 0;
|
||||||
|
b = (b << 30) | (b >>> 2);
|
||||||
|
|
||||||
|
f = (a & b) | (a & c) | (b & c);
|
||||||
|
t = (e << 5) | (e >>> 27);
|
||||||
|
d = t + f + d - 1894007588 + blocks[j + 1] << 0;
|
||||||
|
a = (a << 30) | (a >>> 2);
|
||||||
|
|
||||||
|
f = (e & a) | (e & b) | (a & b);
|
||||||
|
t = (d << 5) | (d >>> 27);
|
||||||
|
c = t + f + c - 1894007588 + blocks[j + 2] << 0;
|
||||||
|
e = (e << 30) | (e >>> 2);
|
||||||
|
|
||||||
|
f = (d & e) | (d & a) | (e & a);
|
||||||
|
t = (c << 5) | (c >>> 27);
|
||||||
|
b = t + f + b - 1894007588 + blocks[j + 3] << 0;
|
||||||
|
d = (d << 30) | (d >>> 2);
|
||||||
|
|
||||||
|
f = (c & d) | (c & e) | (d & e);
|
||||||
|
t = (b << 5) | (b >>> 27);
|
||||||
|
a = t + f + a - 1894007588 + blocks[j + 4] << 0;
|
||||||
|
c = (c << 30) | (c >>> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(; j < 80; j += 5) {
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
t = (a << 5) | (a >>> 27);
|
||||||
|
e = t + f + e - 899497514 + blocks[j] << 0;
|
||||||
|
b = (b << 30) | (b >>> 2);
|
||||||
|
|
||||||
|
f = a ^ b ^ c;
|
||||||
|
t = (e << 5) | (e >>> 27);
|
||||||
|
d = t + f + d - 899497514 + blocks[j + 1] << 0;
|
||||||
|
a = (a << 30) | (a >>> 2);
|
||||||
|
|
||||||
|
f = e ^ a ^ b;
|
||||||
|
t = (d << 5) | (d >>> 27);
|
||||||
|
c = t + f + c - 899497514 + blocks[j + 2] << 0;
|
||||||
|
e = (e << 30) | (e >>> 2);
|
||||||
|
|
||||||
|
f = d ^ e ^ a;
|
||||||
|
t = (c << 5) | (c >>> 27);
|
||||||
|
b = t + f + b - 899497514 + blocks[j + 3] << 0;
|
||||||
|
d = (d << 30) | (d >>> 2);
|
||||||
|
|
||||||
|
f = c ^ d ^ e;
|
||||||
|
t = (b << 5) | (b >>> 27);
|
||||||
|
a = t + f + a - 899497514 + blocks[j + 4] << 0;
|
||||||
|
c = (c << 30) | (c >>> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.h0 = this.h0 + a << 0;
|
||||||
|
this.h1 = this.h1 + b << 0;
|
||||||
|
this.h2 = this.h2 + c << 0;
|
||||||
|
this.h3 = this.h3 + d << 0;
|
||||||
|
this.h4 = this.h4 + e << 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sha1.prototype.hex = function () {
|
||||||
|
this.finalize();
|
||||||
|
|
||||||
|
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
|
||||||
|
|
||||||
|
return HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
|
||||||
|
HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
|
||||||
|
HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
|
||||||
|
HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
|
||||||
|
HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
|
||||||
|
HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
|
||||||
|
HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
|
||||||
|
HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
|
||||||
|
HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
|
||||||
|
HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
|
||||||
|
HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
|
||||||
|
HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
|
||||||
|
HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] +
|
||||||
|
HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
|
||||||
|
HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
|
||||||
|
HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
|
||||||
|
HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] +
|
||||||
|
HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] +
|
||||||
|
HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] +
|
||||||
|
HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F];
|
||||||
|
};
|
||||||
|
|
||||||
|
Sha1.prototype.toString = Sha1.prototype.hex;
|
||||||
|
|
||||||
|
Sha1.prototype.digest = function () {
|
||||||
|
this.finalize();
|
||||||
|
|
||||||
|
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF,
|
||||||
|
(h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF,
|
||||||
|
(h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF,
|
||||||
|
(h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF,
|
||||||
|
(h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
Sha1.prototype.array = Sha1.prototype.digest;
|
||||||
|
|
||||||
|
Sha1.prototype.arrayBuffer = function () {
|
||||||
|
this.finalize();
|
||||||
|
|
||||||
|
const buffer = new ArrayBuffer(20);
|
||||||
|
const dataView = new DataView(buffer);
|
||||||
|
dataView.setUint32(0, this.h0);
|
||||||
|
dataView.setUint32(4, this.h1);
|
||||||
|
dataView.setUint32(8, this.h2);
|
||||||
|
dataView.setUint32(12, this.h3);
|
||||||
|
dataView.setUint32(16, this.h4);
|
||||||
|
return buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
createMethod();
|
||||||
|
})();
|
||||||
|
|
||||||
|
export function encode_text(buffer: string) : ArrayBuffer {
|
||||||
|
if ((window as any).TextEncoder) {
|
||||||
|
return new TextEncoder().encode(buffer).buffer;
|
||||||
}
|
}
|
||||||
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
|
let utf8 = unescape(encodeURIComponent(buffer));
|
||||||
if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!";
|
let result = new Uint8Array(utf8.length);
|
||||||
|
for (let i = 0; i < utf8.length; i++) {
|
||||||
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
|
result[i] = utf8.charCodeAt(i);
|
||||||
|
|
||||||
if(!crypto || !crypto.subtle || !crypto.subtle.digest || /Edge/.test(navigator.userAgent))
|
|
||||||
return new Promise<ArrayBuffer>(resolve => {
|
|
||||||
resolve(_sha1.arrayBuffer(buffer as ArrayBuffer));
|
|
||||||
});
|
|
||||||
else
|
|
||||||
return crypto.subtle.digest("SHA-1", buffer);
|
|
||||||
}
|
}
|
||||||
|
return result.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
export function sha1(message: string | ArrayBuffer) : PromiseLike<ArrayBuffer> {
|
||||||
|
if(!(typeof(message) === "string" || message instanceof ArrayBuffer)) throw "Invalid type!";
|
||||||
|
|
||||||
|
let buffer = message instanceof ArrayBuffer ? message : encode_text(message as string);
|
||||||
|
|
||||||
|
if(!crypto || !crypto.subtle || !crypto.subtle.digest || /Edge/.test(navigator.userAgent))
|
||||||
|
return new Promise<ArrayBuffer>(resolve => {
|
||||||
|
resolve(_sha1.arrayBuffer(buffer as ArrayBuffer));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
return crypto.subtle.digest("SHA-1", buffer);
|
||||||
|
}
|
||||||
|
|
10
shared/js/crypto/uid.ts
Normal file
10
shared/js/crypto/uid.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
function s4() {
|
||||||
|
return Math
|
||||||
|
.floor((1 + Math.random()) * 0x10000)
|
||||||
|
.toString(16)
|
||||||
|
.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function guid() {
|
||||||
|
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||||
|
}
|
|
@ -1,24 +1,22 @@
|
||||||
namespace dns {
|
export interface AddressTarget {
|
||||||
export interface AddressTarget {
|
target_ip: string;
|
||||||
target_ip: string;
|
target_port?: number;
|
||||||
target_port?: number;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResolveOptions {
|
export interface ResolveOptions {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
allow_cache?: boolean;
|
allow_cache?: boolean;
|
||||||
max_depth?: number;
|
max_depth?: number;
|
||||||
|
|
||||||
allow_srv?: boolean;
|
allow_srv?: boolean;
|
||||||
allow_cname?: boolean;
|
allow_cname?: boolean;
|
||||||
allow_any?: boolean;
|
allow_any?: boolean;
|
||||||
allow_a?: boolean;
|
allow_a?: boolean;
|
||||||
allow_aaaa?: boolean;
|
allow_aaaa?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const default_options: ResolveOptions = {
|
export const default_options: ResolveOptions = {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
allow_cache: true,
|
allow_cache: true,
|
||||||
max_depth: 5
|
max_depth: 5
|
||||||
};
|
};
|
||||||
}
|
|
1111
shared/js/events.ts
1111
shared/js/events.ts
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,324 +1,317 @@
|
||||||
function guid() {
|
import * as log from "tc-shared/log";
|
||||||
function s4() {
|
import {LogCategory} from "tc-shared/log";
|
||||||
return Math.floor((1 + Math.random()) * 0x10000)
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
.toString(16)
|
import {StaticSettings} from "tc-shared/settings";
|
||||||
.substring(1);
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
}
|
import * as loader from "tc-loader";
|
||||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
|
||||||
|
export interface TranslationKey {
|
||||||
|
message: string;
|
||||||
|
line?: number;
|
||||||
|
character?: number;
|
||||||
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace i18n {
|
export interface Translation {
|
||||||
export interface TranslationKey {
|
key: TranslationKey;
|
||||||
message: string;
|
translated: string;
|
||||||
line?: number;
|
flags?: string[];
|
||||||
character?: number;
|
}
|
||||||
filename?: string;
|
|
||||||
|
export interface Contributor {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranslationFile {
|
||||||
|
path: string;
|
||||||
|
full_url: string;
|
||||||
|
|
||||||
|
translations: Translation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RepositoryTranslation {
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
country_code: string;
|
||||||
|
name: string;
|
||||||
|
contributors: Contributor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranslationRepository {
|
||||||
|
unique_id: string;
|
||||||
|
url: string;
|
||||||
|
name?: string;
|
||||||
|
contact?: string;
|
||||||
|
translations?: RepositoryTranslation[];
|
||||||
|
load_timestamp?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let translations: Translation[] = [];
|
||||||
|
let fast_translate: { [key:string]:string; } = {};
|
||||||
|
export function tr(message: string, key?: string) {
|
||||||
|
const sloppy = fast_translate[message];
|
||||||
|
if(sloppy) return sloppy;
|
||||||
|
|
||||||
|
log.info(LogCategory.I18N, "Translating \"%s\". Default: \"%s\"", key, message);
|
||||||
|
|
||||||
|
let translated = message;
|
||||||
|
for(const translation of translations) {
|
||||||
|
if(translation.key.message == message) {
|
||||||
|
translated = translation.translated;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Translation {
|
fast_translate[message] = translated;
|
||||||
key: TranslationKey;
|
return translated;
|
||||||
translated: string;
|
}
|
||||||
flags?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Contributor {
|
export function tra(message: string, ...args: any[]) {
|
||||||
name: string;
|
message = /* @tr-ignore */ tr(message);
|
||||||
email: string;
|
return formatMessage(message, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TranslationFile {
|
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
|
||||||
path: string;
|
return new Promise<TranslationFile>((resolve, reject) => {
|
||||||
full_url: string;
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
async: true,
|
||||||
|
success: result => {
|
||||||
|
try {
|
||||||
|
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
||||||
|
if(!file) {
|
||||||
|
reject("Invalid json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
translations: Translation[];
|
file.full_url = url;
|
||||||
}
|
file.path = path;
|
||||||
|
|
||||||
export interface RepositoryTranslation {
|
//TODO: Validate file
|
||||||
key: string;
|
resolve(file);
|
||||||
path: string;
|
} catch(error) {
|
||||||
|
log.warn(LogCategory.I18N, tr("Failed to load translation file %s. Failed to parse or process json: %o"), url, error);
|
||||||
country_code: string;
|
reject(tr("Failed to process or parse json!"));
|
||||||
name: string;
|
}
|
||||||
contributors: Contributor[];
|
},
|
||||||
}
|
error: (xhr, error) => {
|
||||||
|
reject(tr("Failed to load file: ") + error);
|
||||||
export interface TranslationRepository {
|
|
||||||
unique_id: string;
|
|
||||||
url: string;
|
|
||||||
name?: string;
|
|
||||||
contact?: string;
|
|
||||||
translations?: RepositoryTranslation[];
|
|
||||||
load_timestamp?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
let translations: Translation[] = [];
|
|
||||||
let fast_translate: { [key:string]:string; } = {};
|
|
||||||
export function tr(message: string, key?: string) {
|
|
||||||
const sloppy = fast_translate[message];
|
|
||||||
if(sloppy) return sloppy;
|
|
||||||
|
|
||||||
log.info(LogCategory.I18N, "Translating \"%s\". Default: \"%s\"", key, message);
|
|
||||||
|
|
||||||
let translated = message;
|
|
||||||
for(const translation of translations) {
|
|
||||||
if(translation.key.message == message) {
|
|
||||||
translated = translation.translated;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function load_file(url: string, path: string) : Promise<void> {
|
||||||
|
return load_translation_file(url, path).then(async result => {
|
||||||
|
/* TODO: Improve this test?!*/
|
||||||
|
try {
|
||||||
|
tr("Dummy translation test");
|
||||||
|
} catch(error) {
|
||||||
|
throw "dummy test failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
fast_translate[message] = translated;
|
log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url);
|
||||||
return translated;
|
translations = result.translations;
|
||||||
}
|
}).catch(error => {
|
||||||
|
log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function tra(message: string, ...args: any[]) {
|
async function load_repository0(repo: TranslationRepository, reload: boolean) {
|
||||||
message = tr(message);
|
if(!repo.load_timestamp || repo.load_timestamp < 1000 || reload) {
|
||||||
return MessageHelper.formatMessage(message, ...args);
|
const info_json = await new Promise((resolve, reject) => {
|
||||||
}
|
|
||||||
|
|
||||||
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
|
|
||||||
return new Promise<TranslationFile>((resolve, reject) => {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: repo.url + "/info.json",
|
||||||
async: true,
|
async: true,
|
||||||
|
cache: !reload,
|
||||||
success: result => {
|
success: result => {
|
||||||
try {
|
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
||||||
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
if(!file) {
|
||||||
if(!file) {
|
reject("Invalid json");
|
||||||
reject("Invalid json");
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.full_url = url;
|
|
||||||
file.path = path;
|
|
||||||
|
|
||||||
//TODO: Validate file
|
|
||||||
resolve(file);
|
|
||||||
} catch(error) {
|
|
||||||
log.warn(LogCategory.I18N, tr("Failed to load translation file %s. Failed to parse or process json: %o"), url, error);
|
|
||||||
reject(tr("Failed to process or parse json!"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve(file);
|
||||||
},
|
},
|
||||||
error: (xhr, error) => {
|
error: (xhr, error) => {
|
||||||
reject(tr("Failed to load file: ") + error);
|
reject(tr("Failed to load file: ") + error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Object.assign(repo, info_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function load_file(url: string, path: string) : Promise<void> {
|
if(!repo.unique_id)
|
||||||
return load_translation_file(url, path).then(async result => {
|
repo.unique_id = guid();
|
||||||
/* TODO: Improve this test?!*/
|
|
||||||
try {
|
|
||||||
tr("Dummy translation test");
|
|
||||||
} catch(error) {
|
|
||||||
throw "dummy test failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url);
|
repo.translations = repo.translations || [];
|
||||||
translations = result.translations;
|
repo.load_timestamp = Date.now();
|
||||||
}).catch(error => {
|
}
|
||||||
log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error);
|
|
||||||
return Promise.reject(error);
|
export async function load_repository(url: string) : Promise<TranslationRepository> {
|
||||||
});
|
const result = {} as TranslationRepository;
|
||||||
|
result.url = url;
|
||||||
|
await load_repository0(result, false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace config {
|
||||||
|
export interface TranslationConfig {
|
||||||
|
current_repository_url?: string;
|
||||||
|
current_language?: string;
|
||||||
|
|
||||||
|
current_translation_url?: string;
|
||||||
|
current_translation_path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load_repository0(repo: TranslationRepository, reload: boolean) {
|
export interface RepositoryConfig {
|
||||||
if(!repo.load_timestamp || repo.load_timestamp < 1000 || reload) {
|
repositories?: {
|
||||||
const info_json = await new Promise((resolve, reject) => {
|
url?: string;
|
||||||
$.ajax({
|
repository?: TranslationRepository;
|
||||||
url: repo.url + "/info.json",
|
}[];
|
||||||
async: true,
|
}
|
||||||
cache: !reload,
|
|
||||||
success: result => {
|
|
||||||
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
|
||||||
if(!file) {
|
|
||||||
reject("Invalid json");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(file);
|
const repository_config_key = "i18n.repository";
|
||||||
},
|
let _cached_repository_config: RepositoryConfig;
|
||||||
error: (xhr, error) => {
|
export function repository_config() {
|
||||||
reject(tr("Failed to load file: ") + error);
|
if(_cached_repository_config)
|
||||||
}
|
return _cached_repository_config;
|
||||||
})
|
|
||||||
|
const config_string = localStorage.getItem(repository_config_key);
|
||||||
|
let config: RepositoryConfig;
|
||||||
|
try {
|
||||||
|
config = config_string ? JSON.parse(config_string) : {};
|
||||||
|
} catch(error) {
|
||||||
|
log.error(LogCategory.I18N, tr("Failed to parse repository config: %o"), error);
|
||||||
|
}
|
||||||
|
config.repositories = config.repositories || [];
|
||||||
|
for(const repo of config.repositories)
|
||||||
|
(repo.repository || {load_timestamp: 0}).load_timestamp = 0;
|
||||||
|
|
||||||
|
if(config.repositories.length == 0) {
|
||||||
|
//Add the default TeaSpeak repository
|
||||||
|
load_repository(StaticSettings.instance.static("i18n.default_repository", "https://web.teaspeak.de/i18n/")).then(repo => {
|
||||||
|
log.info(LogCategory.I18N, tr("Successfully added default repository from \"%s\"."), repo.url);
|
||||||
|
register_repository(repo);
|
||||||
|
}).catch(error => {
|
||||||
|
log.warn(LogCategory.I18N, tr("Failed to add default repository. Error: %o"), error);
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(repo, info_json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!repo.unique_id)
|
return _cached_repository_config = config;
|
||||||
repo.unique_id = guid();
|
|
||||||
|
|
||||||
repo.translations = repo.translations || [];
|
|
||||||
repo.load_timestamp = Date.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load_repository(url: string) : Promise<TranslationRepository> {
|
export function save_repository_config() {
|
||||||
const result = {} as TranslationRepository;
|
localStorage.setItem(repository_config_key, JSON.stringify(_cached_repository_config));
|
||||||
result.url = url;
|
|
||||||
await load_repository0(result, false);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace config {
|
const translation_config_key = "i18n.translation";
|
||||||
export interface TranslationConfig {
|
let _cached_translation_config: TranslationConfig;
|
||||||
current_repository_url?: string;
|
|
||||||
current_language?: string;
|
|
||||||
|
|
||||||
current_translation_url?: string;
|
export function translation_config() : TranslationConfig {
|
||||||
current_translation_path?: string;
|
if(_cached_translation_config)
|
||||||
}
|
|
||||||
|
|
||||||
export interface RepositoryConfig {
|
|
||||||
repositories?: {
|
|
||||||
url?: string;
|
|
||||||
repository?: TranslationRepository;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const repository_config_key = "i18n.repository";
|
|
||||||
let _cached_repository_config: RepositoryConfig;
|
|
||||||
export function repository_config() {
|
|
||||||
if(_cached_repository_config)
|
|
||||||
return _cached_repository_config;
|
|
||||||
|
|
||||||
const config_string = localStorage.getItem(repository_config_key);
|
|
||||||
let config: RepositoryConfig;
|
|
||||||
try {
|
|
||||||
config = config_string ? JSON.parse(config_string) : {};
|
|
||||||
} catch(error) {
|
|
||||||
log.error(LogCategory.I18N, tr("Failed to parse repository config: %o"), error);
|
|
||||||
}
|
|
||||||
config.repositories = config.repositories || [];
|
|
||||||
for(const repo of config.repositories)
|
|
||||||
(repo.repository || {load_timestamp: 0}).load_timestamp = 0;
|
|
||||||
|
|
||||||
if(config.repositories.length == 0) {
|
|
||||||
//Add the default TeaSpeak repository
|
|
||||||
load_repository(StaticSettings.instance.static("i18n.default_repository", "https://web.teaspeak.de/i18n/")).then(repo => {
|
|
||||||
log.info(LogCategory.I18N, tr("Successfully added default repository from \"%s\"."), repo.url);
|
|
||||||
register_repository(repo);
|
|
||||||
}).catch(error => {
|
|
||||||
log.warn(LogCategory.I18N, tr("Failed to add default repository. Error: %o"), error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _cached_repository_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function save_repository_config() {
|
|
||||||
localStorage.setItem(repository_config_key, JSON.stringify(_cached_repository_config));
|
|
||||||
}
|
|
||||||
|
|
||||||
const translation_config_key = "i18n.translation";
|
|
||||||
let _cached_translation_config: TranslationConfig;
|
|
||||||
|
|
||||||
export function translation_config() : TranslationConfig {
|
|
||||||
if(_cached_translation_config)
|
|
||||||
return _cached_translation_config;
|
|
||||||
|
|
||||||
const config_string = localStorage.getItem(translation_config_key);
|
|
||||||
try {
|
|
||||||
_cached_translation_config = config_string ? JSON.parse(config_string) : {};
|
|
||||||
} catch(error) {
|
|
||||||
log.error(LogCategory.I18N, tr("Failed to initialize translation config. Using default one. Error: %o"), error);
|
|
||||||
_cached_translation_config = {} as any;
|
|
||||||
}
|
|
||||||
return _cached_translation_config;
|
return _cached_translation_config;
|
||||||
|
|
||||||
|
const config_string = localStorage.getItem(translation_config_key);
|
||||||
|
try {
|
||||||
|
_cached_translation_config = config_string ? JSON.parse(config_string) : {};
|
||||||
|
} catch(error) {
|
||||||
|
log.error(LogCategory.I18N, tr("Failed to initialize translation config. Using default one. Error: %o"), error);
|
||||||
|
_cached_translation_config = {} as any;
|
||||||
}
|
}
|
||||||
|
return _cached_translation_config;
|
||||||
export function save_translation_config() {
|
|
||||||
localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function register_repository(repository: TranslationRepository) {
|
export function save_translation_config() {
|
||||||
if(!repository) return;
|
localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config));
|
||||||
|
|
||||||
for(const repo of config.repository_config().repositories)
|
|
||||||
if(repo.url == repository.url) return;
|
|
||||||
|
|
||||||
config.repository_config().repositories.push(repository);
|
|
||||||
config.save_repository_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registered_repositories() : TranslationRepository[] {
|
|
||||||
return config.repository_config().repositories.map(e => e.repository || {url: e.url, load_timestamp: 0} as TranslationRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function delete_repository(repository: TranslationRepository) {
|
|
||||||
if(!repository) return;
|
|
||||||
|
|
||||||
for(const repo of [...config.repository_config().repositories])
|
|
||||||
if(repo.url == repository.url) {
|
|
||||||
config.repository_config().repositories.remove(repo);
|
|
||||||
}
|
|
||||||
config.save_repository_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) {
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for(const repository of registered_repositories()) {
|
|
||||||
promises.push(load_repository0(repository, false).then(() => callback_entry(repository)).catch(error => {
|
|
||||||
log.warn(LogCategory.I18N, "Failed to fetch repository %s. error: %o", repository.url, error);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) {
|
|
||||||
const cfg = config.translation_config();
|
|
||||||
|
|
||||||
if(entry && repository) {
|
|
||||||
cfg.current_language = entry.name;
|
|
||||||
cfg.current_repository_url = repository.url;
|
|
||||||
cfg.current_translation_url = repository.url + entry.path;
|
|
||||||
cfg.current_translation_path = entry.path;
|
|
||||||
} else {
|
|
||||||
cfg.current_language = undefined;
|
|
||||||
cfg.current_repository_url = undefined;
|
|
||||||
cfg.current_translation_url = undefined;
|
|
||||||
cfg.current_translation_path = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.save_translation_config();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ATTENTION: This method is called before most other library inizialisations! */
|
|
||||||
export async function initialize() {
|
|
||||||
const rcfg = config.repository_config(); /* initialize */
|
|
||||||
const cfg = config.translation_config();
|
|
||||||
|
|
||||||
if(cfg.current_translation_url) {
|
|
||||||
try {
|
|
||||||
await load_file(cfg.current_translation_url, cfg.current_translation_path);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(tr("Failed to initialize selected translation: %o"), error);
|
|
||||||
const show_error = () => {
|
|
||||||
createErrorModal(tr("Translation System"), tra("Failed to load current selected translation file.{:br:}File: {0}{:br:}Error: {1}{:br:}{:br:}Using default fallback translations.", cfg.current_translation_url, error)).open()
|
|
||||||
};
|
|
||||||
if(loader.running())
|
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
priority: 10,
|
|
||||||
function: async () => show_error(),
|
|
||||||
name: "I18N error display"
|
|
||||||
});
|
|
||||||
else
|
|
||||||
show_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/de_DE.translation");
|
|
||||||
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
export function register_repository(repository: TranslationRepository) {
|
||||||
const tr: typeof i18n.tr = i18n.tr;
|
if(!repository) return;
|
||||||
const tra: typeof i18n.tra = i18n.tra;
|
|
||||||
|
|
||||||
(window as any).tr = i18n.tr;
|
for(const repo of config.repository_config().repositories)
|
||||||
(window as any).tra = i18n.tra;
|
if(repo.url == repository.url) return;
|
||||||
|
|
||||||
|
config.repository_config().repositories.push(repository);
|
||||||
|
config.save_repository_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registered_repositories() : TranslationRepository[] {
|
||||||
|
return config.repository_config().repositories.map(e => e.repository || {url: e.url, load_timestamp: 0} as TranslationRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delete_repository(repository: TranslationRepository) {
|
||||||
|
if(!repository) return;
|
||||||
|
|
||||||
|
for(const repo of [...config.repository_config().repositories])
|
||||||
|
if(repo.url == repository.url) {
|
||||||
|
config.repository_config().repositories.remove(repo);
|
||||||
|
}
|
||||||
|
config.save_repository_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for(const repository of registered_repositories()) {
|
||||||
|
promises.push(load_repository0(repository, false).then(() => callback_entry(repository)).catch(error => {
|
||||||
|
log.warn(LogCategory.I18N, "Failed to fetch repository %s. error: %o", repository.url, error);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) {
|
||||||
|
const cfg = config.translation_config();
|
||||||
|
|
||||||
|
if(entry && repository) {
|
||||||
|
cfg.current_language = entry.name;
|
||||||
|
cfg.current_repository_url = repository.url;
|
||||||
|
cfg.current_translation_url = repository.url + entry.path;
|
||||||
|
cfg.current_translation_path = entry.path;
|
||||||
|
} else {
|
||||||
|
cfg.current_language = undefined;
|
||||||
|
cfg.current_repository_url = undefined;
|
||||||
|
cfg.current_translation_url = undefined;
|
||||||
|
cfg.current_translation_path = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.save_translation_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ATTENTION: This method is called before most other library initialisations! */
|
||||||
|
export async function initialize() {
|
||||||
|
const rcfg = config.repository_config(); /* initialize */
|
||||||
|
const cfg = config.translation_config();
|
||||||
|
|
||||||
|
if(cfg.current_translation_url) {
|
||||||
|
try {
|
||||||
|
await load_file(cfg.current_translation_url, cfg.current_translation_path);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(tr("Failed to initialize selected translation: %o"), error);
|
||||||
|
const show_error = () => {
|
||||||
|
createErrorModal(tr("Translation System"), tra("Failed to load current selected translation file.{:br:}File: {0}{:br:}Error: {1}{:br:}{:br:}Using default fallback translations.", cfg.current_translation_url, error)).open()
|
||||||
|
};
|
||||||
|
if(loader.running())
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
priority: 10,
|
||||||
|
function: async () => show_error(),
|
||||||
|
name: "I18N error display"
|
||||||
|
});
|
||||||
|
else
|
||||||
|
show_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/de_DE.translation");
|
||||||
|
// await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.tr = tr;
|
||||||
|
window.tra = tra;
|
455
shared/js/log.ts
455
shared/js/log.ts
|
@ -1,6 +1,8 @@
|
||||||
//Used by CertAccept popup
|
//Used by CertAccept popup
|
||||||
|
import {settings} from "tc-shared/settings";
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
|
||||||
enum LogCategory {
|
export enum LogCategory {
|
||||||
CHANNEL,
|
CHANNEL,
|
||||||
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
|
CHANNEL_PROPERTIES, /* separating channel and channel properties because on channel init logging is a big bottleneck */
|
||||||
CLIENT,
|
CLIENT,
|
||||||
|
@ -19,233 +21,238 @@ enum LogCategory {
|
||||||
DNS
|
DNS
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace log {
|
export enum LogType {
|
||||||
export enum LogType {
|
TRACE,
|
||||||
TRACE,
|
DEBUG,
|
||||||
DEBUG,
|
INFO,
|
||||||
INFO,
|
WARNING,
|
||||||
WARNING,
|
ERROR
|
||||||
ERROR
|
}
|
||||||
|
|
||||||
|
let category_mapping = new Map<number, string>([
|
||||||
|
[LogCategory.CHANNEL, "Channel "],
|
||||||
|
[LogCategory.CHANNEL_PROPERTIES, "Channel "],
|
||||||
|
[LogCategory.CLIENT, "Client "],
|
||||||
|
[LogCategory.SERVER, "Server "],
|
||||||
|
[LogCategory.BOOKMARKS, "Bookmark "],
|
||||||
|
[LogCategory.PERMISSIONS, "Permission "],
|
||||||
|
[LogCategory.GENERAL, "General "],
|
||||||
|
[LogCategory.NETWORKING, "Network "],
|
||||||
|
[LogCategory.VOICE, "Voice "],
|
||||||
|
[LogCategory.AUDIO, "Audio "],
|
||||||
|
[LogCategory.CHANNEL, "Chat "],
|
||||||
|
[LogCategory.I18N, "I18N "],
|
||||||
|
[LogCategory.IDENTITIES, "Identities "],
|
||||||
|
[LogCategory.IPC, "IPC "],
|
||||||
|
[LogCategory.STATISTICS, "Statistics "],
|
||||||
|
[LogCategory.DNS, "DNS "]
|
||||||
|
]);
|
||||||
|
|
||||||
|
export let enabled_mapping = new Map<number, boolean>([
|
||||||
|
[LogCategory.CHANNEL, true],
|
||||||
|
[LogCategory.CHANNEL_PROPERTIES, false],
|
||||||
|
[LogCategory.CLIENT, true],
|
||||||
|
[LogCategory.SERVER, true],
|
||||||
|
[LogCategory.BOOKMARKS, true],
|
||||||
|
[LogCategory.PERMISSIONS, true],
|
||||||
|
[LogCategory.GENERAL, true],
|
||||||
|
[LogCategory.NETWORKING, true],
|
||||||
|
[LogCategory.VOICE, true],
|
||||||
|
[LogCategory.AUDIO, true],
|
||||||
|
[LogCategory.CHAT, true],
|
||||||
|
[LogCategory.I18N, false],
|
||||||
|
[LogCategory.IDENTITIES, true],
|
||||||
|
[LogCategory.IPC, true],
|
||||||
|
[LogCategory.STATISTICS, true],
|
||||||
|
[LogCategory.DNS, true]
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Values will be overridden by initialize()
|
||||||
|
export let level_mapping = new Map<LogType, boolean>([
|
||||||
|
[LogType.TRACE, true],
|
||||||
|
[LogType.DEBUG, true],
|
||||||
|
[LogType.INFO, true],
|
||||||
|
[LogType.WARNING, true],
|
||||||
|
[LogType.ERROR, true]
|
||||||
|
]);
|
||||||
|
|
||||||
|
enum GroupMode {
|
||||||
|
NATIVE,
|
||||||
|
PREFIX
|
||||||
|
}
|
||||||
|
const group_mode: GroupMode = GroupMode.PREFIX;
|
||||||
|
|
||||||
|
//Category Example: <url>?log.i18n.enabled=0
|
||||||
|
//Level Example A: <url>?log.level.trace.enabled=0
|
||||||
|
//Level Example B: <url>?log.level=0
|
||||||
|
export function initialize(default_level: LogType) {
|
||||||
|
for(const category of Object.keys(LogCategory).map(e => parseInt(e))) {
|
||||||
|
if(isNaN(category)) continue;
|
||||||
|
const category_name = LogCategory[category].toLowerCase();
|
||||||
|
enabled_mapping.set(category, settings.static_global<boolean>("log." + category_name.toLowerCase() + ".enabled", enabled_mapping.get(category)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let category_mapping = new Map<number, string>([
|
const base_level = settings.static_global<number>("log.level", default_level);
|
||||||
[LogCategory.CHANNEL, "Channel "],
|
|
||||||
[LogCategory.CHANNEL_PROPERTIES, "Channel "],
|
|
||||||
[LogCategory.CLIENT, "Client "],
|
|
||||||
[LogCategory.SERVER, "Server "],
|
|
||||||
[LogCategory.BOOKMARKS, "Bookmark "],
|
|
||||||
[LogCategory.PERMISSIONS, "Permission "],
|
|
||||||
[LogCategory.GENERAL, "General "],
|
|
||||||
[LogCategory.NETWORKING, "Network "],
|
|
||||||
[LogCategory.VOICE, "Voice "],
|
|
||||||
[LogCategory.AUDIO, "Audio "],
|
|
||||||
[LogCategory.CHANNEL, "Chat "],
|
|
||||||
[LogCategory.I18N, "I18N "],
|
|
||||||
[LogCategory.IDENTITIES, "Identities "],
|
|
||||||
[LogCategory.IPC, "IPC "],
|
|
||||||
[LogCategory.STATISTICS, "Statistics "],
|
|
||||||
[LogCategory.DNS, "DNS "]
|
|
||||||
]);
|
|
||||||
|
|
||||||
export let enabled_mapping = new Map<number, boolean>([
|
for(const level of Object.keys(LogType).map(e => parseInt(e))) {
|
||||||
[LogCategory.CHANNEL, true],
|
if(isNaN(level)) continue;
|
||||||
[LogCategory.CHANNEL_PROPERTIES, false],
|
|
||||||
[LogCategory.CLIENT, true],
|
|
||||||
[LogCategory.SERVER, true],
|
|
||||||
[LogCategory.BOOKMARKS, true],
|
|
||||||
[LogCategory.PERMISSIONS, true],
|
|
||||||
[LogCategory.GENERAL, true],
|
|
||||||
[LogCategory.NETWORKING, true],
|
|
||||||
[LogCategory.VOICE, true],
|
|
||||||
[LogCategory.AUDIO, true],
|
|
||||||
[LogCategory.CHAT, true],
|
|
||||||
[LogCategory.I18N, false],
|
|
||||||
[LogCategory.IDENTITIES, true],
|
|
||||||
[LogCategory.IPC, true],
|
|
||||||
[LogCategory.STATISTICS, true],
|
|
||||||
[LogCategory.DNS, true]
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Values will be overridden by initialize()
|
const level_name = LogType[level].toLowerCase();
|
||||||
export let level_mapping = new Map<LogType, boolean>([
|
level_mapping.set(level, settings.static_global<boolean>("log." + level_name + ".enabled", level >= base_level));
|
||||||
[LogType.TRACE, true],
|
|
||||||
[LogType.DEBUG, true],
|
|
||||||
[LogType.INFO, true],
|
|
||||||
[LogType.WARNING, true],
|
|
||||||
[LogType.ERROR, true]
|
|
||||||
]);
|
|
||||||
|
|
||||||
enum GroupMode {
|
|
||||||
NATIVE,
|
|
||||||
PREFIX
|
|
||||||
}
|
|
||||||
const group_mode: GroupMode = GroupMode.PREFIX;
|
|
||||||
|
|
||||||
//Category Example: <url>?log.i18n.enabled=0
|
|
||||||
//Level Example A: <url>?log.level.trace.enabled=0
|
|
||||||
//Level Example B: <url>?log.level=0
|
|
||||||
export function initialize(default_level: LogType) {
|
|
||||||
for(const category of Object.keys(LogCategory).map(e => parseInt(e))) {
|
|
||||||
if(isNaN(category)) continue;
|
|
||||||
const category_name = LogCategory[category].toLowerCase();
|
|
||||||
enabled_mapping.set(category, settings.static_global<boolean>("log." + category_name.toLowerCase() + ".enabled", enabled_mapping.get(category)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const base_level = settings.static_global<number>("log.level", default_level);
|
|
||||||
|
|
||||||
for(const level of Object.keys(LogType).map(e => parseInt(e))) {
|
|
||||||
if(isNaN(level)) continue;
|
|
||||||
|
|
||||||
const level_name = LogType[level].toLowerCase();
|
|
||||||
level_mapping.set(level, settings.static_global<boolean>("log." + level_name + ".enabled", level >= base_level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function logDirect(type: LogType, message: string, ...optionalParams: any[]) {
|
|
||||||
if(!level_mapping.get(type))
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case LogType.TRACE:
|
|
||||||
case LogType.DEBUG:
|
|
||||||
console.debug(message, ...optionalParams);
|
|
||||||
break;
|
|
||||||
case LogType.INFO:
|
|
||||||
console.log(message, ...optionalParams);
|
|
||||||
break;
|
|
||||||
case LogType.WARNING:
|
|
||||||
console.warn(message, ...optionalParams);
|
|
||||||
break;
|
|
||||||
case LogType.ERROR:
|
|
||||||
console.error(message, ...optionalParams);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log(type: LogType, category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
if(!enabled_mapping.get(category)) return;
|
|
||||||
|
|
||||||
optionalParams.unshift(category_mapping.get(category));
|
|
||||||
message = "[%s] " + message;
|
|
||||||
logDirect(type, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trace(category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
log(LogType.TRACE, category, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debug(category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
log(LogType.DEBUG, category, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function info(category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
log(LogType.INFO, category, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function warn(category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
log(LogType.WARNING, category, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function error(category: LogCategory, message: string, ...optionalParams: any[]) {
|
|
||||||
log(LogType.ERROR, category, message, ...optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function group(level: LogType, category: LogCategory, name: string, ...optionalParams: any[]) : Group {
|
|
||||||
name = "[%s] " + name;
|
|
||||||
optionalParams.unshift(category_mapping.get(category));
|
|
||||||
|
|
||||||
return new Group(group_mode, level, category, name, optionalParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function table(level: LogType, category: LogCategory, title: string, arguments: any) {
|
|
||||||
if(group_mode == GroupMode.NATIVE) {
|
|
||||||
console.groupCollapsed(title);
|
|
||||||
console.table(arguments);
|
|
||||||
console.groupEnd();
|
|
||||||
} else {
|
|
||||||
if(!enabled_mapping.get(category) || !level_mapping.get(level))
|
|
||||||
return;
|
|
||||||
logDirect(level, tr("Snipped table \"%s\""), title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Group {
|
|
||||||
readonly mode: GroupMode;
|
|
||||||
readonly level: LogType;
|
|
||||||
readonly category: LogCategory;
|
|
||||||
readonly enabled: boolean;
|
|
||||||
|
|
||||||
owner: Group = undefined;
|
|
||||||
|
|
||||||
private readonly name: string;
|
|
||||||
private readonly optionalParams: any[][];
|
|
||||||
private _collapsed: boolean = false;
|
|
||||||
private initialized = false;
|
|
||||||
private _log_prefix: string;
|
|
||||||
|
|
||||||
constructor(mode: GroupMode, level: LogType, category: LogCategory, name: string, optionalParams: any[][], owner: Group = undefined) {
|
|
||||||
this.level = level;
|
|
||||||
this.mode = mode;
|
|
||||||
this.category = category;
|
|
||||||
this.name = name;
|
|
||||||
this.optionalParams = optionalParams;
|
|
||||||
this.enabled = enabled_mapping.get(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
group(level: LogType, name: string, ...optionalParams: any[]) : Group {
|
|
||||||
return new Group(this.mode, level, this.category, name, optionalParams, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
collapsed(flag: boolean = true) : this {
|
|
||||||
this._collapsed = flag;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(message: string, ...optionalParams: any[]) : this {
|
|
||||||
if(!this.enabled)
|
|
||||||
return this;
|
|
||||||
|
|
||||||
if(!this.initialized) {
|
|
||||||
if(this.mode == GroupMode.NATIVE) {
|
|
||||||
if(this._collapsed && console.groupCollapsed)
|
|
||||||
console.groupCollapsed(this.name, ...this.optionalParams);
|
|
||||||
else
|
|
||||||
console.group(this.name, ...this.optionalParams);
|
|
||||||
} else {
|
|
||||||
this._log_prefix = " ";
|
|
||||||
let parent = this.owner;
|
|
||||||
while(parent) {
|
|
||||||
if(parent.mode == GroupMode.PREFIX)
|
|
||||||
this._log_prefix = this._log_prefix + parent._log_prefix;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.initialized = true;
|
|
||||||
}
|
|
||||||
if(this.mode == GroupMode.NATIVE)
|
|
||||||
logDirect(this.level, message, ...optionalParams);
|
|
||||||
else {
|
|
||||||
logDirect(this.level, "[%s] " + this._log_prefix + message, category_mapping.get(this.category), ...optionalParams);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
end() {
|
|
||||||
if(this.initialized) {
|
|
||||||
if(this.mode == GroupMode.NATIVE)
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get prefix() : string {
|
|
||||||
return this._log_prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
set prefix(prefix: string) {
|
|
||||||
this._log_prefix = prefix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
import LogType = log.LogType;
|
function logDirect(type: LogType, message: string, ...optionalParams: any[]) {
|
||||||
|
if(!level_mapping.get(type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case LogType.TRACE:
|
||||||
|
case LogType.DEBUG:
|
||||||
|
console.debug(message, ...optionalParams);
|
||||||
|
break;
|
||||||
|
case LogType.INFO:
|
||||||
|
console.log(message, ...optionalParams);
|
||||||
|
break;
|
||||||
|
case LogType.WARNING:
|
||||||
|
console.warn(message, ...optionalParams);
|
||||||
|
break;
|
||||||
|
case LogType.ERROR:
|
||||||
|
console.error(message, ...optionalParams);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function log(type: LogType, category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
if(!enabled_mapping.get(category)) return;
|
||||||
|
|
||||||
|
optionalParams.unshift(category_mapping.get(category));
|
||||||
|
message = "[%s] " + message;
|
||||||
|
logDirect(type, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trace(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
log(LogType.TRACE, category, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debug(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
log(LogType.DEBUG, category, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function info(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
log(LogType.INFO, category, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warn(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
log(LogType.WARNING, category, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||||
|
log(LogType.ERROR, category, message, ...optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function group(level: LogType, category: LogCategory, name: string, ...optionalParams: any[]) : Group {
|
||||||
|
name = "[%s] " + name;
|
||||||
|
optionalParams.unshift(category_mapping.get(category));
|
||||||
|
|
||||||
|
return new Group(group_mode, level, category, name, optionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function table(level: LogType, category: LogCategory, title: string, args: any) {
|
||||||
|
if(group_mode == GroupMode.NATIVE) {
|
||||||
|
console.groupCollapsed(title);
|
||||||
|
console.table(args);
|
||||||
|
console.groupEnd();
|
||||||
|
} else {
|
||||||
|
if(!enabled_mapping.get(category) || !level_mapping.get(level))
|
||||||
|
return;
|
||||||
|
logDirect(level, tr("Snipped table \"%s\""), title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Group {
|
||||||
|
readonly mode: GroupMode;
|
||||||
|
readonly level: LogType;
|
||||||
|
readonly category: LogCategory;
|
||||||
|
readonly enabled: boolean;
|
||||||
|
|
||||||
|
owner: Group = undefined;
|
||||||
|
|
||||||
|
private readonly name: string;
|
||||||
|
private readonly optionalParams: any[][];
|
||||||
|
private _collapsed: boolean = false;
|
||||||
|
private initialized = false;
|
||||||
|
private _log_prefix: string;
|
||||||
|
|
||||||
|
constructor(mode: GroupMode, level: LogType, category: LogCategory, name: string, optionalParams: any[][], owner: Group = undefined) {
|
||||||
|
this.level = level;
|
||||||
|
this.mode = mode;
|
||||||
|
this.category = category;
|
||||||
|
this.name = name;
|
||||||
|
this.optionalParams = optionalParams;
|
||||||
|
this.enabled = enabled_mapping.get(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
group(level: LogType, name: string, ...optionalParams: any[]) : Group {
|
||||||
|
return new Group(this.mode, level, this.category, name, optionalParams, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
collapsed(flag: boolean = true) : this {
|
||||||
|
this._collapsed = flag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(message: string, ...optionalParams: any[]) : this {
|
||||||
|
if(!this.enabled)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
if(!this.initialized) {
|
||||||
|
if(this.mode == GroupMode.NATIVE) {
|
||||||
|
if(this._collapsed && console.groupCollapsed)
|
||||||
|
console.groupCollapsed(this.name, ...this.optionalParams);
|
||||||
|
else
|
||||||
|
console.group(this.name, ...this.optionalParams);
|
||||||
|
} else {
|
||||||
|
this._log_prefix = " ";
|
||||||
|
let parent = this.owner;
|
||||||
|
while(parent) {
|
||||||
|
if(parent.mode == GroupMode.PREFIX)
|
||||||
|
this._log_prefix = this._log_prefix + parent._log_prefix;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
if(this.mode == GroupMode.NATIVE)
|
||||||
|
logDirect(this.level, message, ...optionalParams);
|
||||||
|
else {
|
||||||
|
logDirect(this.level, "[%s] " + this._log_prefix + message, category_mapping.get(this.category), ...optionalParams);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
if(this.initialized) {
|
||||||
|
if(this.mode == GroupMode.NATIVE)
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get prefix() : string {
|
||||||
|
return this._log_prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
set prefix(prefix: string) {
|
||||||
|
this._log_prefix = prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "log enabled initialisation",
|
||||||
|
function: async () => initialize(__build.mode === "debug" ? LogType.TRACE : LogType.INFO),
|
||||||
|
priority: 150
|
||||||
|
});
|
||||||
|
|
||||||
|
/* initialize global logging system, use by the loader for example */
|
||||||
|
window.log = module.exports;
|
|
@ -1,31 +1,43 @@
|
||||||
/// <reference path="ui/frames/chat.ts" />
|
import * as moment from "moment";
|
||||||
/// <reference path="ui/modal/ModalConnect.ts" />
|
import * as loader from "tc-loader";
|
||||||
/// <reference path="ui/modal/ModalCreateChannel.ts" />
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
/// <reference path="ui/modal/ModalBanClient.ts" />
|
import * as profiles from "tc-shared/profiles/ConnectionProfile";
|
||||||
/// <reference path="ui/modal/ModalYesNo.ts" />
|
import {LogCategory} from "tc-shared/log";
|
||||||
/// <reference path="ui/modal/ModalBanList.ts" />
|
import * as log from "tc-shared/log";
|
||||||
/// <reference path="settings.ts" />
|
import * as bipc from "./BrowserIPC";
|
||||||
/// <reference path="log.ts" />
|
import * as sound from "./sound/Sounds";
|
||||||
/// <reference path="PPTListener.ts" />
|
import * as i18n from "./i18n/localize";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import {tra} from "./i18n/localize";
|
||||||
|
import {RequestFileUpload} from "tc-shared/FileManager";
|
||||||
|
import * as stats from "./stats";
|
||||||
|
import * as fidentity from "./profiles/identities/TeaForumIdentity";
|
||||||
|
import {default_recorder, RecorderProfile, set_default_recorder} from "tc-shared/voice/RecorderProfile";
|
||||||
|
import * as cmanager from "tc-shared/ui/frames/connection_handlers";
|
||||||
|
import {server_connections, ServerConnectionManager} from "tc-shared/ui/frames/connection_handlers";
|
||||||
|
import * as control_bar from "tc-shared/ui/frames/ControlBar";
|
||||||
|
import {spawnConnectModal} from "tc-shared/ui/modal/ModalConnect";
|
||||||
|
import * as top_menu from "./ui/frames/MenuBar";
|
||||||
|
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||||
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
import {openModalNewcomer} from "tc-shared/ui/modal/ModalNewcomer";
|
||||||
|
import * as aplayer from "tc-backend/audio/player";
|
||||||
|
import * as arecorder from "tc-backend/audio/recorder";
|
||||||
|
import * as ppt from "tc-backend/ppt";
|
||||||
|
|
||||||
import spawnYesNo = Modals.spawnYesNo;
|
/* required import for init */
|
||||||
|
require("./proto").initialize();
|
||||||
|
require("./ui/elements/ContextDivider").initialize();
|
||||||
|
require("./connection/CommandHandler"); /* else it might not get bundled because only the backends are accessing it */
|
||||||
|
|
||||||
const js_render = window.jsrender || $;
|
const js_render = window.jsrender || $;
|
||||||
const native_client = window.require !== undefined;
|
const native_client = window.require !== undefined;
|
||||||
|
|
||||||
function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> {
|
declare global {
|
||||||
if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices)
|
interface Window {
|
||||||
return constraints => navigator.mediaDevices.getUserMedia(constraints);
|
open_connected_question: () => Promise<boolean>;
|
||||||
|
}
|
||||||
const _callbacked_function = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
|
||||||
if(!_callbacked_function)
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
return constraints => new Promise<MediaStream>((resolve, reject) => _callbacked_function(constraints, resolve, reject));
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Window {
|
|
||||||
open_connected_question: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup_close() {
|
function setup_close() {
|
||||||
|
@ -50,7 +62,7 @@ function setup_close() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const exit = () => {
|
const exit = () => {
|
||||||
const {remote} = require('electron');
|
const {remote} = window.require('electron');
|
||||||
remote.getCurrentWindow().close();
|
remote.getCurrentWindow().close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +92,6 @@ function setup_close() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare function moment(...arguments) : any;
|
|
||||||
function setup_jsrender() : boolean {
|
function setup_jsrender() : boolean {
|
||||||
if(!js_render) {
|
if(!js_render) {
|
||||||
loader.critical_error("Missing jsrender extension!");
|
loader.critical_error("Missing jsrender extension!");
|
||||||
|
@ -103,7 +114,7 @@ function setup_jsrender() : boolean {
|
||||||
});
|
});
|
||||||
|
|
||||||
js_render.views.tags("tr", (...args) => {
|
js_render.views.tags("tr", (...args) => {
|
||||||
return tr(args[0]);
|
return /* @tr-ignore */ tr(args[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".jsrender-template").each((idx, _entry) => {
|
$(".jsrender-template").each((idx, _entry) => {
|
||||||
|
@ -133,7 +144,7 @@ async function initialize_app() {
|
||||||
try { //Initialize main template
|
try { //Initialize main template
|
||||||
const main = $("#tmpl_main").renderTag({
|
const main = $("#tmpl_main").renderTag({
|
||||||
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
|
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
|
||||||
app_version: app.ui_version()
|
app_version: __build.version
|
||||||
}).dividerfy();
|
}).dividerfy();
|
||||||
|
|
||||||
$("body").append(main);
|
$("body").append(main);
|
||||||
|
@ -143,21 +154,21 @@ async function initialize_app() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
control_bar = new ControlBar($("#control_bar")); /* setup the control bar */
|
control_bar.set_control_bar(new control_bar.ControlBar($("#control_bar"))); /* setup the control bar */
|
||||||
|
|
||||||
if(!audio.player.initialize())
|
if(!aplayer.initialize())
|
||||||
console.warn(tr("Failed to initialize audio controller!"));
|
console.warn(tr("Failed to initialize audio controller!"));
|
||||||
|
|
||||||
audio.player.on_ready(() => {
|
aplayer.on_ready(() => {
|
||||||
if(audio.player.set_master_volume)
|
if(aplayer.set_master_volume)
|
||||||
audio.player.on_ready(() => audio.player.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
|
aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100));
|
||||||
else
|
else
|
||||||
log.warn(LogCategory.GENERAL, tr("Client does not support audio.player.set_master_volume()... May client is too old?"));
|
log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?"));
|
||||||
if(audio.recorder.device_refresh_available())
|
if(arecorder.device_refresh_available())
|
||||||
audio.recorder.refresh_devices();
|
arecorder.refresh_devices();
|
||||||
});
|
});
|
||||||
|
|
||||||
default_recorder = new RecorderProfile("default");
|
set_default_recorder(new RecorderProfile("default"));
|
||||||
default_recorder.initialize().catch(error => {
|
default_recorder.initialize().catch(error => {
|
||||||
log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
|
log.error(LogCategory.AUDIO, tr("Failed to initialize default recorder: %o"), error);
|
||||||
});
|
});
|
||||||
|
@ -180,78 +191,6 @@ async function initialize_app() {
|
||||||
setup_close();
|
setup_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function str2ab8(str) {
|
|
||||||
const buf = new ArrayBuffer(str.length);
|
|
||||||
const bufView = new Uint8Array(buf);
|
|
||||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
|
||||||
bufView[i] = str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME Dont use atob, because it sucks for non UTF-8 tings */
|
|
||||||
function arrayBufferBase64(base64: string) {
|
|
||||||
base64 = atob(base64);
|
|
||||||
const buf = new ArrayBuffer(base64.length);
|
|
||||||
const bufView = new Uint8Array(buf);
|
|
||||||
for (let i = 0, strLen = base64.length; i < strLen; i++) {
|
|
||||||
bufView[i] = base64.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64_encode_ab(source: ArrayBufferLike) {
|
|
||||||
const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
let base64 = "";
|
|
||||||
|
|
||||||
const bytes = new Uint8Array(source);
|
|
||||||
const byte_length = bytes.byteLength;
|
|
||||||
const byte_reminder = byte_length % 3;
|
|
||||||
const main_length = byte_length - byte_reminder;
|
|
||||||
|
|
||||||
let a, b, c, d;
|
|
||||||
let chunk;
|
|
||||||
|
|
||||||
// Main loop deals with bytes in chunks of 3
|
|
||||||
for (let i = 0; i < main_length; i = i + 3) {
|
|
||||||
// Combine the three bytes into a single integer
|
|
||||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
||||||
|
|
||||||
// Use bitmasks to extract 6-bit segments from the triplet
|
|
||||||
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
|
|
||||||
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
|
|
||||||
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
|
|
||||||
d = (chunk & 63) >> 0; // 63 = (2^6 - 1) << 0
|
|
||||||
|
|
||||||
// Convert the raw binary segments to the appropriate ASCII encoding
|
|
||||||
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with the remaining bytes and padding
|
|
||||||
if (byte_reminder == 1) {
|
|
||||||
chunk = bytes[main_length];
|
|
||||||
|
|
||||||
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
|
|
||||||
|
|
||||||
// Set the 4 least significant bits to zero
|
|
||||||
b = (chunk & 3) << 4; // 3 = 2^2 - 1
|
|
||||||
|
|
||||||
base64 += encodings[a] + encodings[b] + '==';
|
|
||||||
} else if (byte_reminder == 2) {
|
|
||||||
chunk = (bytes[main_length] << 8) | bytes[main_length + 1];
|
|
||||||
|
|
||||||
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
|
|
||||||
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
|
|
||||||
|
|
||||||
// Set the 2 least significant bits to zero
|
|
||||||
c = (chunk & 15) << 2; // 15 = 2^4 - 1
|
|
||||||
|
|
||||||
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
class TestProxy extends bipc.MethodProxy {
|
class TestProxy extends bipc.MethodProxy {
|
||||||
constructor(params: bipc.MethodProxyConnectParameters) {
|
constructor(params: bipc.MethodProxyConnectParameters) {
|
||||||
|
@ -295,7 +234,7 @@ interface Window {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function handle_connect_request(properties: bipc.connect.ConnectRequestData, connection: ConnectionHandler) {
|
export function handle_connect_request(properties: bipc.connect.ConnectRequestData, connection: ConnectionHandler) {
|
||||||
const profile_uuid = properties.profile || (profiles.default_profile() || {id: 'default'}).id;
|
const profile_uuid = properties.profile || (profiles.default_profile() || {id: 'default'}).id;
|
||||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||||
const username = properties.username || profile.connect_username();
|
const username = properties.username || profile.connect_username();
|
||||||
|
@ -313,7 +252,7 @@ function handle_connect_request(properties: bipc.connect.ConnectRequestData, con
|
||||||
});
|
});
|
||||||
server_connections.set_active_connection_handler(connection);
|
server_connections.set_active_connection_handler(connection);
|
||||||
} else {
|
} else {
|
||||||
Modals.spawnConnectModal({},{
|
spawnConnectModal({},{
|
||||||
url: properties.address,
|
url: properties.address,
|
||||||
enforce: true
|
enforce: true
|
||||||
}, {
|
}, {
|
||||||
|
@ -356,14 +295,14 @@ function main() {
|
||||||
|
|
||||||
top_menu.initialize();
|
top_menu.initialize();
|
||||||
|
|
||||||
server_connections = new ServerConnectionManager($("#connection-handlers"));
|
cmanager.initialize(new ServerConnectionManager($("#connection-handlers")));
|
||||||
control_bar.initialise(); /* before connection handler to allow property apply */
|
control_bar.control_bar.initialise(); /* before connection handler to allow property apply */
|
||||||
|
|
||||||
const initial_handler = server_connections.spawn_server_connection_handler();
|
const initial_handler = server_connections.spawn_server_connection_handler();
|
||||||
initial_handler.acquire_recorder(default_recorder, false);
|
initial_handler.acquire_recorder(default_recorder, false);
|
||||||
control_bar.set_connection_handler(initial_handler);
|
control_bar.control_bar.set_connection_handler(initial_handler);
|
||||||
/** Setup the XF forum identity **/
|
/** Setup the XF forum identity **/
|
||||||
profiles.identities.update_forum();
|
fidentity.update_forum();
|
||||||
|
|
||||||
let _resize_timeout: NodeJS.Timer;
|
let _resize_timeout: NodeJS.Timer;
|
||||||
$(window).on('resize', event => {
|
$(window).on('resize', event => {
|
||||||
|
@ -481,7 +420,7 @@ function main() {
|
||||||
|
|
||||||
/* for testing */
|
/* for testing */
|
||||||
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
|
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
|
||||||
const modal = Modals.openModalNewcomer();
|
const modal = openModalNewcomer();
|
||||||
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,12 +431,12 @@ const task_teaweb_starter: loader.Task = {
|
||||||
try {
|
try {
|
||||||
await initialize_app();
|
await initialize_app();
|
||||||
main();
|
main();
|
||||||
if(!audio.player.initialized()) {
|
if(!aplayer.initialized()) {
|
||||||
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
|
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
|
||||||
if(!audio.player.initializeFromGesture) {
|
if(!aplayer.initializeFromGesture) {
|
||||||
console.error(tr("Missing audio.player.initializeFromGesture"));
|
console.error(tr("Missing aplayer.initializeFromGesture"));
|
||||||
} else
|
} else
|
||||||
$(document).one('click', event => audio.player.initializeFromGesture());
|
$(document).one('click', event => aplayer.initializeFromGesture());
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(ex.stack);
|
console.error(ex.stack);
|
||||||
|
@ -543,7 +482,7 @@ const task_connect_handler: loader.Task = {
|
||||||
"You could now close this page.";
|
"You could now close this page.";
|
||||||
createInfoModal(
|
createInfoModal(
|
||||||
tr("Connecting successfully within other instance"),
|
tr("Connecting successfully within other instance"),
|
||||||
MessageHelper.formatMessage(tr(message), connect_data.address),
|
formatMessage(/* @tr-ignore */ tr(message), connect_data.address),
|
||||||
{
|
{
|
||||||
closeable: false,
|
closeable: false,
|
||||||
footer: undefined
|
footer: undefined
|
||||||
|
@ -610,7 +549,7 @@ const task_certificate_callback: loader.Task = {
|
||||||
"This page will close in {0} seconds.";
|
"This page will close in {0} seconds.";
|
||||||
createInfoModal(
|
createInfoModal(
|
||||||
tr("Certificate acccepted successfully"),
|
tr("Certificate acccepted successfully"),
|
||||||
MessageHelper.formatMessage(tr(message), seconds_tag),
|
formatMessage(/* @tr-ignore */ tr(message), seconds_tag),
|
||||||
{
|
{
|
||||||
closeable: false,
|
closeable: false,
|
||||||
footer: undefined
|
footer: undefined
|
||||||
|
@ -650,7 +589,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
try {
|
try {
|
||||||
await initialize();
|
await initialize();
|
||||||
|
|
||||||
if(app.is_web()) {
|
if(__build.target == "web") {
|
||||||
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
||||||
} else {
|
} else {
|
||||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||||
|
@ -667,3 +606,13 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
priority: 1000
|
priority: 1000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
|
name: "error task",
|
||||||
|
function: async () => {
|
||||||
|
if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) {
|
||||||
|
loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good.");
|
||||||
|
throw "The tea is cold!";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: 20
|
||||||
|
});
|
|
@ -1,17 +1,24 @@
|
||||||
/// <reference path="../connection/ConnectionBase.ts" />
|
import {LaterPromise} from "tc-shared/utils/LaterPromise";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {PermissionManager, PermissionValue} from "tc-shared/permission/PermissionManager";
|
||||||
|
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
enum GroupType {
|
export enum GroupType {
|
||||||
QUERY,
|
QUERY,
|
||||||
TEMPLATE,
|
TEMPLATE,
|
||||||
NORMAL
|
NORMAL
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GroupTarget {
|
export enum GroupTarget {
|
||||||
SERVER,
|
SERVER,
|
||||||
CHANNEL
|
CHANNEL
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupProperties {
|
export class GroupProperties {
|
||||||
iconid: number = 0;
|
iconid: number = 0;
|
||||||
|
|
||||||
sortid: number = 0;
|
sortid: number = 0;
|
||||||
|
@ -19,12 +26,12 @@ class GroupProperties {
|
||||||
namemode: number = 0;
|
namemode: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupPermissionRequest {
|
export class GroupPermissionRequest {
|
||||||
group_id: number;
|
group_id: number;
|
||||||
promise: LaterPromise<PermissionValue[]>;
|
promise: LaterPromise<PermissionValue[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Group {
|
export class Group {
|
||||||
properties: GroupProperties = new GroupProperties();
|
properties: GroupProperties = new GroupProperties();
|
||||||
|
|
||||||
readonly handle: GroupManager;
|
readonly handle: GroupManager;
|
||||||
|
@ -63,7 +70,7 @@ class Group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupManager extends connection.AbstractCommandHandler {
|
export class GroupManager extends AbstractCommandHandler {
|
||||||
readonly handle: ConnectionHandler;
|
readonly handle: ConnectionHandler;
|
||||||
|
|
||||||
serverGroups: Group[] = [];
|
serverGroups: Group[] = [];
|
||||||
|
@ -83,7 +90,7 @@ class GroupManager extends connection.AbstractCommandHandler {
|
||||||
this.channelGroups = undefined;
|
this.channelGroups = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
handle_command(command: ServerCommand): boolean {
|
||||||
switch (command.command) {
|
switch (command.command) {
|
||||||
case "notifyservergrouplist":
|
case "notifyservergrouplist":
|
||||||
case "notifychannelgrouplist":
|
case "notifychannelgrouplist":
|
||||||
|
@ -160,7 +167,7 @@ class GroupManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
let group = new Group(this,parseInt(target == GroupTarget.SERVER ? groupData["sgid"] : groupData["cgid"]), target, type, groupData["name"]);
|
let group = new Group(this,parseInt(target == GroupTarget.SERVER ? groupData["sgid"] : groupData["cgid"]), target, type, groupData["name"]);
|
||||||
for(let key in groupData as any) {
|
for(let key in Object.keys(groupData)) {
|
||||||
if(key == "sgid") continue;
|
if(key == "sgid") continue;
|
||||||
if(key == "cgid") continue;
|
if(key == "cgid") continue;
|
||||||
if(key == "type") continue;
|
if(key == "type") continue;
|
||||||
|
|
|
@ -1,358 +1,13 @@
|
||||||
/// <reference path="../ConnectionHandler.ts" />
|
import * as log from "tc-shared/log";
|
||||||
/// <reference path="../connection/ConnectionBase.ts" />
|
import {LogCategory, LogType} from "tc-shared/log";
|
||||||
/// <reference path="../i18n/localize.ts" />
|
import PermissionType from "tc-shared/permission/PermissionType";
|
||||||
|
import {LaterPromise} from "tc-shared/utils/LaterPromise";
|
||||||
|
import {ServerCommand} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
|
|
||||||
enum PermissionType {
|
export class PermissionInfo {
|
||||||
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
|
|
||||||
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
|
|
||||||
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
|
|
||||||
B_SERVERINSTANCE_VIRTUALSERVER_LIST = "b_serverinstance_virtualserver_list",
|
|
||||||
B_SERVERINSTANCE_BINDING_LIST = "b_serverinstance_binding_list",
|
|
||||||
B_SERVERINSTANCE_PERMISSION_LIST = "b_serverinstance_permission_list",
|
|
||||||
B_SERVERINSTANCE_PERMISSION_FIND = "b_serverinstance_permission_find",
|
|
||||||
B_VIRTUALSERVER_CREATE = "b_virtualserver_create",
|
|
||||||
B_VIRTUALSERVER_DELETE = "b_virtualserver_delete",
|
|
||||||
B_VIRTUALSERVER_START_ANY = "b_virtualserver_start_any",
|
|
||||||
B_VIRTUALSERVER_STOP_ANY = "b_virtualserver_stop_any",
|
|
||||||
B_VIRTUALSERVER_CHANGE_MACHINE_ID = "b_virtualserver_change_machine_id",
|
|
||||||
B_VIRTUALSERVER_CHANGE_TEMPLATE = "b_virtualserver_change_template",
|
|
||||||
B_SERVERQUERY_LOGIN = "b_serverquery_login",
|
|
||||||
B_SERVERINSTANCE_TEXTMESSAGE_SEND = "b_serverinstance_textmessage_send",
|
|
||||||
B_SERVERINSTANCE_LOG_VIEW = "b_serverinstance_log_view",
|
|
||||||
B_SERVERINSTANCE_LOG_ADD = "b_serverinstance_log_add",
|
|
||||||
B_SERVERINSTANCE_STOP = "b_serverinstance_stop",
|
|
||||||
B_SERVERINSTANCE_MODIFY_SETTINGS = "b_serverinstance_modify_settings",
|
|
||||||
B_SERVERINSTANCE_MODIFY_QUERYGROUP = "b_serverinstance_modify_querygroup",
|
|
||||||
B_SERVERINSTANCE_MODIFY_TEMPLATES = "b_serverinstance_modify_templates",
|
|
||||||
B_VIRTUALSERVER_SELECT = "b_virtualserver_select",
|
|
||||||
B_VIRTUALSERVER_SELECT_GODMODE = "b_virtualserver_select_godmode",
|
|
||||||
B_VIRTUALSERVER_INFO_VIEW = "b_virtualserver_info_view",
|
|
||||||
B_VIRTUALSERVER_CONNECTIONINFO_VIEW = "b_virtualserver_connectioninfo_view",
|
|
||||||
B_VIRTUALSERVER_CHANNEL_LIST = "b_virtualserver_channel_list",
|
|
||||||
B_VIRTUALSERVER_CHANNEL_SEARCH = "b_virtualserver_channel_search",
|
|
||||||
B_VIRTUALSERVER_CLIENT_LIST = "b_virtualserver_client_list",
|
|
||||||
B_VIRTUALSERVER_CLIENT_SEARCH = "b_virtualserver_client_search",
|
|
||||||
B_VIRTUALSERVER_CLIENT_DBLIST = "b_virtualserver_client_dblist",
|
|
||||||
B_VIRTUALSERVER_CLIENT_DBSEARCH = "b_virtualserver_client_dbsearch",
|
|
||||||
B_VIRTUALSERVER_CLIENT_DBINFO = "b_virtualserver_client_dbinfo",
|
|
||||||
B_VIRTUALSERVER_PERMISSION_FIND = "b_virtualserver_permission_find",
|
|
||||||
B_VIRTUALSERVER_CUSTOM_SEARCH = "b_virtualserver_custom_search",
|
|
||||||
B_VIRTUALSERVER_START = "b_virtualserver_start",
|
|
||||||
B_VIRTUALSERVER_STOP = "b_virtualserver_stop",
|
|
||||||
B_VIRTUALSERVER_TOKEN_LIST = "b_virtualserver_token_list",
|
|
||||||
B_VIRTUALSERVER_TOKEN_ADD = "b_virtualserver_token_add",
|
|
||||||
B_VIRTUALSERVER_TOKEN_USE = "b_virtualserver_token_use",
|
|
||||||
B_VIRTUALSERVER_TOKEN_DELETE = "b_virtualserver_token_delete",
|
|
||||||
B_VIRTUALSERVER_LOG_VIEW = "b_virtualserver_log_view",
|
|
||||||
B_VIRTUALSERVER_LOG_ADD = "b_virtualserver_log_add",
|
|
||||||
B_VIRTUALSERVER_JOIN_IGNORE_PASSWORD = "b_virtualserver_join_ignore_password",
|
|
||||||
B_VIRTUALSERVER_NOTIFY_REGISTER = "b_virtualserver_notify_register",
|
|
||||||
B_VIRTUALSERVER_NOTIFY_UNREGISTER = "b_virtualserver_notify_unregister",
|
|
||||||
B_VIRTUALSERVER_SNAPSHOT_CREATE = "b_virtualserver_snapshot_create",
|
|
||||||
B_VIRTUALSERVER_SNAPSHOT_DEPLOY = "b_virtualserver_snapshot_deploy",
|
|
||||||
B_VIRTUALSERVER_PERMISSION_RESET = "b_virtualserver_permission_reset",
|
|
||||||
B_VIRTUALSERVER_MODIFY_NAME = "b_virtualserver_modify_name",
|
|
||||||
B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE = "b_virtualserver_modify_welcomemessage",
|
|
||||||
B_VIRTUALSERVER_MODIFY_MAXCLIENTS = "b_virtualserver_modify_maxclients",
|
|
||||||
B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS = "b_virtualserver_modify_reserved_slots",
|
|
||||||
B_VIRTUALSERVER_MODIFY_PASSWORD = "b_virtualserver_modify_password",
|
|
||||||
B_VIRTUALSERVER_MODIFY_DEFAULT_SERVERGROUP = "b_virtualserver_modify_default_servergroup",
|
|
||||||
B_VIRTUALSERVER_MODIFY_DEFAULT_MUSICGROUP = "b_virtualserver_modify_default_musicgroup",
|
|
||||||
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELGROUP = "b_virtualserver_modify_default_channelgroup",
|
|
||||||
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELADMINGROUP = "b_virtualserver_modify_default_channeladmingroup",
|
|
||||||
B_VIRTUALSERVER_MODIFY_CHANNEL_FORCED_SILENCE = "b_virtualserver_modify_channel_forced_silence",
|
|
||||||
B_VIRTUALSERVER_MODIFY_COMPLAIN = "b_virtualserver_modify_complain",
|
|
||||||
B_VIRTUALSERVER_MODIFY_ANTIFLOOD = "b_virtualserver_modify_antiflood",
|
|
||||||
B_VIRTUALSERVER_MODIFY_FT_SETTINGS = "b_virtualserver_modify_ft_settings",
|
|
||||||
B_VIRTUALSERVER_MODIFY_FT_QUOTAS = "b_virtualserver_modify_ft_quotas",
|
|
||||||
B_VIRTUALSERVER_MODIFY_HOSTMESSAGE = "b_virtualserver_modify_hostmessage",
|
|
||||||
B_VIRTUALSERVER_MODIFY_HOSTBANNER = "b_virtualserver_modify_hostbanner",
|
|
||||||
B_VIRTUALSERVER_MODIFY_HOSTBUTTON = "b_virtualserver_modify_hostbutton",
|
|
||||||
B_VIRTUALSERVER_MODIFY_PORT = "b_virtualserver_modify_port",
|
|
||||||
B_VIRTUALSERVER_MODIFY_HOST = "b_virtualserver_modify_host",
|
|
||||||
B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES = "b_virtualserver_modify_default_messages",
|
|
||||||
B_VIRTUALSERVER_MODIFY_AUTOSTART = "b_virtualserver_modify_autostart",
|
|
||||||
B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL = "b_virtualserver_modify_needed_identity_security_level",
|
|
||||||
B_VIRTUALSERVER_MODIFY_PRIORITY_SPEAKER_DIMM_MODIFICATOR = "b_virtualserver_modify_priority_speaker_dimm_modificator",
|
|
||||||
B_VIRTUALSERVER_MODIFY_LOG_SETTINGS = "b_virtualserver_modify_log_settings",
|
|
||||||
B_VIRTUALSERVER_MODIFY_MIN_CLIENT_VERSION = "b_virtualserver_modify_min_client_version",
|
|
||||||
B_VIRTUALSERVER_MODIFY_ICON_ID = "b_virtualserver_modify_icon_id",
|
|
||||||
B_VIRTUALSERVER_MODIFY_WEBLIST = "b_virtualserver_modify_weblist",
|
|
||||||
B_VIRTUALSERVER_MODIFY_CODEC_ENCRYPTION_MODE = "b_virtualserver_modify_codec_encryption_mode",
|
|
||||||
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS = "b_virtualserver_modify_temporary_passwords",
|
|
||||||
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS_OWN = "b_virtualserver_modify_temporary_passwords_own",
|
|
||||||
B_VIRTUALSERVER_MODIFY_CHANNEL_TEMP_DELETE_DELAY_DEFAULT = "b_virtualserver_modify_channel_temp_delete_delay_default",
|
|
||||||
B_VIRTUALSERVER_MODIFY_MUSIC_BOT_LIMIT = "b_virtualserver_modify_music_bot_limit",
|
|
||||||
B_VIRTUALSERVER_MODIFY_COUNTRY_CODE = "b_virtualserver_modify_country_code",
|
|
||||||
I_CHANNEL_MIN_DEPTH = "i_channel_min_depth",
|
|
||||||
I_CHANNEL_MAX_DEPTH = "i_channel_max_depth",
|
|
||||||
B_CHANNEL_GROUP_INHERITANCE_END = "b_channel_group_inheritance_end",
|
|
||||||
I_CHANNEL_PERMISSION_MODIFY_POWER = "i_channel_permission_modify_power",
|
|
||||||
I_CHANNEL_NEEDED_PERMISSION_MODIFY_POWER = "i_channel_needed_permission_modify_power",
|
|
||||||
B_CHANNEL_INFO_VIEW = "b_channel_info_view",
|
|
||||||
B_CHANNEL_CREATE_CHILD = "b_channel_create_child",
|
|
||||||
B_CHANNEL_CREATE_PERMANENT = "b_channel_create_permanent",
|
|
||||||
B_CHANNEL_CREATE_SEMI_PERMANENT = "b_channel_create_semi_permanent",
|
|
||||||
B_CHANNEL_CREATE_TEMPORARY = "b_channel_create_temporary",
|
|
||||||
B_CHANNEL_CREATE_PRIVATE = "b_channel_create_private",
|
|
||||||
B_CHANNEL_CREATE_WITH_TOPIC = "b_channel_create_with_topic",
|
|
||||||
B_CHANNEL_CREATE_WITH_DESCRIPTION = "b_channel_create_with_description",
|
|
||||||
B_CHANNEL_CREATE_WITH_PASSWORD = "b_channel_create_with_password",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8 = "b_channel_create_modify_with_codec_speex8",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16 = "b_channel_create_modify_with_codec_speex16",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32 = "b_channel_create_modify_with_codec_speex32",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48 = "b_channel_create_modify_with_codec_celtmono48",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE = "b_channel_create_modify_with_codec_opusvoice",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC = "b_channel_create_modify_with_codec_opusmusic",
|
|
||||||
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_MAXQUALITY = "i_channel_create_modify_with_codec_maxquality",
|
|
||||||
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_LATENCY_FACTOR_MIN = "i_channel_create_modify_with_codec_latency_factor_min",
|
|
||||||
I_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_LENGTH = "i_channel_create_modify_conversation_history_length",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_UNLIMITED = "b_channel_create_modify_conversation_history_unlimited",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_CONVERSATION_PRIVATE = "b_channel_create_modify_conversation_private",
|
|
||||||
B_CHANNEL_CREATE_WITH_MAXCLIENTS = "b_channel_create_with_maxclients",
|
|
||||||
B_CHANNEL_CREATE_WITH_MAXFAMILYCLIENTS = "b_channel_create_with_maxfamilyclients",
|
|
||||||
B_CHANNEL_CREATE_WITH_SORTORDER = "b_channel_create_with_sortorder",
|
|
||||||
B_CHANNEL_CREATE_WITH_DEFAULT = "b_channel_create_with_default",
|
|
||||||
B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER = "b_channel_create_with_needed_talk_power",
|
|
||||||
B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD = "b_channel_create_modify_with_force_password",
|
|
||||||
I_CHANNEL_CREATE_MODIFY_WITH_TEMP_DELETE_DELAY = "i_channel_create_modify_with_temp_delete_delay",
|
|
||||||
B_CHANNEL_MODIFY_PARENT = "b_channel_modify_parent",
|
|
||||||
B_CHANNEL_MODIFY_MAKE_DEFAULT = "b_channel_modify_make_default",
|
|
||||||
B_CHANNEL_MODIFY_MAKE_PERMANENT = "b_channel_modify_make_permanent",
|
|
||||||
B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT = "b_channel_modify_make_semi_permanent",
|
|
||||||
B_CHANNEL_MODIFY_MAKE_TEMPORARY = "b_channel_modify_make_temporary",
|
|
||||||
B_CHANNEL_MODIFY_NAME = "b_channel_modify_name",
|
|
||||||
B_CHANNEL_MODIFY_TOPIC = "b_channel_modify_topic",
|
|
||||||
B_CHANNEL_MODIFY_DESCRIPTION = "b_channel_modify_description",
|
|
||||||
B_CHANNEL_MODIFY_PASSWORD = "b_channel_modify_password",
|
|
||||||
B_CHANNEL_MODIFY_CODEC = "b_channel_modify_codec",
|
|
||||||
B_CHANNEL_MODIFY_CODEC_QUALITY = "b_channel_modify_codec_quality",
|
|
||||||
B_CHANNEL_MODIFY_CODEC_LATENCY_FACTOR = "b_channel_modify_codec_latency_factor",
|
|
||||||
B_CHANNEL_MODIFY_MAXCLIENTS = "b_channel_modify_maxclients",
|
|
||||||
B_CHANNEL_MODIFY_MAXFAMILYCLIENTS = "b_channel_modify_maxfamilyclients",
|
|
||||||
B_CHANNEL_MODIFY_SORTORDER = "b_channel_modify_sortorder",
|
|
||||||
B_CHANNEL_MODIFY_NEEDED_TALK_POWER = "b_channel_modify_needed_talk_power",
|
|
||||||
I_CHANNEL_MODIFY_POWER = "i_channel_modify_power",
|
|
||||||
I_CHANNEL_NEEDED_MODIFY_POWER = "i_channel_needed_modify_power",
|
|
||||||
B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED = "b_channel_modify_make_codec_encrypted",
|
|
||||||
B_CHANNEL_MODIFY_TEMP_DELETE_DELAY = "b_channel_modify_temp_delete_delay",
|
|
||||||
B_CHANNEL_DELETE_PERMANENT = "b_channel_delete_permanent",
|
|
||||||
B_CHANNEL_DELETE_SEMI_PERMANENT = "b_channel_delete_semi_permanent",
|
|
||||||
B_CHANNEL_DELETE_TEMPORARY = "b_channel_delete_temporary",
|
|
||||||
B_CHANNEL_DELETE_FLAG_FORCE = "b_channel_delete_flag_force",
|
|
||||||
I_CHANNEL_DELETE_POWER = "i_channel_delete_power",
|
|
||||||
B_CHANNEL_CONVERSATION_MESSAGE_DELETE = "b_channel_conversation_message_delete",
|
|
||||||
I_CHANNEL_NEEDED_DELETE_POWER = "i_channel_needed_delete_power",
|
|
||||||
B_CHANNEL_JOIN_PERMANENT = "b_channel_join_permanent",
|
|
||||||
B_CHANNEL_JOIN_SEMI_PERMANENT = "b_channel_join_semi_permanent",
|
|
||||||
B_CHANNEL_JOIN_TEMPORARY = "b_channel_join_temporary",
|
|
||||||
B_CHANNEL_JOIN_IGNORE_PASSWORD = "b_channel_join_ignore_password",
|
|
||||||
B_CHANNEL_JOIN_IGNORE_MAXCLIENTS = "b_channel_join_ignore_maxclients",
|
|
||||||
B_CHANNEL_IGNORE_VIEW_POWER = "b_channel_ignore_view_power",
|
|
||||||
I_CHANNEL_JOIN_POWER = "i_channel_join_power",
|
|
||||||
I_CHANNEL_NEEDED_JOIN_POWER = "i_channel_needed_join_power",
|
|
||||||
B_CHANNEL_IGNORE_JOIN_POWER = "b_channel_ignore_join_power",
|
|
||||||
B_CHANNEL_IGNORE_DESCRIPTION_VIEW_POWER = "b_channel_ignore_description_view_power",
|
|
||||||
I_CHANNEL_VIEW_POWER = "i_channel_view_power",
|
|
||||||
I_CHANNEL_NEEDED_VIEW_POWER = "i_channel_needed_view_power",
|
|
||||||
I_CHANNEL_SUBSCRIBE_POWER = "i_channel_subscribe_power",
|
|
||||||
I_CHANNEL_NEEDED_SUBSCRIBE_POWER = "i_channel_needed_subscribe_power",
|
|
||||||
I_CHANNEL_DESCRIPTION_VIEW_POWER = "i_channel_description_view_power",
|
|
||||||
I_CHANNEL_NEEDED_DESCRIPTION_VIEW_POWER = "i_channel_needed_description_view_power",
|
|
||||||
I_ICON_ID = "i_icon_id",
|
|
||||||
I_MAX_ICON_FILESIZE = "i_max_icon_filesize",
|
|
||||||
I_MAX_PLAYLIST_SIZE = "i_max_playlist_size",
|
|
||||||
I_MAX_PLAYLISTS = "i_max_playlists",
|
|
||||||
B_ICON_MANAGE = "b_icon_manage",
|
|
||||||
B_GROUP_IS_PERMANENT = "b_group_is_permanent",
|
|
||||||
I_GROUP_AUTO_UPDATE_TYPE = "i_group_auto_update_type",
|
|
||||||
I_GROUP_AUTO_UPDATE_MAX_VALUE = "i_group_auto_update_max_value",
|
|
||||||
I_GROUP_SORT_ID = "i_group_sort_id",
|
|
||||||
I_GROUP_SHOW_NAME_IN_TREE = "i_group_show_name_in_tree",
|
|
||||||
B_VIRTUALSERVER_SERVERGROUP_CREATE = "b_virtualserver_servergroup_create",
|
|
||||||
B_VIRTUALSERVER_SERVERGROUP_LIST = "b_virtualserver_servergroup_list",
|
|
||||||
B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST = "b_virtualserver_servergroup_permission_list",
|
|
||||||
B_VIRTUALSERVER_SERVERGROUP_CLIENT_LIST = "b_virtualserver_servergroup_client_list",
|
|
||||||
B_VIRTUALSERVER_CHANNELGROUP_CREATE = "b_virtualserver_channelgroup_create",
|
|
||||||
B_VIRTUALSERVER_CHANNELGROUP_LIST = "b_virtualserver_channelgroup_list",
|
|
||||||
B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST = "b_virtualserver_channelgroup_permission_list",
|
|
||||||
B_VIRTUALSERVER_CHANNELGROUP_CLIENT_LIST = "b_virtualserver_channelgroup_client_list",
|
|
||||||
B_VIRTUALSERVER_CLIENT_PERMISSION_LIST = "b_virtualserver_client_permission_list",
|
|
||||||
B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST = "b_virtualserver_channel_permission_list",
|
|
||||||
B_VIRTUALSERVER_CHANNELCLIENT_PERMISSION_LIST = "b_virtualserver_channelclient_permission_list",
|
|
||||||
B_VIRTUALSERVER_PLAYLIST_PERMISSION_LIST = "b_virtualserver_playlist_permission_list",
|
|
||||||
I_SERVER_GROUP_MODIFY_POWER = "i_server_group_modify_power",
|
|
||||||
I_SERVER_GROUP_NEEDED_MODIFY_POWER = "i_server_group_needed_modify_power",
|
|
||||||
I_SERVER_GROUP_MEMBER_ADD_POWER = "i_server_group_member_add_power",
|
|
||||||
I_SERVER_GROUP_SELF_ADD_POWER = "i_server_group_self_add_power",
|
|
||||||
I_SERVER_GROUP_NEEDED_MEMBER_ADD_POWER = "i_server_group_needed_member_add_power",
|
|
||||||
I_SERVER_GROUP_MEMBER_REMOVE_POWER = "i_server_group_member_remove_power",
|
|
||||||
I_SERVER_GROUP_SELF_REMOVE_POWER = "i_server_group_self_remove_power",
|
|
||||||
I_SERVER_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_server_group_needed_member_remove_power",
|
|
||||||
I_CHANNEL_GROUP_MODIFY_POWER = "i_channel_group_modify_power",
|
|
||||||
I_CHANNEL_GROUP_NEEDED_MODIFY_POWER = "i_channel_group_needed_modify_power",
|
|
||||||
I_CHANNEL_GROUP_MEMBER_ADD_POWER = "i_channel_group_member_add_power",
|
|
||||||
I_CHANNEL_GROUP_SELF_ADD_POWER = "i_channel_group_self_add_power",
|
|
||||||
I_CHANNEL_GROUP_NEEDED_MEMBER_ADD_POWER = "i_channel_group_needed_member_add_power",
|
|
||||||
I_CHANNEL_GROUP_MEMBER_REMOVE_POWER = "i_channel_group_member_remove_power",
|
|
||||||
I_CHANNEL_GROUP_SELF_REMOVE_POWER = "i_channel_group_self_remove_power",
|
|
||||||
I_CHANNEL_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_channel_group_needed_member_remove_power",
|
|
||||||
I_GROUP_MEMBER_ADD_POWER = "i_group_member_add_power",
|
|
||||||
I_GROUP_NEEDED_MEMBER_ADD_POWER = "i_group_needed_member_add_power",
|
|
||||||
I_GROUP_MEMBER_REMOVE_POWER = "i_group_member_remove_power",
|
|
||||||
I_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_group_needed_member_remove_power",
|
|
||||||
I_GROUP_MODIFY_POWER = "i_group_modify_power",
|
|
||||||
I_GROUP_NEEDED_MODIFY_POWER = "i_group_needed_modify_power",
|
|
||||||
I_PERMISSION_MODIFY_POWER = "i_permission_modify_power",
|
|
||||||
B_PERMISSION_MODIFY_POWER_IGNORE = "b_permission_modify_power_ignore",
|
|
||||||
B_VIRTUALSERVER_SERVERGROUP_DELETE = "b_virtualserver_servergroup_delete",
|
|
||||||
B_VIRTUALSERVER_CHANNELGROUP_DELETE = "b_virtualserver_channelgroup_delete",
|
|
||||||
I_CLIENT_PERMISSION_MODIFY_POWER = "i_client_permission_modify_power",
|
|
||||||
I_CLIENT_NEEDED_PERMISSION_MODIFY_POWER = "i_client_needed_permission_modify_power",
|
|
||||||
I_CLIENT_MAX_CLONES_UID = "i_client_max_clones_uid",
|
|
||||||
I_CLIENT_MAX_CLONES_IP = "i_client_max_clones_ip",
|
|
||||||
I_CLIENT_MAX_CLONES_HWID = "i_client_max_clones_hwid",
|
|
||||||
I_CLIENT_MAX_IDLETIME = "i_client_max_idletime",
|
|
||||||
I_CLIENT_MAX_AVATAR_FILESIZE = "i_client_max_avatar_filesize",
|
|
||||||
I_CLIENT_MAX_CHANNEL_SUBSCRIPTIONS = "i_client_max_channel_subscriptions",
|
|
||||||
I_CLIENT_MAX_CHANNELS = "i_client_max_channels",
|
|
||||||
I_CLIENT_MAX_TEMPORARY_CHANNELS = "i_client_max_temporary_channels",
|
|
||||||
I_CLIENT_MAX_SEMI_CHANNELS = "i_client_max_semi_channels",
|
|
||||||
I_CLIENT_MAX_PERMANENT_CHANNELS = "i_client_max_permanent_channels",
|
|
||||||
B_CLIENT_USE_PRIORITY_SPEAKER = "b_client_use_priority_speaker",
|
|
||||||
B_CLIENT_SKIP_CHANNELGROUP_PERMISSIONS = "b_client_skip_channelgroup_permissions",
|
|
||||||
B_CLIENT_FORCE_PUSH_TO_TALK = "b_client_force_push_to_talk",
|
|
||||||
B_CLIENT_IGNORE_BANS = "b_client_ignore_bans",
|
|
||||||
B_CLIENT_IGNORE_VPN = "b_client_ignore_vpn",
|
|
||||||
B_CLIENT_IGNORE_ANTIFLOOD = "b_client_ignore_antiflood",
|
|
||||||
B_CLIENT_ENFORCE_VALID_HWID = "b_client_enforce_valid_hwid",
|
|
||||||
B_CLIENT_ALLOW_INVALID_PACKET = "b_client_allow_invalid_packet",
|
|
||||||
B_CLIENT_ALLOW_INVALID_BADGES = "b_client_allow_invalid_badges",
|
|
||||||
B_CLIENT_ISSUE_CLIENT_QUERY_COMMAND = "b_client_issue_client_query_command",
|
|
||||||
B_CLIENT_USE_RESERVED_SLOT = "b_client_use_reserved_slot",
|
|
||||||
B_CLIENT_USE_CHANNEL_COMMANDER = "b_client_use_channel_commander",
|
|
||||||
B_CLIENT_REQUEST_TALKER = "b_client_request_talker",
|
|
||||||
B_CLIENT_AVATAR_DELETE_OTHER = "b_client_avatar_delete_other",
|
|
||||||
B_CLIENT_IS_STICKY = "b_client_is_sticky",
|
|
||||||
B_CLIENT_IGNORE_STICKY = "b_client_ignore_sticky",
|
|
||||||
B_CLIENT_MUSIC_CREATE_PERMANENT = "b_client_music_create_permanent",
|
|
||||||
B_CLIENT_MUSIC_CREATE_SEMI_PERMANENT = "b_client_music_create_semi_permanent",
|
|
||||||
B_CLIENT_MUSIC_CREATE_TEMPORARY = "b_client_music_create_temporary",
|
|
||||||
B_CLIENT_MUSIC_MODIFY_PERMANENT = "b_client_music_modify_permanent",
|
|
||||||
B_CLIENT_MUSIC_MODIFY_SEMI_PERMANENT = "b_client_music_modify_semi_permanent",
|
|
||||||
B_CLIENT_MUSIC_MODIFY_TEMPORARY = "b_client_music_modify_temporary",
|
|
||||||
I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME = "i_client_music_create_modify_max_volume",
|
|
||||||
I_CLIENT_MUSIC_LIMIT = "i_client_music_limit",
|
|
||||||
I_CLIENT_MUSIC_NEEDED_DELETE_POWER = "i_client_music_needed_delete_power",
|
|
||||||
I_CLIENT_MUSIC_DELETE_POWER = "i_client_music_delete_power",
|
|
||||||
I_CLIENT_MUSIC_PLAY_POWER = "i_client_music_play_power",
|
|
||||||
I_CLIENT_MUSIC_NEEDED_PLAY_POWER = "i_client_music_needed_play_power",
|
|
||||||
I_CLIENT_MUSIC_MODIFY_POWER = "i_client_music_modify_power",
|
|
||||||
I_CLIENT_MUSIC_NEEDED_MODIFY_POWER = "i_client_music_needed_modify_power",
|
|
||||||
I_CLIENT_MUSIC_RENAME_POWER = "i_client_music_rename_power",
|
|
||||||
I_CLIENT_MUSIC_NEEDED_RENAME_POWER = "i_client_music_needed_rename_power",
|
|
||||||
B_PLAYLIST_CREATE = "b_playlist_create",
|
|
||||||
I_PLAYLIST_VIEW_POWER = "i_playlist_view_power",
|
|
||||||
I_PLAYLIST_NEEDED_VIEW_POWER = "i_playlist_needed_view_power",
|
|
||||||
I_PLAYLIST_MODIFY_POWER = "i_playlist_modify_power",
|
|
||||||
I_PLAYLIST_NEEDED_MODIFY_POWER = "i_playlist_needed_modify_power",
|
|
||||||
I_PLAYLIST_PERMISSION_MODIFY_POWER = "i_playlist_permission_modify_power",
|
|
||||||
I_PLAYLIST_NEEDED_PERMISSION_MODIFY_POWER = "i_playlist_needed_permission_modify_power",
|
|
||||||
I_PLAYLIST_DELETE_POWER = "i_playlist_delete_power",
|
|
||||||
I_PLAYLIST_NEEDED_DELETE_POWER = "i_playlist_needed_delete_power",
|
|
||||||
I_PLAYLIST_SONG_ADD_POWER = "i_playlist_song_add_power",
|
|
||||||
I_PLAYLIST_SONG_NEEDED_ADD_POWER = "i_playlist_song_needed_add_power",
|
|
||||||
I_PLAYLIST_SONG_REMOVE_POWER = "i_playlist_song_remove_power",
|
|
||||||
I_PLAYLIST_SONG_NEEDED_REMOVE_POWER = "i_playlist_song_needed_remove_power",
|
|
||||||
B_CLIENT_INFO_VIEW = "b_client_info_view",
|
|
||||||
B_CLIENT_PERMISSIONOVERVIEW_VIEW = "b_client_permissionoverview_view",
|
|
||||||
B_CLIENT_PERMISSIONOVERVIEW_OWN = "b_client_permissionoverview_own",
|
|
||||||
B_CLIENT_REMOTEADDRESS_VIEW = "b_client_remoteaddress_view",
|
|
||||||
I_CLIENT_SERVERQUERY_VIEW_POWER = "i_client_serverquery_view_power",
|
|
||||||
I_CLIENT_NEEDED_SERVERQUERY_VIEW_POWER = "i_client_needed_serverquery_view_power",
|
|
||||||
B_CLIENT_CUSTOM_INFO_VIEW = "b_client_custom_info_view",
|
|
||||||
B_CLIENT_MUSIC_CHANNEL_LIST = "b_client_music_channel_list",
|
|
||||||
B_CLIENT_MUSIC_SERVER_LIST = "b_client_music_server_list",
|
|
||||||
I_CLIENT_MUSIC_INFO = "i_client_music_info",
|
|
||||||
I_CLIENT_MUSIC_NEEDED_INFO = "i_client_music_needed_info",
|
|
||||||
I_CLIENT_KICK_FROM_SERVER_POWER = "i_client_kick_from_server_power",
|
|
||||||
I_CLIENT_NEEDED_KICK_FROM_SERVER_POWER = "i_client_needed_kick_from_server_power",
|
|
||||||
I_CLIENT_KICK_FROM_CHANNEL_POWER = "i_client_kick_from_channel_power",
|
|
||||||
I_CLIENT_NEEDED_KICK_FROM_CHANNEL_POWER = "i_client_needed_kick_from_channel_power",
|
|
||||||
I_CLIENT_BAN_POWER = "i_client_ban_power",
|
|
||||||
I_CLIENT_NEEDED_BAN_POWER = "i_client_needed_ban_power",
|
|
||||||
I_CLIENT_MOVE_POWER = "i_client_move_power",
|
|
||||||
I_CLIENT_NEEDED_MOVE_POWER = "i_client_needed_move_power",
|
|
||||||
I_CLIENT_COMPLAIN_POWER = "i_client_complain_power",
|
|
||||||
I_CLIENT_NEEDED_COMPLAIN_POWER = "i_client_needed_complain_power",
|
|
||||||
B_CLIENT_COMPLAIN_LIST = "b_client_complain_list",
|
|
||||||
B_CLIENT_COMPLAIN_DELETE_OWN = "b_client_complain_delete_own",
|
|
||||||
B_CLIENT_COMPLAIN_DELETE = "b_client_complain_delete",
|
|
||||||
B_CLIENT_BAN_LIST = "b_client_ban_list",
|
|
||||||
B_CLIENT_BAN_LIST_GLOBAL = "b_client_ban_list_global",
|
|
||||||
B_CLIENT_BAN_TRIGGER_LIST = "b_client_ban_trigger_list",
|
|
||||||
B_CLIENT_BAN_CREATE = "b_client_ban_create",
|
|
||||||
B_CLIENT_BAN_CREATE_GLOBAL = "b_client_ban_create_global",
|
|
||||||
B_CLIENT_BAN_NAME = "b_client_ban_name",
|
|
||||||
B_CLIENT_BAN_IP = "b_client_ban_ip",
|
|
||||||
B_CLIENT_BAN_HWID = "b_client_ban_hwid",
|
|
||||||
B_CLIENT_BAN_EDIT = "b_client_ban_edit",
|
|
||||||
B_CLIENT_BAN_EDIT_GLOBAL = "b_client_ban_edit_global",
|
|
||||||
B_CLIENT_BAN_DELETE_OWN = "b_client_ban_delete_own",
|
|
||||||
B_CLIENT_BAN_DELETE = "b_client_ban_delete",
|
|
||||||
B_CLIENT_BAN_DELETE_OWN_GLOBAL = "b_client_ban_delete_own_global",
|
|
||||||
B_CLIENT_BAN_DELETE_GLOBAL = "b_client_ban_delete_global",
|
|
||||||
I_CLIENT_BAN_MAX_BANTIME = "i_client_ban_max_bantime",
|
|
||||||
I_CLIENT_PRIVATE_TEXTMESSAGE_POWER = "i_client_private_textmessage_power",
|
|
||||||
I_CLIENT_NEEDED_PRIVATE_TEXTMESSAGE_POWER = "i_client_needed_private_textmessage_power",
|
|
||||||
B_CLIENT_EVEN_TEXTMESSAGE_SEND = "b_client_even_textmessage_send",
|
|
||||||
B_CLIENT_SERVER_TEXTMESSAGE_SEND = "b_client_server_textmessage_send",
|
|
||||||
B_CLIENT_CHANNEL_TEXTMESSAGE_SEND = "b_client_channel_textmessage_send",
|
|
||||||
B_CLIENT_OFFLINE_TEXTMESSAGE_SEND = "b_client_offline_textmessage_send",
|
|
||||||
I_CLIENT_TALK_POWER = "i_client_talk_power",
|
|
||||||
I_CLIENT_NEEDED_TALK_POWER = "i_client_needed_talk_power",
|
|
||||||
I_CLIENT_POKE_POWER = "i_client_poke_power",
|
|
||||||
I_CLIENT_NEEDED_POKE_POWER = "i_client_needed_poke_power",
|
|
||||||
B_CLIENT_SET_FLAG_TALKER = "b_client_set_flag_talker",
|
|
||||||
I_CLIENT_WHISPER_POWER = "i_client_whisper_power",
|
|
||||||
I_CLIENT_NEEDED_WHISPER_POWER = "i_client_needed_whisper_power",
|
|
||||||
B_CLIENT_MODIFY_DESCRIPTION = "b_client_modify_description",
|
|
||||||
B_CLIENT_MODIFY_OWN_DESCRIPTION = "b_client_modify_own_description",
|
|
||||||
B_CLIENT_USE_BBCODE_ANY = "b_client_use_bbcode_any",
|
|
||||||
B_CLIENT_USE_BBCODE_URL = "b_client_use_bbcode_url",
|
|
||||||
B_CLIENT_USE_BBCODE_IMAGE = "b_client_use_bbcode_image",
|
|
||||||
B_CLIENT_MODIFY_DBPROPERTIES = "b_client_modify_dbproperties",
|
|
||||||
B_CLIENT_DELETE_DBPROPERTIES = "b_client_delete_dbproperties",
|
|
||||||
B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN = "b_client_create_modify_serverquery_login",
|
|
||||||
B_CLIENT_QUERY_CREATE = "b_client_query_create",
|
|
||||||
B_CLIENT_QUERY_LIST = "b_client_query_list",
|
|
||||||
B_CLIENT_QUERY_LIST_OWN = "b_client_query_list_own",
|
|
||||||
B_CLIENT_QUERY_RENAME = "b_client_query_rename",
|
|
||||||
B_CLIENT_QUERY_RENAME_OWN = "b_client_query_rename_own",
|
|
||||||
B_CLIENT_QUERY_CHANGE_PASSWORD = "b_client_query_change_password",
|
|
||||||
B_CLIENT_QUERY_CHANGE_OWN_PASSWORD = "b_client_query_change_own_password",
|
|
||||||
B_CLIENT_QUERY_CHANGE_PASSWORD_GLOBAL = "b_client_query_change_password_global",
|
|
||||||
B_CLIENT_QUERY_DELETE = "b_client_query_delete",
|
|
||||||
B_CLIENT_QUERY_DELETE_OWN = "b_client_query_delete_own",
|
|
||||||
B_FT_IGNORE_PASSWORD = "b_ft_ignore_password",
|
|
||||||
B_FT_TRANSFER_LIST = "b_ft_transfer_list",
|
|
||||||
I_FT_FILE_UPLOAD_POWER = "i_ft_file_upload_power",
|
|
||||||
I_FT_NEEDED_FILE_UPLOAD_POWER = "i_ft_needed_file_upload_power",
|
|
||||||
I_FT_FILE_DOWNLOAD_POWER = "i_ft_file_download_power",
|
|
||||||
I_FT_NEEDED_FILE_DOWNLOAD_POWER = "i_ft_needed_file_download_power",
|
|
||||||
I_FT_FILE_DELETE_POWER = "i_ft_file_delete_power",
|
|
||||||
I_FT_NEEDED_FILE_DELETE_POWER = "i_ft_needed_file_delete_power",
|
|
||||||
I_FT_FILE_RENAME_POWER = "i_ft_file_rename_power",
|
|
||||||
I_FT_NEEDED_FILE_RENAME_POWER = "i_ft_needed_file_rename_power",
|
|
||||||
I_FT_FILE_BROWSE_POWER = "i_ft_file_browse_power",
|
|
||||||
I_FT_NEEDED_FILE_BROWSE_POWER = "i_ft_needed_file_browse_power",
|
|
||||||
I_FT_DIRECTORY_CREATE_POWER = "i_ft_directory_create_power",
|
|
||||||
I_FT_NEEDED_DIRECTORY_CREATE_POWER = "i_ft_needed_directory_create_power",
|
|
||||||
I_FT_QUOTA_MB_DOWNLOAD_PER_CLIENT = "i_ft_quota_mb_download_per_client",
|
|
||||||
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
|
|
||||||
}
|
|
||||||
|
|
||||||
class PermissionInfo {
|
|
||||||
name: string;
|
name: string;
|
||||||
id: number;
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -363,21 +18,21 @@ class PermissionInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionGroup {
|
export class PermissionGroup {
|
||||||
begin: number;
|
begin: number;
|
||||||
end: number;
|
end: number;
|
||||||
deep: number;
|
deep: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupedPermissions {
|
export class GroupedPermissions {
|
||||||
group: PermissionGroup;
|
group: PermissionGroup;
|
||||||
permissions: PermissionInfo[];
|
permissions: PermissionInfo[];
|
||||||
children: GroupedPermissions[];
|
children: GroupedPermissions[];
|
||||||
parent: GroupedPermissions;
|
parent: GroupedPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionValue {
|
export class PermissionValue {
|
||||||
readonly type: PermissionInfo;
|
readonly type: PermissionInfo;
|
||||||
value: number;
|
value: number;
|
||||||
flag_skip: boolean;
|
flag_skip: boolean;
|
||||||
|
@ -411,75 +66,74 @@ class PermissionValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NeededPermissionValue extends PermissionValue {
|
export class NeededPermissionValue extends PermissionValue {
|
||||||
constructor(type, value) {
|
constructor(type, value) {
|
||||||
super(type, value);
|
super(type, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace permissions {
|
export type PermissionRequestKeys = {
|
||||||
export type PermissionRequestKeys = {
|
client_id?: number;
|
||||||
client_id?: number;
|
channel_id?: number;
|
||||||
channel_id?: number;
|
playlist_id?: number;
|
||||||
playlist_id?: number;
|
}
|
||||||
|
|
||||||
|
export type PermissionRequest = PermissionRequestKeys & {
|
||||||
|
timeout_id: any;
|
||||||
|
promise: LaterPromise<PermissionValue[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace find {
|
||||||
|
export type Entry = {
|
||||||
|
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
|
||||||
|
value: number;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PermissionRequest = PermissionRequestKeys & {
|
export type Client = Entry & {
|
||||||
timeout_id: any;
|
type: "client",
|
||||||
promise: LaterPromise<PermissionValue[]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export namespace find {
|
client_id: number;
|
||||||
export type Entry = {
|
}
|
||||||
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
|
|
||||||
value: number;
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Client = Entry & {
|
export type Channel = Entry & {
|
||||||
type: "client",
|
type: "channel",
|
||||||
|
|
||||||
client_id: number;
|
channel_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Channel = Entry & {
|
export type Server = Entry & {
|
||||||
type: "channel",
|
type: "server"
|
||||||
|
}
|
||||||
|
|
||||||
channel_id: number;
|
export type ClientChannel = Entry & {
|
||||||
}
|
type: "client_channel",
|
||||||
|
|
||||||
export type Server = Entry & {
|
client_id: number;
|
||||||
type: "server"
|
channel_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientChannel = Entry & {
|
export type ChannelGroup = Entry & {
|
||||||
type: "client_channel",
|
type: "channel_group",
|
||||||
|
|
||||||
client_id: number;
|
group_id: number;
|
||||||
channel_id: number;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export type ChannelGroup = Entry & {
|
export type ServerGroup = Entry & {
|
||||||
type: "channel_group",
|
type: "server_group",
|
||||||
|
|
||||||
group_id: number;
|
group_id: number;
|
||||||
}
|
|
||||||
|
|
||||||
export type ServerGroup = Entry & {
|
|
||||||
type: "server_group",
|
|
||||||
|
|
||||||
group_id: number;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestLists =
|
export type RequestLists =
|
||||||
"requests_channel_permissions" |
|
"requests_channel_permissions" |
|
||||||
"requests_client_permissions" |
|
"requests_client_permissions" |
|
||||||
"requests_client_channel_permissions" |
|
"requests_client_channel_permissions" |
|
||||||
"requests_playlist_permissions" |
|
"requests_playlist_permissions" |
|
||||||
"requests_playlist_client_permissions";
|
"requests_playlist_client_permissions";
|
||||||
class PermissionManager extends connection.AbstractCommandHandler {
|
|
||||||
|
export class PermissionManager extends AbstractCommandHandler {
|
||||||
readonly handle: ConnectionHandler;
|
readonly handle: ConnectionHandler;
|
||||||
|
|
||||||
permissionList: PermissionInfo[] = [];
|
permissionList: PermissionInfo[] = [];
|
||||||
|
@ -488,11 +142,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
|
|
||||||
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
|
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
|
||||||
|
|
||||||
requests_channel_permissions: permissions.PermissionRequest[] = [];
|
requests_channel_permissions: PermissionRequest[] = [];
|
||||||
requests_client_permissions: permissions.PermissionRequest[] = [];
|
requests_client_permissions: PermissionRequest[] = [];
|
||||||
requests_client_channel_permissions: permissions.PermissionRequest[] = [];
|
requests_client_channel_permissions: PermissionRequest[] = [];
|
||||||
requests_playlist_permissions: permissions.PermissionRequest[] = [];
|
requests_playlist_permissions: PermissionRequest[] = [];
|
||||||
requests_playlist_client_permissions: permissions.PermissionRequest[] = [];
|
requests_playlist_client_permissions: PermissionRequest[] = [];
|
||||||
|
|
||||||
requests_permfind: {
|
requests_permfind: {
|
||||||
timeout_id: number,
|
timeout_id: number,
|
||||||
|
@ -603,7 +257,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
this._cacheNeededPermissions = undefined;
|
this._cacheNeededPermissions = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
handle_command(command: ServerCommand): boolean {
|
||||||
switch (command.command) {
|
switch (command.command) {
|
||||||
case "notifyclientneededpermissions":
|
case "notifyclientneededpermissions":
|
||||||
this.onNeededPermissions(command.arguments);
|
this.onNeededPermissions(command.arguments);
|
||||||
|
@ -775,7 +429,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private execute_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
private execute_channel_permission_request(request: PermissionRequestKeys) {
|
||||||
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
|
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
|
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
|
||||||
|
@ -785,7 +439,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
||||||
const keys: permissions.PermissionRequestKeys = {
|
const keys: PermissionRequestKeys = {
|
||||||
channel_id: channelId
|
channel_id: channelId
|
||||||
};
|
};
|
||||||
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this));
|
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this));
|
||||||
|
@ -799,7 +453,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private execute_client_permission_request(request: permissions.PermissionRequestKeys) {
|
private execute_client_permission_request(request: PermissionRequestKeys) {
|
||||||
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
|
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
|
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
|
||||||
|
@ -809,7 +463,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
||||||
const keys: permissions.PermissionRequestKeys = {
|
const keys: PermissionRequestKeys = {
|
||||||
client_id: client_id
|
client_id: client_id
|
||||||
};
|
};
|
||||||
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
|
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
|
||||||
|
@ -826,7 +480,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private execute_client_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
private execute_client_channel_permission_request(request: PermissionRequestKeys) {
|
||||||
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
|
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
@ -837,7 +491,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
||||||
const keys: permissions.PermissionRequestKeys = {
|
const keys: PermissionRequestKeys = {
|
||||||
client_id: client_id
|
client_id: client_id
|
||||||
};
|
};
|
||||||
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
|
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
|
||||||
|
@ -852,7 +506,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private execute_playlist_permission_request(request: permissions.PermissionRequestKeys) {
|
private execute_playlist_permission_request(request: PermissionRequestKeys) {
|
||||||
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
|
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
@ -863,7 +517,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
||||||
const keys: permissions.PermissionRequestKeys = {
|
const keys: PermissionRequestKeys = {
|
||||||
playlist_id: playlist_id
|
playlist_id: playlist_id
|
||||||
};
|
};
|
||||||
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
|
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
|
||||||
|
@ -880,7 +534,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private execute_playlist_client_permission_request(request: permissions.PermissionRequestKeys) {
|
private execute_playlist_client_permission_request(request: PermissionRequestKeys) {
|
||||||
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
|
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
@ -891,7 +545,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
|
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
|
||||||
const keys: permissions.PermissionRequestKeys = {
|
const keys: PermissionRequestKeys = {
|
||||||
playlist_id: playlist_id,
|
playlist_id: playlist_id,
|
||||||
client_id: client_database_id
|
client_id: client_database_id
|
||||||
};
|
};
|
||||||
|
@ -907,8 +561,8 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
private execute_permission_request(list: RequestLists,
|
private execute_permission_request(list: RequestLists,
|
||||||
criteria: permissions.PermissionRequestKeys,
|
criteria: PermissionRequestKeys,
|
||||||
execute: (criteria: permissions.PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
|
execute: (criteria: PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
|
||||||
for(const request of this[list])
|
for(const request of this[list])
|
||||||
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
|
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
|
||||||
return request.promise;
|
return request.promise;
|
||||||
|
@ -922,7 +576,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
return result.promise;
|
return result.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
private fullfill_permission_request(list: RequestLists, criteria: permissions.PermissionRequestKeys, status: "success" | "error", result: any) {
|
private fullfill_permission_request(list: RequestLists, criteria: PermissionRequestKeys, status: "success" | "error", result: any) {
|
||||||
for(const request of this[list]) {
|
for(const request of this[list]) {
|
||||||
if(this.criteria_equal(request, criteria)) {
|
if(this.criteria_equal(request, criteria)) {
|
||||||
this[list].remove(request);
|
this[list].remove(request);
|
||||||
|
@ -932,7 +586,7 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
find_permission(...permissions: string[]) : Promise<permissions.find.Entry[]> {
|
find_permission(...permissions: string[]) : Promise<find.Entry[]> {
|
||||||
const permission_ids = [];
|
const permission_ids = [];
|
||||||
for(const permission of permissions) {
|
for(const permission of permissions) {
|
||||||
const info = this.resolveInfo(permission);
|
const info = this.resolveInfo(permission);
|
||||||
|
@ -942,11 +596,11 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
}
|
}
|
||||||
if(!permission_ids.length) return Promise.resolve([]);
|
if(!permission_ids.length) return Promise.resolve([]);
|
||||||
|
|
||||||
return new Promise<permissions.find.Entry[]>((resolve, reject) => {
|
return new Promise<find.Entry[]>((resolve, reject) => {
|
||||||
const single_handler = {
|
const single_handler = {
|
||||||
command: "notifypermfind",
|
command: "notifypermfind",
|
||||||
function: command => {
|
function: command => {
|
||||||
const result: permissions.find.Entry[] = [];
|
const result: find.Entry[] = [];
|
||||||
for(const entry of command.arguments) {
|
for(const entry of command.arguments) {
|
||||||
const perm_id = parseInt(entry["p"]);
|
const perm_id = parseInt(entry["p"]);
|
||||||
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
|
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
|
||||||
|
@ -960,32 +614,32 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
data = {
|
data = {
|
||||||
type: "server_group",
|
type: "server_group",
|
||||||
group_id: parseInt(entry["id1"]),
|
group_id: parseInt(entry["id1"]),
|
||||||
} as permissions.find.ServerGroup;
|
} as find.ServerGroup;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
data = {
|
data = {
|
||||||
type: "client",
|
type: "client",
|
||||||
client_id: parseInt(entry["id2"]),
|
client_id: parseInt(entry["id2"]),
|
||||||
} as permissions.find.Client;
|
} as find.Client;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
data = {
|
data = {
|
||||||
type: "channel",
|
type: "channel",
|
||||||
channel_id: parseInt(entry["id2"]),
|
channel_id: parseInt(entry["id2"]),
|
||||||
} as permissions.find.Channel;
|
} as find.Channel;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
data = {
|
data = {
|
||||||
type: "channel_group",
|
type: "channel_group",
|
||||||
group_id: parseInt(entry["id1"]),
|
group_id: parseInt(entry["id1"]),
|
||||||
} as permissions.find.ChannelGroup;
|
} as find.ChannelGroup;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
data = {
|
data = {
|
||||||
type: "client_channel",
|
type: "client_channel",
|
||||||
client_id: parseInt(entry["id1"]),
|
client_id: parseInt(entry["id1"]),
|
||||||
channel_id: parseInt(entry["id1"]),
|
channel_id: parseInt(entry["id1"]),
|
||||||
} as permissions.find.ClientChannel;
|
} as find.ClientChannel;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
|
|
350
shared/js/permission/PermissionType.ts
Normal file
350
shared/js/permission/PermissionType.ts
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
export enum PermissionType {
|
||||||
|
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
|
||||||
|
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
|
||||||
|
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
|
||||||
|
B_SERVERINSTANCE_VIRTUALSERVER_LIST = "b_serverinstance_virtualserver_list",
|
||||||
|
B_SERVERINSTANCE_BINDING_LIST = "b_serverinstance_binding_list",
|
||||||
|
B_SERVERINSTANCE_PERMISSION_LIST = "b_serverinstance_permission_list",
|
||||||
|
B_SERVERINSTANCE_PERMISSION_FIND = "b_serverinstance_permission_find",
|
||||||
|
B_VIRTUALSERVER_CREATE = "b_virtualserver_create",
|
||||||
|
B_VIRTUALSERVER_DELETE = "b_virtualserver_delete",
|
||||||
|
B_VIRTUALSERVER_START_ANY = "b_virtualserver_start_any",
|
||||||
|
B_VIRTUALSERVER_STOP_ANY = "b_virtualserver_stop_any",
|
||||||
|
B_VIRTUALSERVER_CHANGE_MACHINE_ID = "b_virtualserver_change_machine_id",
|
||||||
|
B_VIRTUALSERVER_CHANGE_TEMPLATE = "b_virtualserver_change_template",
|
||||||
|
B_SERVERQUERY_LOGIN = "b_serverquery_login",
|
||||||
|
B_SERVERINSTANCE_TEXTMESSAGE_SEND = "b_serverinstance_textmessage_send",
|
||||||
|
B_SERVERINSTANCE_LOG_VIEW = "b_serverinstance_log_view",
|
||||||
|
B_SERVERINSTANCE_LOG_ADD = "b_serverinstance_log_add",
|
||||||
|
B_SERVERINSTANCE_STOP = "b_serverinstance_stop",
|
||||||
|
B_SERVERINSTANCE_MODIFY_SETTINGS = "b_serverinstance_modify_settings",
|
||||||
|
B_SERVERINSTANCE_MODIFY_QUERYGROUP = "b_serverinstance_modify_querygroup",
|
||||||
|
B_SERVERINSTANCE_MODIFY_TEMPLATES = "b_serverinstance_modify_templates",
|
||||||
|
B_VIRTUALSERVER_SELECT = "b_virtualserver_select",
|
||||||
|
B_VIRTUALSERVER_SELECT_GODMODE = "b_virtualserver_select_godmode",
|
||||||
|
B_VIRTUALSERVER_INFO_VIEW = "b_virtualserver_info_view",
|
||||||
|
B_VIRTUALSERVER_CONNECTIONINFO_VIEW = "b_virtualserver_connectioninfo_view",
|
||||||
|
B_VIRTUALSERVER_CHANNEL_LIST = "b_virtualserver_channel_list",
|
||||||
|
B_VIRTUALSERVER_CHANNEL_SEARCH = "b_virtualserver_channel_search",
|
||||||
|
B_VIRTUALSERVER_CLIENT_LIST = "b_virtualserver_client_list",
|
||||||
|
B_VIRTUALSERVER_CLIENT_SEARCH = "b_virtualserver_client_search",
|
||||||
|
B_VIRTUALSERVER_CLIENT_DBLIST = "b_virtualserver_client_dblist",
|
||||||
|
B_VIRTUALSERVER_CLIENT_DBSEARCH = "b_virtualserver_client_dbsearch",
|
||||||
|
B_VIRTUALSERVER_CLIENT_DBINFO = "b_virtualserver_client_dbinfo",
|
||||||
|
B_VIRTUALSERVER_PERMISSION_FIND = "b_virtualserver_permission_find",
|
||||||
|
B_VIRTUALSERVER_CUSTOM_SEARCH = "b_virtualserver_custom_search",
|
||||||
|
B_VIRTUALSERVER_START = "b_virtualserver_start",
|
||||||
|
B_VIRTUALSERVER_STOP = "b_virtualserver_stop",
|
||||||
|
B_VIRTUALSERVER_TOKEN_LIST = "b_virtualserver_token_list",
|
||||||
|
B_VIRTUALSERVER_TOKEN_ADD = "b_virtualserver_token_add",
|
||||||
|
B_VIRTUALSERVER_TOKEN_USE = "b_virtualserver_token_use",
|
||||||
|
B_VIRTUALSERVER_TOKEN_DELETE = "b_virtualserver_token_delete",
|
||||||
|
B_VIRTUALSERVER_LOG_VIEW = "b_virtualserver_log_view",
|
||||||
|
B_VIRTUALSERVER_LOG_ADD = "b_virtualserver_log_add",
|
||||||
|
B_VIRTUALSERVER_JOIN_IGNORE_PASSWORD = "b_virtualserver_join_ignore_password",
|
||||||
|
B_VIRTUALSERVER_NOTIFY_REGISTER = "b_virtualserver_notify_register",
|
||||||
|
B_VIRTUALSERVER_NOTIFY_UNREGISTER = "b_virtualserver_notify_unregister",
|
||||||
|
B_VIRTUALSERVER_SNAPSHOT_CREATE = "b_virtualserver_snapshot_create",
|
||||||
|
B_VIRTUALSERVER_SNAPSHOT_DEPLOY = "b_virtualserver_snapshot_deploy",
|
||||||
|
B_VIRTUALSERVER_PERMISSION_RESET = "b_virtualserver_permission_reset",
|
||||||
|
B_VIRTUALSERVER_MODIFY_NAME = "b_virtualserver_modify_name",
|
||||||
|
B_VIRTUALSERVER_MODIFY_WELCOMEMESSAGE = "b_virtualserver_modify_welcomemessage",
|
||||||
|
B_VIRTUALSERVER_MODIFY_MAXCLIENTS = "b_virtualserver_modify_maxclients",
|
||||||
|
B_VIRTUALSERVER_MODIFY_RESERVED_SLOTS = "b_virtualserver_modify_reserved_slots",
|
||||||
|
B_VIRTUALSERVER_MODIFY_PASSWORD = "b_virtualserver_modify_password",
|
||||||
|
B_VIRTUALSERVER_MODIFY_DEFAULT_SERVERGROUP = "b_virtualserver_modify_default_servergroup",
|
||||||
|
B_VIRTUALSERVER_MODIFY_DEFAULT_MUSICGROUP = "b_virtualserver_modify_default_musicgroup",
|
||||||
|
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELGROUP = "b_virtualserver_modify_default_channelgroup",
|
||||||
|
B_VIRTUALSERVER_MODIFY_DEFAULT_CHANNELADMINGROUP = "b_virtualserver_modify_default_channeladmingroup",
|
||||||
|
B_VIRTUALSERVER_MODIFY_CHANNEL_FORCED_SILENCE = "b_virtualserver_modify_channel_forced_silence",
|
||||||
|
B_VIRTUALSERVER_MODIFY_COMPLAIN = "b_virtualserver_modify_complain",
|
||||||
|
B_VIRTUALSERVER_MODIFY_ANTIFLOOD = "b_virtualserver_modify_antiflood",
|
||||||
|
B_VIRTUALSERVER_MODIFY_FT_SETTINGS = "b_virtualserver_modify_ft_settings",
|
||||||
|
B_VIRTUALSERVER_MODIFY_FT_QUOTAS = "b_virtualserver_modify_ft_quotas",
|
||||||
|
B_VIRTUALSERVER_MODIFY_HOSTMESSAGE = "b_virtualserver_modify_hostmessage",
|
||||||
|
B_VIRTUALSERVER_MODIFY_HOSTBANNER = "b_virtualserver_modify_hostbanner",
|
||||||
|
B_VIRTUALSERVER_MODIFY_HOSTBUTTON = "b_virtualserver_modify_hostbutton",
|
||||||
|
B_VIRTUALSERVER_MODIFY_PORT = "b_virtualserver_modify_port",
|
||||||
|
B_VIRTUALSERVER_MODIFY_HOST = "b_virtualserver_modify_host",
|
||||||
|
B_VIRTUALSERVER_MODIFY_DEFAULT_MESSAGES = "b_virtualserver_modify_default_messages",
|
||||||
|
B_VIRTUALSERVER_MODIFY_AUTOSTART = "b_virtualserver_modify_autostart",
|
||||||
|
B_VIRTUALSERVER_MODIFY_NEEDED_IDENTITY_SECURITY_LEVEL = "b_virtualserver_modify_needed_identity_security_level",
|
||||||
|
B_VIRTUALSERVER_MODIFY_PRIORITY_SPEAKER_DIMM_MODIFICATOR = "b_virtualserver_modify_priority_speaker_dimm_modificator",
|
||||||
|
B_VIRTUALSERVER_MODIFY_LOG_SETTINGS = "b_virtualserver_modify_log_settings",
|
||||||
|
B_VIRTUALSERVER_MODIFY_MIN_CLIENT_VERSION = "b_virtualserver_modify_min_client_version",
|
||||||
|
B_VIRTUALSERVER_MODIFY_ICON_ID = "b_virtualserver_modify_icon_id",
|
||||||
|
B_VIRTUALSERVER_MODIFY_WEBLIST = "b_virtualserver_modify_weblist",
|
||||||
|
B_VIRTUALSERVER_MODIFY_CODEC_ENCRYPTION_MODE = "b_virtualserver_modify_codec_encryption_mode",
|
||||||
|
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS = "b_virtualserver_modify_temporary_passwords",
|
||||||
|
B_VIRTUALSERVER_MODIFY_TEMPORARY_PASSWORDS_OWN = "b_virtualserver_modify_temporary_passwords_own",
|
||||||
|
B_VIRTUALSERVER_MODIFY_CHANNEL_TEMP_DELETE_DELAY_DEFAULT = "b_virtualserver_modify_channel_temp_delete_delay_default",
|
||||||
|
B_VIRTUALSERVER_MODIFY_MUSIC_BOT_LIMIT = "b_virtualserver_modify_music_bot_limit",
|
||||||
|
B_VIRTUALSERVER_MODIFY_COUNTRY_CODE = "b_virtualserver_modify_country_code",
|
||||||
|
I_CHANNEL_MIN_DEPTH = "i_channel_min_depth",
|
||||||
|
I_CHANNEL_MAX_DEPTH = "i_channel_max_depth",
|
||||||
|
B_CHANNEL_GROUP_INHERITANCE_END = "b_channel_group_inheritance_end",
|
||||||
|
I_CHANNEL_PERMISSION_MODIFY_POWER = "i_channel_permission_modify_power",
|
||||||
|
I_CHANNEL_NEEDED_PERMISSION_MODIFY_POWER = "i_channel_needed_permission_modify_power",
|
||||||
|
B_CHANNEL_INFO_VIEW = "b_channel_info_view",
|
||||||
|
B_CHANNEL_CREATE_CHILD = "b_channel_create_child",
|
||||||
|
B_CHANNEL_CREATE_PERMANENT = "b_channel_create_permanent",
|
||||||
|
B_CHANNEL_CREATE_SEMI_PERMANENT = "b_channel_create_semi_permanent",
|
||||||
|
B_CHANNEL_CREATE_TEMPORARY = "b_channel_create_temporary",
|
||||||
|
B_CHANNEL_CREATE_PRIVATE = "b_channel_create_private",
|
||||||
|
B_CHANNEL_CREATE_WITH_TOPIC = "b_channel_create_with_topic",
|
||||||
|
B_CHANNEL_CREATE_WITH_DESCRIPTION = "b_channel_create_with_description",
|
||||||
|
B_CHANNEL_CREATE_WITH_PASSWORD = "b_channel_create_with_password",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX8 = "b_channel_create_modify_with_codec_speex8",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX16 = "b_channel_create_modify_with_codec_speex16",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_SPEEX32 = "b_channel_create_modify_with_codec_speex32",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_CELTMONO48 = "b_channel_create_modify_with_codec_celtmono48",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSVOICE = "b_channel_create_modify_with_codec_opusvoice",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_CODEC_OPUSMUSIC = "b_channel_create_modify_with_codec_opusmusic",
|
||||||
|
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_MAXQUALITY = "i_channel_create_modify_with_codec_maxquality",
|
||||||
|
I_CHANNEL_CREATE_MODIFY_WITH_CODEC_LATENCY_FACTOR_MIN = "i_channel_create_modify_with_codec_latency_factor_min",
|
||||||
|
I_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_LENGTH = "i_channel_create_modify_conversation_history_length",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_CONVERSATION_HISTORY_UNLIMITED = "b_channel_create_modify_conversation_history_unlimited",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_CONVERSATION_PRIVATE = "b_channel_create_modify_conversation_private",
|
||||||
|
B_CHANNEL_CREATE_WITH_MAXCLIENTS = "b_channel_create_with_maxclients",
|
||||||
|
B_CHANNEL_CREATE_WITH_MAXFAMILYCLIENTS = "b_channel_create_with_maxfamilyclients",
|
||||||
|
B_CHANNEL_CREATE_WITH_SORTORDER = "b_channel_create_with_sortorder",
|
||||||
|
B_CHANNEL_CREATE_WITH_DEFAULT = "b_channel_create_with_default",
|
||||||
|
B_CHANNEL_CREATE_WITH_NEEDED_TALK_POWER = "b_channel_create_with_needed_talk_power",
|
||||||
|
B_CHANNEL_CREATE_MODIFY_WITH_FORCE_PASSWORD = "b_channel_create_modify_with_force_password",
|
||||||
|
I_CHANNEL_CREATE_MODIFY_WITH_TEMP_DELETE_DELAY = "i_channel_create_modify_with_temp_delete_delay",
|
||||||
|
B_CHANNEL_MODIFY_PARENT = "b_channel_modify_parent",
|
||||||
|
B_CHANNEL_MODIFY_MAKE_DEFAULT = "b_channel_modify_make_default",
|
||||||
|
B_CHANNEL_MODIFY_MAKE_PERMANENT = "b_channel_modify_make_permanent",
|
||||||
|
B_CHANNEL_MODIFY_MAKE_SEMI_PERMANENT = "b_channel_modify_make_semi_permanent",
|
||||||
|
B_CHANNEL_MODIFY_MAKE_TEMPORARY = "b_channel_modify_make_temporary",
|
||||||
|
B_CHANNEL_MODIFY_NAME = "b_channel_modify_name",
|
||||||
|
B_CHANNEL_MODIFY_TOPIC = "b_channel_modify_topic",
|
||||||
|
B_CHANNEL_MODIFY_DESCRIPTION = "b_channel_modify_description",
|
||||||
|
B_CHANNEL_MODIFY_PASSWORD = "b_channel_modify_password",
|
||||||
|
B_CHANNEL_MODIFY_CODEC = "b_channel_modify_codec",
|
||||||
|
B_CHANNEL_MODIFY_CODEC_QUALITY = "b_channel_modify_codec_quality",
|
||||||
|
B_CHANNEL_MODIFY_CODEC_LATENCY_FACTOR = "b_channel_modify_codec_latency_factor",
|
||||||
|
B_CHANNEL_MODIFY_MAXCLIENTS = "b_channel_modify_maxclients",
|
||||||
|
B_CHANNEL_MODIFY_MAXFAMILYCLIENTS = "b_channel_modify_maxfamilyclients",
|
||||||
|
B_CHANNEL_MODIFY_SORTORDER = "b_channel_modify_sortorder",
|
||||||
|
B_CHANNEL_MODIFY_NEEDED_TALK_POWER = "b_channel_modify_needed_talk_power",
|
||||||
|
I_CHANNEL_MODIFY_POWER = "i_channel_modify_power",
|
||||||
|
I_CHANNEL_NEEDED_MODIFY_POWER = "i_channel_needed_modify_power",
|
||||||
|
B_CHANNEL_MODIFY_MAKE_CODEC_ENCRYPTED = "b_channel_modify_make_codec_encrypted",
|
||||||
|
B_CHANNEL_MODIFY_TEMP_DELETE_DELAY = "b_channel_modify_temp_delete_delay",
|
||||||
|
B_CHANNEL_DELETE_PERMANENT = "b_channel_delete_permanent",
|
||||||
|
B_CHANNEL_DELETE_SEMI_PERMANENT = "b_channel_delete_semi_permanent",
|
||||||
|
B_CHANNEL_DELETE_TEMPORARY = "b_channel_delete_temporary",
|
||||||
|
B_CHANNEL_DELETE_FLAG_FORCE = "b_channel_delete_flag_force",
|
||||||
|
I_CHANNEL_DELETE_POWER = "i_channel_delete_power",
|
||||||
|
B_CHANNEL_CONVERSATION_MESSAGE_DELETE = "b_channel_conversation_message_delete",
|
||||||
|
I_CHANNEL_NEEDED_DELETE_POWER = "i_channel_needed_delete_power",
|
||||||
|
B_CHANNEL_JOIN_PERMANENT = "b_channel_join_permanent",
|
||||||
|
B_CHANNEL_JOIN_SEMI_PERMANENT = "b_channel_join_semi_permanent",
|
||||||
|
B_CHANNEL_JOIN_TEMPORARY = "b_channel_join_temporary",
|
||||||
|
B_CHANNEL_JOIN_IGNORE_PASSWORD = "b_channel_join_ignore_password",
|
||||||
|
B_CHANNEL_JOIN_IGNORE_MAXCLIENTS = "b_channel_join_ignore_maxclients",
|
||||||
|
B_CHANNEL_IGNORE_VIEW_POWER = "b_channel_ignore_view_power",
|
||||||
|
I_CHANNEL_JOIN_POWER = "i_channel_join_power",
|
||||||
|
I_CHANNEL_NEEDED_JOIN_POWER = "i_channel_needed_join_power",
|
||||||
|
B_CHANNEL_IGNORE_JOIN_POWER = "b_channel_ignore_join_power",
|
||||||
|
B_CHANNEL_IGNORE_DESCRIPTION_VIEW_POWER = "b_channel_ignore_description_view_power",
|
||||||
|
I_CHANNEL_VIEW_POWER = "i_channel_view_power",
|
||||||
|
I_CHANNEL_NEEDED_VIEW_POWER = "i_channel_needed_view_power",
|
||||||
|
I_CHANNEL_SUBSCRIBE_POWER = "i_channel_subscribe_power",
|
||||||
|
I_CHANNEL_NEEDED_SUBSCRIBE_POWER = "i_channel_needed_subscribe_power",
|
||||||
|
I_CHANNEL_DESCRIPTION_VIEW_POWER = "i_channel_description_view_power",
|
||||||
|
I_CHANNEL_NEEDED_DESCRIPTION_VIEW_POWER = "i_channel_needed_description_view_power",
|
||||||
|
I_ICON_ID = "i_icon_id",
|
||||||
|
I_MAX_ICON_FILESIZE = "i_max_icon_filesize",
|
||||||
|
I_MAX_PLAYLIST_SIZE = "i_max_playlist_size",
|
||||||
|
I_MAX_PLAYLISTS = "i_max_playlists",
|
||||||
|
B_ICON_MANAGE = "b_icon_manage",
|
||||||
|
B_GROUP_IS_PERMANENT = "b_group_is_permanent",
|
||||||
|
I_GROUP_AUTO_UPDATE_TYPE = "i_group_auto_update_type",
|
||||||
|
I_GROUP_AUTO_UPDATE_MAX_VALUE = "i_group_auto_update_max_value",
|
||||||
|
I_GROUP_SORT_ID = "i_group_sort_id",
|
||||||
|
I_GROUP_SHOW_NAME_IN_TREE = "i_group_show_name_in_tree",
|
||||||
|
B_VIRTUALSERVER_SERVERGROUP_CREATE = "b_virtualserver_servergroup_create",
|
||||||
|
B_VIRTUALSERVER_SERVERGROUP_LIST = "b_virtualserver_servergroup_list",
|
||||||
|
B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST = "b_virtualserver_servergroup_permission_list",
|
||||||
|
B_VIRTUALSERVER_SERVERGROUP_CLIENT_LIST = "b_virtualserver_servergroup_client_list",
|
||||||
|
B_VIRTUALSERVER_CHANNELGROUP_CREATE = "b_virtualserver_channelgroup_create",
|
||||||
|
B_VIRTUALSERVER_CHANNELGROUP_LIST = "b_virtualserver_channelgroup_list",
|
||||||
|
B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST = "b_virtualserver_channelgroup_permission_list",
|
||||||
|
B_VIRTUALSERVER_CHANNELGROUP_CLIENT_LIST = "b_virtualserver_channelgroup_client_list",
|
||||||
|
B_VIRTUALSERVER_CLIENT_PERMISSION_LIST = "b_virtualserver_client_permission_list",
|
||||||
|
B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST = "b_virtualserver_channel_permission_list",
|
||||||
|
B_VIRTUALSERVER_CHANNELCLIENT_PERMISSION_LIST = "b_virtualserver_channelclient_permission_list",
|
||||||
|
B_VIRTUALSERVER_PLAYLIST_PERMISSION_LIST = "b_virtualserver_playlist_permission_list",
|
||||||
|
I_SERVER_GROUP_MODIFY_POWER = "i_server_group_modify_power",
|
||||||
|
I_SERVER_GROUP_NEEDED_MODIFY_POWER = "i_server_group_needed_modify_power",
|
||||||
|
I_SERVER_GROUP_MEMBER_ADD_POWER = "i_server_group_member_add_power",
|
||||||
|
I_SERVER_GROUP_SELF_ADD_POWER = "i_server_group_self_add_power",
|
||||||
|
I_SERVER_GROUP_NEEDED_MEMBER_ADD_POWER = "i_server_group_needed_member_add_power",
|
||||||
|
I_SERVER_GROUP_MEMBER_REMOVE_POWER = "i_server_group_member_remove_power",
|
||||||
|
I_SERVER_GROUP_SELF_REMOVE_POWER = "i_server_group_self_remove_power",
|
||||||
|
I_SERVER_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_server_group_needed_member_remove_power",
|
||||||
|
I_CHANNEL_GROUP_MODIFY_POWER = "i_channel_group_modify_power",
|
||||||
|
I_CHANNEL_GROUP_NEEDED_MODIFY_POWER = "i_channel_group_needed_modify_power",
|
||||||
|
I_CHANNEL_GROUP_MEMBER_ADD_POWER = "i_channel_group_member_add_power",
|
||||||
|
I_CHANNEL_GROUP_SELF_ADD_POWER = "i_channel_group_self_add_power",
|
||||||
|
I_CHANNEL_GROUP_NEEDED_MEMBER_ADD_POWER = "i_channel_group_needed_member_add_power",
|
||||||
|
I_CHANNEL_GROUP_MEMBER_REMOVE_POWER = "i_channel_group_member_remove_power",
|
||||||
|
I_CHANNEL_GROUP_SELF_REMOVE_POWER = "i_channel_group_self_remove_power",
|
||||||
|
I_CHANNEL_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_channel_group_needed_member_remove_power",
|
||||||
|
I_GROUP_MEMBER_ADD_POWER = "i_group_member_add_power",
|
||||||
|
I_GROUP_NEEDED_MEMBER_ADD_POWER = "i_group_needed_member_add_power",
|
||||||
|
I_GROUP_MEMBER_REMOVE_POWER = "i_group_member_remove_power",
|
||||||
|
I_GROUP_NEEDED_MEMBER_REMOVE_POWER = "i_group_needed_member_remove_power",
|
||||||
|
I_GROUP_MODIFY_POWER = "i_group_modify_power",
|
||||||
|
I_GROUP_NEEDED_MODIFY_POWER = "i_group_needed_modify_power",
|
||||||
|
I_PERMISSION_MODIFY_POWER = "i_permission_modify_power",
|
||||||
|
B_PERMISSION_MODIFY_POWER_IGNORE = "b_permission_modify_power_ignore",
|
||||||
|
B_VIRTUALSERVER_SERVERGROUP_DELETE = "b_virtualserver_servergroup_delete",
|
||||||
|
B_VIRTUALSERVER_CHANNELGROUP_DELETE = "b_virtualserver_channelgroup_delete",
|
||||||
|
I_CLIENT_PERMISSION_MODIFY_POWER = "i_client_permission_modify_power",
|
||||||
|
I_CLIENT_NEEDED_PERMISSION_MODIFY_POWER = "i_client_needed_permission_modify_power",
|
||||||
|
I_CLIENT_MAX_CLONES_UID = "i_client_max_clones_uid",
|
||||||
|
I_CLIENT_MAX_CLONES_IP = "i_client_max_clones_ip",
|
||||||
|
I_CLIENT_MAX_CLONES_HWID = "i_client_max_clones_hwid",
|
||||||
|
I_CLIENT_MAX_IDLETIME = "i_client_max_idletime",
|
||||||
|
I_CLIENT_MAX_AVATAR_FILESIZE = "i_client_max_avatar_filesize",
|
||||||
|
I_CLIENT_MAX_CHANNEL_SUBSCRIPTIONS = "i_client_max_channel_subscriptions",
|
||||||
|
I_CLIENT_MAX_CHANNELS = "i_client_max_channels",
|
||||||
|
I_CLIENT_MAX_TEMPORARY_CHANNELS = "i_client_max_temporary_channels",
|
||||||
|
I_CLIENT_MAX_SEMI_CHANNELS = "i_client_max_semi_channels",
|
||||||
|
I_CLIENT_MAX_PERMANENT_CHANNELS = "i_client_max_permanent_channels",
|
||||||
|
B_CLIENT_USE_PRIORITY_SPEAKER = "b_client_use_priority_speaker",
|
||||||
|
B_CLIENT_SKIP_CHANNELGROUP_PERMISSIONS = "b_client_skip_channelgroup_permissions",
|
||||||
|
B_CLIENT_FORCE_PUSH_TO_TALK = "b_client_force_push_to_talk",
|
||||||
|
B_CLIENT_IGNORE_BANS = "b_client_ignore_bans",
|
||||||
|
B_CLIENT_IGNORE_VPN = "b_client_ignore_vpn",
|
||||||
|
B_CLIENT_IGNORE_ANTIFLOOD = "b_client_ignore_antiflood",
|
||||||
|
B_CLIENT_ENFORCE_VALID_HWID = "b_client_enforce_valid_hwid",
|
||||||
|
B_CLIENT_ALLOW_INVALID_PACKET = "b_client_allow_invalid_packet",
|
||||||
|
B_CLIENT_ALLOW_INVALID_BADGES = "b_client_allow_invalid_badges",
|
||||||
|
B_CLIENT_ISSUE_CLIENT_QUERY_COMMAND = "b_client_issue_client_query_command",
|
||||||
|
B_CLIENT_USE_RESERVED_SLOT = "b_client_use_reserved_slot",
|
||||||
|
B_CLIENT_USE_CHANNEL_COMMANDER = "b_client_use_channel_commander",
|
||||||
|
B_CLIENT_REQUEST_TALKER = "b_client_request_talker",
|
||||||
|
B_CLIENT_AVATAR_DELETE_OTHER = "b_client_avatar_delete_other",
|
||||||
|
B_CLIENT_IS_STICKY = "b_client_is_sticky",
|
||||||
|
B_CLIENT_IGNORE_STICKY = "b_client_ignore_sticky",
|
||||||
|
B_CLIENT_MUSIC_CREATE_PERMANENT = "b_client_music_create_permanent",
|
||||||
|
B_CLIENT_MUSIC_CREATE_SEMI_PERMANENT = "b_client_music_create_semi_permanent",
|
||||||
|
B_CLIENT_MUSIC_CREATE_TEMPORARY = "b_client_music_create_temporary",
|
||||||
|
B_CLIENT_MUSIC_MODIFY_PERMANENT = "b_client_music_modify_permanent",
|
||||||
|
B_CLIENT_MUSIC_MODIFY_SEMI_PERMANENT = "b_client_music_modify_semi_permanent",
|
||||||
|
B_CLIENT_MUSIC_MODIFY_TEMPORARY = "b_client_music_modify_temporary",
|
||||||
|
I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME = "i_client_music_create_modify_max_volume",
|
||||||
|
I_CLIENT_MUSIC_LIMIT = "i_client_music_limit",
|
||||||
|
I_CLIENT_MUSIC_NEEDED_DELETE_POWER = "i_client_music_needed_delete_power",
|
||||||
|
I_CLIENT_MUSIC_DELETE_POWER = "i_client_music_delete_power",
|
||||||
|
I_CLIENT_MUSIC_PLAY_POWER = "i_client_music_play_power",
|
||||||
|
I_CLIENT_MUSIC_NEEDED_PLAY_POWER = "i_client_music_needed_play_power",
|
||||||
|
I_CLIENT_MUSIC_MODIFY_POWER = "i_client_music_modify_power",
|
||||||
|
I_CLIENT_MUSIC_NEEDED_MODIFY_POWER = "i_client_music_needed_modify_power",
|
||||||
|
I_CLIENT_MUSIC_RENAME_POWER = "i_client_music_rename_power",
|
||||||
|
I_CLIENT_MUSIC_NEEDED_RENAME_POWER = "i_client_music_needed_rename_power",
|
||||||
|
B_PLAYLIST_CREATE = "b_playlist_create",
|
||||||
|
I_PLAYLIST_VIEW_POWER = "i_playlist_view_power",
|
||||||
|
I_PLAYLIST_NEEDED_VIEW_POWER = "i_playlist_needed_view_power",
|
||||||
|
I_PLAYLIST_MODIFY_POWER = "i_playlist_modify_power",
|
||||||
|
I_PLAYLIST_NEEDED_MODIFY_POWER = "i_playlist_needed_modify_power",
|
||||||
|
I_PLAYLIST_PERMISSION_MODIFY_POWER = "i_playlist_permission_modify_power",
|
||||||
|
I_PLAYLIST_NEEDED_PERMISSION_MODIFY_POWER = "i_playlist_needed_permission_modify_power",
|
||||||
|
I_PLAYLIST_DELETE_POWER = "i_playlist_delete_power",
|
||||||
|
I_PLAYLIST_NEEDED_DELETE_POWER = "i_playlist_needed_delete_power",
|
||||||
|
I_PLAYLIST_SONG_ADD_POWER = "i_playlist_song_add_power",
|
||||||
|
I_PLAYLIST_SONG_NEEDED_ADD_POWER = "i_playlist_song_needed_add_power",
|
||||||
|
I_PLAYLIST_SONG_REMOVE_POWER = "i_playlist_song_remove_power",
|
||||||
|
I_PLAYLIST_SONG_NEEDED_REMOVE_POWER = "i_playlist_song_needed_remove_power",
|
||||||
|
B_CLIENT_INFO_VIEW = "b_client_info_view",
|
||||||
|
B_CLIENT_PERMISSIONOVERVIEW_VIEW = "b_client_permissionoverview_view",
|
||||||
|
B_CLIENT_PERMISSIONOVERVIEW_OWN = "b_client_permissionoverview_own",
|
||||||
|
B_CLIENT_REMOTEADDRESS_VIEW = "b_client_remoteaddress_view",
|
||||||
|
I_CLIENT_SERVERQUERY_VIEW_POWER = "i_client_serverquery_view_power",
|
||||||
|
I_CLIENT_NEEDED_SERVERQUERY_VIEW_POWER = "i_client_needed_serverquery_view_power",
|
||||||
|
B_CLIENT_CUSTOM_INFO_VIEW = "b_client_custom_info_view",
|
||||||
|
B_CLIENT_MUSIC_CHANNEL_LIST = "b_client_music_channel_list",
|
||||||
|
B_CLIENT_MUSIC_SERVER_LIST = "b_client_music_server_list",
|
||||||
|
I_CLIENT_MUSIC_INFO = "i_client_music_info",
|
||||||
|
I_CLIENT_MUSIC_NEEDED_INFO = "i_client_music_needed_info",
|
||||||
|
I_CLIENT_KICK_FROM_SERVER_POWER = "i_client_kick_from_server_power",
|
||||||
|
I_CLIENT_NEEDED_KICK_FROM_SERVER_POWER = "i_client_needed_kick_from_server_power",
|
||||||
|
I_CLIENT_KICK_FROM_CHANNEL_POWER = "i_client_kick_from_channel_power",
|
||||||
|
I_CLIENT_NEEDED_KICK_FROM_CHANNEL_POWER = "i_client_needed_kick_from_channel_power",
|
||||||
|
I_CLIENT_BAN_POWER = "i_client_ban_power",
|
||||||
|
I_CLIENT_NEEDED_BAN_POWER = "i_client_needed_ban_power",
|
||||||
|
I_CLIENT_MOVE_POWER = "i_client_move_power",
|
||||||
|
I_CLIENT_NEEDED_MOVE_POWER = "i_client_needed_move_power",
|
||||||
|
I_CLIENT_COMPLAIN_POWER = "i_client_complain_power",
|
||||||
|
I_CLIENT_NEEDED_COMPLAIN_POWER = "i_client_needed_complain_power",
|
||||||
|
B_CLIENT_COMPLAIN_LIST = "b_client_complain_list",
|
||||||
|
B_CLIENT_COMPLAIN_DELETE_OWN = "b_client_complain_delete_own",
|
||||||
|
B_CLIENT_COMPLAIN_DELETE = "b_client_complain_delete",
|
||||||
|
B_CLIENT_BAN_LIST = "b_client_ban_list",
|
||||||
|
B_CLIENT_BAN_LIST_GLOBAL = "b_client_ban_list_global",
|
||||||
|
B_CLIENT_BAN_TRIGGER_LIST = "b_client_ban_trigger_list",
|
||||||
|
B_CLIENT_BAN_CREATE = "b_client_ban_create",
|
||||||
|
B_CLIENT_BAN_CREATE_GLOBAL = "b_client_ban_create_global",
|
||||||
|
B_CLIENT_BAN_NAME = "b_client_ban_name",
|
||||||
|
B_CLIENT_BAN_IP = "b_client_ban_ip",
|
||||||
|
B_CLIENT_BAN_HWID = "b_client_ban_hwid",
|
||||||
|
B_CLIENT_BAN_EDIT = "b_client_ban_edit",
|
||||||
|
B_CLIENT_BAN_EDIT_GLOBAL = "b_client_ban_edit_global",
|
||||||
|
B_CLIENT_BAN_DELETE_OWN = "b_client_ban_delete_own",
|
||||||
|
B_CLIENT_BAN_DELETE = "b_client_ban_delete",
|
||||||
|
B_CLIENT_BAN_DELETE_OWN_GLOBAL = "b_client_ban_delete_own_global",
|
||||||
|
B_CLIENT_BAN_DELETE_GLOBAL = "b_client_ban_delete_global",
|
||||||
|
I_CLIENT_BAN_MAX_BANTIME = "i_client_ban_max_bantime",
|
||||||
|
I_CLIENT_PRIVATE_TEXTMESSAGE_POWER = "i_client_private_textmessage_power",
|
||||||
|
I_CLIENT_NEEDED_PRIVATE_TEXTMESSAGE_POWER = "i_client_needed_private_textmessage_power",
|
||||||
|
B_CLIENT_EVEN_TEXTMESSAGE_SEND = "b_client_even_textmessage_send",
|
||||||
|
B_CLIENT_SERVER_TEXTMESSAGE_SEND = "b_client_server_textmessage_send",
|
||||||
|
B_CLIENT_CHANNEL_TEXTMESSAGE_SEND = "b_client_channel_textmessage_send",
|
||||||
|
B_CLIENT_OFFLINE_TEXTMESSAGE_SEND = "b_client_offline_textmessage_send",
|
||||||
|
I_CLIENT_TALK_POWER = "i_client_talk_power",
|
||||||
|
I_CLIENT_NEEDED_TALK_POWER = "i_client_needed_talk_power",
|
||||||
|
I_CLIENT_POKE_POWER = "i_client_poke_power",
|
||||||
|
I_CLIENT_NEEDED_POKE_POWER = "i_client_needed_poke_power",
|
||||||
|
B_CLIENT_SET_FLAG_TALKER = "b_client_set_flag_talker",
|
||||||
|
I_CLIENT_WHISPER_POWER = "i_client_whisper_power",
|
||||||
|
I_CLIENT_NEEDED_WHISPER_POWER = "i_client_needed_whisper_power",
|
||||||
|
B_CLIENT_MODIFY_DESCRIPTION = "b_client_modify_description",
|
||||||
|
B_CLIENT_MODIFY_OWN_DESCRIPTION = "b_client_modify_own_description",
|
||||||
|
B_CLIENT_USE_BBCODE_ANY = "b_client_use_bbcode_any",
|
||||||
|
B_CLIENT_USE_BBCODE_URL = "b_client_use_bbcode_url",
|
||||||
|
B_CLIENT_USE_BBCODE_IMAGE = "b_client_use_bbcode_image",
|
||||||
|
B_CLIENT_MODIFY_DBPROPERTIES = "b_client_modify_dbproperties",
|
||||||
|
B_CLIENT_DELETE_DBPROPERTIES = "b_client_delete_dbproperties",
|
||||||
|
B_CLIENT_CREATE_MODIFY_SERVERQUERY_LOGIN = "b_client_create_modify_serverquery_login",
|
||||||
|
B_CLIENT_QUERY_CREATE = "b_client_query_create",
|
||||||
|
B_CLIENT_QUERY_LIST = "b_client_query_list",
|
||||||
|
B_CLIENT_QUERY_LIST_OWN = "b_client_query_list_own",
|
||||||
|
B_CLIENT_QUERY_RENAME = "b_client_query_rename",
|
||||||
|
B_CLIENT_QUERY_RENAME_OWN = "b_client_query_rename_own",
|
||||||
|
B_CLIENT_QUERY_CHANGE_PASSWORD = "b_client_query_change_password",
|
||||||
|
B_CLIENT_QUERY_CHANGE_OWN_PASSWORD = "b_client_query_change_own_password",
|
||||||
|
B_CLIENT_QUERY_CHANGE_PASSWORD_GLOBAL = "b_client_query_change_password_global",
|
||||||
|
B_CLIENT_QUERY_DELETE = "b_client_query_delete",
|
||||||
|
B_CLIENT_QUERY_DELETE_OWN = "b_client_query_delete_own",
|
||||||
|
B_FT_IGNORE_PASSWORD = "b_ft_ignore_password",
|
||||||
|
B_FT_TRANSFER_LIST = "b_ft_transfer_list",
|
||||||
|
I_FT_FILE_UPLOAD_POWER = "i_ft_file_upload_power",
|
||||||
|
I_FT_NEEDED_FILE_UPLOAD_POWER = "i_ft_needed_file_upload_power",
|
||||||
|
I_FT_FILE_DOWNLOAD_POWER = "i_ft_file_download_power",
|
||||||
|
I_FT_NEEDED_FILE_DOWNLOAD_POWER = "i_ft_needed_file_download_power",
|
||||||
|
I_FT_FILE_DELETE_POWER = "i_ft_file_delete_power",
|
||||||
|
I_FT_NEEDED_FILE_DELETE_POWER = "i_ft_needed_file_delete_power",
|
||||||
|
I_FT_FILE_RENAME_POWER = "i_ft_file_rename_power",
|
||||||
|
I_FT_NEEDED_FILE_RENAME_POWER = "i_ft_needed_file_rename_power",
|
||||||
|
I_FT_FILE_BROWSE_POWER = "i_ft_file_browse_power",
|
||||||
|
I_FT_NEEDED_FILE_BROWSE_POWER = "i_ft_needed_file_browse_power",
|
||||||
|
I_FT_DIRECTORY_CREATE_POWER = "i_ft_directory_create_power",
|
||||||
|
I_FT_NEEDED_DIRECTORY_CREATE_POWER = "i_ft_needed_directory_create_power",
|
||||||
|
I_FT_QUOTA_MB_DOWNLOAD_PER_CLIENT = "i_ft_quota_mb_download_per_client",
|
||||||
|
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
|
||||||
|
}
|
||||||
|
export default PermissionType;
|
|
@ -1,251 +1,257 @@
|
||||||
namespace profiles {
|
import {decode_identity, IdentitifyType, Identity} from "tc-shared/profiles/Identity";
|
||||||
export class ConnectionProfile {
|
import {guid} from "tc-shared/crypto/uid";
|
||||||
id: string;
|
import {TeaForumIdentity} from "tc-shared/profiles/identities/TeaForumIdentity";
|
||||||
|
import {TeaSpeakIdentity} from "tc-shared/profiles/identities/TeamSpeakIdentity";
|
||||||
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
|
||||||
profile_name: string;
|
export class ConnectionProfile {
|
||||||
default_username: string;
|
id: string;
|
||||||
default_password: string;
|
|
||||||
|
|
||||||
selected_identity_type: string = "unset";
|
profile_name: string;
|
||||||
identities: { [key: string]: identities.Identity } = {};
|
default_username: string;
|
||||||
|
default_password: string;
|
||||||
|
|
||||||
constructor(id: string) {
|
selected_identity_type: string = "unset";
|
||||||
this.id = id;
|
identities: { [key: string]: Identity } = {};
|
||||||
}
|
|
||||||
|
|
||||||
connect_username(): string {
|
constructor(id: string) {
|
||||||
if (this.default_username && this.default_username !== "Another TeaSpeak user")
|
this.id = id;
|
||||||
return this.default_username;
|
}
|
||||||
|
|
||||||
let selected = this.selected_identity();
|
connect_username(): string {
|
||||||
let name = selected ? selected.fallback_name() : undefined;
|
if (this.default_username && this.default_username !== "Another TeaSpeak user")
|
||||||
return name || "Another TeaSpeak user";
|
return this.default_username;
|
||||||
}
|
|
||||||
|
|
||||||
selected_identity(current_type?: identities.IdentitifyType): identities.Identity {
|
let selected = this.selected_identity();
|
||||||
if (!current_type)
|
let name = selected ? selected.fallback_name() : undefined;
|
||||||
current_type = this.selected_type();
|
return name || "Another TeaSpeak user";
|
||||||
|
}
|
||||||
|
|
||||||
if (current_type === undefined)
|
selected_identity(current_type?: IdentitifyType): Identity {
|
||||||
return undefined;
|
if (!current_type)
|
||||||
|
current_type = this.selected_type();
|
||||||
if (current_type == identities.IdentitifyType.TEAFORO) {
|
|
||||||
return identities.static_forum_identity();
|
|
||||||
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
|
|
||||||
return this.identities[identities.IdentitifyType[current_type].toLowerCase()];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (current_type === undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
|
if (current_type == IdentitifyType.TEAFORO) {
|
||||||
|
return TeaForumIdentity.identity();
|
||||||
|
} else if (current_type == IdentitifyType.TEAMSPEAK || current_type == IdentitifyType.NICKNAME) {
|
||||||
|
return this.identities[IdentitifyType[current_type].toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
selected_type?(): identities.IdentitifyType {
|
return undefined;
|
||||||
return this.selected_identity_type ? identities.IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_identity(type: identities.IdentitifyType, identity: identities.Identity) {
|
|
||||||
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
|
|
||||||
const identity = this.selected_identity();
|
|
||||||
if (!identity)
|
|
||||||
return undefined;
|
|
||||||
return identity.spawn_identity_handshake_handler(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
encode?(): string {
|
|
||||||
const identity_data = {};
|
|
||||||
for (const key in this.identities)
|
|
||||||
if (this.identities[key])
|
|
||||||
identity_data[key] = this.identities[key].encode();
|
|
||||||
|
|
||||||
return JSON.stringify({
|
|
||||||
version: 1,
|
|
||||||
username: this.default_username,
|
|
||||||
password: this.default_password,
|
|
||||||
profile_name: this.profile_name,
|
|
||||||
identity_type: this.selected_identity_type,
|
|
||||||
identity_data: identity_data,
|
|
||||||
id: this.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
valid(): boolean {
|
|
||||||
const identity = this.selected_identity();
|
|
||||||
if (!identity || !identity.valid()) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decode_profile(data): Promise<ConnectionProfile | string> {
|
selected_type?(): IdentitifyType {
|
||||||
data = JSON.parse(data);
|
return this.selected_identity_type ? IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
|
||||||
if (data.version !== 1)
|
|
||||||
return "invalid version";
|
|
||||||
|
|
||||||
const result: ConnectionProfile = new ConnectionProfile(data.id);
|
|
||||||
result.default_username = data.username;
|
|
||||||
result.default_password = data.password;
|
|
||||||
result.profile_name = data.profile_name;
|
|
||||||
result.selected_identity_type = (data.identity_type || "").toLowerCase();
|
|
||||||
|
|
||||||
if (data.identity_data) {
|
|
||||||
for (const key in data.identity_data) {
|
|
||||||
const type = identities.IdentitifyType[key.toUpperCase() as string];
|
|
||||||
const _data = data.identity_data[key];
|
|
||||||
if (type == undefined) continue;
|
|
||||||
|
|
||||||
const identity = await identities.decode_identity(type, _data);
|
|
||||||
if (identity == undefined) continue;
|
|
||||||
|
|
||||||
result.identities[key.toLowerCase()] = identity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProfilesData {
|
set_identity(type: IdentitifyType, identity: Identity) {
|
||||||
version: number;
|
this.identities[IdentitifyType[type].toLowerCase()] = identity;
|
||||||
profiles: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let available_profiles: ConnectionProfile[] = [];
|
spawn_identity_handshake_handler?(connection: AbstractServerConnection): HandshakeIdentityHandler {
|
||||||
|
const identity = this.selected_identity();
|
||||||
export async function load() {
|
if (!identity)
|
||||||
available_profiles = [];
|
return undefined;
|
||||||
|
return identity.spawn_identity_handshake_handler(connection);
|
||||||
const profiles_json = localStorage.getItem("profiles");
|
|
||||||
let profiles_data: ProfilesData = (() => {
|
|
||||||
try {
|
|
||||||
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
|
||||||
} catch (error) {
|
|
||||||
debugger;
|
|
||||||
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
|
|
||||||
createErrorModal(tr("Profile data invalid"), MessageHelper.formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
|
|
||||||
return {version: 0};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (profiles_data.version === 0) {
|
|
||||||
profiles_data = {
|
|
||||||
version: 1,
|
|
||||||
profiles: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (profiles_data.version == 1) {
|
|
||||||
for (const profile_data of profiles_data.profiles) {
|
|
||||||
const profile = await decode_profile(profile_data);
|
|
||||||
if (typeof (profile) === 'string') {
|
|
||||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
available_profiles.push(profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!find_profile("default")) { //Create a default profile and teaforo profile
|
|
||||||
{
|
|
||||||
const profile = create_new_profile("default", "default");
|
|
||||||
profile.default_password = "";
|
|
||||||
profile.default_username = "";
|
|
||||||
profile.profile_name = "Default Profile";
|
|
||||||
|
|
||||||
/* generate default identity */
|
|
||||||
try {
|
|
||||||
const identity = await identities.TeaSpeakIdentity.generate_new();
|
|
||||||
let active = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
active = false;
|
|
||||||
}, 1000);
|
|
||||||
await identity.improve_level(8, 1, () => active);
|
|
||||||
profile.set_identity(identities.IdentitifyType.TEAMSPEAK, identity);
|
|
||||||
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAMSPEAK];
|
|
||||||
} catch (error) {
|
|
||||||
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ /* forum identity (works only when connected to the forum) */
|
|
||||||
const profile = create_new_profile("TeaSpeak Forum", "teaforo");
|
|
||||||
profile.default_password = "";
|
|
||||||
profile.default_username = "";
|
|
||||||
profile.profile_name = "TeaSpeak Forum profile";
|
|
||||||
|
|
||||||
profile.set_identity(identities.IdentitifyType.TEAFORO, identities.static_forum_identity());
|
|
||||||
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAFORO];
|
|
||||||
}
|
|
||||||
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function create_new_profile(name: string, id?: string): ConnectionProfile {
|
encode?(): string {
|
||||||
const profile = new ConnectionProfile(id || guid());
|
const identity_data = {};
|
||||||
profile.profile_name = name;
|
for (const key in this.identities)
|
||||||
profile.default_username = "";
|
if (this.identities[key])
|
||||||
available_profiles.push(profile);
|
identity_data[key] = this.identities[key].encode();
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _requires_save = false;
|
return JSON.stringify({
|
||||||
|
|
||||||
export function save() {
|
|
||||||
const profiles: string[] = [];
|
|
||||||
for (const profile of available_profiles)
|
|
||||||
profiles.push(profile.encode());
|
|
||||||
|
|
||||||
const data = JSON.stringify({
|
|
||||||
version: 1,
|
version: 1,
|
||||||
profiles: profiles
|
username: this.default_username,
|
||||||
|
password: this.default_password,
|
||||||
|
profile_name: this.profile_name,
|
||||||
|
identity_type: this.selected_identity_type,
|
||||||
|
identity_data: identity_data,
|
||||||
|
id: this.id
|
||||||
});
|
});
|
||||||
localStorage.setItem("profiles", data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mark_need_save() {
|
valid(): boolean {
|
||||||
_requires_save = true;
|
const identity = this.selected_identity();
|
||||||
|
|
||||||
|
return !!identity && identity.valid();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function requires_save(): boolean {
|
async function decode_profile(data): Promise<ConnectionProfile | string> {
|
||||||
return _requires_save;
|
data = JSON.parse(data);
|
||||||
}
|
if (data.version !== 1)
|
||||||
|
return "invalid version";
|
||||||
|
|
||||||
export function profiles(): ConnectionProfile[] {
|
const result: ConnectionProfile = new ConnectionProfile(data.id);
|
||||||
return available_profiles;
|
result.default_username = data.username;
|
||||||
}
|
result.default_password = data.password;
|
||||||
|
result.profile_name = data.profile_name;
|
||||||
|
result.selected_identity_type = (data.identity_type || "").toLowerCase();
|
||||||
|
|
||||||
export function find_profile(id: string): ConnectionProfile | undefined {
|
if (data.identity_data) {
|
||||||
for (const profile of profiles())
|
for (const key of Object.keys(data.identity_data)) {
|
||||||
if (profile.id == id)
|
const type = IdentitifyType[key.toUpperCase() as string];
|
||||||
return profile;
|
const _data = data.identity_data[key];
|
||||||
|
if (type == undefined) continue;
|
||||||
|
|
||||||
return undefined;
|
const identity = await decode_identity(type, _data);
|
||||||
}
|
if (identity == undefined) continue;
|
||||||
|
|
||||||
export function find_profile_by_name(name: string): ConnectionProfile | undefined {
|
result.identities[key.toLowerCase()] = identity;
|
||||||
name = name.toLowerCase();
|
|
||||||
for (const profile of profiles())
|
|
||||||
if ((profile.profile_name || "").toLowerCase() == name)
|
|
||||||
return profile;
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function default_profile(): ConnectionProfile {
|
|
||||||
return find_profile("default");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_default_profile(profile: ConnectionProfile) {
|
|
||||||
const old_default = default_profile();
|
|
||||||
if (old_default && old_default != profile) {
|
|
||||||
old_default.id = guid();
|
|
||||||
}
|
}
|
||||||
profile.id = "default";
|
|
||||||
return old_default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delete_profile(profile: ConnectionProfile) {
|
return result;
|
||||||
available_profiles.remove(profile);
|
}
|
||||||
|
|
||||||
|
interface ProfilesData {
|
||||||
|
version: number;
|
||||||
|
profiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let available_profiles: ConnectionProfile[] = [];
|
||||||
|
|
||||||
|
export async function load() {
|
||||||
|
available_profiles = [];
|
||||||
|
|
||||||
|
const profiles_json = localStorage.getItem("profiles");
|
||||||
|
let profiles_data: ProfilesData = (() => {
|
||||||
|
try {
|
||||||
|
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
||||||
|
} catch (error) {
|
||||||
|
debugger;
|
||||||
|
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
|
||||||
|
createErrorModal(tr("Profile data invalid"), formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
|
||||||
|
return {version: 0};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (profiles_data.version === 0) {
|
||||||
|
profiles_data = {
|
||||||
|
version: 1,
|
||||||
|
profiles: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
if (profiles_data.version == 1) {
|
||||||
|
for (const profile_data of profiles_data.profiles) {
|
||||||
|
const profile = await decode_profile(profile_data);
|
||||||
|
if (typeof (profile) === 'string') {
|
||||||
|
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
available_profiles.push(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!find_profile("default")) { //Create a default profile and teaforo profile
|
||||||
|
{
|
||||||
|
const profile = create_new_profile("default", "default");
|
||||||
|
profile.default_password = "";
|
||||||
|
profile.default_username = "";
|
||||||
|
profile.profile_name = "Default Profile";
|
||||||
|
|
||||||
|
/* generate default identity */
|
||||||
|
try {
|
||||||
|
const identity = await TeaSpeakIdentity.generate_new();
|
||||||
|
let active = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
active = false;
|
||||||
|
}, 1000);
|
||||||
|
await identity.improve_level(8, 1, () => active);
|
||||||
|
profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
|
||||||
|
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAMSPEAK];
|
||||||
|
} catch (error) {
|
||||||
|
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ /* forum identity (works only when connected to the forum) */
|
||||||
|
const profile = create_new_profile("TeaSpeak Forum", "teaforo");
|
||||||
|
profile.default_password = "";
|
||||||
|
profile.default_username = "";
|
||||||
|
profile.profile_name = "TeaSpeak Forum profile";
|
||||||
|
|
||||||
|
profile.set_identity(IdentitifyType.TEAFORO, TeaForumIdentity.identity());
|
||||||
|
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAFORO];
|
||||||
|
}
|
||||||
|
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create_new_profile(name: string, id?: string): ConnectionProfile {
|
||||||
|
const profile = new ConnectionProfile(id || guid());
|
||||||
|
profile.profile_name = name;
|
||||||
|
profile.default_username = "";
|
||||||
|
available_profiles.push(profile);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _requires_save = false;
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
const profiles: string[] = [];
|
||||||
|
for (const profile of available_profiles)
|
||||||
|
profiles.push(profile.encode());
|
||||||
|
|
||||||
|
const data = JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
profiles: profiles
|
||||||
|
});
|
||||||
|
localStorage.setItem("profiles", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mark_need_save() {
|
||||||
|
_requires_save = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requires_save(): boolean {
|
||||||
|
return _requires_save;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function profiles(): ConnectionProfile[] {
|
||||||
|
return available_profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function find_profile(id: string): ConnectionProfile | undefined {
|
||||||
|
for (const profile of profiles())
|
||||||
|
if (profile.id == id)
|
||||||
|
return profile;
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function find_profile_by_name(name: string): ConnectionProfile | undefined {
|
||||||
|
name = name.toLowerCase();
|
||||||
|
for (const profile of profiles())
|
||||||
|
if ((profile.profile_name || "").toLowerCase() == name)
|
||||||
|
return profile;
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function default_profile(): ConnectionProfile {
|
||||||
|
return find_profile("default");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set_default_profile(profile: ConnectionProfile) {
|
||||||
|
const old_default = default_profile();
|
||||||
|
if (old_default && old_default != profile) {
|
||||||
|
old_default.id = guid();
|
||||||
|
}
|
||||||
|
profile.id = "default";
|
||||||
|
return old_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delete_profile(profile: ConnectionProfile) {
|
||||||
|
available_profiles.remove(profile);
|
||||||
}
|
}
|
|
@ -1,110 +1,119 @@
|
||||||
namespace profiles.identities {
|
import {AbstractServerConnection, ServerCommand} from "tc-shared/connection/ConnectionBase";
|
||||||
export enum IdentitifyType {
|
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
TEAFORO,
|
import {AbstractCommandHandler} from "tc-shared/connection/AbstractCommandHandler";
|
||||||
TEAMSPEAK,
|
|
||||||
NICKNAME
|
export enum IdentitifyType {
|
||||||
|
TEAFORO,
|
||||||
|
TEAMSPEAK,
|
||||||
|
NICKNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Identity {
|
||||||
|
fallback_name(): string | undefined ;
|
||||||
|
uid() : string;
|
||||||
|
type() : IdentitifyType;
|
||||||
|
|
||||||
|
valid() : boolean;
|
||||||
|
|
||||||
|
encode?() : string;
|
||||||
|
decode(data: string) : Promise<void>;
|
||||||
|
|
||||||
|
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* avoid circular dependencies here */
|
||||||
|
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
|
||||||
|
let identity: Identity;
|
||||||
|
switch (type) {
|
||||||
|
case IdentitifyType.NICKNAME:
|
||||||
|
const nidentity = require("tc-shared/profiles/identities/NameIdentity");
|
||||||
|
identity = new nidentity.NameIdentity();
|
||||||
|
break;
|
||||||
|
case IdentitifyType.TEAFORO:
|
||||||
|
const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
|
||||||
|
identity = new fidentity.TeaForumIdentity(undefined);
|
||||||
|
break;
|
||||||
|
case IdentitifyType.TEAMSPEAK:
|
||||||
|
const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
|
||||||
|
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!identity)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await identity.decode(data)
|
||||||
|
} catch(error) {
|
||||||
|
/* todo better error handling! */
|
||||||
|
console.error(error);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Identity {
|
return identity;
|
||||||
fallback_name(): string | undefined ;
|
}
|
||||||
uid() : string;
|
|
||||||
type() : IdentitifyType;
|
|
||||||
|
|
||||||
valid() : boolean;
|
export function create_identity(type: IdentitifyType) {
|
||||||
|
let identity: Identity;
|
||||||
|
switch (type) {
|
||||||
|
case IdentitifyType.NICKNAME:
|
||||||
|
const nidentity = require("tc-shared/profiles/identities/NameIdentity");
|
||||||
|
identity = new nidentity.NameIdentity();
|
||||||
|
break;
|
||||||
|
case IdentitifyType.TEAFORO:
|
||||||
|
const fidentity = require("tc-shared/profiles/identities/TeaForumIdentity");
|
||||||
|
identity = new fidentity.TeaForumIdentity(undefined);
|
||||||
|
break;
|
||||||
|
case IdentitifyType.TEAMSPEAK:
|
||||||
|
const tidentity = require("tc-shared/profiles/identities/TeamSpeakIdentity");
|
||||||
|
identity = new tidentity.TeaSpeakIdentity(undefined, undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
encode?() : string;
|
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends AbstractCommandHandler {
|
||||||
decode(data: string) : Promise<void>;
|
readonly handle: T;
|
||||||
|
|
||||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler;
|
constructor(connection: AbstractServerConnection, handle: T) {
|
||||||
|
super(connection);
|
||||||
|
this.handle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decode_identity(type: IdentitifyType, data: string) : Promise<Identity> {
|
|
||||||
let identity: Identity;
|
|
||||||
switch (type) {
|
|
||||||
case IdentitifyType.NICKNAME:
|
|
||||||
identity = new NameIdentity();
|
|
||||||
break;
|
|
||||||
case IdentitifyType.TEAFORO:
|
|
||||||
identity = new TeaForumIdentity(undefined);
|
|
||||||
break;
|
|
||||||
case IdentitifyType.TEAMSPEAK:
|
|
||||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(!identity)
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
try {
|
handle_command(command: ServerCommand): boolean {
|
||||||
await identity.decode(data)
|
if($.isFunction(this[command.command]))
|
||||||
} catch(error) {
|
this[command.command](command.arguments);
|
||||||
/* todo better error handling! */
|
else if(command.command == "error") {
|
||||||
console.error(error);
|
return false;
|
||||||
return undefined;
|
} else {
|
||||||
|
console.warn(tr("Received unknown command while handshaking (%o)"), command);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return identity;
|
export abstract class AbstractHandshakeIdentityHandler implements HandshakeIdentityHandler {
|
||||||
|
connection: AbstractServerConnection;
|
||||||
|
|
||||||
|
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
|
||||||
|
|
||||||
|
protected constructor(connection: AbstractServerConnection) {
|
||||||
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function create_identity(type: IdentitifyType) {
|
register_callback(callback: (success: boolean, message?: string) => any) {
|
||||||
let identity: Identity;
|
this.callbacks.push(callback);
|
||||||
switch (type) {
|
|
||||||
case IdentitifyType.NICKNAME:
|
|
||||||
identity = new NameIdentity();
|
|
||||||
break;
|
|
||||||
case IdentitifyType.TEAFORO:
|
|
||||||
identity = new TeaForumIdentity(undefined);
|
|
||||||
break;
|
|
||||||
case IdentitifyType.TEAMSPEAK:
|
|
||||||
identity = new TeaSpeakIdentity(undefined, undefined);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends connection.AbstractCommandHandler {
|
abstract start_handshake();
|
||||||
readonly handle: T;
|
|
||||||
|
|
||||||
constructor(connection: connection.AbstractServerConnection, handle: T) {
|
protected trigger_success() {
|
||||||
super(connection);
|
for(const callback of this.callbacks)
|
||||||
this.handle = handle;
|
callback(true);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
|
||||||
if($.isFunction(this[command.command]))
|
|
||||||
this[command.command](command.arguments);
|
|
||||||
else if(command.command == "error") {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
console.warn(tr("Received unknown command while handshaking (%o)"), command);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
|
protected trigger_fail(message: string) {
|
||||||
connection: connection.AbstractServerConnection;
|
for(const callback of this.callbacks)
|
||||||
|
callback(false, message);
|
||||||
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
|
|
||||||
|
|
||||||
protected constructor(connection: connection.AbstractServerConnection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
register_callback(callback: (success: boolean, message?: string) => any) {
|
|
||||||
this.callbacks.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract start_handshake();
|
|
||||||
|
|
||||||
protected trigger_success() {
|
|
||||||
for(const callback of this.callbacks)
|
|
||||||
callback(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected trigger_fail(message: string) {
|
|
||||||
for(const callback of this.callbacks)
|
|
||||||
callback(false, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,88 +1,96 @@
|
||||||
/// <reference path="../Identity.ts" />
|
import {
|
||||||
|
AbstractHandshakeIdentityHandler,
|
||||||
|
HandshakeCommandHandler,
|
||||||
|
IdentitifyType,
|
||||||
|
Identity
|
||||||
|
} from "tc-shared/profiles/Identity";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
|
|
||||||
namespace profiles.identities {
|
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||||
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
readonly identity: NameIdentity;
|
||||||
readonly identity: NameIdentity;
|
handler: HandshakeCommandHandler<NameHandshakeHandler>;
|
||||||
handler: HandshakeCommandHandler<NameHandshakeHandler>;
|
|
||||||
|
|
||||||
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) {
|
constructor(connection: AbstractServerConnection, identity: NameIdentity) {
|
||||||
super(connection);
|
super(connection);
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
|
|
||||||
this.handler = new HandshakeCommandHandler(connection, this);
|
this.handler = new HandshakeCommandHandler(connection, this);
|
||||||
this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
|
this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
|
||||||
}
|
|
||||||
|
|
||||||
start_handshake() {
|
|
||||||
this.connection.command_handler_boss().register_handler(this.handler);
|
|
||||||
this.connection.send_command("handshakebegin", {
|
|
||||||
intention: 0,
|
|
||||||
authentication_method: this.identity.type(),
|
|
||||||
client_nickname: this.identity.name()
|
|
||||||
}).catch(error => {
|
|
||||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize name based handshake. Error: %o"), error);
|
|
||||||
if(error instanceof CommandResult)
|
|
||||||
error = error.extra_message || error.message;
|
|
||||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
|
||||||
}).then(() => this.trigger_success());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected trigger_fail(message: string) {
|
|
||||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
|
||||||
super.trigger_fail(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected trigger_success() {
|
|
||||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
|
||||||
super.trigger_success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NameIdentity implements Identity {
|
start_handshake() {
|
||||||
private _name: string;
|
this.connection.command_handler_boss().register_handler(this.handler);
|
||||||
|
this.connection.send_command("handshakebegin", {
|
||||||
|
intention: 0,
|
||||||
|
authentication_method: this.identity.type(),
|
||||||
|
client_nickname: this.identity.name()
|
||||||
|
}).catch(error => {
|
||||||
|
log.error(LogCategory.IDENTITIES, tr("Failed to initialize name based handshake. Error: %o"), error);
|
||||||
|
if(error instanceof CommandResult)
|
||||||
|
error = error.extra_message || error.message;
|
||||||
|
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||||
|
}).then(() => this.trigger_success());
|
||||||
|
}
|
||||||
|
|
||||||
constructor(name?: string) {
|
protected trigger_fail(message: string) {
|
||||||
this._name = name;
|
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||||
}
|
super.trigger_fail(message);
|
||||||
|
}
|
||||||
|
|
||||||
set_name(name: string) { this._name = name; }
|
protected trigger_success() {
|
||||||
|
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||||
|
super.trigger_success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
name() : string { return this._name; }
|
export class NameIdentity implements Identity {
|
||||||
|
private _name: string;
|
||||||
|
|
||||||
fallback_name(): string | undefined {
|
constructor(name?: string) {
|
||||||
return this._name;
|
this._name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
uid(): string {
|
set_name(name: string) { this._name = name; }
|
||||||
return btoa(this._name); //FIXME hash!
|
|
||||||
}
|
|
||||||
|
|
||||||
type(): IdentitifyType {
|
name() : string { return this._name; }
|
||||||
return IdentitifyType.NICKNAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
valid(): boolean {
|
fallback_name(): string | undefined {
|
||||||
return this._name != undefined && this._name.length >= 5;
|
return this._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
decode(data) : Promise<void> {
|
uid(): string {
|
||||||
data = JSON.parse(data);
|
return btoa(this._name); //FIXME hash!
|
||||||
if(data.version !== 1)
|
}
|
||||||
throw "invalid version";
|
|
||||||
|
|
||||||
this._name = data["name"];
|
type(): IdentitifyType {
|
||||||
return;
|
return IdentitifyType.NICKNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
encode?() : string {
|
valid(): boolean {
|
||||||
return JSON.stringify({
|
return this._name != undefined && this._name.length >= 5;
|
||||||
version: 1,
|
}
|
||||||
name: this._name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
|
decode(data) : Promise<void> {
|
||||||
return new NameHandshakeHandler(connection, this);
|
data = JSON.parse(data);
|
||||||
}
|
if(data.version !== 1)
|
||||||
|
throw "invalid version";
|
||||||
|
|
||||||
|
this._name = data["name"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode?() : string {
|
||||||
|
return JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
name: this._name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
|
||||||
|
return new NameHandshakeHandler(connection, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,122 +1,135 @@
|
||||||
/// <reference path="../Identity.ts" />
|
import {
|
||||||
|
AbstractHandshakeIdentityHandler,
|
||||||
|
HandshakeCommandHandler,
|
||||||
|
IdentitifyType,
|
||||||
|
Identity
|
||||||
|
} from "tc-shared/profiles/Identity";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {AbstractServerConnection} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import {HandshakeIdentityHandler} from "tc-shared/connection/HandshakeHandler";
|
||||||
|
import * as forum from "./teaspeak-forum";
|
||||||
|
|
||||||
namespace profiles.identities {
|
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||||
class TeaForumHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
readonly identity: TeaForumIdentity;
|
||||||
readonly identity: TeaForumIdentity;
|
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
|
||||||
handler: HandshakeCommandHandler<TeaForumHandshakeHandler>;
|
|
||||||
|
|
||||||
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.TeaForumIdentity) {
|
constructor(connection: AbstractServerConnection, identity: TeaForumIdentity) {
|
||||||
super(connection);
|
super(connection);
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
this.handler = new HandshakeCommandHandler(connection, this);
|
this.handler = new HandshakeCommandHandler(connection, this);
|
||||||
this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
this.handler["handshakeidentityproof"] = this.handle_proof.bind(this);
|
||||||
}
|
|
||||||
|
|
||||||
start_handshake() {
|
|
||||||
this.connection.command_handler_boss().register_handler(this.handler);
|
|
||||||
this.connection.send_command("handshakebegin", {
|
|
||||||
intention: 0,
|
|
||||||
authentication_method: this.identity.type(),
|
|
||||||
data: this.identity.data().data_json()
|
|
||||||
}).catch(error => {
|
|
||||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
|
|
||||||
|
|
||||||
if(error instanceof CommandResult)
|
|
||||||
error = error.extra_message || error.message;
|
|
||||||
this.trigger_fail("failed to execute begin (" + error + ")");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private handle_proof(json) {
|
|
||||||
this.connection.send_command("handshakeindentityproof", {
|
|
||||||
proof: this.identity.data().data_sign()
|
|
||||||
}).catch(error => {
|
|
||||||
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
|
|
||||||
|
|
||||||
if(error instanceof CommandResult)
|
|
||||||
error = error.extra_message || error.message;
|
|
||||||
this.trigger_fail("failed to execute proof (" + error + ")");
|
|
||||||
}).then(() => this.trigger_success());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected trigger_fail(message: string) {
|
|
||||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
|
||||||
super.trigger_fail(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected trigger_success() {
|
|
||||||
this.connection.command_handler_boss().unregister_handler(this.handler);
|
|
||||||
super.trigger_success();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeaForumIdentity implements Identity {
|
start_handshake() {
|
||||||
private readonly identity_data: forum.Data;
|
this.connection.command_handler_boss().register_handler(this.handler);
|
||||||
|
this.connection.send_command("handshakebegin", {
|
||||||
|
intention: 0,
|
||||||
|
authentication_method: this.identity.type(),
|
||||||
|
data: this.identity.data().data_json()
|
||||||
|
}).catch(error => {
|
||||||
|
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
|
||||||
|
|
||||||
valid() : boolean {
|
if(error instanceof CommandResult)
|
||||||
return !!this.identity_data && !this.identity_data.is_expired();
|
error = error.extra_message || error.message;
|
||||||
}
|
this.trigger_fail("failed to execute begin (" + error + ")");
|
||||||
|
});
|
||||||
constructor(data: forum.Data) {
|
|
||||||
this.identity_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
data() : forum.Data {
|
|
||||||
return this.identity_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
decode(data) : Promise<void> {
|
|
||||||
data = JSON.parse(data);
|
|
||||||
if(data.version !== 1)
|
|
||||||
throw "invalid version";
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
encode() : string {
|
|
||||||
return JSON.stringify({
|
|
||||||
version: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
|
|
||||||
return new TeaForumHandshakeHandler(connection, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
fallback_name(): string | undefined {
|
|
||||||
return this.identity_data ? this.identity_data.name() : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
type(): profiles.identities.IdentitifyType {
|
|
||||||
return IdentitifyType.TEAFORO;
|
|
||||||
}
|
|
||||||
|
|
||||||
uid(): string {
|
|
||||||
//FIXME: Real UID!
|
|
||||||
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let static_identity: TeaForumIdentity;
|
|
||||||
|
|
||||||
export function set_static_identity(identity: TeaForumIdentity) {
|
private handle_proof(json) {
|
||||||
static_identity = identity;
|
this.connection.send_command("handshakeindentityproof", {
|
||||||
|
proof: this.identity.data().data_sign()
|
||||||
|
}).catch(error => {
|
||||||
|
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
|
||||||
|
|
||||||
|
if(error instanceof CommandResult)
|
||||||
|
error = error.extra_message || error.message;
|
||||||
|
this.trigger_fail("failed to execute proof (" + error + ")");
|
||||||
|
}).then(() => this.trigger_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update_forum() {
|
protected trigger_fail(message: string) {
|
||||||
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
|
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||||
static_identity = new TeaForumIdentity(forum.data());
|
super.trigger_fail(message);
|
||||||
} else {
|
|
||||||
static_identity = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function valid_static_forum_identity() : boolean {
|
protected trigger_success() {
|
||||||
return static_identity && static_identity.valid();
|
this.connection.command_handler_boss().unregister_handler(this.handler);
|
||||||
|
super.trigger_success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TeaForumIdentity implements Identity {
|
||||||
|
private readonly identity_data: forum.Data;
|
||||||
|
|
||||||
|
valid() : boolean {
|
||||||
|
return !!this.identity_data && !this.identity_data.is_expired();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function static_forum_identity() : TeaForumIdentity | undefined {
|
constructor(data: forum.Data) {
|
||||||
|
this.identity_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return this.identity_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
decode(data) : Promise<void> {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
if(data.version !== 1)
|
||||||
|
throw "invalid version";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode() : string {
|
||||||
|
return JSON.stringify({
|
||||||
|
version: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn_identity_handshake_handler(connection: AbstractServerConnection) : HandshakeIdentityHandler {
|
||||||
|
return new TeaForumHandshakeHandler(connection, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback_name(): string | undefined {
|
||||||
|
return this.identity_data ? this.identity_data.name() : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type(): IdentitifyType {
|
||||||
|
return IdentitifyType.TEAFORO;
|
||||||
|
}
|
||||||
|
|
||||||
|
uid(): string {
|
||||||
|
//FIXME: Real UID!
|
||||||
|
return "TeaForo#" + ((this.identity_data ? this.identity_data.name() : "Another TeaSpeak user"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static identity() {
|
||||||
return static_identity;
|
return static_identity;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let static_identity: TeaForumIdentity;
|
||||||
|
|
||||||
|
export function set_static_identity(identity: TeaForumIdentity) {
|
||||||
|
static_identity = identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update_forum() {
|
||||||
|
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
|
||||||
|
static_identity = new TeaForumIdentity(forum.data());
|
||||||
|
} else {
|
||||||
|
static_identity = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function valid_static_forum_identity() : boolean {
|
||||||
|
return static_identity && static_identity.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function static_forum_identity() : TeaForumIdentity | undefined {
|
||||||
|
return static_identity;
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,11 @@
|
||||||
interface Window {
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
grecaptcha: GReCaptcha;
|
import * as loader from "tc-loader";
|
||||||
|
import * as fidentity from "./TeaForumIdentity";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
grecaptcha: GReCaptcha;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GReCaptcha {
|
interface GReCaptcha {
|
||||||
|
@ -18,349 +24,347 @@ interface GReCaptcha {
|
||||||
reset(widget_id?: string);
|
reset(widget_id?: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace forum {
|
export namespace gcaptcha {
|
||||||
export namespace gcaptcha {
|
export async function initialize() {
|
||||||
export async function initialize() {
|
if(typeof(window.grecaptcha) === "undefined") {
|
||||||
if(typeof(window.grecaptcha) === "undefined") {
|
let script = document.createElement("script");
|
||||||
let script = document.createElement("script");
|
script.async = true;
|
||||||
script.async = true;
|
|
||||||
|
|
||||||
let timeout;
|
let timeout;
|
||||||
const callback_name = "captcha_callback_" + Math.random().toString().replace(".", "");
|
const callback_name = "captcha_callback_" + Math.random().toString().replace(".", "");
|
||||||
try {
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
script.onerror = reject;
|
|
||||||
window[callback_name] = resolve;
|
|
||||||
script.src = "https://www.google.com/recaptcha/api.js?onload=" + encodeURIComponent(callback_name) + "&render=explicit";
|
|
||||||
|
|
||||||
document.body.append(script);
|
|
||||||
timeout = setTimeout(() => reject("timeout"), 15000);
|
|
||||||
});
|
|
||||||
} catch(error) {
|
|
||||||
script.remove();
|
|
||||||
script = undefined;
|
|
||||||
|
|
||||||
console.error(tr("Failed to fetch recaptcha javascript source: %o"), error);
|
|
||||||
throw tr("failed to download source");
|
|
||||||
} finally {
|
|
||||||
if(script)
|
|
||||||
script.onerror = undefined;
|
|
||||||
delete window[callback_name];
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof(window.grecaptcha) === "undefined")
|
|
||||||
throw tr("failed to load recaptcha");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function spawn(container: JQuery, key: string, callback_data: (token: string) => any) {
|
|
||||||
try {
|
try {
|
||||||
await initialize();
|
await new Promise((resolve, reject) => {
|
||||||
|
script.onerror = reject;
|
||||||
|
window[callback_name] = resolve;
|
||||||
|
script.src = "https://www.google.com/recaptcha/api.js?onload=" + encodeURIComponent(callback_name) + "&render=explicit";
|
||||||
|
|
||||||
|
document.body.append(script);
|
||||||
|
timeout = setTimeout(() => reject("timeout"), 15000);
|
||||||
|
});
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.error(tr("Failed to initialize G-Recaptcha. Error: %o"), error);
|
script.remove();
|
||||||
throw tr("initialisation failed");
|
script = undefined;
|
||||||
}
|
|
||||||
if(container.attr("captcha-uuid"))
|
console.error(tr("Failed to fetch recaptcha javascript source: %o"), error);
|
||||||
window.grecaptcha.reset(container.attr("captcha-uuid"));
|
throw tr("failed to download source");
|
||||||
else {
|
} finally {
|
||||||
container.attr("captcha-uuid", window.grecaptcha.render(container[0], {
|
if(script)
|
||||||
"sitekey": key,
|
script.onerror = undefined;
|
||||||
callback: callback_data
|
delete window[callback_name];
|
||||||
}));
|
timeout && clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(typeof(window.grecaptcha) === "undefined")
|
||||||
|
throw tr("failed to load recaptcha");
|
||||||
}
|
}
|
||||||
|
|
||||||
function api_url() {
|
export async function spawn(container: JQuery, key: string, callback_data: (token: string) => any) {
|
||||||
return settings.static_global(Settings.KEY_TEAFORO_URL);
|
try {
|
||||||
}
|
await initialize();
|
||||||
|
} catch(error) {
|
||||||
export class Data {
|
console.error(tr("Failed to initialize G-Recaptcha. Error: %o"), error);
|
||||||
readonly auth_key: string;
|
throw tr("initialisation failed");
|
||||||
readonly raw: string;
|
}
|
||||||
readonly sign: string;
|
if(container.attr("captcha-uuid"))
|
||||||
|
window.grecaptcha.reset(container.attr("captcha-uuid"));
|
||||||
parsed: {
|
else {
|
||||||
user_id: number;
|
container.attr("captcha-uuid", window.grecaptcha.render(container[0], {
|
||||||
user_name: string;
|
"sitekey": key,
|
||||||
|
callback: callback_data
|
||||||
data_age: number;
|
}));
|
||||||
|
|
||||||
user_group_id: number;
|
|
||||||
|
|
||||||
is_staff: boolean;
|
|
||||||
user_groups: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(auth: string, raw: string, sign: string) {
|
|
||||||
this.auth_key = auth;
|
|
||||||
this.raw = raw;
|
|
||||||
this.sign = sign;
|
|
||||||
|
|
||||||
this.parsed = JSON.parse(raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data_json() : string { return this.raw; }
|
|
||||||
data_sign() : string { return this.sign; }
|
|
||||||
|
|
||||||
name() : string { return this.parsed.user_name; }
|
|
||||||
|
|
||||||
user_id() { return this.parsed.user_id; }
|
|
||||||
user_group() { return this.parsed.user_group_id; }
|
|
||||||
|
|
||||||
is_stuff() : boolean { return this.parsed.is_staff; }
|
|
||||||
is_premium() : boolean { return this.parsed.user_groups.indexOf(5) != -1; }
|
|
||||||
|
|
||||||
data_age() : Date { return new Date(this.parsed.data_age); }
|
|
||||||
|
|
||||||
is_expired() : boolean { return this.parsed.data_age + 48 * 60 * 60 * 1000 < Date.now(); }
|
|
||||||
should_renew() : boolean { return this.parsed.data_age + 24 * 60 * 60 * 1000 < Date.now(); } /* renew data all 24hrs */
|
|
||||||
}
|
}
|
||||||
let _data: Data | undefined;
|
}
|
||||||
|
|
||||||
export function logged_in() : boolean {
|
function api_url() {
|
||||||
return !!_data && !_data.is_expired();
|
return settings.static_global(Settings.KEY_TEAFORO_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Data {
|
||||||
|
readonly auth_key: string;
|
||||||
|
readonly raw: string;
|
||||||
|
readonly sign: string;
|
||||||
|
|
||||||
|
parsed: {
|
||||||
|
user_id: number;
|
||||||
|
user_name: string;
|
||||||
|
|
||||||
|
data_age: number;
|
||||||
|
|
||||||
|
user_group_id: number;
|
||||||
|
|
||||||
|
is_staff: boolean;
|
||||||
|
user_groups: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(auth: string, raw: string, sign: string) {
|
||||||
|
this.auth_key = auth;
|
||||||
|
this.raw = raw;
|
||||||
|
this.sign = sign;
|
||||||
|
|
||||||
|
this.parsed = JSON.parse(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function data() : Data { return _data; }
|
|
||||||
|
|
||||||
export interface LoginResult {
|
data_json() : string { return this.raw; }
|
||||||
status: "success" | "captcha" | "error";
|
data_sign() : string { return this.sign; }
|
||||||
|
|
||||||
error_message?: string;
|
name() : string { return this.parsed.user_name; }
|
||||||
captcha?: {
|
|
||||||
type: "gre-captcha" | "unknown";
|
user_id() { return this.parsed.user_id; }
|
||||||
data: any; /* in case of gre-captcha it would be the side key */
|
user_group() { return this.parsed.user_group_id; }
|
||||||
|
|
||||||
|
is_stuff() : boolean { return this.parsed.is_staff; }
|
||||||
|
is_premium() : boolean { return this.parsed.user_groups.indexOf(5) != -1; }
|
||||||
|
|
||||||
|
data_age() : Date { return new Date(this.parsed.data_age); }
|
||||||
|
|
||||||
|
is_expired() : boolean { return this.parsed.data_age + 48 * 60 * 60 * 1000 < Date.now(); }
|
||||||
|
should_renew() : boolean { return this.parsed.data_age + 24 * 60 * 60 * 1000 < Date.now(); } /* renew data all 24hrs */
|
||||||
|
}
|
||||||
|
let _data: Data | undefined;
|
||||||
|
|
||||||
|
export function logged_in() : boolean {
|
||||||
|
return !!_data && !_data.is_expired();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function data() : Data { return _data; }
|
||||||
|
|
||||||
|
export interface LoginResult {
|
||||||
|
status: "success" | "captcha" | "error";
|
||||||
|
|
||||||
|
error_message?: string;
|
||||||
|
captcha?: {
|
||||||
|
type: "gre-captcha" | "unknown";
|
||||||
|
data: any; /* in case of gre-captcha it would be the side key */
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(username: string, password: string, captcha?: any) : Promise<LoginResult> {
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await new Promise<any>((resolve, reject) => {
|
||||||
|
$.ajax({
|
||||||
|
url: api_url() + "?web-api/v1/login",
|
||||||
|
type: "POST",
|
||||||
|
cache: false,
|
||||||
|
data: {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
remember: true,
|
||||||
|
"g-recaptcha-response": captcha
|
||||||
|
},
|
||||||
|
|
||||||
|
crossDomain: true,
|
||||||
|
|
||||||
|
success: resolve,
|
||||||
|
error: (xhr, status, error) => {
|
||||||
|
console.log(tr("Login request failed %o: %o"), status, error);
|
||||||
|
reject(tr("request failed"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
error_message: tr("failed to send login request")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(username: string, password: string, captcha?: any) : Promise<LoginResult> {
|
if(response["status"] !== "ok") {
|
||||||
let response;
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
||||||
try {
|
return {
|
||||||
response = await new Promise<any>((resolve, reject) => {
|
status: "error",
|
||||||
$.ajax({
|
error_message: (response["errors"] || [])[0] || tr("Unknown error")
|
||||||
url: api_url() + "?web-api/v1/login",
|
};
|
||||||
type: "POST",
|
}
|
||||||
cache: false,
|
|
||||||
data: {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
remember: true,
|
|
||||||
"g-recaptcha-response": captcha
|
|
||||||
},
|
|
||||||
|
|
||||||
crossDomain: true,
|
if(!response["success"]) {
|
||||||
|
console.error(tr("Login failed. Response %o"), response);
|
||||||
|
|
||||||
success: resolve,
|
let message = tr("failed to login");
|
||||||
error: (xhr, status, error) => {
|
let captcha;
|
||||||
console.log(tr("Login request failed %o: %o"), status, error);
|
/* user/password wrong | and maybe captcha required */
|
||||||
reject(tr("request failed"));
|
if(response["code"] == 1 || response["code"] == 3)
|
||||||
}
|
message = tr("Invalid username or password");
|
||||||
})
|
if(response["code"] == 2 || response["code"] == 3) {
|
||||||
});
|
captcha = {
|
||||||
} catch(error) {
|
type: response["captcha"]["type"],
|
||||||
return {
|
data: response["captcha"]["siteKey"] //TODO: Why so static here?
|
||||||
status: "error",
|
|
||||||
error_message: tr("failed to send login request")
|
|
||||||
};
|
};
|
||||||
}
|
if(response["code"] == 2)
|
||||||
|
message = tr("captcha required");
|
||||||
if(response["status"] !== "ok") {
|
|
||||||
console.error(tr("Response status not okey. Error happend: %o"), response);
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
error_message: (response["errors"] || [])[0] || tr("Unknown error")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!response["success"]) {
|
|
||||||
console.error(tr("Login failed. Response %o"), response);
|
|
||||||
|
|
||||||
let message = tr("failed to login");
|
|
||||||
let captcha;
|
|
||||||
/* user/password wrong | and maybe captcha required */
|
|
||||||
if(response["code"] == 1 || response["code"] == 3)
|
|
||||||
message = tr("Invalid username or password");
|
|
||||||
if(response["code"] == 2 || response["code"] == 3) {
|
|
||||||
captcha = {
|
|
||||||
type: response["captcha"]["type"],
|
|
||||||
data: response["captcha"]["siteKey"] //TODO: Why so static here?
|
|
||||||
};
|
|
||||||
if(response["code"] == 2)
|
|
||||||
message = tr("captcha required");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: typeof(captcha) !== "undefined" ? "captcha" : "error",
|
|
||||||
error_message: message,
|
|
||||||
captcha: captcha
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//document.cookie = "user_data=" + response["data"] + ";path=/";
|
|
||||||
//document.cookie = "user_sign=" + response["sign"] + ";path=/";
|
|
||||||
|
|
||||||
try {
|
|
||||||
_data = new Data(response["auth-key"], response["data"], response["sign"]);
|
|
||||||
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
|
||||||
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
|
||||||
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
|
|
||||||
profiles.identities.update_forum();
|
|
||||||
} catch(error) {
|
|
||||||
console.error(tr("Failed to parse forum given data: %o"), error);
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
error_message: tr("Failed to parse response data")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "success"
|
status: typeof(captcha) !== "undefined" ? "captcha" : "error",
|
||||||
|
error_message: message,
|
||||||
|
captcha: captcha
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//document.cookie = "user_data=" + response["data"] + ";path=/";
|
||||||
|
//document.cookie = "user_sign=" + response["sign"] + ";path=/";
|
||||||
|
|
||||||
export async function renew_data() : Promise<"success" | "login-required"> {
|
try {
|
||||||
let response;
|
_data = new Data(response["auth-key"], response["data"], response["sign"]);
|
||||||
try {
|
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
||||||
response = await new Promise<any>((resolve, reject) => {
|
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
||||||
$.ajax({
|
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
|
||||||
url: api_url() + "?web-api/v1/renew-data",
|
fidentity.update_forum();
|
||||||
type: "GET",
|
} catch(error) {
|
||||||
cache: false,
|
console.error(tr("Failed to parse forum given data: %o"), error);
|
||||||
|
return {
|
||||||
crossDomain: true,
|
status: "error",
|
||||||
|
error_message: tr("Failed to parse response data")
|
||||||
data: {
|
|
||||||
"auth-key": _data.auth_key
|
|
||||||
},
|
|
||||||
|
|
||||||
success: resolve,
|
|
||||||
error: (xhr, status, error) => {
|
|
||||||
console.log(tr("Renew request failed %o: %o"), status, error);
|
|
||||||
reject(tr("request failed"));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} catch(error) {
|
|
||||||
throw tr("failed to send renew request");
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(response["status"] !== "ok") {
|
return {
|
||||||
console.error(tr("Response status not okey. Error happend: %o"), response);
|
status: "success"
|
||||||
throw (response["errors"] || [])[0] || tr("Unknown error");
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function renew_data() : Promise<"success" | "login-required"> {
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await new Promise<any>((resolve, reject) => {
|
||||||
|
$.ajax({
|
||||||
|
url: api_url() + "?web-api/v1/renew-data",
|
||||||
|
type: "GET",
|
||||||
|
cache: false,
|
||||||
|
|
||||||
|
crossDomain: true,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
"auth-key": _data.auth_key
|
||||||
|
},
|
||||||
|
|
||||||
|
success: resolve,
|
||||||
|
error: (xhr, status, error) => {
|
||||||
|
console.log(tr("Renew request failed %o: %o"), status, error);
|
||||||
|
reject(tr("request failed"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
throw tr("failed to send renew request");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response["status"] !== "ok") {
|
||||||
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
||||||
|
throw (response["errors"] || [])[0] || tr("Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!response["success"]) {
|
||||||
|
if(response["code"] == 1) {
|
||||||
|
return "login-required";
|
||||||
}
|
}
|
||||||
|
throw "invalid error code (" + response["code"] + ")";
|
||||||
|
}
|
||||||
|
if(!response["data"] || !response["sign"])
|
||||||
|
throw tr("response missing data");
|
||||||
|
|
||||||
if(!response["success"]) {
|
console.debug(tr("Renew succeeded. Parsing data."));
|
||||||
if(response["code"] == 1) {
|
|
||||||
return "login-required";
|
try {
|
||||||
}
|
_data = new Data(_data.auth_key, response["data"], response["sign"]);
|
||||||
|
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
||||||
|
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
||||||
|
fidentity.update_forum();
|
||||||
|
} catch(error) {
|
||||||
|
console.error(tr("Failed to parse forum given data: %o"), error);
|
||||||
|
throw tr("failed to parse data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() : Promise<void> {
|
||||||
|
if(!logged_in())
|
||||||
|
return;
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await new Promise<any>((resolve, reject) => {
|
||||||
|
$.ajax({
|
||||||
|
url: api_url() + "?web-api/v1/logout",
|
||||||
|
type: "GET",
|
||||||
|
cache: false,
|
||||||
|
|
||||||
|
crossDomain: true,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
"auth-key": _data.auth_key
|
||||||
|
},
|
||||||
|
|
||||||
|
success: resolve,
|
||||||
|
error: (xhr, status, error) => {
|
||||||
|
console.log(tr("Logout request failed %o: %o"), status, error);
|
||||||
|
reject(tr("request failed"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
throw tr("failed to send logout request");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response["status"] !== "ok") {
|
||||||
|
console.error(tr("Response status not okey. Error happend: %o"), response);
|
||||||
|
throw (response["errors"] || [])[0] || tr("Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!response["success"]) {
|
||||||
|
/* code 1 means not logged in, its an success */
|
||||||
|
if(response["code"] != 1) {
|
||||||
throw "invalid error code (" + response["code"] + ")";
|
throw "invalid error code (" + response["code"] + ")";
|
||||||
}
|
}
|
||||||
if(!response["data"] || !response["sign"])
|
|
||||||
throw tr("response missing data");
|
|
||||||
|
|
||||||
console.debug(tr("Renew succeeded. Parsing data."));
|
|
||||||
|
|
||||||
try {
|
|
||||||
_data = new Data(_data.auth_key, response["data"], response["sign"]);
|
|
||||||
localStorage.setItem("teaspeak-forum-data", response["data"]);
|
|
||||||
localStorage.setItem("teaspeak-forum-sign", response["sign"]);
|
|
||||||
profiles.identities.update_forum();
|
|
||||||
} catch(error) {
|
|
||||||
console.error(tr("Failed to parse forum given data: %o"), error);
|
|
||||||
throw tr("failed to parse data");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "success";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout() : Promise<void> {
|
_data = undefined;
|
||||||
if(!logged_in())
|
localStorage.removeItem("teaspeak-forum-data");
|
||||||
|
localStorage.removeItem("teaspeak-forum-sign");
|
||||||
|
localStorage.removeItem("teaspeak-forum-auth");
|
||||||
|
fidentity.update_forum();
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
name: "TeaForo initialize",
|
||||||
|
priority: 10,
|
||||||
|
function: async () => {
|
||||||
|
const raw_data = localStorage.getItem("teaspeak-forum-data");
|
||||||
|
const raw_sign = localStorage.getItem("teaspeak-forum-sign");
|
||||||
|
const forum_auth = localStorage.getItem("teaspeak-forum-auth");
|
||||||
|
if(!raw_data || !raw_sign || !forum_auth) {
|
||||||
|
console.log(tr("No TeaForo authentification found. TeaForo connection status: unconnected"));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let response;
|
|
||||||
try {
|
try {
|
||||||
response = await new Promise<any>((resolve, reject) => {
|
_data = new Data(forum_auth, raw_data, raw_sign);
|
||||||
$.ajax({
|
|
||||||
url: api_url() + "?web-api/v1/logout",
|
|
||||||
type: "GET",
|
|
||||||
cache: false,
|
|
||||||
|
|
||||||
crossDomain: true,
|
|
||||||
|
|
||||||
data: {
|
|
||||||
"auth-key": _data.auth_key
|
|
||||||
},
|
|
||||||
|
|
||||||
success: resolve,
|
|
||||||
error: (xhr, status, error) => {
|
|
||||||
console.log(tr("Logout request failed %o: %o"), status, error);
|
|
||||||
reject(tr("request failed"));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
throw tr("failed to send logout request");
|
console.error(tr("Failed to initialize TeaForo connection from local data. Error: %o"), error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(_data.should_renew()) {
|
||||||
|
console.info(tr("TeaForo data should be renewed. Executing renew."));
|
||||||
|
renew_data().then(status => {
|
||||||
|
if(status === "success") {
|
||||||
|
console.info(tr("TeaForo data has been successfully renewed."));
|
||||||
|
} else {
|
||||||
|
console.warn(tr("Failed to renew TeaForo data. New login required."));
|
||||||
|
localStorage.removeItem("teaspeak-forum-data");
|
||||||
|
localStorage.removeItem("teaspeak-forum-sign");
|
||||||
|
localStorage.removeItem("teaspeak-forum-auth");
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.warn(tr("Failed to renew TeaForo data. An error occurred: %o"), error);
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(response["status"] !== "ok") {
|
if(_data && _data.is_expired()) {
|
||||||
console.error(tr("Response status not okey. Error happend: %o"), response);
|
console.error(tr("TeaForo data is expired. TeaForo connection isn't available!"));
|
||||||
throw (response["errors"] || [])[0] || tr("Unknown error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!response["success"]) {
|
|
||||||
/* code 1 means not logged in, its an success */
|
|
||||||
if(response["code"] != 1) {
|
|
||||||
throw "invalid error code (" + response["code"] + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = undefined;
|
|
||||||
localStorage.removeItem("teaspeak-forum-data");
|
|
||||||
localStorage.removeItem("teaspeak-forum-sign");
|
|
||||||
localStorage.removeItem("teaspeak-forum-auth");
|
|
||||||
profiles.identities.update_forum();
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
||||||
name: "TeaForo initialize",
|
|
||||||
priority: 10,
|
|
||||||
function: async () => {
|
|
||||||
const raw_data = localStorage.getItem("teaspeak-forum-data");
|
|
||||||
const raw_sign = localStorage.getItem("teaspeak-forum-sign");
|
|
||||||
const forum_auth = localStorage.getItem("teaspeak-forum-auth");
|
|
||||||
if(!raw_data || !raw_sign || !forum_auth) {
|
|
||||||
console.log(tr("No TeaForo authentification found. TeaForo connection status: unconnected"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_data = new Data(forum_auth, raw_data, raw_sign);
|
|
||||||
} catch(error) {
|
|
||||||
console.error(tr("Failed to initialize TeaForo connection from local data. Error: %o"), error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(_data.should_renew()) {
|
|
||||||
console.info(tr("TeaForo data should be renewed. Executing renew."));
|
|
||||||
renew_data().then(status => {
|
|
||||||
if(status === "success") {
|
|
||||||
console.info(tr("TeaForo data has been successfully renewed."));
|
|
||||||
} else {
|
|
||||||
console.warn(tr("Failed to renew TeaForo data. New login required."));
|
|
||||||
localStorage.removeItem("teaspeak-forum-data");
|
|
||||||
localStorage.removeItem("teaspeak-forum-sign");
|
|
||||||
localStorage.removeItem("teaspeak-forum-auth");
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.warn(tr("Failed to renew TeaForo data. An error occurred: %o"), error);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_data && _data.is_expired()) {
|
|
||||||
console.error(tr("TeaForo data is expired. TeaForo connection isn't available!"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,45 +1,98 @@
|
||||||
//Used by CertAccept popup
|
//Used by CertAccept popup
|
||||||
|
|
||||||
interface Array<T> {
|
declare global {
|
||||||
remove(elem?: T): boolean;
|
interface Array<T> {
|
||||||
last?(): T;
|
remove(elem?: T): boolean;
|
||||||
|
last?(): T;
|
||||||
|
|
||||||
pop_front(): T | undefined;
|
pop_front(): T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_field_to<T>(object: T, value: any, field: string) : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type JQueryScrollType = "height" | "width";
|
||||||
|
interface JQuery<TElement = HTMLElement> {
|
||||||
|
render(values?: any) : string;
|
||||||
|
renderTag(values?: any) : JQuery<TElement>;
|
||||||
|
hasScrollBar(direction?: JQueryScrollType) : boolean;
|
||||||
|
|
||||||
|
|
||||||
|
visible_height() : number;
|
||||||
|
visible_width() : number;
|
||||||
|
|
||||||
|
/* bootstrap */
|
||||||
|
alert() : JQuery<TElement>;
|
||||||
|
modal(properties: any) : this;
|
||||||
|
bootstrapMaterialDesign() : this;
|
||||||
|
|
||||||
|
/* first element which matches the selector, could be the element itself or a parent */
|
||||||
|
firstParent(selector: string) : JQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JQueryStatic<TElement extends Node = HTMLElement> {
|
||||||
|
spawn<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]>;
|
||||||
|
views: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface String {
|
||||||
|
format(...fmt): string;
|
||||||
|
format(arguments: string[]): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Twemoji {
|
||||||
|
parse(message: string) : string;
|
||||||
|
}
|
||||||
|
let twemoji: Twemoji;
|
||||||
|
|
||||||
|
interface HighlightJS {
|
||||||
|
listLanguages() : string[];
|
||||||
|
getLanguage(name: string) : any | undefined;
|
||||||
|
|
||||||
|
highlight(language: string, text: string, ignore_illegals?: boolean) : HighlightJSResult;
|
||||||
|
highlightAuto(text: string) : HighlightJSResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HighlightJSResult {
|
||||||
|
language: string;
|
||||||
|
relevance: number;
|
||||||
|
|
||||||
|
value: string;
|
||||||
|
second_best?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
let remarkable: typeof window.remarkable;
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
readonly webkitAudioContext: typeof AudioContext;
|
||||||
|
readonly AudioContext: typeof OfflineAudioContext;
|
||||||
|
readonly OfflineAudioContext: typeof OfflineAudioContext;
|
||||||
|
readonly webkitOfflineAudioContext: typeof webkitOfflineAudioContext;
|
||||||
|
readonly RTCPeerConnection: typeof RTCPeerConnection;
|
||||||
|
readonly Pointer_stringify: any;
|
||||||
|
readonly jsrender: any;
|
||||||
|
|
||||||
|
twemoji: Twemoji;
|
||||||
|
hljs: HighlightJS;
|
||||||
|
remarkable: any;
|
||||||
|
|
||||||
|
require(id: string): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Navigator {
|
||||||
|
browserSpecs: {
|
||||||
|
name: string,
|
||||||
|
version: string
|
||||||
|
};
|
||||||
|
|
||||||
|
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
||||||
|
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JSON {
|
export function initialize() { }
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
type JQueryScrollType = "height" | "width";
|
|
||||||
interface JQuery<TElement = HTMLElement> {
|
|
||||||
render(values?: any) : string;
|
|
||||||
renderTag(values?: any) : JQuery<TElement>;
|
|
||||||
hasScrollBar(direction?: JQueryScrollType) : boolean;
|
|
||||||
|
|
||||||
|
|
||||||
visible_height() : number;
|
|
||||||
visible_width() : number;
|
|
||||||
|
|
||||||
/* bootstrap */
|
|
||||||
alert() : JQuery<TElement>;
|
|
||||||
modal(properties: any) : this;
|
|
||||||
bootstrapMaterialDesign() : this;
|
|
||||||
|
|
||||||
/* first element which matches the selector, could be the element itself or a parent */
|
|
||||||
firstParent(selector: string) : JQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JQueryStatic<TElement extends Node = HTMLElement> {
|
|
||||||
spawn<K extends keyof HTMLElementTagNameMap>(tagName: K): JQuery<HTMLElementTagNameMap[K]>;
|
|
||||||
views: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface String {
|
|
||||||
format(...fmt): string;
|
|
||||||
format(arguments: string[]): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -131,6 +184,7 @@ if(typeof ($) !== "undefined") {
|
||||||
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;
|
||||||
|
@ -184,7 +238,8 @@ if(typeof ($) !== "undefined") {
|
||||||
const result = this.height();
|
const result = this.height();
|
||||||
this.attr("style", original_style || "");
|
this.attr("style", original_style || "");
|
||||||
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");
|
||||||
|
@ -197,7 +252,8 @@ if(typeof ($) !== "undefined") {
|
||||||
const result = this.width();
|
const result = this.width();
|
||||||
this.attr("style", original_style || "");
|
this.attr("style", original_style || "");
|
||||||
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))
|
||||||
|
@ -232,30 +288,6 @@ function concatenate(resultConstructor, ...arrays) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(secs: number) : string {
|
|
||||||
let years = Math.floor(secs / (60 * 60 * 24 * 365));
|
|
||||||
let days = Math.floor(secs / (60 * 60 * 24)) % 365;
|
|
||||||
let hours = Math.floor(secs / (60 * 60)) % 24;
|
|
||||||
let minutes = Math.floor(secs / 60) % 60;
|
|
||||||
let seconds = Math.floor(secs % 60);
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
if(years > 0)
|
|
||||||
result += years + " " + tr("years") + " ";
|
|
||||||
if(years > 0 || days > 0)
|
|
||||||
result += days + " " + tr("days") + " ";
|
|
||||||
if(years > 0 || days > 0 || hours > 0)
|
|
||||||
result += hours + " " + tr("hours") + " ";
|
|
||||||
if(years > 0 || days > 0 || hours > 0 || minutes > 0)
|
|
||||||
result += minutes + " " + tr("minutes") + " ";
|
|
||||||
if(years > 0 || days > 0 || hours > 0 || minutes > 0 || seconds > 0)
|
|
||||||
result += seconds + " " + tr("seconds") + " ";
|
|
||||||
else
|
|
||||||
result = tr("now") + " ";
|
|
||||||
|
|
||||||
return result.substr(0, result.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculate_width(text: string) : number {
|
function calculate_width(text: string) : number {
|
||||||
let element = $.spawn("div");
|
let element = $.spawn("div");
|
||||||
element.text(text)
|
element.text(text)
|
||||||
|
@ -265,64 +297,4 @@ function calculate_width(text: string) : number {
|
||||||
let size = element.width();
|
let size = element.width();
|
||||||
element.detach();
|
element.detach();
|
||||||
return size;
|
return size;
|
||||||
}
|
|
||||||
|
|
||||||
interface Twemoji {
|
|
||||||
parse(message: string) : string;
|
|
||||||
}
|
|
||||||
declare let twemoji: Twemoji;
|
|
||||||
|
|
||||||
interface HighlightJS {
|
|
||||||
listLanguages() : string[];
|
|
||||||
getLanguage(name: string) : any | undefined;
|
|
||||||
|
|
||||||
highlight(language: string, text: string, ignore_illegals?: boolean) : HighlightJSResult;
|
|
||||||
highlightAuto(text: string) : HighlightJSResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HighlightJSResult {
|
|
||||||
language: string;
|
|
||||||
relevance: number;
|
|
||||||
|
|
||||||
value: string;
|
|
||||||
second_best?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMPurify {
|
|
||||||
sanitize(html: string, config?: {
|
|
||||||
ADD_ATTR?: string[]
|
|
||||||
ADD_TAGS?: string[];
|
|
||||||
}) : string;
|
|
||||||
}
|
|
||||||
declare let DOMPurify: DOMPurify;
|
|
||||||
|
|
||||||
declare let remarkable: typeof window.remarkable;
|
|
||||||
|
|
||||||
declare class webkitAudioContext extends AudioContext {}
|
|
||||||
declare class webkitOfflineAudioContext extends OfflineAudioContext {}
|
|
||||||
|
|
||||||
interface Window {
|
|
||||||
readonly webkitAudioContext: typeof webkitAudioContext;
|
|
||||||
readonly AudioContext: typeof webkitAudioContext;
|
|
||||||
readonly OfflineAudioContext: typeof OfflineAudioContext;
|
|
||||||
readonly webkitOfflineAudioContext: typeof webkitOfflineAudioContext;
|
|
||||||
readonly RTCPeerConnection: typeof RTCPeerConnection;
|
|
||||||
readonly Pointer_stringify: any;
|
|
||||||
readonly jsrender: any;
|
|
||||||
|
|
||||||
twemoji: Twemoji;
|
|
||||||
hljs: HighlightJS;
|
|
||||||
remarkable: any;
|
|
||||||
|
|
||||||
require(id: string): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Navigator {
|
|
||||||
browserSpecs: {
|
|
||||||
name: string,
|
|
||||||
version: string
|
|
||||||
};
|
|
||||||
|
|
||||||
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
|
|
||||||
}
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
/// <reference path="ui/elements/modal.ts" />
|
|
||||||
//Used by CertAccept popup
|
//Used by CertAccept popup
|
||||||
|
|
||||||
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import * as loader from "tc-loader";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
|
||||||
if(typeof(customElements) !== "undefined") {
|
if(typeof(customElements) !== "undefined") {
|
||||||
try {
|
try {
|
||||||
class X_Properties extends HTMLElement {}
|
class X_Properties extends HTMLElement {}
|
||||||
|
@ -9,12 +13,12 @@ if(typeof(customElements) !== "undefined") {
|
||||||
customElements.define('x-properties', X_Properties, { extends: 'div' });
|
customElements.define('x-properties', X_Properties, { extends: 'div' });
|
||||||
customElements.define('x-property', X_Property, { extends: 'div' });
|
customElements.define('x-property', X_Property, { extends: 'div' });
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.warn("failed to define costum elements");
|
console.warn("failed to define costume elements");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* T = value type */
|
/* T = value type */
|
||||||
interface SettingsKey<T> {
|
export interface SettingsKey<T> {
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
fallback_keys?: string | string[];
|
fallback_keys?: string | string[];
|
||||||
|
@ -25,7 +29,7 @@ interface SettingsKey<T> {
|
||||||
require_restart?: boolean;
|
require_restart?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsBase {
|
export class SettingsBase {
|
||||||
protected static readonly UPDATE_DIRECT: boolean = true;
|
protected static readonly UPDATE_DIRECT: boolean = true;
|
||||||
|
|
||||||
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
|
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
|
||||||
|
@ -77,7 +81,7 @@ class SettingsBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StaticSettings extends SettingsBase {
|
export class StaticSettings extends SettingsBase {
|
||||||
private static _instance: StaticSettings;
|
private static _instance: StaticSettings;
|
||||||
static get instance() : StaticSettings {
|
static get instance() : StaticSettings {
|
||||||
if(!this._instance)
|
if(!this._instance)
|
||||||
|
@ -139,7 +143,7 @@ class StaticSettings extends SettingsBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Settings extends StaticSettings {
|
export class Settings extends StaticSettings {
|
||||||
static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
|
static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
|
||||||
key: 'user_is_new_user',
|
key: 'user_is_new_user',
|
||||||
default_value: true
|
default_value: true
|
||||||
|
@ -433,7 +437,7 @@ class Settings extends StaticSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerSettings extends SettingsBase {
|
export class ServerSettings extends SettingsBase {
|
||||||
private cacheServer = {};
|
private cacheServer = {};
|
||||||
private _server_unique_id: string;
|
private _server_unique_id: string;
|
||||||
private _server_save_worker: NodeJS.Timer;
|
private _server_save_worker: NodeJS.Timer;
|
||||||
|
@ -511,4 +515,4 @@ class ServerSettings extends SettingsBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings: Settings;
|
export let settings: Settings = null;
|
|
@ -1,4 +1,10 @@
|
||||||
enum Sound {
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {settings} from "tc-shared/settings";
|
||||||
|
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||||
|
import * as sbackend from "tc-backend/audio/sounds";
|
||||||
|
|
||||||
|
export enum Sound {
|
||||||
SOUND_TEST = "sound.test",
|
SOUND_TEST = "sound.test",
|
||||||
SOUND_EGG = "sound.egg",
|
SOUND_EGG = "sound.egg",
|
||||||
|
|
||||||
|
@ -61,235 +67,211 @@ enum Sound {
|
||||||
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
|
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace sound {
|
export interface SoundHandle {
|
||||||
export interface SoundHandle {
|
key: string;
|
||||||
key: string;
|
filename: string;
|
||||||
filename: string;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface SoundFile {
|
export interface SoundFile {
|
||||||
path: string;
|
path: string;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
let speech_mapping: {[key: string]:SoundHandle} = {};
|
let speech_mapping: {[key: string]:SoundHandle} = {};
|
||||||
|
|
||||||
let volume_require_save = false;
|
let volume_require_save = false;
|
||||||
let speech_volume: {[key: string]:number} = {};
|
let speech_volume: {[key: string]:number} = {};
|
||||||
let master_volume: number;
|
let master_volume: number;
|
||||||
|
|
||||||
let overlap_sounds: boolean;
|
let overlap_sounds: boolean;
|
||||||
let ignore_muted: boolean;
|
let ignore_muted: boolean;
|
||||||
|
|
||||||
let master_mixed: GainNode;
|
let master_mixed: GainNode;
|
||||||
|
|
||||||
function register_sound(key: string, file: string) {
|
function register_sound(key: string, file: string) {
|
||||||
speech_mapping[key] = {key: key, filename: file} as SoundHandle;
|
speech_mapping[key] = {key: key, filename: file} as SoundHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_sound_volume(sound: Sound, default_volume?: number) : number {
|
export function get_sound_volume(sound: Sound, default_volume?: number) : number {
|
||||||
let result = speech_volume[sound];
|
let result = speech_volume[sound];
|
||||||
if(typeof(result) === "undefined") {
|
if(typeof(result) === "undefined") {
|
||||||
if(typeof(default_volume) !== "undefined")
|
if(typeof(default_volume) !== "undefined")
|
||||||
result = default_volume;
|
result = default_volume;
|
||||||
else
|
|
||||||
result = 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_sound_volume(sound: Sound, volume: number) {
|
|
||||||
volume_require_save = volume_require_save || speech_volume[sound] != volume;
|
|
||||||
speech_volume[sound] = volume == 1 ? undefined : volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get_master_volume() : number {
|
|
||||||
return master_volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_master_volume(volume: number) {
|
|
||||||
volume_require_save = volume_require_save || master_volume != volume;
|
|
||||||
master_volume = volume;
|
|
||||||
if(master_mixed) {
|
|
||||||
if(master_mixed.gain.setValueAtTime)
|
|
||||||
master_mixed.gain.setValueAtTime(volume, 0);
|
|
||||||
else
|
|
||||||
master_mixed.gain.value = volume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function overlap_activated() : boolean {
|
|
||||||
return overlap_sounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_overlap_activated(flag: boolean) {
|
|
||||||
volume_require_save = volume_require_save || overlap_sounds != flag;
|
|
||||||
overlap_sounds = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ignore_output_muted() : boolean {
|
|
||||||
return ignore_muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_ignore_output_muted(flag: boolean) {
|
|
||||||
volume_require_save = volume_require_save || ignore_muted != flag;
|
|
||||||
ignore_muted = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reinitialisize_audio() {
|
|
||||||
const context = audio.player.context();
|
|
||||||
const destination = audio.player.destination();
|
|
||||||
|
|
||||||
if(master_mixed)
|
|
||||||
master_mixed.disconnect();
|
|
||||||
|
|
||||||
master_mixed = context.createGain();
|
|
||||||
if(master_mixed.gain.setValueAtTime)
|
|
||||||
master_mixed.gain.setValueAtTime(master_volume, 0);
|
|
||||||
else
|
else
|
||||||
master_mixed.gain.value = master_volume;
|
result = 1;
|
||||||
master_mixed.connect(destination);
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function save() {
|
export function set_sound_volume(sound: Sound, volume: number) {
|
||||||
if(volume_require_save) {
|
volume_require_save = volume_require_save || speech_volume[sound] != volume;
|
||||||
volume_require_save = false;
|
speech_volume[sound] = volume == 1 ? undefined : volume;
|
||||||
|
}
|
||||||
|
|
||||||
const data: any = {};
|
export function get_master_volume() : number {
|
||||||
data.version = 1;
|
return master_volume;
|
||||||
|
}
|
||||||
|
|
||||||
for(const key in Sound) {
|
export function set_master_volume(volume: number) {
|
||||||
if(typeof(speech_volume[Sound[key]]) !== "undefined")
|
volume_require_save = volume_require_save || master_volume != volume;
|
||||||
data[Sound[key]] = speech_volume[Sound[key]];
|
master_volume = volume;
|
||||||
}
|
if(master_mixed) {
|
||||||
data.master = master_volume;
|
if(master_mixed.gain.setValueAtTime)
|
||||||
data.overlap = overlap_sounds;
|
master_mixed.gain.setValueAtTime(volume, 0);
|
||||||
data.ignore_muted = ignore_muted;
|
else
|
||||||
|
master_mixed.gain.value = volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settings.changeGlobal("sound_volume", JSON.stringify(data));
|
export function overlap_activated() : boolean {
|
||||||
|
return overlap_sounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set_overlap_activated(flag: boolean) {
|
||||||
|
volume_require_save = volume_require_save || overlap_sounds != flag;
|
||||||
|
overlap_sounds = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ignore_output_muted() : boolean {
|
||||||
|
return ignore_muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set_ignore_output_muted(flag: boolean) {
|
||||||
|
volume_require_save = volume_require_save || ignore_muted != flag;
|
||||||
|
ignore_muted = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
if(volume_require_save) {
|
||||||
|
volume_require_save = false;
|
||||||
|
|
||||||
|
const data: any = {};
|
||||||
|
data.version = 1;
|
||||||
|
|
||||||
|
for(const key of Object.keys(Sound)) {
|
||||||
|
if(typeof(speech_volume[Sound[key]]) !== "undefined")
|
||||||
|
data[Sound[key]] = speech_volume[Sound[key]];
|
||||||
}
|
}
|
||||||
|
data.master = master_volume;
|
||||||
|
data.overlap = overlap_sounds;
|
||||||
|
data.ignore_muted = ignore_muted;
|
||||||
|
|
||||||
|
settings.changeGlobal("sound_volume", JSON.stringify(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initialize() : Promise<void> {
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(jqXHR,settings){
|
||||||
|
if (settings.dataType === 'binary') {
|
||||||
|
settings.xhr().responseType = 'arraybuffer';
|
||||||
|
settings.processData = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* volumes */
|
||||||
|
{
|
||||||
|
const data = JSON.parse(settings.static_global("sound_volume", "{}"));
|
||||||
|
for(const sound_key of Object.keys(Sound)) {
|
||||||
|
if(typeof(data[Sound[sound_key]]) !== "undefined")
|
||||||
|
speech_volume[Sound[sound_key]] = data[Sound[sound_key]];
|
||||||
|
}
|
||||||
|
|
||||||
|
master_volume = typeof(data.master) === "number" ? data.master : 1;
|
||||||
|
overlap_sounds = typeof(data.overlap) === "boolean" ? data.overlap : true;
|
||||||
|
ignore_muted = typeof(data.ignore_muted) === "boolean" ? data.ignore_muted : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialize() : Promise<void> {
|
register_sound("message.received", "effects/message_received.wav");
|
||||||
$.ajaxSetup({
|
register_sound("message.send", "effects/message_send.wav");
|
||||||
beforeSend: function(jqXHR,settings){
|
|
||||||
if (settings.dataType === 'binary') {
|
manager = new SoundManager(undefined);
|
||||||
settings.xhr().responseType = 'arraybuffer';
|
return new Promise<void>((resolve, reject) => {
|
||||||
settings.processData = false;
|
$.ajax({
|
||||||
}
|
url: "audio/speech/mapping.json",
|
||||||
}
|
success: response => {
|
||||||
|
if(typeof(response) === "string")
|
||||||
|
response = JSON.parse(response);
|
||||||
|
for(const entry of response)
|
||||||
|
register_sound(entry.key, "speech/" + entry.file);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
error: error => {
|
||||||
|
log.error(LogCategory.AUDIO, "error: %o", error);
|
||||||
|
reject();
|
||||||
|
},
|
||||||
|
timeout: 5000,
|
||||||
|
async: true,
|
||||||
|
type: 'GET'
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* volumes */
|
export interface PlaybackOptions {
|
||||||
{
|
ignore_muted?: boolean;
|
||||||
const data = JSON.parse(settings.static_global("sound_volume", "{}"));
|
ignore_overlap?: boolean;
|
||||||
for(const sound_key in Sound) {
|
|
||||||
if(typeof(data[Sound[sound_key]]) !== "undefined")
|
|
||||||
speech_volume[Sound[sound_key]] = data[Sound[sound_key]];
|
|
||||||
}
|
|
||||||
|
|
||||||
master_volume = typeof(data.master) === "number" ? data.master : 1;
|
default_volume?: number;
|
||||||
overlap_sounds = typeof(data.overlap) === "boolean" ? data.overlap : true;
|
|
||||||
ignore_muted = typeof(data.ignore_muted) === "boolean" ? data.ignore_muted : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
register_sound("message.received", "effects/message_received.wav");
|
callback?: (flag: boolean) => any;
|
||||||
register_sound("message.send", "effects/message_send.wav");
|
}
|
||||||
|
|
||||||
manager = new SoundManager(undefined);
|
export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
|
||||||
audio.player.on_ready(reinitialisize_audio);
|
const file: SoundHandle = speech_mapping[sound];
|
||||||
return new Promise<void>((resolve, reject) => {
|
if(!file) throw tr("Missing sound handle");
|
||||||
$.ajax({
|
|
||||||
url: "audio/speech/mapping.json",
|
return file;
|
||||||
success: response => {
|
}
|
||||||
if(typeof(response) === "string")
|
|
||||||
response = JSON.parse(response);
|
export let manager: SoundManager;
|
||||||
for(const entry of response)
|
|
||||||
register_sound(entry.key, "speech/" + entry.file);
|
export class SoundManager {
|
||||||
resolve();
|
private readonly _handle: ConnectionHandler;
|
||||||
},
|
private _playing_sounds: {[key: string]:number} = {};
|
||||||
error: error => {
|
|
||||||
log.error(LogCategory.AUDIO, "error: %o", error);
|
constructor(handle: ConnectionHandler) {
|
||||||
reject();
|
this._handle = handle;
|
||||||
},
|
|
||||||
timeout: 5000,
|
|
||||||
async: true,
|
|
||||||
type: 'GET'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaybackOptions {
|
play(_sound: Sound, options?: PlaybackOptions) {
|
||||||
ignore_muted?: boolean;
|
options = options || {};
|
||||||
ignore_overlap?: boolean;
|
|
||||||
|
|
||||||
default_volume?: number;
|
const volume = get_sound_volume(_sound, options.default_volume);
|
||||||
|
log.info(LogCategory.AUDIO, tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), _sound, volume, master_volume);
|
||||||
|
|
||||||
callback?: (flag: boolean) => any;
|
if(volume == 0 || master_volume == 0)
|
||||||
}
|
return;
|
||||||
|
|
||||||
export async function resolve_sound(sound: Sound) : Promise<SoundHandle> {
|
if(this._handle && !options.ignore_muted && !ignore_output_muted() && this._handle.client_status.output_muted)
|
||||||
const file: SoundHandle = speech_mapping[sound];
|
return;
|
||||||
if(!file) throw tr("Missing sound handle");
|
|
||||||
|
|
||||||
return file;
|
resolve_sound(_sound).then(handle => {
|
||||||
}
|
if(!handle) return;
|
||||||
|
|
||||||
export let manager: SoundManager;
|
if(!options.ignore_overlap && (this._playing_sounds[handle.filename] > 0) && !overlap_activated()) {
|
||||||
|
log.info(LogCategory.AUDIO, tr("Dropping requested playback for sound %s because it would overlap."), _sound);
|
||||||
export class SoundManager {
|
|
||||||
private readonly _handle: ConnectionHandler;
|
|
||||||
private _playing_sounds: {[key: string]:number} = {};
|
|
||||||
|
|
||||||
constructor(handle: ConnectionHandler) {
|
|
||||||
this._handle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
play(_sound: Sound, options?: PlaybackOptions) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
const volume = get_sound_volume(_sound, options.default_volume);
|
|
||||||
log.info(LogCategory.AUDIO, tr("Replaying sound %s (Sound volume: %o | Master volume %o)"), _sound, volume, master_volume);
|
|
||||||
|
|
||||||
if(volume == 0 || master_volume == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(this._handle && !options.ignore_muted && !sound.ignore_output_muted() && this._handle.client_status.output_muted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const context = audio.player.context();
|
|
||||||
if(!context) {
|
|
||||||
log.warn(LogCategory.AUDIO, tr("Tried to replay a sound without an audio context (Sound: %o). Dropping playback"), _sound);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sound.resolve_sound(_sound).then(handle => {
|
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
|
||||||
if(!handle) return;
|
sbackend.play_sound({
|
||||||
|
path: "audio/" + handle.filename,
|
||||||
if(!options.ignore_overlap && (this._playing_sounds[handle.filename] > 0) && !sound.overlap_activated()) {
|
volume: volume * master_volume
|
||||||
log.info(LogCategory.AUDIO, tr("Dropping requested playback for sound %s because it would overlap."), _sound);
|
}).then(() => {
|
||||||
return;
|
if(options.callback)
|
||||||
}
|
options.callback(true);
|
||||||
|
|
||||||
this._playing_sounds[handle.filename] = (this._playing_sounds[handle.filename] || 0) + 1;
|
|
||||||
audio.sounds.play_sound({
|
|
||||||
path: "audio/" + handle.filename,
|
|
||||||
volume: volume * master_volume
|
|
||||||
}).then(() => {
|
|
||||||
if(options.callback)
|
|
||||||
options.callback(true);
|
|
||||||
}).catch(error => {
|
|
||||||
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %s: %o"), handle.filename, error);
|
|
||||||
if(options.callback)
|
|
||||||
options.callback(false);
|
|
||||||
}).then(() => {
|
|
||||||
this._playing_sounds[handle.filename]--;
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because it could not be resolved: %o"), sound, error);
|
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %s: %o"), handle.filename, error);
|
||||||
if(options.callback)
|
if(options.callback)
|
||||||
options.callback(false);
|
options.callback(false);
|
||||||
|
}).then(() => {
|
||||||
|
this._playing_sounds[handle.filename]--;
|
||||||
});
|
});
|
||||||
}
|
}).catch(error => {
|
||||||
|
log.warn(LogCategory.AUDIO, tr("Failed to replay sound %o because it could not be resolved: %o"), _sound, error);
|
||||||
|
if(options.callback)
|
||||||
|
options.callback(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,243 +1,244 @@
|
||||||
namespace stats {
|
import {LogCategory} from "tc-shared/log";
|
||||||
const LOG_PREFIX = "[Statistics] ";
|
import * as log from "tc-shared/log";
|
||||||
|
|
||||||
export enum CloseCodes {
|
const LOG_PREFIX = "[Statistics] ";
|
||||||
UNSET = 3000,
|
|
||||||
RECONNECT = 3001,
|
|
||||||
INTERNAL_ERROR = 3002,
|
|
||||||
|
|
||||||
BANNED = 3100,
|
enum CloseCodes {
|
||||||
|
UNSET = 3000,
|
||||||
|
RECONNECT = 3001,
|
||||||
|
INTERNAL_ERROR = 3002,
|
||||||
|
|
||||||
|
BANNED = 3100,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConnectionState {
|
||||||
|
CONNECTING,
|
||||||
|
INITIALIZING,
|
||||||
|
CONNECTED,
|
||||||
|
UNSET
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SessionConfig {
|
||||||
|
/*
|
||||||
|
* All collected statistics will only be cached by the stats server.
|
||||||
|
* No data will be saved.
|
||||||
|
*/
|
||||||
|
volatile_collection_only?: boolean;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Anonymize all IP addresses which will be provided while the stats collection.
|
||||||
|
* This option is quite useless when volatile_collection_only is active.
|
||||||
|
*/
|
||||||
|
anonymize_ip_addresses?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Config extends SessionConfig {
|
||||||
|
verbose?: boolean;
|
||||||
|
|
||||||
|
reconnect_interval?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserCountData {
|
||||||
|
online_users: number;
|
||||||
|
unique_online_users: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserCountListener = (data: UserCountData) => any;
|
||||||
|
|
||||||
|
let reconnect_timer: NodeJS.Timer;
|
||||||
|
let current_config: Config;
|
||||||
|
|
||||||
|
let last_user_count_update: number;
|
||||||
|
let user_count_listener: UserCountListener[] = [];
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: Config = {
|
||||||
|
verbose: true,
|
||||||
|
reconnect_interval: 5000,
|
||||||
|
anonymize_ip_addresses: true,
|
||||||
|
volatile_collection_only: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function initialize_config_object(target_object: any, source_object: any) : any {
|
||||||
|
for(const key of Object.keys(source_object)) {
|
||||||
|
if(typeof(source_object[key]) === 'object')
|
||||||
|
initialize_config_object(target_object[key] || (target_object[key] = {}), source_object[key]);
|
||||||
|
|
||||||
|
if(typeof(target_object[key]) !== 'undefined')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
target_object[key] = source_object[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionState {
|
return target_object;
|
||||||
CONNECTING,
|
}
|
||||||
INITIALIZING,
|
|
||||||
CONNECTED,
|
|
||||||
UNSET
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SessionConfig {
|
export function initialize(config: Config) {
|
||||||
/*
|
current_config = initialize_config_object(config || {}, DEFAULT_CONFIG);
|
||||||
* All collected statistics will only be cached by the stats server.
|
if(current_config.verbose)
|
||||||
* No data will be saved.
|
log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config);
|
||||||
*/
|
|
||||||
volatile_collection_only?: boolean;
|
|
||||||
|
|
||||||
/*
|
connection.start_connection();
|
||||||
* Anonymize all IP addresses which will be provided while the stats collection.
|
}
|
||||||
* This option is quite useless when volatile_collection_only is active.
|
|
||||||
*/
|
|
||||||
anonymize_ip_addresses?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Config extends SessionConfig {
|
export function register_user_count_listener(listener: UserCountListener) {
|
||||||
verbose?: boolean;
|
user_count_listener.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
reconnect_interval?: number;
|
export function all_user_count_listener() : UserCountListener[] {
|
||||||
}
|
return user_count_listener;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserCountData {
|
export function deregister_user_count_listener(listener: UserCountListener) {
|
||||||
online_users: number;
|
user_count_listener.remove(listener);
|
||||||
unique_online_users: number;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export type UserCountListener = (data: UserCountData) => any;
|
namespace connection {
|
||||||
|
let connection: WebSocket;
|
||||||
|
export let connection_state: ConnectionState = ConnectionState.UNSET;
|
||||||
|
|
||||||
let reconnect_timer: NodeJS.Timer;
|
export function start_connection() {
|
||||||
let current_config: Config;
|
cancel_reconnect();
|
||||||
|
close_connection();
|
||||||
|
|
||||||
let last_user_count_update: number;
|
connection_state = ConnectionState.CONNECTING;
|
||||||
let user_count_listener: UserCountListener[] = [];
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG: Config = {
|
connection = new WebSocket('wss://web-stats.teaspeak.de:27790');
|
||||||
verbose: true,
|
if(!connection)
|
||||||
reconnect_interval: 5000,
|
connection = new WebSocket('wss://localhost:27788');
|
||||||
anonymize_ip_addresses: true,
|
|
||||||
volatile_collection_only: false
|
|
||||||
};
|
|
||||||
|
|
||||||
function initialize_config_object(target_object: any, source_object: any) : any {
|
{
|
||||||
for(const key of Object.keys(source_object)) {
|
const connection_copy = connection;
|
||||||
if(typeof(source_object[key]) === 'object')
|
connection.onclose = (event: CloseEvent) => {
|
||||||
initialize_config_object(target_object[key] || (target_object[key] = {}), source_object[key]);
|
if(connection_copy !== connection) return;
|
||||||
|
|
||||||
if(typeof(target_object[key]) !== 'undefined')
|
if(current_config.verbose)
|
||||||
continue;
|
log.warn(LogCategory.STATISTICS, tr("Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o"), CloseCodes[event.code] || event.code, event);
|
||||||
|
|
||||||
target_object[key] = source_object[key];
|
if(event.code != CloseCodes.BANNED)
|
||||||
}
|
|
||||||
|
|
||||||
return target_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initialize(config: Config) {
|
|
||||||
current_config = initialize_config_object(config || {}, DEFAULT_CONFIG);
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config);
|
|
||||||
|
|
||||||
connection.start_connection();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function register_user_count_listener(listener: UserCountListener) {
|
|
||||||
user_count_listener.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function all_user_count_listener() : UserCountListener[] {
|
|
||||||
return user_count_listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deregister_user_count_listener(listener: UserCountListener) {
|
|
||||||
user_count_listener.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace connection {
|
|
||||||
let connection: WebSocket;
|
|
||||||
export let connection_state: ConnectionState = ConnectionState.UNSET;
|
|
||||||
|
|
||||||
export function start_connection() {
|
|
||||||
cancel_reconnect();
|
|
||||||
close_connection();
|
|
||||||
|
|
||||||
connection_state = ConnectionState.CONNECTING;
|
|
||||||
|
|
||||||
connection = new WebSocket('wss://web-stats.teaspeak.de:27790');
|
|
||||||
if(!connection)
|
|
||||||
connection = new WebSocket('wss://localhost:27788');
|
|
||||||
|
|
||||||
{
|
|
||||||
const connection_copy = connection;
|
|
||||||
connection.onclose = (event: CloseEvent) => {
|
|
||||||
if(connection_copy !== connection) return;
|
|
||||||
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.warn(LogCategory.STATISTICS, tr("Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o"), CloseCodes[event.code] || event.code, event);
|
|
||||||
|
|
||||||
if(event.code != CloseCodes.BANNED)
|
|
||||||
invoke_reconnect();
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.onopen = () => {
|
|
||||||
if(connection_copy !== connection) return;
|
|
||||||
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.info(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
|
|
||||||
|
|
||||||
connection_state = ConnectionState.INITIALIZING;
|
|
||||||
initialize_session();
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.onerror = (event: ErrorEvent) => {
|
|
||||||
if(connection_copy !== connection) return;
|
|
||||||
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.warn(LogCategory.STATISTICS, tr("Received an error. Closing connection. Object: %o"), event);
|
|
||||||
|
|
||||||
connection.close(CloseCodes.INTERNAL_ERROR);
|
|
||||||
invoke_reconnect();
|
invoke_reconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.onmessage = (event: MessageEvent) => {
|
connection.onopen = () => {
|
||||||
if(connection_copy !== connection) return;
|
if(connection_copy !== connection) return;
|
||||||
|
|
||||||
if(typeof(event.data) !== 'string') {
|
if(current_config.verbose)
|
||||||
if(current_config.verbose)
|
log.info(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
|
||||||
log.info(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_message(event.data as string);
|
connection_state = ConnectionState.INITIALIZING;
|
||||||
};
|
initialize_session();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
connection.onerror = (event: ErrorEvent) => {
|
||||||
|
if(connection_copy !== connection) return;
|
||||||
|
|
||||||
|
if(current_config.verbose)
|
||||||
|
log.warn(LogCategory.STATISTICS, tr("Received an error. Closing connection. Object: %o"), event);
|
||||||
|
|
||||||
|
connection.close(CloseCodes.INTERNAL_ERROR);
|
||||||
|
invoke_reconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onmessage = (event: MessageEvent) => {
|
||||||
|
if(connection_copy !== connection) return;
|
||||||
|
|
||||||
|
if(typeof(event.data) !== 'string') {
|
||||||
|
if(current_config.verbose)
|
||||||
|
log.info(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_message(event.data as string);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function close_connection() {
|
||||||
|
if(connection) {
|
||||||
|
const connection_copy = connection;
|
||||||
|
connection = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection_copy.close(3001);
|
||||||
|
} catch(_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invoke_reconnect() {
|
||||||
|
close_connection();
|
||||||
|
|
||||||
|
if(reconnect_timer) {
|
||||||
|
clearTimeout(reconnect_timer);
|
||||||
|
reconnect_timer = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function close_connection() {
|
if(current_config.verbose)
|
||||||
if(connection) {
|
log.info(LogCategory.STATISTICS, tr("Scheduled reconnect in %dms"), current_config.reconnect_interval);
|
||||||
const connection_copy = connection;
|
|
||||||
connection = undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
connection_copy.close(3001);
|
|
||||||
} catch(_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function invoke_reconnect() {
|
|
||||||
close_connection();
|
|
||||||
|
|
||||||
if(reconnect_timer) {
|
|
||||||
clearTimeout(reconnect_timer);
|
|
||||||
reconnect_timer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
reconnect_timer = setTimeout(() => {
|
||||||
if(current_config.verbose)
|
if(current_config.verbose)
|
||||||
log.info(LogCategory.STATISTICS, tr("Scheduled reconnect in %dms"), current_config.reconnect_interval);
|
log.info(LogCategory.STATISTICS, tr("Reconnecting"));
|
||||||
|
start_connection();
|
||||||
|
}, current_config.reconnect_interval);
|
||||||
|
}
|
||||||
|
|
||||||
reconnect_timer = setTimeout(() => {
|
export function cancel_reconnect() {
|
||||||
if(current_config.verbose)
|
if(reconnect_timer) {
|
||||||
log.info(LogCategory.STATISTICS, tr("Reconnecting"));
|
clearTimeout(reconnect_timer);
|
||||||
start_connection();
|
reconnect_timer = undefined;
|
||||||
}, current_config.reconnect_interval);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_message(type: string, data: any) {
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: type,
|
||||||
|
data: data
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize_session() {
|
||||||
|
const config_object = {};
|
||||||
|
for(const key in SessionConfig) {
|
||||||
|
if(SessionConfig.hasOwnProperty(key))
|
||||||
|
config_object[key] = current_config[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cancel_reconnect() {
|
send_message('initialize', {
|
||||||
if(reconnect_timer) {
|
config: config_object
|
||||||
clearTimeout(reconnect_timer);
|
})
|
||||||
reconnect_timer = undefined;
|
}
|
||||||
}
|
|
||||||
|
function handle_message(message: string) {
|
||||||
|
const data_object = JSON.parse(message);
|
||||||
|
const type = data_object.type as string;
|
||||||
|
const data = data_object.data;
|
||||||
|
|
||||||
|
if(typeof(handler[type]) === 'function') {
|
||||||
|
if(current_config.verbose)
|
||||||
|
log.debug(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
|
||||||
|
handler[type](data);
|
||||||
|
} else if(current_config.verbose) {
|
||||||
|
log.warn(LogCategory.STATISTICS, tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace handler {
|
||||||
|
interface NotifyUserCount extends UserCountData { }
|
||||||
|
|
||||||
|
function handle_notify_user_count(data: NotifyUserCount) {
|
||||||
|
last_user_count_update = Date.now();
|
||||||
|
for(const listener of [...user_count_listener])
|
||||||
|
listener(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function send_message(type: string, data: any) {
|
interface NotifyInitialized {}
|
||||||
connection.send(JSON.stringify({
|
function handle_notify_initialized(json: NotifyInitialized) {
|
||||||
type: type,
|
if(current_config.verbose)
|
||||||
data: data
|
log.info(LogCategory.STATISTICS, tr("Session successfully initialized."));
|
||||||
}));
|
|
||||||
|
connection_state = ConnectionState.CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize_session() {
|
handler["notifyinitialized"] = handle_notify_initialized;
|
||||||
const config_object = {};
|
handler["notifyusercount"] = handle_notify_user_count;
|
||||||
for(const key in SessionConfig) {
|
|
||||||
if(SessionConfig.hasOwnProperty(key))
|
|
||||||
config_object[key] = current_config[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
send_message('initialize', {
|
|
||||||
config: config_object
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle_message(message: string) {
|
|
||||||
const data_object = JSON.parse(message);
|
|
||||||
const type = data_object.type as string;
|
|
||||||
const data = data_object.data;
|
|
||||||
|
|
||||||
if(typeof(handler[type]) === 'function') {
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.debug(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
|
|
||||||
handler[type](data);
|
|
||||||
} else if(current_config.verbose) {
|
|
||||||
log.warn(LogCategory.STATISTICS, tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace handler {
|
|
||||||
interface NotifyUserCount extends UserCountData { }
|
|
||||||
|
|
||||||
function handle_notify_user_count(data: NotifyUserCount) {
|
|
||||||
last_user_count_update = Date.now();
|
|
||||||
for(const listener of [...user_count_listener])
|
|
||||||
listener(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NotifyInitialized {}
|
|
||||||
function handle_notify_initialized(json: NotifyInitialized) {
|
|
||||||
if(current_config.verbose)
|
|
||||||
log.info(LogCategory.STATISTICS, tr("Session successfully initialized."));
|
|
||||||
|
|
||||||
connection_state = ConnectionState.CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler["notifyinitialized"] = handle_notify_initialized;
|
|
||||||
handler["notifyusercount"] = handle_notify_user_count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,26 @@
|
||||||
/// <reference path="view.ts" />
|
import {ChannelTree} from "tc-shared/ui/view";
|
||||||
/// <reference path="../utils/helpers.ts" />
|
import {ClientEntry} from "tc-shared/ui/client";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory, LogType} from "tc-shared/log";
|
||||||
|
import PermissionType from "tc-shared/permission/PermissionType";
|
||||||
|
import {settings, Settings} from "tc-shared/settings";
|
||||||
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
|
import {Sound} from "tc-shared/sound/Sounds";
|
||||||
|
import {createErrorModal, createInfoModal, createInputModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import * as htmltags from "./htmltags";
|
||||||
|
import {hashPassword} from "tc-shared/utils/helpers";
|
||||||
|
import * as server_log from "tc-shared/ui/frames/server_log";
|
||||||
|
import {openChannelInfo} from "tc-shared/ui/modal/ModalChannelInfo";
|
||||||
|
import {createChannelModal} from "tc-shared/ui/modal/ModalCreateChannel";
|
||||||
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
|
||||||
enum ChannelType {
|
export enum ChannelType {
|
||||||
PERMANENT,
|
PERMANENT,
|
||||||
SEMI_PERMANENT,
|
SEMI_PERMANENT,
|
||||||
TEMPORARY
|
TEMPORARY
|
||||||
}
|
}
|
||||||
namespace ChannelType {
|
export namespace ChannelType {
|
||||||
export function normalize(mode: ChannelType) {
|
export function normalize(mode: ChannelType) {
|
||||||
let value: string = ChannelType[mode];
|
let value: string = ChannelType[mode];
|
||||||
value = value.toLowerCase();
|
value = value.toLowerCase();
|
||||||
|
@ -14,13 +28,13 @@ namespace ChannelType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelSubscribeMode {
|
export enum ChannelSubscribeMode {
|
||||||
SUBSCRIBED,
|
SUBSCRIBED,
|
||||||
UNSUBSCRIBED,
|
UNSUBSCRIBED,
|
||||||
INHERITED
|
INHERITED
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelProperties {
|
export class ChannelProperties {
|
||||||
channel_order: number = 0;
|
channel_order: number = 0;
|
||||||
channel_name: string = "";
|
channel_name: string = "";
|
||||||
channel_name_phonetic: string = "";
|
channel_name_phonetic: string = "";
|
||||||
|
@ -55,7 +69,7 @@ class ChannelProperties {
|
||||||
channel_conversation_history_length: number = -1;
|
channel_conversation_history_length: number = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelEntry {
|
export class ChannelEntry {
|
||||||
channelTree: ChannelTree;
|
channelTree: ChannelTree;
|
||||||
channelId: number;
|
channelId: number;
|
||||||
parent?: ChannelEntry;
|
parent?: ChannelEntry;
|
||||||
|
@ -525,7 +539,7 @@ class ChannelEntry {
|
||||||
name: tr("Show channel info"),
|
name: tr("Show channel info"),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
trigger_close = false;
|
trigger_close = false;
|
||||||
Modals.openChannelInfo(this);
|
openChannelInfo(this);
|
||||||
},
|
},
|
||||||
icon_class: "client-about"
|
icon_class: "client-about"
|
||||||
},
|
},
|
||||||
|
@ -565,7 +579,7 @@ class ChannelEntry {
|
||||||
name: tr("Edit channel"),
|
name: tr("Edit channel"),
|
||||||
invalidPermission: !channelModify,
|
invalidPermission: !channelModify,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.createChannelModal(this.channelTree.client, this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
|
createChannelModal(this.channelTree.client, this, undefined, this.channelTree.client.permissions, (changes?, permissions?) => {
|
||||||
if(changes) {
|
if(changes) {
|
||||||
changes["cid"] = this.channelId;
|
changes["cid"] = this.channelId;
|
||||||
this.channelTree.client.serverConnection.send_command("channeledit", changes);
|
this.channelTree.client.serverConnection.send_command("channeledit", changes);
|
||||||
|
@ -617,7 +631,7 @@ class ChannelEntry {
|
||||||
error = error.extra_message || error.message;
|
error = error.extra_message || error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
createErrorModal(tr("Failed to create bot"), MessageHelper.formatMessage(tr("Failed to create the music bot:<br>{0}"), error)).open();
|
createErrorModal(tr("Failed to create bot"), formatMessage(tr("Failed to create the music bot:<br>{0}"), error)).open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -834,7 +848,7 @@ class ChannelEntry {
|
||||||
!this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD).granted(1)) {
|
!this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD).granted(1)) {
|
||||||
createInputModal(tr("Channel password"), tr("Channel password:"), () => true, text => {
|
createInputModal(tr("Channel password"), tr("Channel password:"), () => true, text => {
|
||||||
if(typeof(text) == typeof(true)) return;
|
if(typeof(text) == typeof(true)) return;
|
||||||
helpers.hashPassword(text as string).then(result => {
|
hashPassword(text as string).then(result => {
|
||||||
this._cachedPassword = result;
|
this._cachedPassword = result;
|
||||||
this.joinChannel();
|
this.joinChannel();
|
||||||
this.updateChannelTypeIcon();
|
this.updateChannelTypeIcon();
|
||||||
|
@ -923,7 +937,7 @@ class ChannelEntry {
|
||||||
this._tag_channel.find(".marker-text-unread").toggleClass("hidden", !flag);
|
this._tag_channel.find(".marker-text-unread").toggleClass("hidden", !flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_data() : log.server.base.Channel {
|
log_data() : server_log.base.Channel {
|
||||||
return {
|
return {
|
||||||
channel_name: this.channelName(),
|
channel_name: this.channelName(),
|
||||||
channel_id: this.channelId
|
channel_id: this.channelId
|
||||||
|
|
|
@ -1,8 +1,34 @@
|
||||||
/// <reference path="channel.ts" />
|
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
import {channel_tree, Registry} from "tc-shared/events";
|
||||||
/// <reference path="client_move.ts" />
|
import {ChannelTree} from "tc-shared/ui/view";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory, LogType} from "tc-shared/log";
|
||||||
|
import {Settings, settings} from "tc-shared/settings";
|
||||||
|
import {KeyCode, SpecialKey} from "tc-shared/PPTListener";
|
||||||
|
import {Sound} from "tc-shared/sound/Sounds";
|
||||||
|
import {Group, GroupManager, GroupTarget, GroupType} from "tc-shared/permission/GroupManager";
|
||||||
|
import PermissionType from "tc-shared/permission/PermissionType";
|
||||||
|
import {createErrorModal, createInputModal} from "tc-shared/ui/elements/Modal";
|
||||||
|
import * as htmltags from "tc-shared/ui/htmltags";
|
||||||
|
import * as server_log from "tc-shared/ui/frames/server_log";
|
||||||
|
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
|
||||||
|
import {ChannelEntry} from "tc-shared/ui/channel";
|
||||||
|
import {ConnectionHandler, ViewReasonId} from "tc-shared/ConnectionHandler";
|
||||||
|
import {voice} from "tc-shared/connection/ConnectionBase";
|
||||||
|
import VoiceClient = voice.VoiceClient;
|
||||||
|
import {spawnPermissionEdit} from "tc-shared/ui/modal/permission/ModalPermissionEdit";
|
||||||
|
import {createServerGroupAssignmentModal} from "tc-shared/ui/modal/ModalGroupAssignment";
|
||||||
|
import {openClientInfo} from "tc-shared/ui/modal/ModalClientInfo";
|
||||||
|
import {spawnBanClient} from "tc-shared/ui/modal/ModalBanClient";
|
||||||
|
import {spawnChangeVolume} from "tc-shared/ui/modal/ModalChangeVolume";
|
||||||
|
import {spawnChangeLatency} from "tc-shared/ui/modal/ModalChangeLatency";
|
||||||
|
import {spawnPlaylistEdit} from "tc-shared/ui/modal/ModalPlaylistEdit";
|
||||||
|
import {formatMessage} from "tc-shared/ui/frames/chat";
|
||||||
|
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||||
|
import * as ppt from "tc-backend/ppt";
|
||||||
|
import * as hex from "tc-shared/crypto/hex";
|
||||||
|
|
||||||
enum ClientType {
|
export enum ClientType {
|
||||||
CLIENT_VOICE,
|
CLIENT_VOICE,
|
||||||
CLIENT_QUERY,
|
CLIENT_QUERY,
|
||||||
CLIENT_INTERNAL,
|
CLIENT_INTERNAL,
|
||||||
|
@ -11,7 +37,7 @@ enum ClientType {
|
||||||
CLIENT_UNDEFINED
|
CLIENT_UNDEFINED
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientProperties {
|
export class ClientProperties {
|
||||||
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
|
client_type: ClientType = ClientType.CLIENT_VOICE; //TeamSpeaks type
|
||||||
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
|
client_type_exact: ClientType = ClientType.CLIENT_VOICE;
|
||||||
|
|
||||||
|
@ -57,7 +83,7 @@ class ClientProperties {
|
||||||
client_is_priority_speaker: boolean = false;
|
client_is_priority_speaker: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientConnectionInfo {
|
export class ClientConnectionInfo {
|
||||||
connection_bandwidth_received_last_minute_control: number = -1;
|
connection_bandwidth_received_last_minute_control: number = -1;
|
||||||
connection_bandwidth_received_last_minute_keepalive: number = -1;
|
connection_bandwidth_received_last_minute_keepalive: number = -1;
|
||||||
connection_bandwidth_received_last_minute_speech: number = -1;
|
connection_bandwidth_received_last_minute_speech: number = -1;
|
||||||
|
@ -109,8 +135,8 @@ class ClientConnectionInfo {
|
||||||
connection_client_port: number = -1;
|
connection_client_port: number = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientEntry {
|
export class ClientEntry {
|
||||||
readonly events: events.Registry<events.channel_tree.client>;
|
readonly events: Registry<channel_tree.client>;
|
||||||
|
|
||||||
protected _clientId: number;
|
protected _clientId: number;
|
||||||
protected _channel: ChannelEntry;
|
protected _channel: ChannelEntry;
|
||||||
|
@ -121,7 +147,7 @@ class ClientEntry {
|
||||||
protected _speaking: boolean;
|
protected _speaking: boolean;
|
||||||
protected _listener_initialized: boolean;
|
protected _listener_initialized: boolean;
|
||||||
|
|
||||||
protected _audio_handle: connection.voice.VoiceClient;
|
protected _audio_handle: VoiceClient;
|
||||||
protected _audio_volume: number;
|
protected _audio_volume: number;
|
||||||
protected _audio_muted: boolean;
|
protected _audio_muted: boolean;
|
||||||
|
|
||||||
|
@ -136,7 +162,7 @@ class ClientEntry {
|
||||||
channelTree: ChannelTree;
|
channelTree: ChannelTree;
|
||||||
|
|
||||||
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
|
constructor(clientId: number, clientName, properties: ClientProperties = new ClientProperties()) {
|
||||||
this.events = new events.Registry<events.channel_tree.client>();
|
this.events = new Registry<channel_tree.client>();
|
||||||
|
|
||||||
this._properties = properties;
|
this._properties = properties;
|
||||||
this._properties.client_nickname = clientName;
|
this._properties.client_nickname = clientName;
|
||||||
|
@ -181,7 +207,7 @@ class ClientEntry {
|
||||||
this._channel = undefined;
|
this._channel = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_audio_handle(handle: connection.voice.VoiceClient) {
|
set_audio_handle(handle: VoiceClient) {
|
||||||
if(this._audio_handle === handle)
|
if(this._audio_handle === handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -200,7 +226,7 @@ class ClientEntry {
|
||||||
handle.callback_stopped = () => this.speaking = false;
|
handle.callback_stopped = () => this.speaking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_audio_handle() : connection.voice.VoiceClient {
|
get_audio_handle() : VoiceClient {
|
||||||
return this._audio_handle;
|
return this._audio_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +311,7 @@ class ClientEntry {
|
||||||
|
|
||||||
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
|
let clients = this.channelTree.currently_selected as (ClientEntry | ClientEntry[]);
|
||||||
|
|
||||||
if(ppt.key_pressed(ppt.SpecialKey.SHIFT)) {
|
if(ppt.key_pressed(SpecialKey.SHIFT)) {
|
||||||
if(clients != this && !($.isArray(clients) && clients.indexOf(this) != -1))
|
if(clients != this && !($.isArray(clients) && clients.indexOf(this) != -1))
|
||||||
clients = $.isArray(clients) ? [...clients, this] : [clients, this];
|
clients = $.isArray(clients) ? [...clients, this] : [clients, this];
|
||||||
} else {
|
} else {
|
||||||
|
@ -418,20 +444,20 @@ class ClientEntry {
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
icon_class: "client-permission_client",
|
icon_class: "client-permission_client",
|
||||||
name: tr("Client permissions"),
|
name: tr("Client permissions"),
|
||||||
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clp", {unique_id: this.clientUid()}).open()
|
callback: () => spawnPermissionEdit(this.channelTree.client, "clp", {unique_id: this.clientUid()}).open()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
icon_class: "client-permission_client",
|
icon_class: "client-permission_client",
|
||||||
name: tr("Client channel permissions"),
|
name: tr("Client channel permissions"),
|
||||||
callback: () => Modals.spawnPermissionEdit(this.channelTree.client, "clchp", {unique_id: this.clientUid(), channel_id: this._channel ? this._channel.channelId : undefined }).open()
|
callback: () => spawnPermissionEdit(this.channelTree.client, "clchp", {unique_id: this.clientUid(), channel_id: this._channel ? this._channel.channelId : undefined }).open()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
open_assignment_modal() {
|
open_assignment_modal() {
|
||||||
Modals.createServerGroupAssignmentModal(this, (groups, flag) => {
|
createServerGroupAssignmentModal(this, (groups, flag) => {
|
||||||
if(groups.length == 0) return Promise.resolve(true);
|
if(groups.length == 0) return Promise.resolve(true);
|
||||||
|
|
||||||
if(groups.length == 1) {
|
if(groups.length == 1) {
|
||||||
|
@ -490,7 +516,7 @@ class ClientEntry {
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
icon_class: "client-about",
|
icon_class: "client-about",
|
||||||
name: tr("Show client info"),
|
name: tr("Show client info"),
|
||||||
callback: () => Modals.openClientInfo(this)
|
callback: () => openClientInfo(this)
|
||||||
},
|
},
|
||||||
contextmenu.Entry.HR(),
|
contextmenu.Entry.HR(),
|
||||||
{
|
{
|
||||||
|
@ -582,7 +608,7 @@ class ClientEntry {
|
||||||
name: tr("Ban client"),
|
name: tr("Ban client"),
|
||||||
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
|
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.spawnBanClient(this.channelTree.client, [{
|
spawnBanClient(this.channelTree.client, [{
|
||||||
name: this.properties.client_nickname,
|
name: this.properties.client_nickname,
|
||||||
unique_id: this.properties.client_unique_identifier
|
unique_id: this.properties.client_unique_identifier
|
||||||
}], (data) => {
|
}], (data) => {
|
||||||
|
@ -622,7 +648,7 @@ class ClientEntry {
|
||||||
icon_class: "client-volume",
|
icon_class: "client-volume",
|
||||||
name: tr("Change Volume"),
|
name: tr("Change Volume"),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.spawnChangeVolume(this, true, this._audio_volume, undefined, volume => {
|
spawnChangeVolume(this, true, this._audio_volume, undefined, volume => {
|
||||||
this._audio_volume = volume;
|
this._audio_volume = volume;
|
||||||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||||
if(this._audio_handle)
|
if(this._audio_handle)
|
||||||
|
@ -635,7 +661,7 @@ class ClientEntry {
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
name: tr("Change playback latency"),
|
name: tr("Change playback latency"),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
||||||
this._audio_handle.reset_latency_settings();
|
this._audio_handle.reset_latency_settings();
|
||||||
return this._audio_handle.latency_settings();
|
return this._audio_handle.latency_settings();
|
||||||
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
||||||
|
@ -843,7 +869,7 @@ class ClientEntry {
|
||||||
if(variable.key == "client_nickname") {
|
if(variable.key == "client_nickname") {
|
||||||
if(variable.value !== old_value && typeof(old_value) === "string") {
|
if(variable.value !== old_value && typeof(old_value) === "string") {
|
||||||
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
|
if(!(this instanceof LocalClientEntry)) { /* own changes will be logged somewhere else */
|
||||||
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
|
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGED, {
|
||||||
own_client: false,
|
own_client: false,
|
||||||
client: this.log_data(),
|
client: this.log_data(),
|
||||||
new_name: variable.value,
|
new_name: variable.value,
|
||||||
|
@ -1082,7 +1108,7 @@ class ClientEntry {
|
||||||
this.tag.css('padding-left', (5 + (index + 2) * 16) + "px");
|
this.tag.css('padding-left', (5 + (index + 2) * 16) + "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
log_data() : log.server.base.Client {
|
log_data() : server_log.base.Client {
|
||||||
return {
|
return {
|
||||||
client_unique_id: this.properties.client_unique_identifier,
|
client_unique_id: this.properties.client_unique_identifier,
|
||||||
client_name: this.clientNickName(),
|
client_name: this.clientNickName(),
|
||||||
|
@ -1123,7 +1149,7 @@ class ClientEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalClientEntry extends ClientEntry {
|
export class LocalClientEntry extends ClientEntry {
|
||||||
handle: ConnectionHandler;
|
handle: ConnectionHandler;
|
||||||
|
|
||||||
private renaming: boolean;
|
private renaming: boolean;
|
||||||
|
@ -1216,14 +1242,14 @@ class LocalClientEntry extends ClientEntry {
|
||||||
const old_name = this.clientNickName();
|
const old_name = this.clientNickName();
|
||||||
this.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
|
this.handle.serverConnection.command_helper.updateClient("client_nickname", text).then((e) => {
|
||||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, text);
|
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, text);
|
||||||
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGED, {
|
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGED, {
|
||||||
client: this.log_data(),
|
client: this.log_data(),
|
||||||
old_name: old_name,
|
old_name: old_name,
|
||||||
new_name: text,
|
new_name: text,
|
||||||
own_client: true
|
own_client: true
|
||||||
});
|
});
|
||||||
}).catch((e: CommandResult) => {
|
}).catch((e: CommandResult) => {
|
||||||
this.channelTree.client.log.log(log.server.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
|
this.channelTree.client.log.log(server_log.Type.CLIENT_NICKNAME_CHANGE_FAILED, {
|
||||||
reason: e.extra_message
|
reason: e.extra_message
|
||||||
});
|
});
|
||||||
this.openRename();
|
this.openRename();
|
||||||
|
@ -1232,7 +1258,7 @@ class LocalClientEntry extends ClientEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MusicClientProperties extends ClientProperties {
|
export class MusicClientProperties extends ClientProperties {
|
||||||
player_state: number = 0;
|
player_state: number = 0;
|
||||||
player_volume: number = 0;
|
player_volume: number = 0;
|
||||||
|
|
||||||
|
@ -1264,7 +1290,7 @@ class MusicClientProperties extends ClientProperties {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SongInfo {
|
export class SongInfo {
|
||||||
song_id: number = 0;
|
song_id: number = 0;
|
||||||
song_url: string = "";
|
song_url: string = "";
|
||||||
song_invoker: number = 0;
|
song_invoker: number = 0;
|
||||||
|
@ -1277,7 +1303,7 @@ class SongInfo {
|
||||||
song_length: number = 0;
|
song_length: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MusicClientPlayerInfo extends SongInfo {
|
export class MusicClientPlayerInfo extends SongInfo {
|
||||||
bot_id: number = 0;
|
bot_id: number = 0;
|
||||||
player_state: number = 0;
|
player_state: number = 0;
|
||||||
|
|
||||||
|
@ -1290,7 +1316,7 @@ class MusicClientPlayerInfo extends SongInfo {
|
||||||
player_description: string = "";
|
player_description: string = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
class MusicClientEntry extends ClientEntry {
|
export class MusicClientEntry extends ClientEntry {
|
||||||
private _info_promise: Promise<MusicClientPlayerInfo>;
|
private _info_promise: Promise<MusicClientPlayerInfo>;
|
||||||
private _info_promise_age: number = 0;
|
private _info_promise_age: number = 0;
|
||||||
private _info_promise_resolve: any;
|
private _info_promise_resolve: any;
|
||||||
|
@ -1366,7 +1392,7 @@ class MusicClientEntry extends ClientEntry {
|
||||||
this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
|
this.channelTree.client.serverConnection.command_helper.request_playlist_list().then(lists => {
|
||||||
for(const entry of lists) {
|
for(const entry of lists) {
|
||||||
if(entry.playlist_id == this.properties.client_playlist_id) {
|
if(entry.playlist_id == this.properties.client_playlist_id) {
|
||||||
Modals.spawnPlaylistEdit(this.channelTree.client, entry);
|
spawnPlaylistEdit(this.channelTree.client, entry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1435,7 +1461,7 @@ class MusicClientEntry extends ClientEntry {
|
||||||
icon_class: "client-volume",
|
icon_class: "client-volume",
|
||||||
name: tr("Change local volume"),
|
name: tr("Change local volume"),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.spawnChangeVolume(this, true, this._audio_handle.get_volume(), undefined, volume => {
|
spawnChangeVolume(this, true, this._audio_handle.get_volume(), undefined, volume => {
|
||||||
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
this.channelTree.client.settings.changeServer("volume_client_" + this.clientUid(), volume);
|
||||||
this._audio_handle.set_volume(volume);
|
this._audio_handle.set_volume(volume);
|
||||||
});
|
});
|
||||||
|
@ -1450,7 +1476,7 @@ class MusicClientEntry extends ClientEntry {
|
||||||
if(max_volume < 0)
|
if(max_volume < 0)
|
||||||
max_volume = 100;
|
max_volume = 100;
|
||||||
|
|
||||||
Modals.spawnChangeVolume(this, false, this.properties.player_volume, max_volume / 100, value => {
|
spawnChangeVolume(this, false, this.properties.player_volume, max_volume / 100, value => {
|
||||||
if(typeof(value) !== "number")
|
if(typeof(value) !== "number")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1467,7 +1493,7 @@ class MusicClientEntry extends ClientEntry {
|
||||||
type: contextmenu.MenuEntryType.ENTRY,
|
type: contextmenu.MenuEntryType.ENTRY,
|
||||||
name: tr("Change playback latency"),
|
name: tr("Change playback latency"),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
Modals.spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
spawnChangeLatency(this, this._audio_handle.latency_settings(), () => {
|
||||||
this._audio_handle.reset_latency_settings();
|
this._audio_handle.reset_latency_settings();
|
||||||
return this._audio_handle.latency_settings();
|
return this._audio_handle.latency_settings();
|
||||||
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
}, settings => this._audio_handle.latency_settings(settings), this._audio_handle.support_flush ? () => {
|
||||||
|
@ -1482,8 +1508,8 @@ class MusicClientEntry extends ClientEntry {
|
||||||
icon_class: "client-delete",
|
icon_class: "client-delete",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const tag = $.spawn("div").append(MessageHelper.formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
|
const tag = $.spawn("div").append(formatMessage(tr("Do you really want to delete {0}"), this.createChatTag(false)));
|
||||||
Modals.spawnYesNo(tr("Are you sure?"), $.spawn("div").append(tag), result => {
|
spawnYesNo(tr("Are you sure?"), $.spawn("div").append(tag), result => {
|
||||||
if(result) {
|
if(result) {
|
||||||
this.channelTree.client.serverConnection.send_command("musicbotdelete", {
|
this.channelTree.client.serverConnection.send_command("musicbotdelete", {
|
||||||
bot_id: this.properties.client_database_id
|
bot_id: this.properties.client_database_id
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
/// <reference path="client.ts" />
|
import {ChannelTree} from "tc-shared/ui/view";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import {ClientEntry} from "tc-shared/ui/client";
|
||||||
|
import {ChannelEntry} from "tc-shared/ui/channel";
|
||||||
|
|
||||||
class ClientMover {
|
export class ClientMover {
|
||||||
static readonly listener_root = $(document);
|
static readonly listener_root = $(document);
|
||||||
static readonly move_element = $("#mouse-move");
|
static readonly move_element = $("#mouse-move");
|
||||||
readonly channel_tree: ChannelTree;
|
readonly channel_tree: ChannelTree;
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
interface JQuery<TElement = HTMLElement> {
|
import {settings} from "tc-shared/settings";
|
||||||
dividerfy() : this;
|
import {LogCategory} from "tc-shared/log";
|
||||||
|
import * as log from "tc-shared/log";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface JQuery<TElement = HTMLElement> {
|
||||||
|
dividerfy() : this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initialize() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$.fn.dividerfy) {
|
if(!$.fn.dividerfy) {
|
||||||
|
@ -58,8 +68,8 @@ if(!$.fn.dividerfy) {
|
||||||
Math.max(previous_offset.top + previous_element.height(), next_offset.top + next_element.height());
|
Math.max(previous_offset.top + previous_element.height(), next_offset.top + next_element.height());
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let previous = 0;
|
let previous;
|
||||||
let next = 0;
|
let next;
|
||||||
if(current < min) {
|
if(current < min) {
|
||||||
previous = 0;
|
previous = 0;
|
||||||
next = 1;
|
next = 1;
|
||||||
|
@ -89,7 +99,7 @@ if(!$.fn.dividerfy) {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const listener_up = (event: MouseEvent) => {
|
const listener_up = () => {
|
||||||
document.removeEventListener('mousemove', listener_move);
|
document.removeEventListener('mousemove', listener_move);
|
||||||
document.removeEventListener('touchmove', listener_move);
|
document.removeEventListener('touchmove', listener_move);
|
||||||
|
|
|
@ -1,82 +1,80 @@
|
||||||
namespace contextmenu {
|
export interface MenuEntry {
|
||||||
export interface MenuEntry {
|
callback?: () => void;
|
||||||
callback?: () => void;
|
type: MenuEntryType;
|
||||||
type: MenuEntryType;
|
name: (() => string) | string;
|
||||||
name: (() => string) | string;
|
icon_class?: string;
|
||||||
icon_class?: string;
|
icon_path?: string;
|
||||||
icon_path?: string;
|
disabled?: boolean;
|
||||||
disabled?: boolean;
|
visible?: boolean;
|
||||||
visible?: boolean;
|
|
||||||
|
|
||||||
checkbox_checked?: boolean;
|
checkbox_checked?: boolean;
|
||||||
|
|
||||||
invalidPermission?: boolean;
|
invalidPermission?: boolean;
|
||||||
sub_menu?: MenuEntry[];
|
sub_menu?: MenuEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MenuEntryType {
|
export enum MenuEntryType {
|
||||||
CLOSE,
|
CLOSE,
|
||||||
ENTRY,
|
ENTRY,
|
||||||
CHECKBOX,
|
CHECKBOX,
|
||||||
HR,
|
HR,
|
||||||
SUB_MENU
|
SUB_MENU
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Entry {
|
export class Entry {
|
||||||
static HR() {
|
static HR() {
|
||||||
return {
|
return {
|
||||||
callback: () => {},
|
callback: () => {},
|
||||||
type: MenuEntryType.HR,
|
type: MenuEntryType.HR,
|
||||||
name: "",
|
name: "",
|
||||||
icon: ""
|
icon: ""
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
static CLOSE(callback: () => void) {
|
static CLOSE(callback: () => void) {
|
||||||
return {
|
return {
|
||||||
callback: callback,
|
callback: callback,
|
||||||
type: MenuEntryType.CLOSE,
|
type: MenuEntryType.CLOSE,
|
||||||
name: "",
|
name: "",
|
||||||
icon: ""
|
icon: ""
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContextMenuProvider {
|
|
||||||
despawn_context_menu();
|
|
||||||
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]);
|
|
||||||
|
|
||||||
initialize();
|
|
||||||
finalize();
|
|
||||||
|
|
||||||
html_format_enabled() : boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let provider: ContextMenuProvider;
|
|
||||||
export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
|
|
||||||
if(!provider) {
|
|
||||||
console.error(tr("Failed to spawn context menu! Missing provider!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
provider.spawn_context_menu(x, y, ...entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function despawn_context_menu() {
|
|
||||||
if(!provider)
|
|
||||||
return;
|
|
||||||
|
|
||||||
provider.despawn_context_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get_provider() : ContextMenuProvider { return provider; }
|
|
||||||
export function set_provider(_provider: ContextMenuProvider) {
|
|
||||||
provider = _provider;
|
|
||||||
provider.initialize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
export interface ContextMenuProvider {
|
||||||
|
despawn_context_menu();
|
||||||
|
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]);
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
finalize();
|
||||||
|
|
||||||
|
html_format_enabled() : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let provider: ContextMenuProvider;
|
||||||
|
export function spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
|
||||||
|
if(!provider) {
|
||||||
|
console.error(tr("Failed to spawn context menu! Missing provider!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.spawn_context_menu(x, y, ...entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function despawn_context_menu() {
|
||||||
|
if(!provider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
provider.despawn_context_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_provider() : ContextMenuProvider { return provider; }
|
||||||
|
export function set_provider(_provider: ContextMenuProvider) {
|
||||||
|
provider = _provider;
|
||||||
|
provider.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
class HTMLContextMenuProvider implements ContextMenuProvider {
|
||||||
private _global_click_listener: (event) => any;
|
private _global_click_listener: (event) => any;
|
||||||
private _context_menu: JQuery;
|
private _context_menu: JQuery;
|
||||||
private _close_callbacks: (() => any)[] = [];
|
private _close_callbacks: (() => any)[] = [];
|
||||||
|
@ -118,10 +116,10 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private generate_tag(entry: contextmenu.MenuEntry) : JQuery {
|
private generate_tag(entry: MenuEntry) : JQuery {
|
||||||
if(entry.type == contextmenu.MenuEntryType.HR) {
|
if(entry.type == MenuEntryType.HR) {
|
||||||
return $.spawn("hr");
|
return $.spawn("hr");
|
||||||
} else if(entry.type == contextmenu.MenuEntryType.ENTRY) {
|
} else if(entry.type == MenuEntryType.ENTRY) {
|
||||||
let icon = entry.icon_class;
|
let icon = entry.icon_class;
|
||||||
if(!icon || icon.length == 0) icon = "icon_empty";
|
if(!icon || icon.length == 0) icon = "icon_empty";
|
||||||
else icon = "icon " + icon;
|
else icon = "icon " + icon;
|
||||||
|
@ -141,7 +139,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return tag;
|
return tag;
|
||||||
} else if(entry.type == contextmenu.MenuEntryType.CHECKBOX) {
|
} else if(entry.type == MenuEntryType.CHECKBOX) {
|
||||||
let checkbox = $.spawn("label").addClass("ccheckbox");
|
let checkbox = $.spawn("label").addClass("ccheckbox");
|
||||||
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
|
$.spawn("input").attr("type", "checkbox").prop("checked", !!entry.checkbox_checked).appendTo(checkbox);
|
||||||
$.spawn("span").addClass("checkmark").appendTo(checkbox);
|
$.spawn("span").addClass("checkmark").appendTo(checkbox);
|
||||||
|
@ -161,7 +159,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return tag;
|
return tag;
|
||||||
} else if(entry.type == contextmenu.MenuEntryType.SUB_MENU) {
|
} else if(entry.type == MenuEntryType.SUB_MENU) {
|
||||||
let icon = entry.icon_class;
|
let icon = entry.icon_class;
|
||||||
if(!icon || icon.length == 0) icon = "icon_empty";
|
if(!icon || icon.length == 0) icon = "icon_empty";
|
||||||
else icon = "icon " + icon;
|
else icon = "icon " + icon;
|
||||||
|
@ -187,7 +185,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
return $.spawn("div").text("undefined");
|
return $.spawn("div").text("undefined");
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn_context_menu(x: number, y: number, ...entries: contextmenu.MenuEntry[]) {
|
spawn_context_menu(x: number, y: number, ...entries: MenuEntry[]) {
|
||||||
this._visible = true;
|
this._visible = true;
|
||||||
|
|
||||||
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
|
let menu_tag = this._context_menu || (this._context_menu = $(".context-menu"));
|
||||||
|
@ -200,7 +198,7 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(entry.type == contextmenu.MenuEntryType.CLOSE) {
|
if(entry.type == MenuEntryType.CLOSE) {
|
||||||
if(entry.callback)
|
if(entry.callback)
|
||||||
this._close_callbacks.push(entry.callback);
|
this._close_callbacks.push(entry.callback);
|
||||||
} else
|
} else
|
||||||
|
@ -225,5 +223,4 @@ class HTMLContextMenuProvider implements contextmenu.ContextMenuProvider {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
set_provider(new HTMLContextMenuProvider());
|
||||||
contextmenu.set_provider(new HTMLContextMenuProvider());
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue