Starting with webpack

This commit is contained in:
WolverinDEV 2020-03-27 23:36:57 +01:00
parent a6f0fcdda9
commit 13b65a1f35
114 changed files with 14384 additions and 3963 deletions

6186
dist/bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

3889
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,29 +17,41 @@
"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": "webpack --config webpack.config.js",
"watch": "webpack --watch"
}, },
"author": "TeaSpeak (WolverinDEV)", "author": "TeaSpeak (WolverinDEV)",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.38.0", "@types/emscripten": "^1.38.0",
"@types/jquery": "^3.3.31", "@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",
"clean-css": "^4.2.1", "clean-css": "^4.2.1",
"css-loader": "^3.4.2",
"csso-cli": "^2.0.2", "csso-cli": "^2.0.2",
"fs-extra": "latest",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"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",
"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",
"ts-loader": "^6.2.2",
"ttypescript": "^1.5.10", "ttypescript": "^1.5.10",
"typescript": "3.6.5", "typescript": "3.6.5",
"wat2wasm": "^1.0.2", "wat2wasm": "^1.0.2",
"fs-extra": "latest" "webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -50,6 +62,8 @@
}, },
"homepage": "https://www.teaspeak.de", "homepage": "https://www.teaspeak.de",
"dependencies": { "dependencies": {
"@types/fs-extra": "^8.0.1" "@types/fs-extra": "^8.0.1",
"react": "^16.13.1",
"react-dom": "^16.13.1"
} }
} }

View file

@ -94,96 +94,7 @@
} }
&.channel { &.channel {
display: flex;
flex-direction: column;
.container-channel {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
width: 100%;
min-height: 16px;
align-items: center;
cursor: pointer;
.channel-type {
flex-grow: 0;
flex-shrink: 0;
margin-right: 2px;
}
.container-channel-name {
display: flex;
flex-direction: row;
flex-grow: 1;
flex-shrink: 1;
justify-content: left;
max-width: 100%; /* important for the repetitive channel name! */
overflow-x: hidden;
height: 16px;
&.align-right {
justify-content: right;
}
&.align-center, &.align-repetitive {
justify-content: center;
}
.channel-name {
align-self: center;
color: $channel_tree_entry_text_color;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&.align-repetitive {
.channel-name {
text-overflow: clip;
}
}
}
.icons {
display: flex;
flex-direction: row;
flex-grow: 0;
flex-shrink: 0;
}
&.move-selected {
border-bottom: 1px solid black;
}
.show-channel-normal-only {
display: none;
&.channel-normal {
display: block;
}
}
.icon_no_sound {
display: flex;
}
}
.container-clients {
display: flex;
flex-direction: column;
}
} }
&.client { &.client {
@ -272,40 +183,6 @@
} }
} }
&.channel .container-channel, &.client, &.server {
.marker-text-unread {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: #a814147F;
opacity: 1;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 24px;
background: -moz-linear-gradient(left, rgba(168,20,20,.18) 0%, rgba(168,20,20,0) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(left, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to right, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
}
&.hidden {
opacity: 0;
}
@include transition(opacity $button_hover_animation_time);
}
}
} }
} }

View file

