commit
ddf099e4e2
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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>
|
|
||||||
*/
|
|
Loading…
Reference in New Issue