Changes are listed bellow:
- Fixed the control bar microphone and speaker buttons - Improved the default identity generation (no web worker required now) - Improved voice connection error handling (especially for firefox) - Adding a max reconnect limit for voice connection - Don't show the newcomer guide when directly connection to a server - Fixed default profile initialisationcanary
parent
4a4cbdf38e
commit
6f56150e0b
|
@ -1,4 +1,12 @@
|
|||
# Changelog:
|
||||
* **16.09.20**
|
||||
- Fixed the control bar microphone and speaker buttons
|
||||
- Improved the default identity generation (no web worker required now)
|
||||
- Improved voice connection error handling (especially for firefox)
|
||||
- Adding a max reconnect limit for voice connection
|
||||
- Don't show the newcomer guide when directly connection to a server
|
||||
- Fixed default profile initialisation
|
||||
|
||||
* **07.09.20**
|
||||
- Fixed the web client for safari
|
||||
|
||||
|
|
|
@ -1167,6 +1167,39 @@
|
|||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@peculiar/asn1-schema": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.17.tgz",
|
||||
"integrity": "sha512-7rJD8bR1r6NFE4skDxXsLsFEO3zM2TfjX9wdq5SERoBNEuxGkAJ3uIH84sIMxvDgJtb3cMfLsv8iNpGN0nAWdw==",
|
||||
"requires": {
|
||||
"@types/asn1js": "^0.0.1",
|
||||
"asn1js": "^2.0.26",
|
||||
"pvtsutils": "^1.0.11",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@peculiar/json-schema": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz",
|
||||
"integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
|
@ -1258,6 +1291,14 @@
|
|||
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/asn1js": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-0.0.1.tgz",
|
||||
"integrity": "sha1-74uflwjLFjKhw6nNJ3F8qr55O8I=",
|
||||
"requires": {
|
||||
"@types/pvutils": "*"
|
||||
}
|
||||
},
|
||||
"@types/clean-css": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.1.tgz",
|
||||
|
@ -1439,6 +1480,11 @@
|
|||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/pvutils": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/pvutils/-/pvutils-0.0.2.tgz",
|
||||
"integrity": "sha512-CgQAm7pjyeF3Gnv78ty4RBVIfluB+Td+2DR8iPaU0prF18pkzptHHP+DoKPfpsJYknKsVZyVsJEu5AuGgAqQ5w=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.9.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.26.tgz",
|
||||
|
@ -2212,6 +2258,11 @@
|
|||
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
|
||||
"dev": true
|
||||
},
|
||||
"asmcrypto.js": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-2.3.2.tgz",
|
||||
"integrity": "sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA=="
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
|
@ -2232,6 +2283,14 @@
|
|||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"asn1js": {
|
||||
"version": "2.0.26",
|
||||
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz",
|
||||
"integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==",
|
||||
"requires": {
|
||||
"pvutils": "^1.0.17"
|
||||
}
|
||||
},
|
||||
"assert": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
||||
|
@ -2577,8 +2636,7 @@
|
|||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
|
@ -2757,8 +2815,7 @@
|
|||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
|
@ -4007,7 +4064,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
|
||||
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
|
@ -4208,7 +4264,6 @@
|
|||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
|
@ -7046,7 +7101,6 @@
|
|||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
|
@ -7067,7 +7121,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
|
@ -8953,14 +9006,12 @@
|
|||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
|
||||
"dev": true
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
|
@ -10288,6 +10339,26 @@
|
|||
"escape-goat": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"pvtsutils": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.12.tgz",
|
||||
"integrity": "sha512-fudCcWFUE7WPHMRVdlEDdeaeLf+8hvZFvfJJ+p8GZlwrrdoiVfv7WZaPt6k7k/NZjMxR8yUbbH51hpwlSmLHiQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"pvutils": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz",
|
||||
"integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
|
@ -13937,6 +14008,49 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"webcrypto-core": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.8.tgz",
|
||||
"integrity": "sha512-hKnFXsqh0VloojNeTfrwFoRM4MnaWzH6vtXcaFcGjPEu+8HmBdQZnps3/2ikOFqS8bJN1RYr6mI2P/FJzyZnXg==",
|
||||
"requires": {
|
||||
"@peculiar/asn1-schema": "^2.0.12",
|
||||
"@peculiar/json-schema": "^1.1.12",
|
||||
"asn1js": "^2.0.26",
|
||||
"pvtsutils": "^1.0.11",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"webcrypto-liner": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webcrypto-liner/-/webcrypto-liner-1.2.3.tgz",
|
||||
"integrity": "sha512-e2P3hMkBVlMo1gMQnkEym0uJh+WVskQbvC3lT278ux3/OnxX7KAVaqROi1nlV9FRQBBvdadjuaWiFSLyESh4mA==",
|
||||
"requires": {
|
||||
"@peculiar/asn1-schema": "^2.0.3",
|
||||
"@peculiar/json-schema": "^1.1.10",
|
||||
"asmcrypto.js": "^2.3.2",
|
||||
"asn1js": "^2.0.26",
|
||||
"core-js": "^3.6.5",
|
||||
"des.js": "^1.0.1",
|
||||
"elliptic": "^6.5.2",
|
||||
"pvtsutils": "^1.0.10",
|
||||
"tslib": "^1.13.0",
|
||||
"webcrypto-core": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "4.42.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.1.tgz",
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
"resize-observer-polyfill": "^1.5.1",
|
||||
"simplebar-react": "^2.2.0",
|
||||
"twemoji": "^13.0.0",
|
||||
"webcrypto-liner": "^1.2.3",
|
||||
"webrtc-adapter": "^7.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -781,11 +781,15 @@ export class ConnectionHandler {
|
|||
if(currentInput) {
|
||||
if(shouldRecord) {
|
||||
if(this.getInputHardwareState() !== InputHardwareState.START_FAILED) {
|
||||
this.startVoiceRecorder(Date.now() - this._last_record_error_popup > 10 * 1000).then(() => {});
|
||||
this.startVoiceRecorder(Date.now() - this._last_record_error_popup > 10 * 1000).then(() => {
|
||||
this.event_registry.fire("notify_state_updated", { state: "microphone" });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
currentInput.stop().catch(error => {
|
||||
logWarn(LogCategory.AUDIO, tr("Failed to stop the microphone input recorder: %o"), error);
|
||||
}).then(() => {
|
||||
this.event_registry.fire("notify_state_updated", { state: "microphone" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -888,7 +892,7 @@ export class ConnectionHandler {
|
|||
reconnect_properties(profile?: ConnectionProfile) : ConnectParameters {
|
||||
const name = (this.getClient() ? this.getClient().clientNickName() : "") ||
|
||||
(this.serverConnection && this.serverConnection.handshake_handler() ? this.serverConnection.handshake_handler().parameters.nickname : "") ||
|
||||
StaticSettings.instance.static(Settings.KEY_CONNECT_USERNAME, profile ? profile.default_username : undefined) ||
|
||||
StaticSettings.instance.static(Settings.KEY_CONNECT_USERNAME, profile ? profile.defaultUsername : undefined) ||
|
||||
"Another TeaSpeak user";
|
||||
const channel = (this.getClient() && this.getClient().currentChannel() ? this.getClient().currentChannel().channelId : 0) ||
|
||||
(this.serverConnection && this.serverConnection.handshake_handler() ? (this.serverConnection.handshake_handler().parameters.channel || {} as any).target : "");
|
||||
|
@ -1048,6 +1052,7 @@ export class ConnectionHandler {
|
|||
this.client_status.input_muted = muted;
|
||||
this.sound.play(muted ? Sound.MICROPHONE_MUTED : Sound.MICROPHONE_ACTIVATED);
|
||||
this.update_voice_status();
|
||||
this.event_registry.fire("notify_state_updated", { state: "microphone" });
|
||||
}
|
||||
toggleMicrophone() { this.setMicrophoneMuted(!this.isMicrophoneMuted()); }
|
||||
|
||||
|
@ -1058,6 +1063,7 @@ export class ConnectionHandler {
|
|||
if(this.client_status.output_muted === muted) return;
|
||||
if(muted) this.sound.play(Sound.SOUND_MUTED); /* play the sound *before* we're setting the muted state */
|
||||
this.client_status.output_muted = muted;
|
||||
this.event_registry.fire("notify_state_updated", { state: "speaker" });
|
||||
if(!muted) this.sound.play(Sound.SOUND_ACTIVATED); /* play the sound *after* we're setting we've unmuted the sound */
|
||||
this.update_voice_status();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as log from "./log";
|
|||
import {LogCategory} from "./log";
|
||||
import {guid} from "./crypto/uid";
|
||||
import {createErrorModal, createInfoModal, createInputModal} from "./ui/elements/Modal";
|
||||
import {default_profile, find_profile} from "./profiles/ConnectionProfile";
|
||||
import {defaultConnectProfile, findConnectProfile} from "./profiles/ConnectionProfile";
|
||||
import {server_connections} from "./ui/frames/connection_handlers";
|
||||
import {spawnConnectModal} from "./ui/modal/ModalConnect";
|
||||
import * as top_menu from "./ui/frames/MenuBar";
|
||||
|
@ -10,7 +10,7 @@ import {control_bar_instance} from "./ui/frames/control-bar";
|
|||
import {ConnectionHandler} from "./ConnectionHandler";
|
||||
|
||||
export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
|
||||
const profile = find_profile(mark.connect_profile) || default_profile();
|
||||
const profile = findConnectProfile(mark.connect_profile) || defaultConnectProfile();
|
||||
if(profile.valid()) {
|
||||
const connection = (typeof(new_tab) !== "boolean" || !new_tab) ? server_connections.active_connection() : server_connections.spawn_server_connection();
|
||||
server_connections.set_active_connection(connection);
|
||||
|
@ -19,7 +19,7 @@ export const boorkmak_connect = (mark: Bookmark, new_tab?: boolean) => {
|
|||
profile,
|
||||
true,
|
||||
{
|
||||
nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connect_username() : mark.nickname,
|
||||
nickname: mark.nickname === "Another TeaSpeak user" || !mark.nickname ? profile.connectUsername() : mark.nickname,
|
||||
password: mark.server_properties.server_password_hash ? {
|
||||
password: mark.server_properties.server_password_hash,
|
||||
hashed: true
|
||||
|
|
|
@ -32,17 +32,18 @@ export class HandshakeHandler {
|
|||
}
|
||||
|
||||
initialize() {
|
||||
this.handshake_handler = this.profile.spawn_identity_handshake_handler(this.connection);
|
||||
this.handshake_handler = this.profile.spawnIdentityHandshakeHandler(this.connection);
|
||||
if(!this.handshake_handler) {
|
||||
this.handshake_failed("failed to create identity handler");
|
||||
return;
|
||||
}
|
||||
|
||||
this.handshake_handler.register_callback((flag, message) => {
|
||||
if(flag)
|
||||
if(flag) {
|
||||
this.handshake_finished();
|
||||
else
|
||||
} else {
|
||||
this.handshake_failed(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -55,7 +56,7 @@ export class HandshakeHandler {
|
|||
}
|
||||
|
||||
on_teamspeak() {
|
||||
const type = this.profile.selected_type();
|
||||
const type = this.profile.selectedType();
|
||||
if(type == IdentitifyType.TEAMSPEAK)
|
||||
this.handshake_finished();
|
||||
else {
|
||||
|
@ -124,8 +125,8 @@ export class HandshakeHandler {
|
|||
data.client_platform = (os_mapping[os.platform()] || os.platform());
|
||||
}
|
||||
|
||||
if(this.profile.selected_type() === IdentitifyType.TEAMSPEAK)
|
||||
data["client_key_offset"] = (this.profile.selected_identity() as TeaSpeakIdentity).hash_number;
|
||||
if(this.profile.selectedType() === IdentitifyType.TEAMSPEAK)
|
||||
data["client_key_offset"] = (this.profile.selectedIdentity() as TeaSpeakIdentity).hash_number;
|
||||
|
||||
this.connection.send_command("clientinit", data).catch(error => {
|
||||
if(error instanceof CommandResult) {
|
||||
|
|
|
@ -11,7 +11,9 @@ export enum VoiceConnectionStatus {
|
|||
Connecting,
|
||||
Connected,
|
||||
Disconnecting,
|
||||
Disconnected
|
||||
Disconnected,
|
||||
|
||||
Failed
|
||||
}
|
||||
|
||||
export interface VoiceConnectionEvents {
|
||||
|
@ -55,6 +57,7 @@ export abstract class AbstractVoiceConnection {
|
|||
}
|
||||
|
||||
abstract getConnectionState() : VoiceConnectionStatus;
|
||||
abstract getFailedMessage() : string;
|
||||
|
||||
abstract encodingSupported(codec: number) : boolean;
|
||||
abstract decodingSupported(codec: number) : boolean;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as loader from "tc-loader";
|
||||
import {settings, Settings} from "tc-shared/settings";
|
||||
import * as profiles from "tc-shared/profiles/ConnectionProfile";
|
||||
import * as log from "tc-shared/log";
|
||||
import {LogCategory} from "tc-shared/log";
|
||||
import * as bipc from "./ipc/BrowserIPC";
|
||||
import * as sound from "./sound/Sounds";
|
||||
import * as i18n from "./i18n/localize";
|
||||
import {tra} from "./i18n/localize";
|
||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||
import {createInfoModal} from "tc-shared/ui/elements/Modal";
|
||||
import * as stats from "./stats";
|
||||
import * as fidentity from "./profiles/identities/TeaForumIdentity";
|
||||
|
@ -32,7 +31,6 @@ import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMe
|
|||
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||
import {checkForUpdatedApp} from "tc-shared/update";
|
||||
import {setupJSRender} from "tc-shared/ui/jsrender";
|
||||
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
||||
import "svg-sprites/client-icons";
|
||||
|
||||
/* required import for init */
|
||||
|
@ -44,8 +42,10 @@ import "./connection/CommandHandler";
|
|||
import "./connection/ConnectionBase";
|
||||
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
|
||||
import "./video-viewer/Controller";
|
||||
|
||||
import "./profiles/ConnectionProfile";
|
||||
import "./update/UpdaterWeb";
|
||||
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
||||
import {defaultConnectProfile, findConnectProfile} from "tc-shared/profiles/ConnectionProfile";
|
||||
|
||||
async function initialize() {
|
||||
try {
|
||||
|
@ -109,8 +109,6 @@ async function initialize_app() {
|
|||
});
|
||||
sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100);
|
||||
|
||||
await profiles.load();
|
||||
|
||||
try {
|
||||
await ppt.initialize();
|
||||
} catch(error) {
|
||||
|
@ -120,57 +118,15 @@ async function initialize_app() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class TestProxy extends bipc.MethodProxy {
|
||||
constructor(params: bipc.MethodProxyConnectParameters) {
|
||||
super(bipc.get_handler(), params.channel_id && params.client_id ? params : undefined);
|
||||
|
||||
if(!this.is_slave()) {
|
||||
this.register_method(this.add_slave);
|
||||
}
|
||||
if(!this.is_master()) {
|
||||
this.register_method(this.say_hello);
|
||||
this.register_method(this.add_master);
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
}
|
||||
|
||||
protected on_connected() {
|
||||
log.info(LogCategory.IPC, "Test proxy connected");
|
||||
}
|
||||
|
||||
protected on_disconnected() {
|
||||
log.info(LogCategory.IPC, "Test proxy disconnected");
|
||||
}
|
||||
|
||||
private async say_hello() : Promise<void> {
|
||||
log.info(LogCategory.IPC, "Hello World");
|
||||
}
|
||||
|
||||
private async add_slave(a: number, b: number) : Promise<number> {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
private async add_master(a: number, b: number) : Promise<number> {
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
interface Window {
|
||||
proxy_instance: TestProxy & {url: () => string};
|
||||
}
|
||||
*/
|
||||
|
||||
export function handle_connect_request(properties: ConnectRequestData, connection: ConnectionHandler) {
|
||||
const profile_uuid = properties.profile || (profiles.default_profile() || {id: 'default'}).id;
|
||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||
const username = properties.username || profile.connect_username();
|
||||
const profile_uuid = properties.profile || (defaultConnectProfile() || { id: 'default' }).id;
|
||||
const profile = findConnectProfile(profile_uuid) || defaultConnectProfile();
|
||||
const username = properties.username || profile.connectUsername();
|
||||
|
||||
const password = properties.password ? properties.password.value : "";
|
||||
const password_hashed = properties.password ? properties.password.hashed : false;
|
||||
|
||||
debugger;
|
||||
if(profile && profile.valid()) {
|
||||
connection.startConnection(properties.address, profile, true, {
|
||||
nickname: username,
|
||||
|
@ -192,21 +148,6 @@ export function handle_connect_request(properties: ConnectRequestData, connectio
|
|||
}
|
||||
|
||||
function main() {
|
||||
/*
|
||||
window.proxy_instance = new TestProxy({
|
||||
client_id: settings.static_global<string>("proxy_client_id", undefined),
|
||||
channel_id: settings.static_global<string>("proxy_channel_id", undefined)
|
||||
}) as any;
|
||||
if(window.proxy_instance.is_master()) {
|
||||
window.proxy_instance.setup();
|
||||
window.proxy_instance.url = () => {
|
||||
const data = window.proxy_instance.generate_connect_parameters();
|
||||
return "proxy_channel_id=" + data.channel_id + "&proxy_client_id=" + data.client_id;
|
||||
};
|
||||
}
|
||||
*/
|
||||
//http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1
|
||||
|
||||
/* initialize font */
|
||||
{
|
||||
const font = settings.static_global(Settings.KEY_FONT_SIZE, 14); //parseInt(getComputedStyle(document.body).fontSize)
|
||||
|
@ -268,32 +209,6 @@ function main() {
|
|||
server_connections.set_active_connection(server_connections.all_connections()[0]);
|
||||
checkForUpdatedApp();
|
||||
|
||||
/*
|
||||
(window as any).test_upload = (message?: string) => {
|
||||
message = message || "Hello World";
|
||||
|
||||
const connection = server_connections.active_connection();
|
||||
connection.fileManager.upload_file({
|
||||
size: message.length,
|
||||
overwrite: true,
|
||||
channel: connection.getClient().currentChannel(),
|
||||
name: '/HelloWorld.txt',
|
||||
path: ''
|
||||
}).then(key => {
|
||||
const upload = new RequestFileUpload(key);
|
||||
|
||||
const buffer = new Uint8Array(message.length);
|
||||
{
|
||||
for(let index = 0; index < message.length; index++)
|
||||
buffer[index] = message.charCodeAt(index);
|
||||
}
|
||||
|
||||
upload.put_data(buffer).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
};
|
||||
*/
|
||||
(window as any).test_download = async () => {
|
||||
const connection = server_connections.active_connection();
|
||||
const download = connection.fileManager.initializeFileDownload({
|
||||
|
@ -387,9 +302,11 @@ function main() {
|
|||
//setTimeout(() => spawnPermissionEditorModal(server_connections.active_connection()), 3000);
|
||||
//setTimeout(() => spawnGroupCreate(server_connections.active_connection(), "server"), 3000);
|
||||
|
||||
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
|
||||
const modal = openModalNewcomer();
|
||||
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
||||
if(server_connections.active_connection().getServerConnection().getConnectionState() === ConnectionState.UNCONNECTED) {
|
||||
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
|
||||
const modal = openModalNewcomer();
|
||||
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
||||
}
|
||||
}
|
||||
|
||||
//spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs");
|
||||
|
|
|
@ -6,33 +6,36 @@ import {AbstractServerConnection} from "../connection/ConnectionBase";
|
|||
import {HandshakeIdentityHandler} from "../connection/HandshakeHandler";
|
||||
import {createErrorModal} from "../ui/elements/Modal";
|
||||
import {formatMessage} from "../ui/frames/chat";
|
||||
import * as loader from "tc-loader";
|
||||
import {Stage} from "tc-loader";
|
||||
import {LogCategory, logDebug, logError} from "tc-shared/log";
|
||||
|
||||
export class ConnectionProfile {
|
||||
id: string;
|
||||
|
||||
profile_name: string;
|
||||
default_username: string;
|
||||
default_password: string;
|
||||
profileName: string;
|
||||
defaultUsername: string;
|
||||
defaultPassword: string;
|
||||
|
||||
selected_identity_type: string = "unset";
|
||||
selectedIdentityType: string = "unset";
|
||||
identities: { [key: string]: Identity } = {};
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
connect_username(): string {
|
||||
if (this.default_username && this.default_username !== "Another TeaSpeak user")
|
||||
return this.default_username;
|
||||
connectUsername(): string {
|
||||
if (this.defaultUsername && this.defaultUsername !== "Another TeaSpeak user")
|
||||
return this.defaultUsername;
|
||||
|
||||
let selected = this.selected_identity();
|
||||
let selected = this.selectedIdentity();
|
||||
let name = selected ? selected.fallback_name() : undefined;
|
||||
return name || "Another TeaSpeak user";
|
||||
}
|
||||
|
||||
selected_identity(current_type?: IdentitifyType): Identity {
|
||||
selectedIdentity(current_type?: IdentitifyType): Identity {
|
||||
if (!current_type)
|
||||
current_type = this.selected_type();
|
||||
current_type = this.selectedType();
|
||||
|
||||
if (current_type === undefined)
|
||||
return undefined;
|
||||
|
@ -46,55 +49,58 @@ export class ConnectionProfile {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
selected_type?(): IdentitifyType {
|
||||
return this.selected_identity_type ? IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
|
||||
selectedType(): IdentitifyType | undefined {
|
||||
return this.selectedIdentityType ? IdentitifyType[this.selectedIdentityType.toUpperCase()] : undefined;
|
||||
}
|
||||
|
||||
set_identity(type: IdentitifyType, identity: Identity) {
|
||||
setIdentity(type: IdentitifyType, identity: Identity) {
|
||||
this.identities[IdentitifyType[type].toLowerCase()] = identity;
|
||||
}
|
||||
|
||||
spawn_identity_handshake_handler?(connection: AbstractServerConnection): HandshakeIdentityHandler {
|
||||
const identity = this.selected_identity();
|
||||
spawnIdentityHandshakeHandler(connection: AbstractServerConnection): HandshakeIdentityHandler | undefined {
|
||||
const identity = this.selectedIdentity();
|
||||
if (!identity)
|
||||
return undefined;
|
||||
return identity.spawn_identity_handshake_handler(connection);
|
||||
}
|
||||
|
||||
encode?(): string {
|
||||
encode(): string {
|
||||
const identity_data = {};
|
||||
for (const key in this.identities)
|
||||
if (this.identities[key])
|
||||
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,
|
||||
username: this.defaultUsername,
|
||||
password: this.defaultPassword,
|
||||
profile_name: this.profileName,
|
||||
identity_type: this.selectedIdentityType,
|
||||
identity_data: identity_data,
|
||||
id: this.id
|
||||
});
|
||||
}
|
||||
|
||||
valid(): boolean {
|
||||
const identity = this.selected_identity();
|
||||
const identity = this.selectedIdentity();
|
||||
|
||||
return !!identity && identity.valid();
|
||||
}
|
||||
}
|
||||
|
||||
async function decode_profile(data): Promise<ConnectionProfile | string> {
|
||||
data = JSON.parse(data);
|
||||
if (data.version !== 1)
|
||||
async function decodeProfile(payload: string): Promise<ConnectionProfile | string> {
|
||||
const data = JSON.parse(payload);
|
||||
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();
|
||||
result.defaultUsername = data.username;
|
||||
result.defaultPassword = data.password;
|
||||
result.profileName = data.profile_name;
|
||||
result.selectedIdentityType = (data.identity_type || "").toLowerCase();
|
||||
|
||||
if (data.identity_data) {
|
||||
for (const key of Object.keys(data.identity_data)) {
|
||||
|
@ -117,20 +123,19 @@ interface ProfilesData {
|
|||
profiles: string[];
|
||||
}
|
||||
|
||||
let available_profiles: ConnectionProfile[] = [];
|
||||
let availableProfiles_: ConnectionProfile[] = [];
|
||||
|
||||
export async function load() {
|
||||
available_profiles = [];
|
||||
async function loadConnectProfiles() {
|
||||
availableProfiles_ = [];
|
||||
|
||||
const profiles_json = localStorage.getItem("profiles");
|
||||
let profiles_data: ProfilesData = (() => {
|
||||
try {
|
||||
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
|
||||
} catch (error) {
|
||||
debugger;
|
||||
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
|
||||
createErrorModal(tr("Profile data invalid"), formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
|
||||
return {version: 0};
|
||||
return { version: 0 };
|
||||
}
|
||||
})();
|
||||
|
||||
|
@ -142,56 +147,58 @@ export async function load() {
|
|||
}
|
||||
if (profiles_data.version == 1) {
|
||||
for (const profile_data of profiles_data.profiles) {
|
||||
const profile = await decode_profile(profile_data);
|
||||
const profile = await decodeProfile(profile_data);
|
||||
if (typeof profile === "string") {
|
||||
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
|
||||
} else {
|
||||
available_profiles.push(profile as ConnectionProfile);
|
||||
availableProfiles_.push(profile as ConnectionProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!find_profile("default")) { //Create a default profile and teaforo profile
|
||||
const defaultProfile = findConnectProfile("default");
|
||||
if (!defaultProfile) { //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";
|
||||
const profile = createConnectProfile(tr("Default Profile"), "default");
|
||||
profile.defaultPassword = "";
|
||||
profile.defaultUsername = "";
|
||||
profile.profileName = "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];
|
||||
const identity = await TeaSpeakIdentity.generateNew();
|
||||
const begin = Date.now();
|
||||
|
||||
const newLevel = await identity.improveLevelJavascript(8, () => Date.now() - begin < 1000);
|
||||
/* await identity.improveLevelNative(8, 1, () => doImprove); */
|
||||
logDebug(LogCategory.IDENTITIES, tr("Improved the identity level to %d within %s milliseconds"), newLevel, Date.now() - begin);
|
||||
|
||||
profile.setIdentity(IdentitifyType.TEAMSPEAK, identity);
|
||||
profile.selectedIdentityType = IdentitifyType[IdentitifyType.TEAMSPEAK];
|
||||
} catch (error) {
|
||||
logError(LogCategory.GENERAL, tr("Failed to generate the default identity: %o"), 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";
|
||||
const profile = createConnectProfile(tr("TeaSpeak Forum Profile"), "teaforo");
|
||||
profile.defaultPassword = "";
|
||||
profile.defaultUsername = "";
|
||||
|
||||
profile.set_identity(IdentitifyType.TEAFORO, TeaForumIdentity.identity());
|
||||
profile.selected_identity_type = IdentitifyType[IdentitifyType.TEAFORO];
|
||||
profile.setIdentity(IdentitifyType.TEAFORO, TeaForumIdentity.identity());
|
||||
profile.selectedIdentityType = IdentitifyType[IdentitifyType.TEAFORO];
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
export function create_new_profile(name: string, id?: string): ConnectionProfile {
|
||||
export function createConnectProfile(name: string, id?: string): ConnectionProfile {
|
||||
const profile = new ConnectionProfile(id || guid());
|
||||
profile.profile_name = name;
|
||||
profile.default_username = "";
|
||||
available_profiles.push(profile);
|
||||
profile.profileName = name;
|
||||
profile.defaultUsername = "";
|
||||
availableProfiles_.push(profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
@ -199,8 +206,9 @@ let _requires_save = false;
|
|||
|
||||
export function save() {
|
||||
const profiles: string[] = [];
|
||||
for (const profile of available_profiles)
|
||||
for (const profile of availableProfiles_) {
|
||||
profiles.push(profile.encode());
|
||||
}
|
||||
|
||||
const data = JSON.stringify({
|
||||
version: 1,
|
||||
|
@ -217,34 +225,36 @@ export function requires_save(): boolean {
|
|||
return _requires_save;
|
||||
}
|
||||
|
||||
export function profiles(): ConnectionProfile[] {
|
||||
return available_profiles;
|
||||
export function availableConnectProfiles(): ConnectionProfile[] {
|
||||
return availableProfiles_;
|
||||
}
|
||||
|
||||
export function find_profile(id: string): ConnectionProfile | undefined {
|
||||
for (const profile of profiles())
|
||||
if (profile.id == id)
|
||||
export function findConnectProfile(id: string): ConnectionProfile | undefined {
|
||||
for (const profile of availableConnectProfiles()) {
|
||||
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)
|
||||
for (const profile of availableConnectProfiles())
|
||||
if ((profile.profileName || "").toLowerCase() == name)
|
||||
return profile;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function default_profile(): ConnectionProfile {
|
||||
return find_profile("default");
|
||||
export function defaultConnectProfile(): ConnectionProfile {
|
||||
return findConnectProfile("default");
|
||||
}
|
||||
|
||||
export function set_default_profile(profile: ConnectionProfile) {
|
||||
const old_default = default_profile();
|
||||
const old_default = defaultConnectProfile();
|
||||
if (old_default && old_default != profile) {
|
||||
old_default.id = guid();
|
||||
}
|
||||
|
@ -253,10 +263,19 @@ export function set_default_profile(profile: ConnectionProfile) {
|
|||
}
|
||||
|
||||
export function delete_profile(profile: ConnectionProfile) {
|
||||
available_profiles.remove(profile);
|
||||
availableProfiles_.remove(profile);
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", event => {
|
||||
if(requires_save())
|
||||
if(requires_save()) {
|
||||
save();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||
name: "Identity setup",
|
||||
function: async () => {
|
||||
await loadConnectProfiles();
|
||||
},
|
||||
priority: 30
|
||||
})
|
|
@ -81,9 +81,9 @@ export class HandshakeCommandHandler<T extends AbstractHandshakeIdentityHandler>
|
|||
|
||||
|
||||
handle_command(command: ServerCommand): boolean {
|
||||
if($.isFunction(this[command.command]))
|
||||
if(typeof this[command.command] === "function") {
|
||||
this[command.command](command.arguments);
|
||||
else if(command.command == "error") {
|
||||
} else if(command.command == "error") {
|
||||
return false;
|
||||
} else {
|
||||
console.warn(tr("Received unknown command while handshaking (%o)"), command);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as log from "../../log";
|
||||
import {LogCategory} from "../../log";
|
||||
import {LogCategory, logDebug, logTrace} from "../../log";
|
||||
import * as asn1 from "../../crypto/asn1";
|
||||
import * as sha from "../../crypto/sha";
|
||||
|
||||
|
@ -217,6 +217,7 @@ export namespace CryptoHelper {
|
|||
};
|
||||
}
|
||||
}
|
||||
import arraybuffer_to_string = CryptoHelper.arraybuffer_to_string;
|
||||
|
||||
export class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
||||
identity: TeaSpeakIdentity;
|
||||
|
@ -234,7 +235,7 @@ export class TeaSpeakHandshakeHandler extends AbstractHandshakeIdentityHandler {
|
|||
this.connection.send_command("handshakebegin", {
|
||||
intention: 0,
|
||||
authentication_method: this.identity.type(),
|
||||
publicKey: this.identity.public_key
|
||||
publicKey: this.identity.publicKey
|
||||
}).catch(error => {
|
||||
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeamSpeak based handshake. Error: %o"), error);
|
||||
|
||||
|
@ -433,7 +434,7 @@ class IdentityPOWWorker {
|
|||
}
|
||||
|
||||
export class TeaSpeakIdentity implements Identity {
|
||||
static async generate_new() : Promise<TeaSpeakIdentity> {
|
||||
static async generateNew() : Promise<TeaSpeakIdentity> {
|
||||
let key: CryptoKeyPair;
|
||||
try {
|
||||
key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
|
||||
|
@ -501,7 +502,7 @@ export class TeaSpeakIdentity implements Identity {
|
|||
private_key: string; /* base64 representation of the private key */
|
||||
_name: string;
|
||||
|
||||
public_key: string; /* only set when initialized */
|
||||
publicKey: string; /* only set when initialized */
|
||||
|
||||
private _initialized: boolean;
|
||||
private _crypto_key: CryptoKey;
|
||||
|
@ -569,21 +570,25 @@ export class TeaSpeakIdentity implements Identity {
|
|||
}
|
||||
|
||||
async level() : Promise<number> {
|
||||
if(!this._initialized || !this.public_key)
|
||||
if(!this._initialized || !this.publicKey)
|
||||
throw "not initialized";
|
||||
|
||||
const hash = new Uint8Array(await sha.sha1(this.public_key + this.hash_number));
|
||||
const hash = new Uint8Array(await sha.sha1(this.publicKey + this.hash_number));
|
||||
|
||||
return TeaSpeakIdentity.calculateLevel(hash);
|
||||
}
|
||||
|
||||
private static calculateLevel(buffer: Uint8Array) : number {
|
||||
let level = 0;
|
||||
while(level < hash.byteLength && hash[level] == 0)
|
||||
while(level < buffer.byteLength && buffer[level] === 0)
|
||||
level++;
|
||||
|
||||
if(level >= hash.byteLength) {
|
||||
if(level >= buffer.byteLength) {
|
||||
level = 256;
|
||||
} else {
|
||||
let byte = hash[level];
|
||||
let byte = buffer[level];
|
||||
level <<= 3;
|
||||
while((byte & 0x1) == 0) {
|
||||
while((byte & 0x1) === 0) {
|
||||
level++;
|
||||
byte >>= 1;
|
||||
}
|
||||
|
@ -597,7 +602,7 @@ export class TeaSpeakIdentity implements Identity {
|
|||
* @param {string} b
|
||||
* @description b must be smaller (in bytes) then a
|
||||
*/
|
||||
private string_add(a: string, b: string) {
|
||||
private static string_add(a: string, b: string) {
|
||||
const char_result: number[] = [];
|
||||
const char_a = [...a].reverse().map(e => e.charCodeAt(0));
|
||||
const char_b = [...b].reverse().map(e => e.charCodeAt(0));
|
||||
|
@ -628,29 +633,34 @@ export class TeaSpeakIdentity implements Identity {
|
|||
let active = true;
|
||||
setTimeout(() => active = false, time);
|
||||
|
||||
return await this.improve_level(-1, threads, () => active);
|
||||
return await this.improveLevelNative(-1, threads, () => active);
|
||||
}
|
||||
|
||||
async improve_level(target: number, threads: number, active_callback: () => boolean, callback_level?: (current: number) => any, callback_status?: (hash_rate: number) => any) : Promise<Boolean> {
|
||||
if(!this._initialized || !this.public_key)
|
||||
async improveLevelNative(target: number, threads: number, active_callback: () => boolean, callback_level?: (current: number) => any, callback_status?: (hash_rate: number) => any) : Promise<Boolean> {
|
||||
if(!this._initialized || !this.publicKey) {
|
||||
throw "not initialized";
|
||||
if(target == -1) /* get the highest level possible */
|
||||
}
|
||||
|
||||
/* get the highest level possible */
|
||||
if(target == -1) {
|
||||
target = 0;
|
||||
else if(target <= await this.level())
|
||||
} else if(target <= await this.level()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const workers: IdentityPOWWorker[] = [];
|
||||
|
||||
const iterations = 100000;
|
||||
let current_hash;
|
||||
const next_hash = () => {
|
||||
if(!current_hash)
|
||||
if(!current_hash) {
|
||||
return (current_hash = this.hash_number);
|
||||
}
|
||||
|
||||
if(current_hash.length < iterations.toString().length) {
|
||||
current_hash = this.string_add(iterations.toString(), current_hash);
|
||||
current_hash = TeaSpeakIdentity.string_add(iterations.toString(), current_hash);
|
||||
} else {
|
||||
current_hash = this.string_add(current_hash, iterations.toString());
|
||||
current_hash = TeaSpeakIdentity.string_add(current_hash, iterations.toString());
|
||||
}
|
||||
return current_hash;
|
||||
};
|
||||
|
@ -661,7 +671,7 @@ export class TeaSpeakIdentity implements Identity {
|
|||
for (let index = 0; index < threads; index++) {
|
||||
const worker = new IdentityPOWWorker();
|
||||
workers.push(worker);
|
||||
initialize_promise.push(worker.initialize(this.public_key));
|
||||
initialize_promise.push(worker.initialize(this.publicKey));
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -778,6 +788,63 @@ export class TeaSpeakIdentity implements Identity {
|
|||
throw "this should never be reached";
|
||||
}
|
||||
|
||||
/* Improve the identity within the current thread */
|
||||
async improveLevelJavascript(target: number, activeCallback: () => boolean) : Promise<number> {
|
||||
const publicKey = str2ab8(this.publicKey);
|
||||
const buffer = new Uint8Array(publicKey.byteLength + 20); /* Max 20 append digest (Appendix is a number in range of [0;2^64]) -> log10(2^64) */
|
||||
|
||||
buffer.set(new Uint8Array(publicKey));
|
||||
buffer.set(new Uint8Array(str2ab8(this.hash_number)), publicKey.byteLength);
|
||||
|
||||
const kChar9 = '9'.charCodeAt(0);
|
||||
const kChar0 = '0'.charCodeAt(0);
|
||||
|
||||
let numberIndex = publicKey.byteLength + this.hash_number.length;
|
||||
let bufferView = buffer.subarray(0, numberIndex);
|
||||
|
||||
const incrementCounter = () => {
|
||||
let currentIndex = numberIndex - 1;
|
||||
while(currentIndex > publicKey.byteLength && buffer[currentIndex] == kChar9) {
|
||||
buffer[currentIndex--] = kChar0;
|
||||
}
|
||||
|
||||
if(currentIndex > publicKey.byteLength) {
|
||||
buffer[currentIndex]++;
|
||||
} else {
|
||||
/* yeah a new diget */
|
||||
if(numberIndex >= buffer.byteLength) {
|
||||
throw "hash number got too big. use another identity";
|
||||
}
|
||||
|
||||
buffer[numberIndex] = kChar0;
|
||||
numberIndex++;
|
||||
buffer[currentIndex] = '1'.charCodeAt(0);
|
||||
bufferView = buffer.subarray(0, numberIndex);
|
||||
}
|
||||
};
|
||||
|
||||
let currentLevel = await this.level();
|
||||
let iteration = 0;
|
||||
const timeBegin = Date.now();
|
||||
|
||||
while(currentLevel < target) {
|
||||
if((iteration++ % 1000) === 0 && !activeCallback()) {
|
||||
break;
|
||||
}
|
||||
|
||||
incrementCounter();
|
||||
const newLevel = await TeaSpeakIdentity.calculateLevel(new Uint8Array(await crypto.subtle.digest("SHA-1", bufferView)));
|
||||
if(newLevel > currentLevel) {
|
||||
this.hash_number = arraybuffer_to_string(buffer.subarray(publicKey.byteLength, numberIndex));
|
||||
logTrace(LogCategory.IDENTITIES, tr("Found a new identity level at %s. Previous level %d now %d (%d hashes/second)"),
|
||||
this.hash_number, currentLevel, newLevel, iteration * 1000 / (Date.now() - timeBegin));
|
||||
currentLevel = newLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return currentLevel;
|
||||
}
|
||||
|
||||
private async initialize() {
|
||||
if(!this.private_key)
|
||||
throw "Invalid private key";
|
||||
|
@ -806,8 +873,8 @@ export class TeaSpeakIdentity implements Identity {
|
|||
}
|
||||
|
||||
try {
|
||||
this.public_key = await CryptoHelper.export_ecc_key(this._crypto_key, true);
|
||||
this._unique_id = base64_encode_ab(await sha.sha1(this.public_key));
|
||||
this.publicKey = await CryptoHelper.export_ecc_key(this._crypto_key, true);
|
||||
this._unique_id = base64_encode_ab(await sha.sha1(this.publicKey));
|
||||
} catch(error) {
|
||||
log.error(LogCategory.IDENTITIES, error);
|
||||
throw "failed to calculate unique id";
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
save_bookmark
|
||||
} from "../../bookmarks";
|
||||
import {connection_log, Regex} from "../../ui/modal/ModalConnect";
|
||||
import {profiles} from "../../profiles/ConnectionProfile";
|
||||
import {availableConnectProfiles} from "../../profiles/ConnectionProfile";
|
||||
import {spawnYesNo} from "../../ui/modal/ModalYesNo";
|
||||
import {Settings, settings} from "../../settings";
|
||||
import * as log from "../../log";
|
||||
|
@ -203,11 +203,11 @@ export function spawnBookmarkModal() {
|
|||
.text("")
|
||||
.css("display", "none")
|
||||
);
|
||||
for (const profile of profiles()) {
|
||||
for (const profile of availableConnectProfiles()) {
|
||||
input_connect_profile.append(
|
||||
$.spawn("option")
|
||||
.attr("value", profile.id)
|
||||
.text(profile.profile_name)
|
||||
.text(profile.profileName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ export function spawnBookmarkModal() {
|
|||
|
||||
input_connect_profile.on('change', event => {
|
||||
const id = input_connect_profile.val() as string;
|
||||
const profile = profiles().find(e => e.id === id);
|
||||
const profile = availableConnectProfiles().find(e => e.id === id);
|
||||
if (profile) {
|
||||
(selected_bookmark as Bookmark).connect_profile = id;
|
||||
save_bookmark(selected_bookmark);
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as log from "../../log";
|
|||
import {LogCategory} from "../../log";
|
||||
import * as loader from "tc-loader";
|
||||
import {createModal} from "../../ui/elements/Modal";
|
||||
import {ConnectionProfile, default_profile, find_profile, profiles} from "../../profiles/ConnectionProfile";
|
||||
import {ConnectionProfile, defaultConnectProfile, findConnectProfile, availableConnectProfiles} from "../../profiles/ConnectionProfile";
|
||||
import {KeyCode} from "../../PPTListener";
|
||||
import * as i18nc from "../../i18n/country";
|
||||
import {spawnSettingsModal} from "../../ui/modal/ModalSettings";
|
||||
|
@ -208,18 +208,18 @@ export function spawnConnectModal(options: {
|
|||
|
||||
/* Connect Profiles */
|
||||
{
|
||||
for (const profile of profiles()) {
|
||||
for (const profile of availableConnectProfiles()) {
|
||||
input_profile.append(
|
||||
$.spawn("option").text(profile.profile_name).val(profile.id)
|
||||
$.spawn("option").text(profile.profileName).val(profile.id)
|
||||
);
|
||||
}
|
||||
|
||||
input_profile.on('change', event => {
|
||||
selected_profile = find_profile(input_profile.val() as string) || default_profile();
|
||||
selected_profile = findConnectProfile(input_profile.val() as string) || defaultConnectProfile();
|
||||
{
|
||||
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, undefined);
|
||||
input_nickname
|
||||
.attr('placeholder', selected_profile.connect_username() || "Another TeaSpeak user")
|
||||
.attr('placeholder', selected_profile.connectUsername() || "Another TeaSpeak user")
|
||||
.val("");
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export function spawnTeamSpeakIdentityImprove(identity: TeaSpeakIdentity, name:
|
|||
const threads = parseInt(input_threads.val() as string);
|
||||
const target_level = parseInt(input_target_level.val() as string);
|
||||
if (target_level == 0) {
|
||||
identity.improve_level(-1, threads, () => active, current_level => {
|
||||
identity.improveLevelNative(-1, threads, () => active, current_level => {
|
||||
input_current_level.val(current_level);
|
||||
}, hash_rate => {
|
||||
input_hash_rate.val(hash_rate);
|
||||
|
@ -63,7 +63,7 @@ export function spawnTeamSpeakIdentityImprove(identity: TeaSpeakIdentity, name:
|
|||
button_start_stop.trigger('click');
|
||||
});
|
||||
} else {
|
||||
identity.improve_level(target_level, threads, () => active, current_level => {
|
||||
identity.improveLevelNative(target_level, threads, () => active, current_level => {
|
||||
input_current_level.val(current_level);
|
||||
}, hash_rate => {
|
||||
input_hash_rate.val(hash_rate);
|
||||
|
|
|
@ -701,7 +701,7 @@ export namespace modal_settings {
|
|||
error: text
|
||||
});
|
||||
event_registry.on("create-profile", event => {
|
||||
const profile = profiles.create_new_profile(event.name);
|
||||
const profile = profiles.createConnectProfile(event.name);
|
||||
profiles.mark_need_save();
|
||||
event_registry.fire_async("create-profile-result", {
|
||||
status: "success",
|
||||
|
@ -711,7 +711,7 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("delete-profile", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("delete-profile-result", event.profile_id, tr("Unknown profile"));
|
||||
|
@ -723,15 +723,15 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
const build_profile_info = (profile: ConnectionProfile) => {
|
||||
const forum_data = profile.selected_identity(IdentitifyType.TEAFORO) as TeaForumIdentity;
|
||||
const teamspeak_data = profile.selected_identity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
const nickname_data = profile.selected_identity(IdentitifyType.NICKNAME) as NameIdentity;
|
||||
const forum_data = profile.selectedIdentity(IdentitifyType.TEAFORO) as TeaForumIdentity;
|
||||
const teamspeak_data = profile.selectedIdentity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
const nickname_data = profile.selectedIdentity(IdentitifyType.NICKNAME) as NameIdentity;
|
||||
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.profile_name,
|
||||
nickname: profile.default_username,
|
||||
identity_type: profile.selected_identity_type as any,
|
||||
name: profile.profileName,
|
||||
nickname: profile.defaultUsername,
|
||||
identity_type: profile.selectedIdentityType as any,
|
||||
identity_forum: !forum_data ? undefined : {
|
||||
valid: forum_data.valid(),
|
||||
fallback_name: forum_data.fallback_name()
|
||||
|
@ -749,12 +749,12 @@ export namespace modal_settings {
|
|||
event_registry.on("query-profile-list", event => {
|
||||
event_registry.fire_async("query-profile-list-result", {
|
||||
status: "success",
|
||||
profiles: profiles.profiles().map(e => build_profile_info(e))
|
||||
profiles: profiles.availableConnectProfiles().map(e => build_profile_info(e))
|
||||
});
|
||||
});
|
||||
|
||||
event_registry.on("query-profile", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("query-profile-result", event.profile_id, tr("Unknown profile"));
|
||||
|
@ -769,7 +769,7 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("set-default-profile", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("set-default-profile-result", event.profile_id, tr("Unknown profile"));
|
||||
|
@ -785,14 +785,14 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("set-profile-name", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("set-profile-name-result", event.profile_id, tr("Unknown profile"));
|
||||
return;
|
||||
}
|
||||
|
||||
profile.profile_name = event.name;
|
||||
profile.profileName = event.name;
|
||||
profiles.mark_need_save();
|
||||
event_registry.fire_async("set-profile-name-result", {
|
||||
name: event.name,
|
||||
|
@ -802,14 +802,14 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("set-default-name", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("set-default-name-result", event.profile_id, tr("Unknown profile"));
|
||||
return;
|
||||
}
|
||||
|
||||
profile.default_username = event.name;
|
||||
profile.defaultUsername = event.name;
|
||||
profiles.mark_need_save();
|
||||
event_registry.fire_async("set-default-name-result", {
|
||||
name: event.name,
|
||||
|
@ -819,16 +819,16 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("set-identity-name-name", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("set-identity-name-name-result", event.profile_id, tr("Unknown profile"));
|
||||
return;
|
||||
}
|
||||
|
||||
let identity = profile.selected_identity(IdentitifyType.NICKNAME) as NameIdentity;
|
||||
let identity = profile.selectedIdentity(IdentitifyType.NICKNAME) as NameIdentity;
|
||||
if (!identity)
|
||||
profile.set_identity(IdentitifyType.NICKNAME, identity = new NameIdentity());
|
||||
profile.setIdentity(IdentitifyType.NICKNAME, identity = new NameIdentity());
|
||||
identity.set_name(event.name);
|
||||
profiles.mark_need_save();
|
||||
|
||||
|
@ -840,7 +840,7 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("query-profile-validity", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("query-profile-validity-result", event.profile_id, tr("Unknown profile"));
|
||||
|
@ -855,14 +855,14 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("query-identity-teamspeak", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("query-identity-teamspeak-result", event.profile_id, tr("Unknown profile"));
|
||||
return;
|
||||
}
|
||||
|
||||
const ts = profile.selected_identity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
const ts = profile.selectedIdentity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
if (!ts) {
|
||||
event_registry.fire_async("query-identity-teamspeak-result", {
|
||||
status: "error",
|
||||
|
@ -884,26 +884,31 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("select-identity-type", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
if(!event.identity_type) {
|
||||
/* dummy event for UI init */
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
return;
|
||||
}
|
||||
|
||||
profile.selected_identity_type = event.identity_type;
|
||||
profile.selectedIdentityType = event.identity_type;
|
||||
profiles.mark_need_save();
|
||||
});
|
||||
|
||||
event_registry.on("generate-identity-teamspeak", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
send_error("generate-identity-teamspeak-result", event.profile_id, tr("Unknown profile"));
|
||||
return;
|
||||
}
|
||||
|
||||
TeaSpeakIdentity.generate_new().then(identity => {
|
||||
profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
|
||||
TeaSpeakIdentity.generateNew().then(identity => {
|
||||
profile.setIdentity(IdentitifyType.TEAMSPEAK, identity);
|
||||
profiles.mark_need_save();
|
||||
|
||||
identity.level().then(level => {
|
||||
|
@ -924,14 +929,14 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("import-identity-teamspeak", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
return;
|
||||
}
|
||||
|
||||
spawnTeamSpeakIdentityImport(identity => {
|
||||
profile.set_identity(IdentitifyType.TEAMSPEAK, identity);
|
||||
profile.setIdentity(IdentitifyType.TEAMSPEAK, identity);
|
||||
profiles.mark_need_save();
|
||||
|
||||
identity.level().catch(error => {
|
||||
|
@ -948,16 +953,16 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("improve-identity-teamspeak-level", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const identity = profile.selected_identity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
const identity = profile.selectedIdentity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
if (!identity) return;
|
||||
|
||||
spawnTeamSpeakIdentityImprove(identity, profile.profile_name).close_listener.push(() => {
|
||||
spawnTeamSpeakIdentityImprove(identity, profile.profileName).close_listener.push(() => {
|
||||
profiles.mark_need_save();
|
||||
|
||||
identity.level().then(level => {
|
||||
|
@ -972,13 +977,13 @@ export namespace modal_settings {
|
|||
});
|
||||
|
||||
event_registry.on("export-identity-teamspeak", event => {
|
||||
const profile = profiles.find_profile(event.profile_id);
|
||||
const profile = profiles.findConnectProfile(event.profile_id);
|
||||
if (!profile) {
|
||||
log.warn(LogCategory.CLIENT, tr("Received profile event with unknown profile id (event: %s, id: %s)"), event.type, event.profile_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const identity = profile.selected_identity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
const identity = profile.selectedIdentity(IdentitifyType.TEAMSPEAK) as TeaSpeakIdentity;
|
||||
if (!identity) return;
|
||||
|
||||
identity.export_ts(true).then(data => {
|
||||
|
|
|
@ -114,6 +114,9 @@ function initializeController(connection: ConnectionHandler, events: Registry<Ec
|
|||
events.fire("notify_voice_connection_state", {state: "unsupported-server"});
|
||||
break;
|
||||
|
||||
case VoiceConnectionStatus.Failed:
|
||||
events.fire("notify_voice_connection_state", {state: "failed", message: connection.getServerConnection().getVoiceConnection().getFailedMessage() });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ export type VoiceConnectionState =
|
|||
| "connected"
|
||||
| "disconnected"
|
||||
| "unsupported-client"
|
||||
| "unsupported-server";
|
||||
| "unsupported-server"
|
||||
| "failed";
|
||||
export type TestState =
|
||||
{ state: "initializing" | "running" | "stopped" | "microphone-invalid" | "unsupported" }
|
||||
| { state: "start-failed", error: string };
|
||||
|
@ -29,7 +30,8 @@ export interface EchoTestEvents {
|
|||
phase: "testing" | "troubleshooting"
|
||||
},
|
||||
notify_voice_connection_state: {
|
||||
state: VoiceConnectionState
|
||||
state: VoiceConnectionState,
|
||||
message?: string
|
||||
},
|
||||
notify_test_state: {
|
||||
state: TestState
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
background-color: #19191bcc;
|
||||
background-color: #19191b;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -120,7 +120,12 @@
|
|||
|
||||
&.shown {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
opacity: .87;
|
||||
}
|
||||
|
||||
&.error {
|
||||
opacity: .92;
|
||||
color: #a10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,23 @@ const VoiceStateOverlay = () => {
|
|||
events.fire("query_voice_connection_state");
|
||||
return "loading";
|
||||
});
|
||||
const [message, setMessage] = useState(undefined);
|
||||
|
||||
events.reactUse("notify_voice_connection_state", event => setState(event.state));
|
||||
events.reactUse("notify_voice_connection_state", event => {
|
||||
setState(event.state);
|
||||
setMessage(event.message);
|
||||
});
|
||||
|
||||
let inner, shown = true;
|
||||
let inner, shown = true, error = false;
|
||||
switch (state) {
|
||||
case "failed":
|
||||
error = true;
|
||||
inner = <a key={state}>
|
||||
<Translatable>Voice connection establishment has been failed:</Translatable><br />
|
||||
{message}
|
||||
</a>;
|
||||
break;
|
||||
|
||||
case "disconnected":
|
||||
inner = <a key={state}><Translatable>Voice connection has been disconnected.</Translatable></a>;
|
||||
break;
|
||||
|
@ -57,7 +69,7 @@ const VoiceStateOverlay = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={cssStyle.overlay + " " + (shown ? cssStyle.shown : "")}>
|
||||
<div className={cssStyle.overlay + " " + (shown ? cssStyle.shown : "") + " " + (error ? cssStyle.error : "")}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,14 +17,15 @@ export class AudioLibrary {
|
|||
private registeredClients: {[key: number]: AudioClient} = {};
|
||||
|
||||
constructor() {
|
||||
this.worker = new WorkerOwner(() => {
|
||||
/*
|
||||
* Attention don't use () => new Worker(...).
|
||||
* This confuses the worker plugin and will not emit any modules
|
||||
*/
|
||||
this.worker = new WorkerOwner(AudioLibrary.spawnNewWorker);
|
||||
}
|
||||
|
||||
return new Worker("./worker/index.ts", { type: "module" });
|
||||
});
|
||||
private static spawnNewWorker() {
|
||||
/*
|
||||
* Attention don't use () => new Worker(...).
|
||||
* This confuses the worker plugin and will not emit any modules
|
||||
*/
|
||||
return new Worker("./worker/index.ts", { type: "module" });
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import "webrtc-adapter";
|
||||
import "webcrypto-liner";
|
||||
|
||||
import "./index.scss";
|
||||
import "./FileTransfer";
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
private readonly serverConnectionStateListener;
|
||||
private connectionType: VoiceEncodeType = VoiceEncodeType.NATIVE_ENCODE;
|
||||
private connectionState: VoiceConnectionStatus;
|
||||
private failedConnectionMessage: string;
|
||||
|
||||
private localAudioStarted = false;
|
||||
private connectionLostModalOpen = false;
|
||||
|
@ -65,6 +66,8 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
|
||||
private encoderCodec: number = 5;
|
||||
|
||||
private lastConnectAttempt: number = 0;
|
||||
|
||||
constructor(connection: ServerConnection) {
|
||||
super(connection);
|
||||
|
||||
|
@ -83,6 +86,10 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
return this.connectionState;
|
||||
}
|
||||
|
||||
getFailedMessage(): string {
|
||||
return this.failedConnectionMessage;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.connection.events.off(this.serverConnectionStateListener);
|
||||
this.dropVoiceBridge();
|
||||
|
@ -98,6 +105,10 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
this.events.destroy();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.dropVoiceBridge();
|
||||
}
|
||||
|
||||
async acquireVoiceRecorder(recorder: RecorderProfile | undefined, enforce?: boolean) {
|
||||
if(this.currentAudioSource === recorder && !enforce)
|
||||
return;
|
||||
|
@ -154,12 +165,14 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
return;
|
||||
}
|
||||
|
||||
if(this.connection.getConnectionState() !== ConnectionState.CONNECTED)
|
||||
if(this.connection.getConnectionState() !== ConnectionState.CONNECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastConnectAttempt = Date.now();
|
||||
this.connectAttemptCounter++;
|
||||
if(this.voiceBridge) {
|
||||
this.voiceBridge.callback_disconnect = undefined;
|
||||
this.voiceBridge.callbackDisconnect = undefined;
|
||||
this.voiceBridge.disconnect();
|
||||
}
|
||||
|
||||
|
@ -172,7 +185,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
request: request
|
||||
}, payload)))
|
||||
};
|
||||
this.voiceBridge.callback_disconnect = () => {
|
||||
this.voiceBridge.callbackDisconnect = () => {
|
||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_DROPPED, { });
|
||||
if(!this.connectionLostModalOpen) {
|
||||
this.connectionLostModalOpen = true;
|
||||
|
@ -181,13 +194,14 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
modal.open();
|
||||
}
|
||||
logInfo(LogCategory.WEBRTC, tr("Lost voice connection to target server. Trying to reconnect."));
|
||||
this.startVoiceBridge();
|
||||
this.executeVoiceBridgeReconnect();
|
||||
}
|
||||
|
||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT, { attemptCount: this.connectAttemptCounter });
|
||||
this.setConnectionState(VoiceConnectionStatus.Connecting);
|
||||
this.voiceBridge.connect().then(result => {
|
||||
if(result.type === "success") {
|
||||
this.lastConnectAttempt = 0;
|
||||
this.connectAttemptCounter = 0;
|
||||
|
||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT_SUCCEEDED, { });
|
||||
|
@ -204,23 +218,32 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
} else if(result.type === "canceled") {
|
||||
/* we've to do nothing here */
|
||||
} else if(result.type === "failed") {
|
||||
logWarn(LogCategory.VOICE, tr("Failed to setup voice bridge: %s. Reconnect: %o"), result.message, result.allowReconnect);
|
||||
let doReconnect = result.allowReconnect && this.connectAttemptCounter < 5;
|
||||
logWarn(LogCategory.VOICE, tr("Failed to setup voice bridge: %s. Reconnect: %o"), result.message, doReconnect);
|
||||
|
||||
this.connection.client.log.log(EventType.CONNECTION_VOICE_CONNECT_FAILED, {
|
||||
reason: result.message,
|
||||
reconnect_delay: result.allowReconnect ? 1 : 0
|
||||
reconnect_delay: doReconnect ? 1 : 0
|
||||
});
|
||||
|
||||
if(result.allowReconnect) {
|
||||
this.startVoiceBridge();
|
||||
if(doReconnect) {
|
||||
this.executeVoiceBridgeReconnect();
|
||||
} else {
|
||||
this.failedConnectionMessage = result.message;
|
||||
this.setConnectionState(VoiceConnectionStatus.Failed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private executeVoiceBridgeReconnect() {
|
||||
/* TODO: May some kind of incremental timeout? */
|
||||
this.startVoiceBridge();
|
||||
}
|
||||
|
||||
private dropVoiceBridge() {
|
||||
if(this.voiceBridge) {
|
||||
this.voiceBridge.callback_disconnect = undefined;
|
||||
this.voiceBridge.callbackDisconnect = undefined;
|
||||
this.voiceBridge.disconnect();
|
||||
this.voiceBridge = undefined;
|
||||
}
|
||||
|
@ -296,6 +319,8 @@ export class VoiceConnection extends AbstractVoiceConnection {
|
|||
if(event.newState === ConnectionState.CONNECTED) {
|
||||
this.startVoiceBridge();
|
||||
} else {
|
||||
this.connectAttemptCounter = 0;
|
||||
this.lastConnectAttempt = 0;
|
||||
this.dropVoiceBridge();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export abstract class VoiceBridge {
|
|||
callback_incoming_voice: (packet: VoicePacket) => void;
|
||||
callback_incoming_whisper: (packet: VoiceWhisperPacket) => void;
|
||||
|
||||
callback_disconnect: () => void;
|
||||
callbackDisconnect: () => void;
|
||||
|
||||
setMuted(flag: boolean) {
|
||||
this.muted = flag;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as aplayer from "tc-backend/web/audio/player";
|
||||
import * as log from "tc-shared/log";
|
||||
import {LogCategory, logDebug, logError, logInfo, logTrace, logWarn} from "tc-shared/log";
|
||||
import {tr} from "tc-shared/i18n/localize";
|
||||
import * as log from "tc-shared/log";
|
||||
import {VoiceBridge, VoiceBridgeConnectResult} from "./VoiceBridge";
|
||||
|
||||
export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
||||
|
@ -14,6 +14,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
private whisperDataChannel: RTCDataChannel;
|
||||
|
||||
private cachedIceCandidates: RTCIceCandidateInit[];
|
||||
private localIceCandidateCount: number;
|
||||
|
||||
private callbackRtcAnswer: (answer: any) => void;
|
||||
|
||||
|
@ -78,8 +79,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
{
|
||||
let rtcConfig: RTCConfiguration = {};
|
||||
rtcConfig.iceServers = [];
|
||||
rtcConfig.iceServers.push({ urls: 'stun:stun.l.google.com:19302' });
|
||||
//rtcConfig.iceServers.push({ urls: "stun:stun.teaspeak.de:3478" });
|
||||
rtcConfig.iceServers.push({ urls: ['stun:stun.l.google.com:19302'] });
|
||||
|
||||
this.rtcConnection = new RTCPeerConnection(rtcConfig);
|
||||
|
||||
|
@ -111,17 +111,23 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
this.whisperDataChannel.binaryType = "arraybuffer";
|
||||
}
|
||||
|
||||
/* setting a dummy connect failed handler in case the rtc peer connection changes it's state to failed */
|
||||
const connectFailedPromise = new Promise((resolve, reject) => this.callbackRtcConnectFailed = reject);
|
||||
const wrapWithError = <T>(promise: Promise<T>) : Promise<T> => Promise.race([ promise, connectFailedPromise ]) as any;
|
||||
|
||||
let offer: RTCSessionDescriptionInit;
|
||||
try {
|
||||
offer = await this.rtcConnection.createOffer(this.generateRtpOfferOptions());
|
||||
offer = await wrapWithError(this.rtcConnection.createOffer(this.generateRtpOfferOptions()));
|
||||
if(canceled.value) return;
|
||||
} catch (error) {
|
||||
logError(LogCategory.VOICE, tr("Failed to generate RTC offer: %o"), error);
|
||||
throw tr("failed to generate local offer");
|
||||
}
|
||||
|
||||
this.localIceCandidateCount = 0;
|
||||
|
||||
try {
|
||||
await this.rtcConnection.setLocalDescription(offer);
|
||||
await wrapWithError(this.rtcConnection.setLocalDescription(offer));
|
||||
if(canceled.value) return;
|
||||
} catch (error) {
|
||||
logError(LogCategory.VOICE, tr("Failed to apply local description: %o"), error);
|
||||
|
@ -140,7 +146,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
sdp: offer.sdp
|
||||
}
|
||||
});
|
||||
answer = await new Promise((resolve, reject) => {
|
||||
answer = await wrapWithError(new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
if(canceled.value) {
|
||||
resolve();
|
||||
|
@ -156,7 +162,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
clearTimeout(timeout);
|
||||
resolve(answer);
|
||||
};
|
||||
});
|
||||
}));
|
||||
if(canceled.value) return;
|
||||
}
|
||||
|
||||
|
@ -165,7 +171,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
}
|
||||
|
||||
try {
|
||||
await this.rtcConnection.setRemoteDescription(new RTCSessionDescription(answer.msg));
|
||||
await wrapWithError(this.rtcConnection.setRemoteDescription(new RTCSessionDescription(answer.msg)));
|
||||
if(canceled.value) return;
|
||||
} catch (error) {
|
||||
const kParseErrorPrefix = "Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': ";
|
||||
|
@ -176,9 +182,11 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
throw tr("failed to apply remotes description");
|
||||
}
|
||||
|
||||
while(this.cachedIceCandidates.length > 0)
|
||||
while(this.cachedIceCandidates.length > 0) {
|
||||
this.registerRemoteIceCandidate(this.cachedIceCandidates.pop_front());
|
||||
}
|
||||
|
||||
/* ATTENTION: Do not use wrapWithError from now on (this.callbackRtcConnectFailed has been changed) */
|
||||
await new Promise((resolve, reject) => {
|
||||
if(this.rtcConnection.connectionState === "connected") {
|
||||
resolve();
|
||||
|
@ -215,8 +223,8 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
this.cleanupRtcResources();
|
||||
this.connectionState = "unconnected";
|
||||
|
||||
if(this.callback_disconnect)
|
||||
this.callback_disconnect();
|
||||
if(this.callbackDisconnect)
|
||||
this.callbackDisconnect();
|
||||
}
|
||||
|
||||
private abortConnectionAttempt() {
|
||||
|
@ -253,8 +261,9 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
if(typeof this.voiceDataChannel === "undefined")
|
||||
throw tr("missing main data channel");
|
||||
|
||||
if(this.voiceDataChannel.readyState === "open")
|
||||
if(this.voiceDataChannel.readyState === "open") {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const id = setTimeout(reject, timeout);
|
||||
|
@ -288,7 +297,7 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
}
|
||||
|
||||
private handleRtcConnectionStateChange() {
|
||||
log.debug(LogCategory.WEBRTC, tr("Connection state changed to %s"), this.rtcConnection.connectionState);
|
||||
log.debug(LogCategory.WEBRTC, tr("Connection state changed to %s (Local connection state: %s)"), this.rtcConnection.connectionState, this.connectionState);
|
||||
switch (this.rtcConnection.connectionState) {
|
||||
case "connected":
|
||||
if(this.callbackRtcConnected)
|
||||
|
@ -298,14 +307,14 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
case "failed":
|
||||
if(this.callbackRtcConnectFailed)
|
||||
this.callbackRtcConnectFailed(tr("connect attempt failed"));
|
||||
else if(this.callback_disconnect)
|
||||
this.callback_disconnect();
|
||||
else if(this.callbackDisconnect)
|
||||
this.callbackDisconnect();
|
||||
break;
|
||||
|
||||
case "disconnected":
|
||||
case "closed":
|
||||
if(this.callback_disconnect)
|
||||
this.callback_disconnect();
|
||||
if(this.callbackDisconnect)
|
||||
this.callbackDisconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -319,12 +328,22 @@ export abstract class WebRTCVoiceBridge extends VoiceBridge {
|
|||
}
|
||||
|
||||
private handleIceCandidate(event: RTCPeerConnectionIceEvent) {
|
||||
if(event.candidate && event.candidate.protocol !== "tcp")
|
||||
if(event.candidate && event.candidate.protocol !== "tcp") {
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.candidate) {
|
||||
this.localIceCandidateCount++;
|
||||
log.debug(LogCategory.WEBRTC, tr("Gathered local ice candidate for stream %d: %s"), event.candidate.sdpMLineIndex, event.candidate.candidate);
|
||||
this.callback_send_control_data("ice", { msg: event.candidate.toJSON() });
|
||||
} else if(this.localIceCandidateCount === 0) {
|
||||
logError(LogCategory.WEBRTC, tr("Failed to gather any local ice candidates... This is a fatal error."));
|
||||
|
||||
/* we don't allow a reconnect here since it's most the times not fixable by just trying again */
|
||||
this.allowReconnect = false;
|
||||
if(this.callbackRtcConnectFailed) {
|
||||
this.callbackRtcConnectFailed(tr("failed to gather any local ICE candidates"));
|
||||
}
|
||||
} else {
|
||||
log.debug(LogCategory.WEBRTC, tr("Local ICE candidate gathering finish."));
|
||||
this.callback_send_control_data("ice_finish", {});
|
||||
|
|
Loading…
Reference in New Issue