@ -1,8 +1,8 @@
interface Window { export interface Window {
BroadcastChannel: BroadcastChannel; BroadcastChannel: BroadcastChannel;
} }
namespace bipc { export namespace bipc {
export interface BroadcastMessage { export interface BroadcastMessage {
timestamp: number; timestamp: number;
receiver: string; receiver: string;

View file

@ -1,6 +1,6 @@
/// <reference path="log.ts" /> /// <reference path="log.ts" />
/// <reference path="proto.ts" /> /// <reference path="proto.ts" />
/// <reference path="ui/view.ts" /> /// <reference path="channel-tree/view.ts" />
/// <reference path="settings.ts" /> /// <reference path="settings.ts" />
/// <reference path="FileManager.ts" /> /// <reference path="FileManager.ts" />
/// <reference path="permission/PermissionManager.ts" /> /// <reference path="permission/PermissionManager.ts" />
@ -8,7 +8,17 @@
/// <reference path="ui/frames/ControlBar.ts" /> /// <reference path="ui/frames/ControlBar.ts" />
/// <reference path="connection/ConnectionBase.ts" /> /// <reference path="connection/ConnectionBase.ts" />
enum DisconnectReason { import {ChannelTree} from "./channel-tree/view";
import {LocalClientEntry} from "./channel-tree/client";
import {ServerAddress} from "./channel-tree/server";
import {ChannelEntry} from "./channel-tree/channel";
import {AbstractServerConnection} from "./connection/ConnectionBase";
import {PermissionManager} from "./permission/PermissionManager";
import {GroupManager} from "./permission/GroupManager";
import {ServerSettings} from "./settings";
import {Hostbanner} from "./ui/frames/hostbanner";
export enum DisconnectReason {
HANDLER_DESTROYED, HANDLER_DESTROYED,
REQUESTED, REQUESTED,
DNS_FAILED, DNS_FAILED,
@ -28,7 +38,7 @@ enum DisconnectReason {
UNKNOWN UNKNOWN
} }
enum ConnectionState { export enum ConnectionState {
UNCONNECTED, UNCONNECTED,
CONNECTING, CONNECTING,
INITIALISING, INITIALISING,
@ -36,7 +46,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 +61,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 +78,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,10 +89,10 @@ interface ConnectParameters {
auto_reconnect_attempt?: boolean; auto_reconnect_attempt?: boolean;
} }
class ConnectionHandler { export class ConnectionHandler {
channelTree: ChannelTree; channelTree: ChannelTree;
serverConnection: connection.AbstractServerConnection; serverConnection: AbstractServerConnection;
fileManager: FileManager; fileManager: FileManager;

View file

@ -1,21 +1,26 @@
/// <reference path="connection/CommandHandler.ts" /> import {ChannelEntry} from "./channel-tree/channel";
/// <reference path="connection/ConnectionBase.ts" /> import {AbstractCommandHandler, ServerCommand} from "./connection/ConnectionBase";
import {ConnectionHandler} from "./ConnectionHandler";
import {CommandResult} from "./connection/ServerConnectionDeclaration";
import {log, LogCategory} from "./log";
import {ClientEntry} from "./channel-tree/client";
import {hex} from "./crypto/hex";
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 namespace transfer {
export interface TransferKey { export interface TransferKey {
client_transfer_id: number; client_transfer_id: number;
server_transfer_id: number; server_transfer_id: number;
@ -152,7 +157,7 @@ class RequestFileUpload implements transfer.UploadTransfer {
} }
} }
class FileManager extends connection.AbstractCommandHandler { class FileManager extends AbstractCommandHandler {
handle: ConnectionHandler; handle: ConnectionHandler;
icons: IconManager; icons: IconManager;
avatars: AvatarManager; avatars: AvatarManager;
@ -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);

View file

@ -1,4 +1,10 @@
namespace messages.formatter { import {Settings, settings} from "./settings";
import {contextmenu} from "./ui/elements/context_menu";
import {image_preview} from "./ui/frames/image_preview";
import {guid} from "./crypto/uid";
declare const xbbcode;
export namespace messages.formatter {
export namespace bbcode { export namespace bbcode {
const sanitizer_escaped = (key: string) => "[-- sescaped: " + key + " --]"; 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_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}) --]/;

View file

@ -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,7 +118,7 @@ enum KeyCode {
KEY_META = 224 KEY_META = 224
} }
namespace ppt { export namespace ppt {
export enum EventType { export enum EventType {
KEY_PRESS, KEY_PRESS,
KEY_RELEASE, KEY_RELEASE,

View file

@ -1,14 +1,6 @@
namespace bookmarks { import {profiles} from "./profiles/ConnectionProfile";
function guid() {
function s4() {
return Math
.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
export namespace bookmarks {
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => { export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile(); const profile = profiles.find_profile(mark.connect_profile) || profiles.default_profile();
if(profile.valid()) { if(profile.valid()) {

View file

@ -1,12 +1,15 @@
/// <reference path="view.ts" /> /// <reference path="view.ts" />
/// <reference path="../utils/helpers.ts" /> /// <reference path="../utils/helpers.ts" />
enum ChannelType { import {ChannelTree} from "./view";
import {ClientEntry} from "./client";
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 +17,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 +58,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;

View file

@ -1,8 +1,11 @@
/// <reference path="channel.ts" /> /// <reference path="channel.ts" />
/// <reference path="modal/ModalChangeVolume.ts" /> /// <reference path="../ui/modal/ModalChangeVolume.ts" />
/// <reference path="client_move.ts" /> /// <reference path="../ui/client_move.ts" />
enum ClientType { import {ChannelEntry} from "./channel";
import {ChannelTree} from "./view";
export enum ClientType {
CLIENT_VOICE, CLIENT_VOICE,
CLIENT_QUERY, CLIENT_QUERY,
CLIENT_INTERNAL, CLIENT_INTERNAL,
@ -11,7 +14,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 +60,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,7 +112,7 @@ 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: events.Registry<events.channel_tree.client>;
protected _clientId: number; protected _clientId: number;
@ -1123,7 +1126,7 @@ class ClientEntry {
} }
} }
class LocalClientEntry extends ClientEntry { export class LocalClientEntry extends ClientEntry {
handle: ConnectionHandler; handle: ConnectionHandler;
private renaming: boolean; private renaming: boolean;
@ -1232,7 +1235,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 +1267,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 +1280,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 +1293,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;

View file

@ -1,7 +1,9 @@
/// <reference path="channel.ts" /> /// <reference path="channel.ts" />
/// <reference path="modal/ModalServerEdit.ts" /> /// <reference path="../ui/modal/ModalServerEdit.ts" />
class ServerProperties { import {ChannelTree} from "./view";
export class ServerProperties {
virtualserver_host: string = ""; virtualserver_host: string = "";
virtualserver_port: number = 0; virtualserver_port: number = 0;
@ -78,7 +80,7 @@ class ServerProperties {
virtualserver_total_bytes_uploaded: number = 0; virtualserver_total_bytes_uploaded: number = 0;
} }
interface ServerConnectionInfo { export interface ServerConnectionInfo {
connection_filetransfer_bandwidth_sent: number; connection_filetransfer_bandwidth_sent: number;
connection_filetransfer_bandwidth_received: number; connection_filetransfer_bandwidth_received: number;
@ -103,12 +105,12 @@ interface ServerConnectionInfo {
connection_ping: number; connection_ping: number;
} }
interface ServerAddress { export interface ServerAddress {
host: string; host: string;
port: number; port: number;
} }
class ServerEntry { export class ServerEntry {
remote_address: ServerAddress; remote_address: ServerAddress;
channelTree: ChannelTree; channelTree: ChannelTree;
properties: ServerProperties; properties: ServerProperties;

View file

@ -4,12 +4,12 @@
/// <reference path="client.ts" /> /// <reference path="client.ts" />
/// <reference path="server.ts" /> /// <reference path="server.ts" />
/// <reference path="../bookmarks.ts" /> /// <reference path="../bookmarks.ts" />
/// <reference path="elements/context_menu.ts" /> /// <reference path="../ui/elements/context_menu.ts" />
/// <reference path="modal/ModalCreateChannel.ts" /> /// <reference path="../ui/modal/ModalCreateChannel.ts" />
/// <reference path="../../backend/ppt.d.ts" /> /// <reference path="../../backend/ppt.d.ts" />
class ChannelTree { export class ChannelTree {
client: ConnectionHandler; client: ConnectionHandler;
server: ServerEntry; server: ServerEntry;

File diff suppressed because it is too large Load diff

View file

@ -1,448 +1,460 @@
namespace connection { import {
export class CommandHelper extends AbstractCommandHandler { ClientNameInfo,
private _who_am_i: any; CommandResult,
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {}; ErrorID,
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {}; Playlist, PlaylistInfo, PlaylistSong,
QueryList,
QueryListEntry, ServerGroupClient
} from "./ServerConnectionDeclaration";
import {ChannelEntry} from "../channel-tree/channel";
import {ChatType} from "../ui/frames/chat";
import {ClientEntry} from "../channel-tree/client";
import {AbstractCommandHandler, ServerCommand, SingleCommandHandler} from "./ConnectionBase";
import {log, LogCategory} from "../log";
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);
}); });
} });
} }
} }

View file

@ -1,216 +1,222 @@
namespace connection { import {ConnectionHandler, ConnectionState} from "../ConnectionHandler";
export interface CommandOptions { import {ServerAddress} from "../channel-tree/server";
flagset?: string[]; /* default: [] */ import {CommandResult} from "./ServerConnectionDeclaration";
process_result?: boolean; /* default: true */ import {RecorderProfile} from "../voice/RecorderProfile";
import {CommandHelper} from "./CommandHelper";
import {connection} from "./HandshakeHandler";
import HandshakeHandler = connection.HandshakeHandler;
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 */
export class ServerCommand {
abstract ping() : { command: string;
native: number, arguments: any[];
javascript?: number }
};
} export abstract class AbstractCommandHandler {
readonly connection: AbstractServerConnection;
export namespace voice {
export enum PlayerState { handler_boss: AbstractCommandHandlerBoss | undefined;
PREBUFFERING, volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */
PLAYING,
BUFFERING, ignore_consumed: boolean = false;
STOPPING,
STOPPED protected constructor(connection: AbstractServerConnection) {
} this.connection = connection;
}
export type LatencySettings = {
min_buffer: number; /* milliseconds */ /**
max_buffer: number; /* milliseconds */ * @return If the command should be consumed
} */
abstract handle_command(command: ServerCommand) : boolean;
export interface VoiceClient { }
client_id: number;
export interface SingleCommandHandler {
callback_playback: () => any; name?: string;
callback_stopped: () => any; command?: string;
timeout?: number;
callback_state_changed: (new_state: PlayerState) => any;
/* if the return is true then the command handler will be removed */
get_state() : PlayerState; function: (command: ServerCommand) => boolean;
}
get_volume() : number;
set_volume(volume: number) : void; export abstract class AbstractCommandHandlerBoss {
readonly connection: AbstractServerConnection;
abort_replay(); protected command_handlers: AbstractCommandHandler[] = [];
/* TODO: Timeout */
support_latency_settings() : boolean; protected single_command_handler: SingleCommandHandler[] = [];
reset_latency_settings(); protected constructor(connection: AbstractServerConnection) {
latency_settings(settings?: LatencySettings) : LatencySettings; this.connection = connection;
}
support_flush() : boolean;
flush(); destroy() {
} this.command_handlers = undefined;
this.single_command_handler = undefined;
export abstract class AbstractVoiceConnection { }
readonly connection: AbstractServerConnection;
register_handler(handler: AbstractCommandHandler) {
protected constructor(connection: AbstractServerConnection) { if(!handler.volatile_handler_boss && handler.handler_boss)
this.connection = connection; throw "handler already registered";
}
this.command_handlers.remove(handler); /* just to be sure */
abstract connected() : boolean; this.command_handlers.push(handler);
abstract encoding_supported(codec: number) : boolean; handler.handler_boss = this;
abstract decoding_supported(codec: number) : boolean; }
abstract register_client(client_id: number) : VoiceClient; unregister_handler(handler: AbstractCommandHandler) {
abstract available_clients() : VoiceClient[]; if(!handler.volatile_handler_boss && handler.handler_boss !== this) {
abstract unregister_client(client: VoiceClient) : Promise<void>; console.warn(tr("Tried to unregister command handler which does not belong to the handler boss"));
return;
abstract voice_recorder() : RecorderProfile; }
abstract acquire_voice_recorder(recorder: RecorderProfile | undefined) : Promise<void>;
this.command_handlers.remove(handler);
abstract get_encoder_codec() : number; handler.handler_boss = undefined;
abstract set_encoder_codec(codec: number); }
}
}
register_single_handler(handler: SingleCommandHandler) {
export class ServerCommand { this.single_command_handler.push(handler);
command: string; }
arguments: any[];
} remove_single_handler(handler: SingleCommandHandler) {
this.single_command_handler.remove(handler);
export abstract class AbstractCommandHandler { }
readonly connection: AbstractServerConnection;
handlers() : AbstractCommandHandler[] {
handler_boss: AbstractCommandHandlerBoss | undefined; return this.command_handlers;
volatile_handler_boss: boolean = false; /* if true than the command handler could be registered twice to two or more handlers */ }
ignore_consumed: boolean = false; invoke_handle(command: ServerCommand) : boolean {
let flag_consumed = false;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection; for(const handler of this.command_handlers) {
} try {
if(!flag_consumed || handler.ignore_consumed)
/** flag_consumed = flag_consumed || handler.handle_command(command);
* @return If the command should be consumed } catch(error) {
*/ console.error(tr("Failed to invoke command handler. Invocation results in an exception: %o"), error);
abstract handle_command(command: ServerCommand) : boolean; }
} }
export interface SingleCommandHandler { for(const handler of [...this.single_command_handler]) {
name?: string; if(handler.command && handler.command != command.command)
command?: string; continue;
timeout?: number;
try {
/* if the return is true then the command handler will be removed */ if(handler.function(command))
function: (command: ServerCommand) => boolean; this.single_command_handler.remove(handler);
} } catch(error) {
console.error(tr("Failed to invoke single command handler. Invocation results in an exception: %o"), error);
export abstract class AbstractCommandHandlerBoss { }
readonly connection: AbstractServerConnection; }
protected command_handlers: AbstractCommandHandler[] = [];
/* TODO: Timeout */ return flag_consumed;
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;
}
} }
} }

View file

@ -1,4 +1,15 @@
namespace connection { import {AbstractServerConnection} from "./ConnectionBase";
import {ConnectParameters, DisconnectReason} from "../ConnectionHandler";
import {profiles} from "../profiles/ConnectionProfile";
import {profiles as iprofiles} from "../profiles/Identity";
import {profiles as tiprofiles} from "../profiles/identities/TeamSpeakIdentity";
import {native_client} from "../main";
import {settings} from "../settings";
import {CommandResult} from "./ServerConnectionDeclaration";
export namespace connection {
import identities = iprofiles.identities;
export interface HandshakeIdentityHandler { export interface HandshakeIdentityHandler {
connection: AbstractServerConnection; connection: AbstractServerConnection;
@ -48,7 +59,7 @@ namespace connection {
on_teamspeak() { on_teamspeak() {
const type = this.profile.selected_type(); const type = this.profile.selected_type();
if(type == profiles.identities.IdentitifyType.TEAMSPEAK) if(type == identities.IdentitifyType.TEAMSPEAK)
this.handshake_finished(); this.handshake_finished();
else { else {
@ -122,8 +133,8 @@ namespace connection {
} }
/* required to keep compatibility */ /* required to keep compatibility */
if(this.profile.selected_type() === profiles.identities.IdentitifyType.TEAMSPEAK) { if(this.profile.selected_type() === identities.IdentitifyType.TEAMSPEAK) {
data["client_key_offset"] = (this.profile.selected_identity() as profiles.identities.TeaSpeakIdentity).hash_number; data["client_key_offset"] = (this.profile.selected_identity() as tiprofiles.identities.TeaSpeakIdentity).hash_number;
} }
this.connection.send_command("clientinit", data).catch(error => { this.connection.send_command("clientinit", data).catch(error => {

View file

@ -1,4 +1,6 @@
enum ErrorID { import {LaterPromise} from "../utils/helpers";
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;

View file

@ -14,7 +14,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
namespace asn1 { export namespace asn1 {
declare class Int10 { declare class Int10 {
constructor(value?: any); constructor(value?: any);

View file

@ -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,

View file

@ -1,4 +1,4 @@
namespace hex { export namespace hex {
export function encode(buffer) { export function encode(buffer) {
let hexCodes = []; let hexCodes = [];
let view = new DataView(buffer); let view = new DataView(buffer);

View file

@ -10,7 +10,7 @@ interface Window {
} }
*/ */
namespace sha { export namespace sha {
/* /*
* [js-sha1]{@link https://github.com/emn178/js-sha1} * [js-sha1]{@link https://github.com/emn178/js-sha1}
* *

View file

@ -0,0 +1,8 @@
export function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

View file

@ -1,4 +1,4 @@
namespace dns { export namespace dns {
export interface AddressTarget { export interface AddressTarget {
target_ip: string; target_ip: string;
target_port?: number; target_port?: number;

View file

@ -1,4 +1,8 @@
namespace events { import {guid} from "./crypto/uid";
import {PlaylistSong} from "./connection/ServerConnectionDeclaration";
import {MusicClientEntry, SongInfo} from "./channel-tree/client";
export namespace events {
export interface EventConvert<All> { export interface EventConvert<All> {
as<T extends keyof All>() : All[T]; as<T extends keyof All>() : All[T];
} }

View file

@ -1,5 +1,4 @@
export namespace i18n {
namespace i18n {
interface CountryInfo { interface CountryInfo {
name: string; name: string;
alpha_2: string; alpha_2: string;

View file

@ -1,13 +1,10 @@
function guid() { import {guid} from "../crypto/uid";
function s4() { import {log, LogCategory} from "../log";
return Math.floor((1 + Math.random()) * 0x10000) import {MessageHelper} from "../ui/frames/chat";
.toString(16) import {StaticSettings} from "../settings";
.substring(1); import {createErrorModal} from "../ui/elements/modal";
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
namespace i18n { export namespace i18n {
export interface TranslationKey { export interface TranslationKey {
message: string; message: string;
line?: number; line?: number;

View file

@ -1,6 +1,8 @@
//Used by CertAccept popup //Used by CertAccept popup
enum LogCategory { import {settings} from "./settings";
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,7 +21,7 @@ enum LogCategory {
DNS DNS
} }
namespace log { export namespace log {
export enum LogType { export enum LogType {
TRACE, TRACE,
DEBUG, DEBUG,

View file

@ -1,19 +1,18 @@
/// <reference path="ui/frames/chat.ts" />
/// <reference path="ui/modal/ModalConnect.ts" />
/// <reference path="ui/modal/ModalCreateChannel.ts" />
/// <reference path="ui/modal/ModalBanClient.ts" />
/// <reference path="ui/modal/ModalYesNo.ts" />
/// <reference path="ui/modal/ModalBanList.ts" />
/// <reference path="settings.ts" />
/// <reference path="log.ts" />
/// <reference path="PPTListener.ts" />
import spawnYesNo = Modals.spawnYesNo; import spawnYesNo = Modals.spawnYesNo;
import {ConnectionHandler} from "./ConnectionHandler";
import {bipc} from "./BrowserIPC";
import {log, LogCategory} from "./log";
import {profiles} from "./profiles/ConnectionProfile";
import {Modals} from "./ui/modal/ModalConnect";
import {settings, Settings} from "./settings";
import {i18n} from "./i18n/localize";
import {createInfoModal} from "./ui/elements/modal";
import {MessageHelper} from "./ui/frames/chat";
const js_render = window.jsrender || $; export const js_render = window.jsrender || $;
const native_client = window.require !== undefined; export const native_client = window.require !== undefined;
function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> { export function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) => Promise<MediaStream> {
if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices)
return constraints => navigator.mediaDevices.getUserMedia(constraints); return constraints => navigator.mediaDevices.getUserMedia(constraints);
@ -24,11 +23,12 @@ function getUserMediaFunctionPromise() : (constraints: MediaStreamConstraints) =
return constraints => new Promise<MediaStream>((resolve, reject) => _callbacked_function(constraints, resolve, reject)); return constraints => new Promise<MediaStream>((resolve, reject) => _callbacked_function(constraints, resolve, reject));
} }
interface Window { export interface Window {
open_connected_question: () => Promise<boolean>; open_connected_question: () => Promise<boolean>;
} }
function setup_close() { export declare const nodeRequire: typeof require;
export function setup_close() {
window.onbeforeunload = event => { window.onbeforeunload = event => {
if(profiles.requires_save()) if(profiles.requires_save())
profiles.save(); profiles.save();
@ -50,7 +50,7 @@ function setup_close() {
})); }));
const exit = () => { const exit = () => {
const {remote} = require('electron'); const {remote} = nodeRequire('electron');
remote.getCurrentWindow().close(); remote.getCurrentWindow().close();
}; };
@ -80,8 +80,8 @@ function setup_close() {
}; };
} }
declare function moment(...arguments) : any; export declare function moment(...arguments) : any;
function setup_jsrender() : boolean { export function setup_jsrender() : boolean {
if(!js_render) { if(!js_render) {
loader.critical_error("Missing jsrender extension!"); loader.critical_error("Missing jsrender extension!");
return false; return false;
@ -115,7 +115,7 @@ function setup_jsrender() : boolean {
return true; return true;
} }
async function initialize() { export async function initialize() {
Settings.initialize(); Settings.initialize();
try { try {
@ -129,7 +129,7 @@ async function initialize() {
bipc.setup(); bipc.setup();
} }
async function initialize_app() { export 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),
@ -180,7 +180,7 @@ async function initialize_app() {
setup_close(); setup_close();
} }
function str2ab8(str) { export function str2ab8(str) {
const buf = new ArrayBuffer(str.length); const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf); const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) { for (let i = 0, strLen = str.length; i < strLen; i++) {
@ -190,7 +190,7 @@ function str2ab8(str) {
} }
/* FIXME Dont use atob, because it sucks for non UTF-8 tings */ /* FIXME Dont use atob, because it sucks for non UTF-8 tings */
function arrayBufferBase64(base64: string) { export function arrayBufferBase64(base64: string) {
base64 = atob(base64); base64 = atob(base64);
const buf = new ArrayBuffer(base64.length); const buf = new ArrayBuffer(base64.length);
const bufView = new Uint8Array(buf); const bufView = new Uint8Array(buf);
@ -200,7 +200,7 @@ function arrayBufferBase64(base64: string) {
return buf; return buf;
} }
function base64_encode_ab(source: ArrayBufferLike) { export function base64_encode_ab(source: ArrayBufferLike) {
const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let base64 = ""; let base64 = "";

View file

@ -1,17 +1,24 @@
/// <reference path="../connection/ConnectionBase.ts" /> /// <reference path="../connection/ConnectionBase.ts" />
enum GroupType { import {LaterPromise} from "../utils/helpers";
import {PermissionManager, PermissionValue} from "./PermissionManager";
import {AbstractCommandHandler, ServerCommand} from "../connection/ConnectionBase";
import {ConnectionHandler} from "../ConnectionHandler";
import {log, LogCategory} from "../log";
import {CommandResult} from "../connection/ServerConnectionDeclaration";
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":

View file

@ -2,7 +2,14 @@
/// <reference path="../connection/ConnectionBase.ts" /> /// <reference path="../connection/ConnectionBase.ts" />
/// <reference path="../i18n/localize.ts" /> /// <reference path="../i18n/localize.ts" />
enum PermissionType { import {ConnectionHandler} from "../ConnectionHandler";
import {log, LogCategory} from "../log";
import {LaterPromise} from "../utils/helpers";
import {AbstractCommandHandler, ServerCommand} from "../connection/ConnectionBase";
import LogType = log.LogType;
import {CommandResult, ErrorID} from "../connection/ServerConnectionDeclaration";
export enum PermissionType {
B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view", B_SERVERINSTANCE_HELP_VIEW = "b_serverinstance_help_view",
B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view", B_SERVERINSTANCE_VERSION_VIEW = "b_serverinstance_version_view",
B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view", B_SERVERINSTANCE_INFO_VIEW = "b_serverinstance_info_view",
@ -352,7 +359,7 @@ enum PermissionType {
I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client" I_FT_QUOTA_MB_UPLOAD_PER_CLIENT = "i_ft_quota_mb_upload_per_client"
} }
class PermissionInfo { export class PermissionInfo {
name: string; name: string;
id: number; id: number;
description: string; description: string;
@ -363,21 +370,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,13 +418,13 @@ 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 namespace permissions {
export type PermissionRequestKeys = { export type PermissionRequestKeys = {
client_id?: number; client_id?: number;
channel_id?: number; channel_id?: number;
@ -473,13 +480,13 @@ namespace permissions {
} }
} }
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[] = [];
@ -603,7 +610,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);

View file

@ -1,251 +1,260 @@
namespace profiles { import {MessageHelper} from "../ui/frames/chat";
export class ConnectionProfile { import {createErrorModal} from "../ui/elements/modal";
id: string; import {guid} from "../crypto/uid";
import {decode_identity, IdentitifyType, Identity} from "./Identity";
import {static_forum_identity} from "./identities/TeaForumIdentity";
import {TeaSpeakIdentity} from "./identities/TeamSpeakIdentity";
import {AbstractServerConnection} from "../connection/ConnectionBase";
import {connection} from "../connection/HandshakeHandler";
profile_name: string; import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
default_username: string;
default_password: string;
selected_identity_type: string = "unset"; export class ConnectionProfile {
identities: { [key: string]: identities.Identity } = {}; id: string;
constructor(id: string) { profile_name: string;
this.id = id; default_username: string;
} default_password: string;
connect_username(): string { selected_identity_type: string = "unset";
if (this.default_username && this.default_username !== "Another TeaSpeak user") identities: { [key: string]: Identity } = {};
return this.default_username;
let selected = this.selected_identity(); constructor(id: string) {
let name = selected ? selected.fallback_name() : undefined; this.id = id;
return name || "Another TeaSpeak user"; }
}
selected_identity(current_type?: identities.IdentitifyType): identities.Identity { connect_username(): string {
if (!current_type) if (this.default_username && this.default_username !== "Another TeaSpeak user")
current_type = this.selected_type(); return this.default_username;
if (current_type === undefined) let selected = this.selected_identity();
return undefined; let name = selected ? selected.fallback_name() : undefined;
return name || "Another TeaSpeak user";
}
if (current_type == identities.IdentitifyType.TEAFORO) { selected_identity(current_type?: IdentitifyType): Identity {
return identities.static_forum_identity(); if (!current_type)
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) { current_type = this.selected_type();
return this.identities[identities.IdentitifyType[current_type].toLowerCase()];
}
if (current_type === undefined)
return undefined; return undefined;
if (current_type == IdentitifyType.TEAFORO) {
return static_forum_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();
} if (!identity || !identity.valid()) return false;
export function requires_save(): boolean { return true;
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);
} }
} }
async function decode_profile(data): Promise<ConnectionProfile | string> {
data = JSON.parse(data);
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 = IdentitifyType[key.toUpperCase() as string];
const _data = data.identity_data[key];
if (type == undefined) continue;
const identity = await decode_identity(type, _data);
if (identity == undefined) continue;
result.identities[key.toLowerCase()] = identity;
}
}
return result;
}
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"), 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 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, static_forum_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);
}

View file

@ -1,110 +1,116 @@
namespace profiles.identities { import {AbstractCommandHandler, AbstractServerConnection, ServerCommand} from "../connection/ConnectionBase";
export enum IdentitifyType { import {connection} from "../connection/HandshakeHandler";
TEAFORO,
TEAMSPEAK, import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
NICKNAME import {NameIdentity} from "./identities/NameIdentity";
import {TeaForumIdentity} from "./identities/TeaForumIdentity";
import {TeaSpeakIdentity} from "./identities/TeamSpeakIdentity";
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;
}
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 {
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:
identity = new NameIdentity();
break;
case IdentitifyType.TEAFORO:
identity = new TeaForumIdentity(undefined);
break;
case IdentitifyType.TEAMSPEAK:
identity = new 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; handle_command(command: ServerCommand): boolean {
switch (type) { if($.isFunction(this[command.command]))
case IdentitifyType.NICKNAME: this[command.command](command.arguments);
identity = new NameIdentity(); else if(command.command == "error") {
break; return false;
case IdentitifyType.TEAFORO: } else {
identity = new TeaForumIdentity(undefined); console.warn(tr("Received unknown command while handshaking (%o)"), command);
break;
case IdentitifyType.TEAMSPEAK:
identity = new TeaSpeakIdentity(undefined, undefined);
break;
}
if(!identity)
return undefined;
try {
await identity.decode(data)
} catch(error) {
/* todo better error handling! */
console.error(error);
return undefined;
}
return identity;
}
export function create_identity(type: IdentitifyType) {
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;
}
return identity;
}
export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler> extends connection.AbstractCommandHandler {
readonly handle: T;
constructor(connection: connection.AbstractServerConnection, handle: T) {
super(connection);
this.handle = handle;
}
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 {
connection: connection.AbstractServerConnection;
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);
} }
return true;
}
}
export abstract class AbstractHandshakeIdentityHandler implements connection.HandshakeIdentityHandler {
connection: AbstractServerConnection;
protected callbacks: ((success: boolean, message?: string) => any)[] = [];
protected constructor(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);
} }
} }

View file

@ -1,88 +1,92 @@
/// <reference path="../Identity.ts" /> import {CommandResult} from "../../connection/ServerConnectionDeclaration";
import {log, LogCategory} from "../../log";
import {AbstractServerConnection} from "../../connection/ConnectionBase";
import {connection} from "../../connection/HandshakeHandler";
namespace profiles.identities { import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
class NameHandshakeHandler extends AbstractHandshakeIdentityHandler { import {AbstractHandshakeIdentityHandler, HandshakeCommandHandler, IdentitifyType, Identity} from "../Identity";
readonly identity: NameIdentity;
handler: HandshakeCommandHandler<NameHandshakeHandler>;
constructor(connection: connection.AbstractServerConnection, identity: profiles.identities.NameIdentity) { class NameHandshakeHandler extends AbstractHandshakeIdentityHandler {
super(connection); readonly identity: NameIdentity;
this.identity = identity; handler: HandshakeCommandHandler<NameHandshakeHandler>;
this.handler = new HandshakeCommandHandler(connection, this); constructor(connection: AbstractServerConnection, identity: NameIdentity) {
this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof"); super(connection);
} this.identity = identity;
start_handshake() { this.handler = new HandshakeCommandHandler(connection, this);
this.connection.command_handler_boss().register_handler(this.handler); this.handler["handshakeidentityproof"] = () => this.trigger_fail("server requested unexpected proof");
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);
name() : string { return this._name; } super.trigger_success();
}
fallback_name(): string | undefined { }
return this._name;
} export class NameIdentity implements Identity {
private _name: string;
uid(): string {
return btoa(this._name); //FIXME hash! constructor(name?: string) {
} this._name = name;
}
type(): IdentitifyType {
return IdentitifyType.NICKNAME; set_name(name: string) { this._name = name; }
}
name() : string { return this._name; }
valid(): boolean {
return this._name != undefined && this._name.length >= 5; fallback_name(): string | undefined {
} return this._name;
}
decode(data) : Promise<void> {
data = JSON.parse(data); uid(): string {
if(data.version !== 1) return btoa(this._name); //FIXME hash!
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
}); decode(data) : Promise<void> {
} data = JSON.parse(data);
if(data.version !== 1)
spawn_identity_handshake_handler(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler { throw "invalid version";
return new NameHandshakeHandler(connection, this);
} 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);
} }
} }

View file

@ -1,122 +1,126 @@
/// <reference path="../Identity.ts" /> import {AbstractServerConnection} from "../../connection/ConnectionBase";
import {log, LogCategory} from "../../log";
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
import {forum} from "./teaspeak-forum";
import {connection} from "../../connection/HandshakeHandler";
import HandshakeIdentityHandler = connection.HandshakeIdentityHandler;
import {AbstractHandshakeIdentityHandler, HandshakeCommandHandler, IdentitifyType, Identity} from "../Identity";
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 function static_forum_identity() : TeaForumIdentity | undefined {
return static_identity;
} }
} }
export class TeaForumIdentity implements Identity {
private readonly identity_data: forum.Data;
valid() : boolean {
return !!this.identity_data && !this.identity_data.is_expired();
}
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: 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"));
}
}
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

View file

@ -1,8 +1,11 @@
interface Window { import {Settings, settings} from "../../settings";
import {update_forum} from "./TeaForumIdentity";
declare interface Window {
grecaptcha: GReCaptcha; grecaptcha: GReCaptcha;
} }
interface GReCaptcha { export interface GReCaptcha {
render(container: string | HTMLElement, parameters: { render(container: string | HTMLElement, parameters: {
sitekey: string; sitekey: string;
theme?: "dark" | "light"; theme?: "dark" | "light";
@ -18,10 +21,10 @@ interface GReCaptcha {
reset(widget_id?: string); reset(widget_id?: string);
} }
namespace forum { export namespace forum {
export namespace gcaptcha { export namespace gcaptcha {
export async function initialize() { export async function initialize() {
if(typeof(window.grecaptcha) === "undefined") { if(typeof((window as any).grecaptcha) === "undefined") {
let script = document.createElement("script"); let script = document.createElement("script");
script.async = true; script.async = true;
@ -50,7 +53,7 @@ namespace forum {
} }
} }
if(typeof(window.grecaptcha) === "undefined") if(typeof((window as any).grecaptcha) === "undefined")
throw tr("failed to load recaptcha"); throw tr("failed to load recaptcha");
} }
@ -62,9 +65,9 @@ namespace forum {
throw tr("initialisation failed"); throw tr("initialisation failed");
} }
if(container.attr("captcha-uuid")) if(container.attr("captcha-uuid"))
window.grecaptcha.reset(container.attr("captcha-uuid")); (window as any).grecaptcha.reset(container.attr("captcha-uuid"));
else { else {
container.attr("captcha-uuid", window.grecaptcha.render(container[0], { container.attr("captcha-uuid", (window as any).grecaptcha.render(container[0], {
"sitekey": key, "sitekey": key,
callback: callback_data callback: callback_data
})); }));
@ -206,7 +209,7 @@ namespace forum {
localStorage.setItem("teaspeak-forum-data", response["data"]); localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]); localStorage.setItem("teaspeak-forum-sign", response["sign"]);
localStorage.setItem("teaspeak-forum-auth", response["auth-key"]); localStorage.setItem("teaspeak-forum-auth", response["auth-key"]);
profiles.identities.update_forum(); update_forum();
} catch(error) { } catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error); console.error(tr("Failed to parse forum given data: %o"), error);
return { return {
@ -266,7 +269,7 @@ namespace forum {
_data = new Data(_data.auth_key, response["data"], response["sign"]); _data = new Data(_data.auth_key, response["data"], response["sign"]);
localStorage.setItem("teaspeak-forum-data", response["data"]); localStorage.setItem("teaspeak-forum-data", response["data"]);
localStorage.setItem("teaspeak-forum-sign", response["sign"]); localStorage.setItem("teaspeak-forum-sign", response["sign"]);
profiles.identities.update_forum(); update_forum();
} catch(error) { } catch(error) {
console.error(tr("Failed to parse forum given data: %o"), error); console.error(tr("Failed to parse forum given data: %o"), error);
throw tr("failed to parse data"); throw tr("failed to parse data");
@ -320,7 +323,7 @@ namespace forum {
localStorage.removeItem("teaspeak-forum-data"); localStorage.removeItem("teaspeak-forum-data");
localStorage.removeItem("teaspeak-forum-sign"); localStorage.removeItem("teaspeak-forum-sign");
localStorage.removeItem("teaspeak-forum-auth"); localStorage.removeItem("teaspeak-forum-auth");
profiles.identities.update_forum(); update_forum();
} }
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {

View file

@ -1,5 +1,3 @@
//Used by CertAccept popup
interface Array<T> { interface Array<T> {
remove(elem?: T): boolean; remove(elem?: T): boolean;
last?(): T; last?(): T;

View file

@ -1,6 +1,9 @@
/// <reference path="ui/elements/modal.ts" /> /// <reference path="ui/elements/modal.ts" />
//Used by CertAccept popup //Used by CertAccept popup
import {log, LogCategory} from "./log";
import {createErrorModal} from "./ui/elements/modal";
if(typeof(customElements) !== "undefined") { if(typeof(customElements) !== "undefined") {
try { try {
class X_Properties extends HTMLElement {} class X_Properties extends HTMLElement {}
@ -14,7 +17,7 @@ if(typeof(customElements) !== "undefined") {
} }
/* 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 +28,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 +80,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 +142,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 +436,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 +514,4 @@ class ServerSettings extends SettingsBase {
} }
} }
let settings: Settings; export let settings: Settings;

View file

@ -1,4 +1,8 @@
enum Sound { import {settings} from "../settings";
import {log, LogCategory} from "../log";
import {ConnectionHandler} from "../ConnectionHandler";
export enum Sound {
SOUND_TEST = "sound.test", SOUND_TEST = "sound.test",
SOUND_EGG = "sound.egg", SOUND_EGG = "sound.egg",
@ -61,7 +65,7 @@ enum Sound {
GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self" GROUP_CHANNEL_CHANGED_SELF = "group.channel.changed.self"
} }
namespace sound { export namespace sound {
export interface SoundHandle { export interface SoundHandle {
key: string; key: string;
filename: string; filename: string;

View file

@ -1,4 +1,6 @@
namespace stats { import {log, LogCategory} from "./log";
export namespace stats {
const LOG_PREFIX = "[Statistics] "; const LOG_PREFIX = "[Statistics] ";
export enum CloseCodes { export enum CloseCodes {

View file

@ -0,0 +1,81 @@
.channel-container {
display: flex;
flex-direction: column;
}
.channel-container .container-channel {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
width: 100%;
min-height: 16px;
align-items: center;
cursor: pointer;
}
.channel-container .container-channel .channel-type {
flex-grow: 0;
flex-shrink: 0;
margin-right: 2px;
}
.channel-container .container-channel .container-channel-name {
display: flex;
flex-direction: row;
flex-grow: 1;
flex-shrink: 1;
justify-content: left;
max-width: 100%;
/* important for the repetitive channel name! */
overflow-x: hidden;
height: 16px;
}
.channel-container .container-channel .container-channel-name.align-right {
justify-content: right;
}
.channel-container .container-channel .container-channel-name.align-center, .channel-container .container-channel .container-channel-name.align-repetitive {
justify-content: center;
}
.channel-container .container-channel .container-channel-name .channel-name {
align-self: center;
color: #828282;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-container .container-channel .container-channel-name.align-repetitive .channel-name {
text-overflow: clip;
}
.channel-container .container-channel .icons {
display: flex;
flex-direction: row;
flex-grow: 0;
flex-shrink: 0;
}
.channel-container .container-channel.move-selected {
border-bottom: 1px solid black;
}
.channel-container .container-channel .show-channel-normal-only {
display: none;
}
.channel-container .container-channel .show-channel-normal-only.channel-normal {
display: block;
}
.channel-container .container-channel .icon_no_sound {
position: relative;
display: flex;
}
.channel-container .container-channel .icon_no_sound .background {
width: 10px;
height: 14px;
background: red;
position: absolute;
top: 1px;
left: 3px;
z-index: -1;
}
.channel-container .container-clients {
display: flex;
flex-direction: column;
}
/*# sourceMappingURL=channel.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["channel.scss","colors.scss"],"names":[],"mappings":"AAEA;EACI;EACA;;AAEA;EACI;EAEA;EACA;EACA;EAEA;EACA;EAEA;EACA;;AAEA;EACI;EACA;EAEA;;AAGJ;EACI;EACA;EAEA;EACA;EAEA;EAEA;AAAiB;EACjB;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA,OC/CW;EDiDX;EACA;EACA;EACA;;AAIA;EACI;;AAKZ;EACI;EACA;EAEA;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAEA;EACI;;AAIR;EACI;EACA;;AAEA;EACI;EACA;EAEA;EACA;EACA;EACA;EACA;;AAKZ;EACI;EACA","file":"channel.css"}

View file

@ -0,0 +1,106 @@
@import "colors";
.channel-container {
display: flex;
flex-direction: column;
.container-channel {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
width: 100%;
min-height: 16px;
align-items: center;
cursor: pointer;
.channel-type {
flex-grow: 0;
flex-shrink: 0;
margin-right: 2px;
}
.container-channel-name {
display: flex;
flex-direction: row;
flex-grow: 1;
flex-shrink: 1;
justify-content: left;
max-width: 100%; /* important for the repetitive channel name! */
overflow-x: hidden;
height: 16px;
&.align-right {
justify-content: right;
}
&.align-center, &.align-repetitive {
justify-content: center;
}
.channel-name {
align-self: center;
color: $channel-tree-entry-color;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&.align-repetitive {
.channel-name {
text-overflow: clip;
}
}
}
.icons {
display: flex;
flex-direction: row;
flex-grow: 0;
flex-shrink: 0;
}
&.move-selected {
border-bottom: 1px solid black;
}
.show-channel-normal-only {
display: none;
&.channel-normal {
display: block;
}
}
.icon_no_sound {
position: relative;
display: flex;
.background {
width: 10px;
height: 14px;
background: red;
position: absolute;
top: 1px;
left: 3px;
z-index: -1;
}
}
}
.container-clients {
display: flex;
flex-direction: column;
}
}

View file

@ -0,0 +1,120 @@
import * as React from "react";
import {ChannelEntry} from "../../channel-tree/channel";
const cssChannel = require("./channel.scss");
const cssTree = require("./tree.scss");
class ChannelIcon extends React.Component<{ channel: ChannelEntry }, {}> {
channel_icon_classes() {
const class_list = [];
const channel = this.props.channel;
if(channel.formattedChannelName() !== channel.channelName())
return "channel-spacer";
let icon_color;
if(channel.properties.channel_flag_password && !channel.cached_password())
icon_color = "yellow";
else if(channel.properties.channel_flag_maxclients_unlimited && channel.clients().length >= channel.properties.channel_maxclients)
icon_color = "red";
else if(channel.properties.channel_flag_maxfamilyclients_unlimited && channel.clients(true).length >= channel.properties.channel_maxfamilyclients)
icon_color = "red";
else
icon_color = "green";
return "channel-normal client-channel_" + icon_color + (channel.flag_subscribed ? "_subscribed" : "");
}
render() {
return <div className={this.channel_icon_classes()} />;
}
}
class ChannelIcons extends React.Component<{channel: ChannelEntry}, {}> {
render_icon(target: HTMLDivElement) {
const props = this.props.channel.properties;
if(!props.channel_icon_id) return;
const tag = this.props.channel.channelTree.client.fileManager.icons.generateTag(props.channel_icon_id);
tag.appendTo($(target));
}
render() {
const icons = [];
const props = this.props.channel.properties;
if(props.channel_flag_default) {
icons.push(<div className={"show-channel-normal-only icon_entry icon_default icon client-channel_default"} title={tr("Default channel")} />);
}
if(props.channel_flag_password) {
icons.push(<div className={"show-channel-normal-only icon_entry icon_password icon client-register"} title={tr("The channel is password protected")} />);
}
if(props.channel_codec_quality > 4) {
icons.push(<div className={"show-channel-normal-only icon_entry icon_moderated icon client-moderated"} title={tr("Music quality")} />);
}
if(props.channel_needed_talk_power > 0) {
icons.push(<div className={"show-channel-normal-only icon_entry icon_moderated icon client-moderated"} title={tr("Channel is moderated")} />);
}
if(props.channel_icon_id != 0) {
icons.push(<div className={"show-channel-normal-only icon_entry channel_icon"} title={tr("Channel icon")} ref={e => this.render_icon(e) }/>)
}
if(!audio.codec.supported(props.channel_codec)) {
icons.push(<div className={cssChannel.icon_no_sound}>
<div className={cssChannel.background} />
<div className={"icon_entry icon client-conflict-icon"} title={tr("You don't support the channel codec")} />
</div>)
}
return (<div className={cssChannel.icons}>{icons}</div>);
}
}
class ChannelLine extends React.Component<{ channel: ChannelEntry }, {}> {
render() {
let depth = 1;
let parent = this.props.channel;
while((parent = parent.parent))
depth++;
//TODO: On handle spacer alignments in channel name!
return (
<div className={cssChannel.containerChannel}>
<div className={cssTree.markerTextUnread} />
<div className={cssTree.depthFiller} style={{ width: depth + "em" }} />
<ChannelIcon channel={this.props.channel} />
<div className={cssChannel.containerChannelName}>
<a className={cssChannel.channelName}>{this.props.channel.formattedChannelName()}</a>
</div>
<ChannelIcons channel={this.props.channel} />
</div>
);
}
}
class ChannelClientsView extends React.Component<{}, {}> {
}
class SubChannelView extends React.Component<{ channels: ChannelEntry[] }, {}> {
render() {
return this.props.channels.map(e => <ChannelLine channel={e} />);
}
}
export class Channel extends React.Component<{ channel: ChannelEntry }, {}> {
children: ChannelEntry[];
channel_entry() : ChannelEntry { return this.props.channel; }
render() {
return (
<div className={[cssTree.entry, cssChannel.channelContainer].join(" ")}>
<ChannelLine channel={this.props.channel} />
<ChannelClientsView />
<SubChannelView channels={[]} />
</div>
);
}
}

View file

@ -0,0 +1,3 @@
/*# sourceMappingURL=colors.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":[],"names":[],"mappings":"","file":"colors.css"}

View file

@ -0,0 +1,3 @@
$channel-tree-new-message-color: #a814147F;
$channel-tree-entry-color: #828282;

View file

@ -0,0 +1,38 @@
/* Some general browser helpers */
.tree-container .tree .entry .depth-filler {
font-size: 16px;
flex-shrink: 0;
flex-grow: 0;
}
.tree-container .marker-text-unread {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: #a814147F;
opacity: 1;
-moz-transition: opacity 0.25s;
-o-transition: opacity 0.25s;
-webkit-transition: opacity 0.25s;
transition: opacity 0.25s;
}
.tree-container .marker-text-unread:before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 24px;
background: -moz-linear-gradient(left, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
/* FF3.6-15 */
background: -webkit-linear-gradient(left, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
/* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to right, rgba(168, 20, 20, 0.18) 0%, rgba(168, 20, 20, 0) 100%);
/* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
}
.tree-container .marker-text-unread.hidden {
opacity: 0;
}
/*# sourceMappingURL=tree.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../css/static/mixin.scss","tree.scss"],"names":[],"mappings":"AAAA;ACQY;EACI;EAEA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EAEA;EACA;EAEA;EDvBP,iBC4CO;ED3CP,eC2CO;ED1CP,oBC0CO;EDzCP,YCyCO;;AAnBA;EACI;EACA;EAEA;EACA;EACA;EAEA;EAEA;AAAyF;EACzF;AAA2F;EAC3F;AAAuF;;AAG3F;EACI","file":"tree.css"}

View file

@ -0,0 +1,50 @@
@import "../../../css/static/mixin";
@import "../../../css/static/properties";
.tree-container {
.tree {
.entry {
.depth-filler {
font-size: 16px;
flex-shrink: 0;
flex-grow: 0;
}
}
}
.marker-text-unread {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: #a814147F;
opacity: 1;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 24px;
background: -moz-linear-gradient(left, rgba(168,20,20,.18) 0%, rgba(168,20,20,0) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(left, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to right, rgba(168,20,20,.18) 0%,rgba(168,20,20,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
}
&.hidden {
opacity: 0;
}
@include transition(opacity $button_hover_animation_time);
}
}

View file

@ -0,0 +1,5 @@
import * as React from "react";
export class TreeView {
}

View file

@ -1,6 +1,4 @@
/// <reference path="client.ts" /> export class ClientMover {
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;

View file

@ -1,10 +1,14 @@
import {settings} from "../../settings";
import {log, LogCategory} from "../../log";
declare const $: any;
interface JQuery<TElement = HTMLElement> { interface JQuery<TElement = HTMLElement> {
dividerfy() : this; dividerfy() : this;
} }
if(!$.fn.dividerfy) { if(!$.fn.dividerfy) {
$.fn.dividerfy = function<T extends HTMLElement>(this: JQuery<T>) : JQuery<T> { $.fn.dividerfy = function<T extends HTMLElement>(this: JQuery<T>) : JQuery<T> {
this.find(".container-seperator").each(function (this: T) { (this as any).find(".container-seperator").each(function (this: T) {
if(!this.previousElementSibling) return; if(!this.previousElementSibling) return;
if(!this.nextElementSibling) return; if(!this.nextElementSibling) return;

View file

@ -1,4 +1,4 @@
namespace contextmenu { export namespace contextmenu {
export interface MenuEntry { export interface MenuEntry {
callback?: () => void; callback?: () => void;
type: MenuEntryType; type: MenuEntryType;

View file

@ -1,13 +1,13 @@
/// <reference path="../../PPTListener.ts" /> import {KeyCode} from "../../PPTListener";
enum ElementType { export enum ElementType {
HEADER, HEADER,
BODY, BODY,
FOOTER FOOTER
} }
type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[]; export type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
const ModalFunctions = { export const ModalFunctions = {
divify: function (val: JQuery) { divify: function (val: JQuery) {
if(val.length > 1) if(val.length > 1)
return $.spawn("div").append(val); return $.spawn("div").append(val);
@ -54,7 +54,7 @@ const ModalFunctions = {
} }
}; };
class ModalProperties { export class ModalProperties {
template?: string; template?: string;
header: BodyCreator = () => "HEADER"; header: BodyCreator = () => "HEADER";
body: BodyCreator = () => "BODY"; body: BodyCreator = () => "BODY";
@ -89,7 +89,7 @@ class ModalProperties {
full_size?: boolean = false; full_size?: boolean = false;
} }
namespace modal { export namespace modal {
export function initialize_modals() { export function initialize_modals() {
register_global_events(); register_global_events();
} }
@ -184,7 +184,7 @@ let _global_modal_count = 0;
let _global_modal_last: HTMLElement; let _global_modal_last: HTMLElement;
let _global_modal_last_time: number; let _global_modal_last_time: number;
class Modal { export class Modal {
private _htmlTag: JQuery; private _htmlTag: JQuery;
properties: ModalProperties; properties: ModalProperties;
shown: boolean; shown: boolean;
@ -296,11 +296,11 @@ class Modal {
} }
} }
function createModal(data: ModalProperties | any) : Modal { export function createModal(data: ModalProperties | any) : Modal {
return new Modal(ModalFunctions.warpProperties(data)); return new Modal(ModalFunctions.warpProperties(data));
} }
class InputModalProperties extends ModalProperties { export class InputModalProperties extends ModalProperties {
maxLength?: number; maxLength?: number;
field_title?: string; field_title?: string;
@ -310,7 +310,7 @@ class InputModalProperties extends ModalProperties {
error_message?: string; error_message?: string;
} }
function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal { export function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
props = ModalFunctions.warpProperties(props); props = ModalFunctions.warpProperties(props);
props.template_properties || (props.template_properties = {}); props.template_properties || (props.template_properties = {});
props.template_properties.field_title = props.field_title; props.template_properties.field_title = props.field_title;
@ -370,7 +370,7 @@ function createInputModal(headMessage: BodyCreator, question: BodyCreator, valid
return modal; return modal;
} }
function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) { export function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
props = ModalFunctions.warpProperties(props); props = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-error"; (props.template_properties || (props.template_properties = {})).header_class = "modal-header-error";
@ -382,7 +382,7 @@ function createErrorModal(header: BodyCreator, message: BodyCreator, props: Moda
return modal; return modal;
} }
function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) { export function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
props = ModalFunctions.warpProperties(props); props = ModalFunctions.warpProperties(props);
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-info"; (props.template_properties || (props.template_properties = {})).header_class = "modal-header-info";
@ -393,73 +393,3 @@ function createInfoModal(header: BodyCreator, message: BodyCreator, props: Modal
modal.htmlTag.find(".modal-body").addClass("modal-info"); modal.htmlTag.find(".modal-body").addClass("modal-info");
return modal; return modal;
} }
/* extend jquery */
interface ModalElements {
header?: BodyCreator;
body?: BodyCreator;
footer?: BodyCreator;
}
interface JQuery<TElement = HTMLElement> {
modalize(entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal;
}
$.fn.modalize = function (this: JQuery, entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal {
properties = properties || {} as ModalProperties;
entry_callback = entry_callback || ((a,b,c) => undefined);
let tag_modal = this[0].tagName.toLowerCase() == "modal" ? this : undefined; /* TODO may throw exception? */
let tag_head = tag_modal ? tag_modal.find("modal-header") : ModalFunctions.jqueriefy(properties.header);
let tag_body = tag_modal ? tag_modal.find("modal-body") : this;
let tag_footer = tag_modal ? tag_modal.find("modal-footer") : ModalFunctions.jqueriefy(properties.footer);
const result = entry_callback(tag_head as any, tag_body, tag_footer as any) || {};
properties.header = result.header || tag_head;
properties.body = result.body || tag_body;
properties.footer = result.footer || tag_footer;
return createModal(properties);
};

View file

@ -1,4 +1,4 @@
namespace net.graph { export namespace net.graph {
export type Entry = { export type Entry = {
timestamp: number; timestamp: number;

View file

@ -1,4 +1,4 @@
interface SliderOptions { export interface SliderOptions {
min_value?: number; min_value?: number;
max_value?: number; max_value?: number;
initial_value?: number; initial_value?: number;
@ -8,11 +8,11 @@ interface SliderOptions {
value_field?: JQuery | JQuery[]; value_field?: JQuery | JQuery[];
} }
interface Slider { export interface Slider {
value(value?: number) : number; value(value?: number) : number;
} }
function sliderfy(slider: JQuery, options?: SliderOptions) : Slider { export function sliderfy(slider: JQuery, options?: SliderOptions) : Slider {
options = Object.assign( { options = Object.assign( {
initial_value: 0, initial_value: 0,
min_value: 0, min_value: 0,

View file

@ -1,8 +1,8 @@
function tooltip(entry: JQuery) { export function tooltip(entry: JQuery) {
return tooltip.initialize(entry); return tooltip.initialize(entry);
} }
namespace tooltip { export namespace tooltip {
let _global_tooltip: JQuery; let _global_tooltip: JQuery;
export type Handle = { export type Handle = {
show(); show();

View file

@ -13,12 +13,27 @@
client_away_message Value: '' client_away_message Value: ''
*/ */
let control_bar: ControlBar; /* global variable to access the control bar */ import {ConnectionHandler, DisconnectReason} from "../../ConnectionHandler";
import {top_menu} from "./MenuBar";
import {Settings, settings} from "../../settings";
import {createErrorModal, createInfoModal, createInputModal} from "../elements/modal";
import {default_recorder} from "../../voice/RecorderProfile";
import {sound, Sound} from "../../sound/Sounds";
import {Modals} from "../modal/ModalConnect";
import {Modal as ModalsS} from "../modal/ModalSettings";
import {log} from "./server_log";
import {MessageHelper} from "./chat";
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
import {PermissionType} from "../../permission/PermissionManager";
import {bookmarks} from "../../bookmarks";
import {contextmenu} from "../elements/context_menu";
type MicrophoneState = "disabled" | "muted" | "enabled"; export let control_bar: ControlBar; /* global variable to access the control bar */
type HeadphoneState = "muted" | "enabled";
type AwayState = "away-global" | "away" | "online"; export type MicrophoneState = "disabled" | "muted" | "enabled";
class ControlBar { export type HeadphoneState = "muted" | "enabled";
export type AwayState = "away-global" | "away" | "online";
export class ControlBar {
private _button_away_active: AwayState; private _button_away_active: AwayState;
private _button_microphone: MicrophoneState; private _button_microphone: MicrophoneState;
private _button_speakers: HeadphoneState; private _button_speakers: HeadphoneState;
@ -421,7 +436,7 @@ class ControlBar {
} }
private on_open_settings() { private on_open_settings() {
Modals.spawnSettingsModal(); ModalsS.spawnSettingsModal();
} }
private on_open_connect() { private on_open_connect() {

View file

@ -1,4 +1,12 @@
namespace top_menu { import {bookmarks} from "../../bookmarks";
import {Modals} from "../modal/ModalBookmarks";
import {DisconnectReason} from "../../ConnectionHandler";
import {control_bar} from "./ControlBar";
import {createErrorModal} from "../elements/modal";
import {PermissionType} from "../../permission/PermissionManager";
import {Sound} from "../../sound/Sounds";
export namespace top_menu {
export interface HRItem { } export interface HRItem { }
export interface MenuItem { export interface MenuItem {

View file

@ -1,4 +1,6 @@
enum ChatType { import {log, LogCategory} from "../../log";
export enum ChatType {
GENERAL, GENERAL,
SERVER, SERVER,
CHANNEL, CHANNEL,
@ -6,7 +8,7 @@ enum ChatType {
} }
declare const xbbcode: any; declare const xbbcode: any;
namespace MessageHelper { export namespace MessageHelper {
export function htmlEscape(message: string) : string[] { export function htmlEscape(message: string) : string[] {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerText = message; div.innerText = message;

View file

@ -1,5 +1,11 @@
/* the bar on the right with the chats (Channel & Client) */ /* the bar on the right with the chats (Channel & Client) */
namespace chat { import {ChannelEntry} from "../../channel-tree/channel";
import {Modals} from "../modal/ModalMusicManage";
import {MessageHelper} from "./chat";
import {ServerEntry} from "../../channel-tree/server";
import {ConnectionHandler} from "../../ConnectionHandler";
export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,7 +1,11 @@
import {ConnectionHandler, DisconnectReason} from "../../ConnectionHandler";
import {Settings, settings} from "../../settings";
import {control_bar} from "./ControlBar";
import {top_menu} from "./MenuBar";
let server_connections: ServerConnectionManager; export let server_connections: ServerConnectionManager;
class ServerConnectionManager { export class ServerConnectionManager {
private connection_handlers: ConnectionHandler[] = []; private connection_handlers: ConnectionHandler[] = [];
private active_handler: ConnectionHandler | undefined; private active_handler: ConnectionHandler | undefined;

View file

@ -1,4 +1,8 @@
class Hostbanner { import {ConnectionHandler} from "../../ConnectionHandler";
import {Settings, settings} from "../../settings";
import {log, LogCategory} from "../../log";
export class Hostbanner {
readonly html_tag: JQuery<HTMLElement>; readonly html_tag: JQuery<HTMLElement>;
readonly client: ConnectionHandler; readonly client: ConnectionHandler;

View file

@ -1,4 +1,4 @@
namespace image_preview { export namespace image_preview {
let preview_overlay: JQuery<HTMLDivElement>; let preview_overlay: JQuery<HTMLDivElement>;
let container_image: JQuery<HTMLDivElement>; let container_image: JQuery<HTMLDivElement>;
let button_open_in_browser: JQuery; let button_open_in_browser: JQuery;

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
namespace chat { export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,4 +1,4 @@
namespace chat { export namespace chat {
export namespace helpers { export namespace helpers {
//https://regex101.com/r/YQbfcX/2 //https://regex101.com/r/YQbfcX/2
//static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm; //static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;

View file

@ -1,4 +1,11 @@
namespace chat { import {ClientEntry, LocalClientEntry} from "../../../channel-tree/client";
import {Modals} from "../../modal/ModalClientInfo";
import {htmltags} from "../../htmltags";
import {image_preview} from "../image_preview";
import {i18n} from "../../../i18n/country";
import {GroupManager} from "../../../permission/GroupManager";
export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,4 +1,10 @@
namespace chat { import {htmltags} from "../../htmltags";
import {MessageHelper} from "../chat";
import {CommandResult, ErrorID} from "../../../connection/ServerConnectionDeclaration";
import {log, LogCategory} from "../../../log";
import {createErrorModal} from "../../elements/modal";
export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,6 +1,7 @@
namespace chat { import {MusicClientEntry} from "../../../channel-tree/client";
import PlayerState = connection.voice.PlayerState; import {image_preview} from "../image_preview";
export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,5 +1,9 @@
/* the bar on the right with the chats (Channel & Client) */ /* the bar on the right with the chats (Channel & Client) */
namespace chat { import {log, LogCategory} from "../../../log";
import {htmltags} from "../../htmltags";
import {MessageHelper} from "../chat";
export namespace chat {
declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;
declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number;

View file

@ -1,4 +1,4 @@
namespace htmltags { export namespace htmltags {
let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0}; let mouse_coordinates: {x: number, y: number} = {x: 0, y: 0};
function initialize() { function initialize() {

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
function format_date(date: number) { function format_date(date: number) {
const d = new Date(date); const d = new Date(date);

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
//TODO: Test if we could render this image and not only the browser by knowing the type. //TODO: Test if we could render this image and not only the browser by knowing the type.
export function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any) { export function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any) {
const modal = createModal({ const modal = createModal({

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
const avatar_to_uid = (id: string) => { const avatar_to_uid = (id: string) => {
const buffer = new Uint8Array(id.length / 2); const buffer = new Uint8Array(id.length / 2);
for(let index = 0; index < id.length; index += 2) { for(let index = 0; index < id.length; index += 2) {

View file

@ -1,8 +1,3 @@
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export type BanEntry = { export type BanEntry = {
name?: string; name?: string;
unique_id: string; unique_id: string;

View file

@ -1,9 +1,4 @@
/// <reference path="../../ConnectionHandler.ts" /> export namespace Modals {
/// <reference path="../../ui/elements/modal.ts" />
/// <reference path="../../i18n/localize.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function openBanList(client: ConnectionHandler) { export function openBanList(client: ConnectionHandler) {
let modal: Modal; let modal: Modal;

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function spawnBookmarkModal() { export function spawnBookmarkModal() {
let modal: Modal; let modal: Modal;
modal = createModal({ modal = createModal({

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
let modal: Modal; let modal: Modal;
export function spawnChangeLatency(client: ClientEntry, current: connection.voice.LatencySettings, reset: () => connection.voice.LatencySettings, apply: (settings: connection.voice.LatencySettings) => any, callback_flush?: () => any) { export function spawnChangeLatency(client: ClientEntry, current: connection.voice.LatencySettings, reset: () => connection.voice.LatencySettings, apply: (settings: connection.voice.LatencySettings) => any, callback_flush?: () => any) {
if(modal) modal.close(); if(modal) modal.close();

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
//TODO: Use the max limit! //TODO: Use the max limit!
let modal: Modal; let modal: Modal;

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
export function openChannelInfo(channel: ChannelEntry) { export function openChannelInfo(channel: ChannelEntry) {
let modal: Modal; let modal: Modal;

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
type InfoUpdateCallback = (info: ClientConnectionInfo) => any; type InfoUpdateCallback = (info: ClientConnectionInfo) => any;
export function openClientInfo(client: ClientEntry) { export function openClientInfo(client: ClientEntry) {
let modal: Modal; let modal: Modal;

View file

@ -1,7 +1,5 @@
/// <reference path="../../ui/elements/modal.ts" />
//FIXME: Move this shit out of this file! //FIXME: Move this shit out of this file!
namespace connection_log { export namespace connection_log {
//TODO: Save password data //TODO: Save password data
export type ConnectionData = { export type ConnectionData = {
name: string; name: string;
@ -91,7 +89,7 @@ namespace connection_log {
}); });
} }
namespace Modals { export namespace Modals {
export function spawnConnectModal(options: { export function spawnConnectModal(options: {
default_connect_new_tab?: boolean /* default false */ default_connect_new_tab?: boolean /* default false */
}, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) { }, defaultHost: { url: string, enforce: boolean} = { url: "ts.TeaSpeak.de", enforce: false}, connect_profile?: { profile: profiles.ConnectionProfile, enforce: boolean}) {

View file

@ -1,6 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
namespace Modals {
export function createChannelModal(connection: ConnectionHandler, channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) { export function createChannelModal(connection: ConnectionHandler, channel: ChannelEntry | undefined, parent: ChannelEntry | undefined, permissions: PermissionManager, callback: (properties?: ChannelProperties, permissions?: PermissionValue[]) => any) {
let properties: ChannelProperties = { } as ChannelProperties; //The changes properties let properties: ChannelProperties = { } as ChannelProperties; //The changes properties
const modal = createModal({ const modal = createModal({

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
let current_modal: Modal; let current_modal: Modal;
export function createServerGroupAssignmentModal(client: ClientEntry, callback: (groups: number[], flag: boolean) => Promise<boolean>) { export function createServerGroupAssignmentModal(client: ClientEntry, callback: (groups: number[], flag: boolean) => Promise<boolean>) {
if(current_modal) if(current_modal)

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) { export function spawnIconSelect(client: ConnectionHandler, callback_icon?: (id: number) => any, selected_icon?: number) {
selected_icon = selected_icon || 0; selected_icon = selected_icon || 0;
let allow_manage = client.permissions.neededPermission(PermissionType.B_ICON_MANAGE).granted(1); let allow_manage = client.permissions.neededPermission(PermissionType.B_ICON_MANAGE).granted(1);

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
export function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity, name: string): Modal { export function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity, name: string): Modal {
let modal: Modal; let modal: Modal;
let elapsed_timer: NodeJS.Timer; let elapsed_timer: NodeJS.Timer;

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
type URLGeneratorSettings = { type URLGeneratorSettings = {
flag_direct: boolean, flag_direct: boolean,
flag_resolved: boolean flag_resolved: boolean

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
export function spawnKeySelect(callback: (key?: ppt.KeyEvent) => void) { export function spawnKeySelect(callback: (key?: ppt.KeyEvent) => void) {
let modal = createModal({ let modal = createModal({
header: tr("Select a key"), header: tr("Select a key"),

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function openMusicManage(client: ConnectionHandler, bot: MusicClientEntry) { export function openMusicManage(client: ConnectionHandler, bot: MusicClientEntry) {
const ev_registry = new events.Registry<events.modal.music_manage>(); const ev_registry = new events.Registry<events.modal.music_manage>();
ev_registry.enable_debug("music-manage"); ev_registry.enable_debug("music-manage");

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
const next_step: {[key: string]:string} = { const next_step: {[key: string]:string} = {
"welcome": "microphone", "welcome": "microphone",
//"microphone": app.is_web() ? "identity" : "speaker", /* speaker setup only for the native client! */ //"microphone": app.is_web() ? "identity" : "speaker", /* speaker setup only for the native client! */

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function spawnPlaylistSongInfo(song: PlaylistSong) { export function spawnPlaylistSongInfo(song: PlaylistSong) {
let modal: Modal; let modal: Modal;

View file

@ -1,9 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../i18n/localize.ts" />
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function spawnPlaylistManage(client: ConnectionHandler) { export function spawnPlaylistManage(client: ConnectionHandler) {
{ {
createErrorModal(tr("Not implemented"), tr("Playlist management hasn't yet been implemented")).open(); createErrorModal(tr("Not implemented"), tr("Playlist management hasn't yet been implemented")).open();

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
let global_modal: PokeModal; let global_modal: PokeModal;
interface ServerEntry { interface ServerEntry {

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
export function spawnQueryCreate(connection: ConnectionHandler, callback_created?: (user, pass) => any) { export function spawnQueryCreate(connection: ConnectionHandler, callback_created?: (user, pass) => any) {
let modal; let modal;
modal = createModal({ modal = createModal({

View file

@ -1,8 +1,4 @@
/// <reference path="../../ui/elements/modal.ts" /> export namespace Modals {
/// <reference path="../../ConnectionHandler.ts" />
/// <reference path="../../proto.ts" />
namespace Modals {
/* /*
export function spawnQueryManage(client: ConnectionHandler) { export function spawnQueryManage(client: ConnectionHandler) {
let modal: Modal; let modal: Modal;

View file

@ -1,4 +1,4 @@
namespace Modals { export namespace Modals {
export function createServerModal(server: ServerEntry, callback: (properties?: ServerProperties) => Promise<void>) { export function createServerModal(server: ServerEntry, callback: (properties?: ServerProperties) => Promise<void>) {
const properties = Object.assign({}, server.properties); const properties = Object.assign({}, server.properties);

Some files were not shown because too many files have changed in this diff Show more