Merge pull request #175 from TeaSpeak/develop

Develop
master ddf099e
WolverinDEV 2021-02-26 19:56:06 +01:00 committed by GitHub
commit ddf099e4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 50 additions and 227 deletions

View File

@ -2,7 +2,14 @@ import * as loader from "tc-loader";
import {Stage} from "tc-loader"; import {Stage} from "tc-loader";
import {config} from "tc-shared/i18n/localize"; import {config} from "tc-shared/i18n/localize";
import {getBackend} from "tc-shared/backend"; import {getBackend} from "tc-shared/backend";
import {ClientServiceConfig, ClientServiceInvite, ClientServices, ClientSessionType, LocalAgent} from "tc-services"; import {
ClientServiceConfig,
ClientServiceInvite,
ClientServices,
ClientSessionType,
initializeClientServices,
LocalAgent
} from "tc-services";
import translation_config = config.translation_config; import translation_config = config.translation_config;
@ -12,6 +19,7 @@ export let clientServiceInvite: ClientServiceInvite;
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 30, priority: 30,
function: async () => { function: async () => {
initializeClientServices();
clientServices = new ClientServices(new class implements ClientServiceConfig { clientServices = new ClientServices(new class implements ClientServiceConfig {
getServiceHost(): string { getServiceHost(): string {
//return "localhost:1244"; //return "localhost:1244";

View File

@ -142,6 +142,8 @@ export enum ErrorCode {
SERVER_CONNECT_BANNED = 0xD01, SERVER_CONNECT_BANNED = 0xD01,
BAN_FLOODING = 0xD03, BAN_FLOODING = 0xD03,
TOKEN_INVALID_ID = 0xF00, TOKEN_INVALID_ID = 0xF00,
TOKEN_EXPIRED = 0xf10,
TOKEN_USE_LIMIT_EXCEEDED = 0xf11,
WEB_HANDSHAKE_INVALID = 0x1000, WEB_HANDSHAKE_INVALID = 0x1000,
WEB_HANDSHAKE_UNSUPPORTED = 0x1001, WEB_HANDSHAKE_UNSUPPORTED = 0x1001,
WEB_HANDSHAKE_IDENTITY_UNSUPPORTED = 0x1002, WEB_HANDSHAKE_IDENTITY_UNSUPPORTED = 0x1002,

View File

@ -51,6 +51,8 @@ import "./clientservice";
import "./text/bbcode/InviteController"; import "./text/bbcode/InviteController";
import {clientServiceInvite} from "tc-shared/clientservice"; import {clientServiceInvite} from "tc-shared/clientservice";
import {ActionResult} from "tc-services"; import {ActionResult} from "tc-services";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ErrorCode} from "tc-shared/connection/ErrorCode";
assertMainApplication(); assertMainApplication();
@ -218,6 +220,8 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
const channel = parameters.getValue(AppParameters.KEY_CONNECT_CHANNEL, undefined); const channel = parameters.getValue(AppParameters.KEY_CONNECT_CHANNEL, undefined);
const channelPassword = parameters.getValue(AppParameters.KEY_CONNECT_CHANNEL_PASSWORD, undefined); const channelPassword = parameters.getValue(AppParameters.KEY_CONNECT_CHANNEL_PASSWORD, undefined);
const connectToken = parameters.getValue(AppParameters.KEY_CONNECT_TOKEN, undefined);
if(!targetServerConnection) { if(!targetServerConnection) {
targetServerConnection = server_connections.getActiveConnectionHandler(); targetServerConnection = server_connections.getActiveConnectionHandler();
if(targetServerConnection.connected) { if(targetServerConnection.connected) {
@ -229,12 +233,29 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
if(targetServerConnection.getCurrentServerUniqueId() === serverUniqueId) { if(targetServerConnection.getCurrentServerUniqueId() === serverUniqueId) {
/* Just join the new channel and may use the token (before) */ /* Just join the new channel and may use the token (before) */
/* TODO: Use the token! */ if(connectToken) {
let containsToken = false; try {
await targetServerConnection.serverConnection.send_command("tokenuse", { token: connectToken }, { process_result: false });
} catch (error) {
if(error instanceof CommandResult) {
if(error.id === ErrorCode.TOKEN_INVALID_ID) {
targetServerConnection.log.log("error.custom", { message: tr("Try to use invite key token but the token is invalid.")});
} else if(error.id == ErrorCode.TOKEN_EXPIRED) {
targetServerConnection.log.log("error.custom", { message: tr("Try to use invite key token but the token is expired.")});
} else if(error.id === ErrorCode.TOKEN_USE_LIMIT_EXCEEDED) {
targetServerConnection.log.log("error.custom", { message: tr("Try to use invite key token but the token has been used too many times.")});
} else {
targetServerConnection.log.log("error.custom", { message: tra("Try to use invite key token but an error occurred: {}", error.formattedMessage())});
}
} else {
logError(LogCategory.GENERAL, tr("Failed to use token: {}"), error);
}
}
}
if(!channel) { if(!channel) {
/* No need to join any channel */ /* No need to join any channel */
if(!containsToken) { if(!connectToken) {
createInfoModal(tr("Already connected"), tr("You're already connected to the target server.")).open(); createInfoModal(tr("Already connected"), tr("You're already connected to the target server.")).open();
} else { } else {
/* Don't show a message since a token has been used */ /* Don't show a message since a token has been used */
@ -263,7 +284,8 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
} }
targetChannel.setCachedHashedPassword(channelPassword); targetChannel.setCachedHashedPassword(channelPassword);
if(await targetChannel.joinChannel()) { /* Force join the channel. Either we have the password, can ignore the password or we don't want to join. */
if(await targetChannel.joinChannel(true)) {
return { status: "success" }; return { status: "success" };
} else { } else {
/* TODO: More detail? */ /* TODO: More detail? */
@ -277,7 +299,7 @@ async function doHandleConnectRequest(serverAddress: string, serverUniqueId: str
nicknameSpecified: false, nicknameSpecified: false,
profile: profile, profile: profile,
token: undefined, token: connectToken,
serverPassword: serverPassword, serverPassword: serverPassword,
serverPasswordHashed: passwordsHashed, serverPasswordHashed: passwordsHashed,
@ -467,7 +489,9 @@ const task_connect_handler: loader.Task = {
preventWelcomeUI = true; preventWelcomeUI = true;
loader.register_task(loader.Stage.LOADED, { loader.register_task(loader.Stage.LOADED, {
priority: 0, priority: 0,
function: async () => handleConnectRequest(address, undefined, AppParameters.Instance), function: async () => {
handleConnectRequest(address, undefined, AppParameters.Instance).then(undefined);
},
name: tr("default url connect") name: tr("default url connect")
}); });
loader.register_task(loader.Stage.LOADED, task_teaweb_starter); loader.register_task(loader.Stage.LOADED, task_teaweb_starter);

View File

@ -1,4 +1,3 @@
import * as log from "../log";
import {LogCategory, logDebug, logTrace, logWarn} from "../log"; import {LogCategory, logDebug, logTrace, logWarn} from "../log";
import { import {
CodeToken, CodeToken,

View File

@ -699,7 +699,9 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
} }
let passwordPrompted = false;
if(this.properties.channel_flag_password === true && !this.cachedPasswordHash && !ignorePasswordFlag) { if(this.properties.channel_flag_password === true && !this.cachedPasswordHash && !ignorePasswordFlag) {
passwordPrompted = true;
const password = await this.requestChannelPassword(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD); const password = await this.requestChannelPassword(PermissionType.B_CHANNEL_JOIN_IGNORE_PASSWORD);
if(typeof password === "undefined") { if(typeof password === "undefined") {
/* aborted */ /* aborted */
@ -717,8 +719,12 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
return true; return true;
} catch (error) { } catch (error) {
if(error instanceof CommandResult) { if(error instanceof CommandResult) {
if(error.id == ErrorCode.CHANNEL_INVALID_PASSWORD) { //Invalid password if(error.id == ErrorCode.CHANNEL_INVALID_PASSWORD) {
this.invalidateCachedPassword(); this.invalidateCachedPassword();
if(!passwordPrompted) {
/* It seems like our cached password isn't valid any more */
return await this.joinChannel(false);
}
} }
} }
return false; return false;

View File

@ -902,6 +902,7 @@ export class ChannelTree {
spawnCreateChannel(parent?: ChannelEntry) { spawnCreateChannel(parent?: ChannelEntry) {
spawnChannelEditNew(this.client, undefined, parent, (properties, permissions) => { spawnChannelEditNew(this.client, undefined, parent, (properties, permissions) => {
properties["cpid"] = parent ? parent.channelId : 0; properties["cpid"] = parent ? parent.channelId : 0;
logDebug(LogCategory.CHANNEL, tr("Creating a new channel.\nProperties: %o\nPermissions: %o"), properties); logDebug(LogCategory.CHANNEL, tr("Creating a new channel.\nProperties: %o\nPermissions: %o"), properties);
this.client.serverConnection.send_command("channelcreate", properties).then(() => { this.client.serverConnection.send_command("channelcreate", properties).then(() => {
let channel = this.find_channel_by_name(properties.channel_name, parent, true); let channel = this.find_channel_by_name(properties.channel_name, parent, true);
@ -910,6 +911,7 @@ export class ChannelTree {
return; return;
} }
channel.setCachedHashedPassword(properties.channel_password);
if(permissions && permissions.length > 0) { if(permissions && permissions.length > 0) {
let perms = []; let perms = [];
for(let perm of permissions) { for(let perm of permissions) {

View File

@ -5,7 +5,6 @@ import * as log from "../log";
import {LogCategory, logInfo, LogType} from "../log"; import {LogCategory, logInfo, LogType} from "../log";
import {Sound} from "../sound/Sounds"; import {Sound} from "../sound/Sounds";
import * as bookmarks from "../bookmarks"; import * as bookmarks from "../bookmarks";
import {spawnInviteEditor} from "../ui/modal/ModalInvite";
import {openServerInfo} from "../ui/modal/ModalServerInfo"; import {openServerInfo} from "../ui/modal/ModalServerInfo";
import {createServerModal} from "../ui/modal/ModalServerEdit"; import {createServerModal} from "../ui/modal/ModalServerEdit";
import {spawnIconSelect} from "../ui/modal/ModalIconSelect"; import {spawnIconSelect} from "../ui/modal/ModalIconSelect";

View File

@ -1,217 +0,0 @@
import {settings, Settings} from "../../settings";
import {createModal, Modal} from "../../ui/elements/Modal";
import {ConnectionHandler} from "../../ConnectionHandler";
import {ServerAddress} from "../../tree/Server";
import {tr} from "tc-shared/i18n/localize";
type URLGeneratorSettings = {
flag_direct: boolean,
flag_resolved: boolean
}
const DefaultGeneratorSettings: URLGeneratorSettings = {
flag_direct: true,
flag_resolved: false
};
type URLGenerator = {
generate: (properties: {
address: ServerAddress,
resolved_address: ServerAddress
} & URLGeneratorSettings) => string;
setting_available: (key: keyof URLGeneratorSettings) => boolean;
};
const build_url = (base, params) => {
if (Object.keys(params).length == 0)
return base;
return base + "?" + Object.keys(params)
.map(e => e + "=" + encodeURIComponent(params[e]))
.join("&");
};
//TODO: Server password
const url_generators: { [key: string]: URLGenerator } = {
"tea-web": {
generate: properties => {
const address = properties.resolved_address ? properties.resolved_address : properties.address;
const address_str = address.host + (address.port === 9987 ? "" : ":" + address.port);
const parameter = "connect_default=" + (properties.flag_direct ? 1 : 0) + "&connect_address=" + encodeURIComponent(address_str);
let pathbase = "";
if (document.location.protocol !== 'https:') {
/*
* Seems to be a test environment or the TeaClient for localhost where we dont have to use https.
*/
pathbase = "https://web.teaspeak.de/";
} else if (document.location.hostname === "localhost" || document.location.host.startsWith("127.")) {
pathbase = "https://web.teaspeak.de/";
} else {
pathbase = document.location.origin + document.location.pathname;
}
return pathbase + "?" + parameter;
},
setting_available: setting => {
return {
flag_direct: true,
flag_resolved: true
}[setting] || false;
}
},
"tea-client": {
generate: properties => {
const address = properties.resolved_address ? properties.resolved_address : properties.address;
let parameters = {
connect_default: properties.flag_direct ? 1 : 0
};
if (address.port != 9987)
parameters["port"] = address.port;
return build_url("teaclient://" + address.host + "/", parameters);
},
setting_available: setting => {
return {
flag_direct: true,
flag_resolved: true
}[setting] || false;
}
},
"teamspeak": {
generate: properties => {
const address = properties.resolved_address ? properties.resolved_address : properties.address;
let parameters = {};
if (address.port != 9987)
parameters["port"] = address.port;
/*
ts3server://<host>?
port=9987
nickname=UserNickname
password=serverPassword
channel=MyDefaultChannel
cid=channelID
channelpassword=defaultChannelPassword
token=TokenKey
addbookmark=MyBookMarkLabel
*/
return build_url("ts3server://" + address.host + "/", parameters);
},
setting_available: setting => {
return {
flag_direct: false,
flag_resolved: true
}[setting] || false;
}
}
};
export function spawnInviteEditor(connection: ConnectionHandler) {
let modal: Modal;
modal = createModal({
header: tr("Invite URL creator"),
body: () => {
let template = $("#tmpl_invite").renderTag();
template.find(".button-close").on('click', event => modal.close());
return template;
},
footer: undefined,
min_width: "20em",
width: "50em"
});
modal.htmlTag.find(".modal-body").addClass("modal-invite");
const button_copy = modal.htmlTag.find(".button-copy");
const input_type = modal.htmlTag.find(".property-type select");
const label_output = modal.htmlTag.find(".text-output");
const invite_settings = [
{
key: "flag_direct",
node: modal.htmlTag.find(".flag-direct-connect input"),
value: node => node.prop('checked'),
set_value: (node, value) => node.prop('checked', value == "1"),
disable: (node, flag) => node.prop('disabled', flag)
.firstParent('.checkbox').toggleClass('disabled', flag)
},
{
key: "flag_resolved",
node: modal.htmlTag.find(".flag-resolved-address input"),
value: node => node.prop('checked'),
set_value: (node, value) => node.prop('checked', value == "1"),
disable: (node, flag) => node.prop('disabled', flag)
.firstParent('.checkbox').toggleClass('disabled', flag)
}
];
const update_buttons = () => {
const generator = url_generators[input_type.val() as string];
if (!generator) {
for (const s of invite_settings)
s.disable(s.node, true);
return;
}
for (const s of invite_settings)
s.disable(s.node, !generator.setting_available(s.key as any));
};
const update_link = () => {
const generator = url_generators[input_type.val() as string];
if (!generator) {
button_copy.prop('disabled', true);
label_output.text(tr("Missing link generator"));
return;
}
button_copy.prop('disabled', false);
const properties = {
address: connection.channelTree.server.remote_address,
resolved_address: connection.channelTree.client.serverConnection.remote_address()
};
for (const s of invite_settings)
properties[s.key] = s.value(s.node);
label_output.text(generator.generate(properties as any));
};
for (const s of invite_settings) {
s.node.on('change keyup', () => {
settings.setValue(Settings.FN_INVITE_LINK_SETTING(s.key), s.value(s.node));
update_link()
});
s.set_value(s.node, settings.getValue(Settings.FN_INVITE_LINK_SETTING(s.key), DefaultGeneratorSettings[s.key]));
}
input_type.on('change', () => {
settings.setValue(Settings.KEY_LAST_INVITE_LINK_TYPE, input_type.val() as string);
update_buttons();
update_link();
}).val(settings.getValue(Settings.KEY_LAST_INVITE_LINK_TYPE));
button_copy.on('click', event => {
label_output.select();
document.execCommand('copy');
});
update_buttons();
update_link();
modal.open();
}
/*
<option value="tea-web">{{tr "TeaWeb" /}}</option>
<option value="tea-client">{{tr "TeaClient" /}}</option>
<option value="teamspeak">{{tr "TeamSpeak" /}}</option>
*/