2020-09-12 13:49:20 +00:00
import { decode_identity , IdentitifyType , Identity } from "../profiles/Identity" ;
import { guid } from "../crypto/uid" ;
import { TeaForumIdentity } from "../profiles/identities/TeaForumIdentity" ;
import { TeaSpeakIdentity } from "../profiles/identities/TeamSpeakIdentity" ;
import { AbstractServerConnection } from "../connection/ConnectionBase" ;
import { HandshakeIdentityHandler } from "../connection/HandshakeHandler" ;
import { createErrorModal } from "../ui/elements/Modal" ;
import { formatMessage } from "../ui/frames/chat" ;
2020-09-16 17:30:28 +00:00
import * as loader from "tc-loader" ;
import { Stage } from "tc-loader" ;
import { LogCategory , logDebug , logError } from "tc-shared/log" ;
2021-01-10 16:36:57 +00:00
import { tr } from "tc-shared/i18n/localize" ;
2021-04-19 18:26:37 +00:00
import { getStorageAdapter } from "tc-shared/StorageAdapter" ;
import { ignorePromise } from "tc-shared/proto" ;
import { assertMainApplication } from "tc-shared/ui/utils" ;
/ *
* We ' re loading & saving profiles with the StorageAdapter .
* We should only access it once . As well why would a renderer want to have access to the
* connect profiles manager ?
* /
assertMainApplication ( ) ;
2020-03-30 11:44:18 +00:00
export class ConnectionProfile {
id : string ;
2020-09-16 17:30:28 +00:00
profileName : string ;
defaultUsername : string ;
defaultPassword : string ;
2020-03-30 11:44:18 +00:00
2020-09-16 17:30:28 +00:00
selectedIdentityType : string = "unset" ;
2020-03-30 11:44:18 +00:00
identities : { [ key : string ] : Identity } = { } ;
constructor ( id : string ) {
this . id = id ;
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
connectUsername ( ) : string {
2021-01-10 12:48:53 +00:00
if ( this . defaultUsername && this . defaultUsername !== "Another TeaSpeak user" ) {
2020-09-16 17:30:28 +00:00
return this . defaultUsername ;
2021-01-10 12:48:53 +00:00
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
let selected = this . selectedIdentity ( ) ;
2020-03-30 11:44:18 +00:00
let name = selected ? selected . fallback_name ( ) : undefined ;
return name || "Another TeaSpeak user" ;
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
selectedIdentity ( current_type? : IdentitifyType ) : Identity {
2020-03-30 11:44:18 +00:00
if ( ! current_type )
2020-09-16 17:30:28 +00:00
current_type = this . selectedType ( ) ;
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
if ( current_type === undefined )
return undefined ;
2019-10-19 15:13:40 +00:00
2020-03-30 11:44:18 +00:00
if ( current_type == IdentitifyType . TEAFORO ) {
return TeaForumIdentity . identity ( ) ;
} else if ( current_type == IdentitifyType . TEAMSPEAK || current_type == IdentitifyType . NICKNAME ) {
return this . identities [ IdentitifyType [ current_type ] . toLowerCase ( ) ] ;
2020-03-29 10:54:15 +00:00
}
2019-10-19 15:13:40 +00:00
2020-03-30 11:44:18 +00:00
return undefined ;
}
2019-01-19 12:42:18 +00:00
2020-09-16 17:30:28 +00:00
selectedType ( ) : IdentitifyType | undefined {
return this . selectedIdentityType ? IdentitifyType [ this . selectedIdentityType . toUpperCase ( ) ] : undefined ;
2020-03-30 11:44:18 +00:00
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
setIdentity ( type : IdentitifyType , identity : Identity ) {
2020-03-30 11:44:18 +00:00
this . identities [ IdentitifyType [ type ] . toLowerCase ( ) ] = identity ;
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
spawnIdentityHandshakeHandler ( connection : AbstractServerConnection ) : HandshakeIdentityHandler | undefined {
const identity = this . selectedIdentity ( ) ;
2021-05-16 10:22:11 +00:00
if ( ! identity ) {
2018-12-28 14:39:23 +00:00
return undefined ;
2021-05-16 10:22:11 +00:00
}
2020-03-30 11:44:18 +00:00
return identity . spawn_identity_handshake_handler ( connection ) ;
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
encode ( ) : string {
2020-03-30 11:44:18 +00:00
const identity_data = { } ;
2020-09-16 17:30:28 +00:00
for ( const key in this . identities ) {
if ( this . identities [ key ] ) {
2020-03-30 11:44:18 +00:00
identity_data [ key ] = this . identities [ key ] . encode ( ) ;
2020-09-16 17:30:28 +00:00
}
}
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
return JSON . stringify ( {
version : 1 ,
2020-09-16 17:30:28 +00:00
username : this.defaultUsername ,
password : this.defaultPassword ,
profile_name : this.profileName ,
identity_type : this.selectedIdentityType ,
2020-03-30 11:44:18 +00:00
identity_data : identity_data ,
id : this.id
} ) ;
}
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
valid ( ) : boolean {
2020-09-16 17:30:28 +00:00
const identity = this . selectedIdentity ( ) ;
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
return ! ! identity && identity . valid ( ) ;
2018-12-28 14:39:23 +00:00
}
2020-03-30 11:44:18 +00:00
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
async function decodeProfile ( payload : string ) : Promise < ConnectionProfile | string > {
const data = JSON . parse ( payload ) ;
if ( data . version !== 1 ) {
2020-03-30 11:44:18 +00:00
return "invalid version" ;
2020-09-16 17:30:28 +00:00
}
2020-03-27 15:15:15 +00:00
2020-03-30 11:44:18 +00:00
const result : ConnectionProfile = new ConnectionProfile ( data . id ) ;
2020-09-16 17:30:28 +00:00
result . defaultUsername = data . username ;
result . defaultPassword = data . password ;
result . profileName = data . profile_name ;
result . selectedIdentityType = ( data . identity_type || "" ) . toLowerCase ( ) ;
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
if ( data . identity_data ) {
for ( const key of Object . keys ( data . identity_data ) ) {
const type = IdentitifyType [ key . toUpperCase ( ) as string ] ;
const _data = data . identity_data [ key ] ;
if ( type == undefined ) continue ;
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
const identity = await decode_identity ( type , _data ) ;
if ( identity == undefined ) continue ;
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
result . identities [ key . toLowerCase ( ) ] = identity ;
2020-03-29 10:54:15 +00:00
}
2020-03-27 22:36:57 +00:00
}
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
return result ;
}
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
interface ProfilesData {
version : number ;
profiles : string [ ] ;
}
2020-03-27 15:15:15 +00:00
2020-09-16 17:30:28 +00:00
let availableProfiles_ : ConnectionProfile [ ] = [ ] ;
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
async function loadConnectProfiles() {
availableProfiles_ = [ ] ;
2018-12-28 14:39:23 +00:00
2021-04-19 18:26:37 +00:00
const profiles_json = await getStorageAdapter ( ) . get ( "profiles" ) ;
2020-03-30 11:44:18 +00:00
let profiles_data : ProfilesData = ( ( ) = > {
try {
return profiles_json ? JSON . parse ( profiles_json ) : { version : 0 } as any ;
} catch ( error ) {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . IDENTITIES , tr ( "Invalid profile json! Resetting profiles :( (%o)" ) , profiles_json ) ;
2020-03-30 11:44:18 +00:00
createErrorModal ( tr ( "Profile data invalid" ) , formatMessage ( tr ( "The profile data is invalid.{:br:}This might cause data loss." ) ) ) . open ( ) ;
2020-09-16 17:30:28 +00:00
return { version : 0 } ;
2020-03-27 22:36:57 +00:00
}
2020-03-30 11:44:18 +00:00
} ) ( ) ;
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
if ( profiles_data . version === 0 ) {
profiles_data = {
2020-03-27 22:36:57 +00:00
version : 1 ,
2020-03-30 11:44:18 +00:00
profiles : [ ]
} ;
2020-03-29 10:54:15 +00:00
}
2020-03-30 11:44:18 +00:00
if ( profiles_data . version == 1 ) {
for ( const profile_data of profiles_data . profiles ) {
2020-09-16 17:30:28 +00:00
const profile = await decodeProfile ( profile_data ) ;
2020-07-19 15:12:41 +00:00
if ( typeof profile === "string" ) {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . IDENTITIES , tr ( "Failed to load profile. Reason: %s, Profile data: %s" ) , profile , profiles_data ) ;
2020-07-19 15:12:41 +00:00
} else {
2020-09-16 17:30:28 +00:00
availableProfiles_ . push ( profile as ConnectionProfile ) ;
2020-03-30 11:44:18 +00:00
}
}
2020-03-29 10:54:15 +00:00
}
2018-12-28 14:39:23 +00:00
2020-09-16 17:30:28 +00:00
const defaultProfile = findConnectProfile ( "default" ) ;
if ( ! defaultProfile ) { //Create a default profile and teaforo profile
2020-03-30 11:44:18 +00:00
{
2020-09-16 17:30:28 +00:00
const profile = createConnectProfile ( tr ( "Default Profile" ) , "default" ) ;
profile . defaultPassword = "" ;
profile . defaultUsername = "" ;
profile . profileName = "Default Profile" ;
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
/* generate default identity */
try {
2020-09-16 17:30:28 +00:00
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 ] ;
2020-03-30 11:44:18 +00:00
} catch ( error ) {
2020-09-16 17:30:28 +00:00
logError ( LogCategory . GENERAL , tr ( "Failed to generate the default identity: %o" ) , error ) ;
2020-03-30 11:44:18 +00:00
createErrorModal ( tr ( "Failed to generate default identity" ) , tr ( "Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles" ) ) . open ( ) ;
}
}
2018-12-28 14:39:23 +00:00
2020-03-30 11:44:18 +00:00
{ /* forum identity (works only when connected to the forum) */
2020-09-16 17:30:28 +00:00
const profile = createConnectProfile ( tr ( "TeaSpeak Forum Profile" ) , "teaforo" ) ;
profile . defaultPassword = "" ;
profile . defaultUsername = "" ;
2020-03-29 10:54:15 +00:00
2020-09-16 17:30:28 +00:00
profile . setIdentity ( IdentitifyType . TEAFORO , TeaForumIdentity . identity ( ) ) ;
profile . selectedIdentityType = IdentitifyType [ IdentitifyType . TEAFORO ] ;
2020-03-30 11:44:18 +00:00
}
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
save ( ) ;
2018-12-28 14:39:23 +00:00
}
2020-03-30 11:44:18 +00:00
}
2020-09-16 17:30:28 +00:00
export function createConnectProfile ( name : string , id? : string ) : ConnectionProfile {
2020-03-30 11:44:18 +00:00
const profile = new ConnectionProfile ( id || guid ( ) ) ;
2020-09-16 17:30:28 +00:00
profile . profileName = name ;
profile . defaultUsername = "" ;
availableProfiles_ . push ( profile ) ;
2020-03-30 11:44:18 +00:00
return profile ;
}
let _requires_save = false ;
export function save() {
const profiles : string [ ] = [ ] ;
2020-09-16 17:30:28 +00:00
for ( const profile of availableProfiles_ ) {
2020-03-30 11:44:18 +00:00
profiles . push ( profile . encode ( ) ) ;
2020-09-16 17:30:28 +00:00
}
2020-03-30 11:44:18 +00:00
const data = JSON . stringify ( {
version : 1 ,
profiles : profiles
} ) ;
2021-04-19 18:26:37 +00:00
ignorePromise ( getStorageAdapter ( ) . set ( "profiles" , data ) ) ;
2020-03-30 11:44:18 +00:00
}
export function mark_need_save() {
_requires_save = true ;
}
export function requires_save ( ) : boolean {
return _requires_save ;
}
2020-09-16 17:30:28 +00:00
export function availableConnectProfiles ( ) : ConnectionProfile [ ] {
return availableProfiles_ ;
2020-03-30 11:44:18 +00:00
}
2020-09-16 17:30:28 +00:00
export function findConnectProfile ( id : string ) : ConnectionProfile | undefined {
for ( const profile of availableConnectProfiles ( ) ) {
if ( profile . id == id ) {
2020-03-30 11:44:18 +00:00
return profile ;
2020-09-16 17:30:28 +00:00
}
}
2020-03-30 11:44:18 +00:00
return undefined ;
}
export function find_profile_by_name ( name : string ) : ConnectionProfile | undefined {
name = name . toLowerCase ( ) ;
2020-09-16 17:30:28 +00:00
for ( const profile of availableConnectProfiles ( ) )
if ( ( profile . profileName || "" ) . toLowerCase ( ) == name )
2020-03-30 11:44:18 +00:00
return profile ;
return undefined ;
}
2020-09-16 17:30:28 +00:00
export function defaultConnectProfile ( ) : ConnectionProfile {
return findConnectProfile ( "default" ) ;
2020-03-30 11:44:18 +00:00
}
export function set_default_profile ( profile : ConnectionProfile ) {
2020-09-16 17:30:28 +00:00
const old_default = defaultConnectProfile ( ) ;
2020-03-30 11:44:18 +00:00
if ( old_default && old_default != profile ) {
old_default . id = guid ( ) ;
2020-03-29 10:54:15 +00:00
}
2020-03-30 11:44:18 +00:00
profile . id = "default" ;
return old_default ;
}
2020-03-29 10:54:15 +00:00
2020-03-30 11:44:18 +00:00
export function delete_profile ( profile : ConnectionProfile ) {
2020-09-16 17:30:28 +00:00
availableProfiles_ . remove ( profile ) ;
2020-08-22 15:50:38 +00:00
}
window . addEventListener ( "beforeunload" , event = > {
2020-09-16 17:30:28 +00:00
if ( requires_save ( ) ) {
2020-08-22 15:50:38 +00:00
save ( ) ;
2020-09-16 17:30:28 +00:00
}
} ) ;
loader . register_task ( Stage . JAVASCRIPT_INITIALIZING , {
name : "Identity setup" ,
function : async ( ) = > {
await loadConnectProfiles ( ) ;
} ,
priority : 30
} )