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"; import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import {LogCategory, logDebug, logError} from "tc-shared/log"; import {tr} from "tc-shared/i18n/localize"; export class ConnectionProfile { id: string; profileName: string; defaultUsername: string; defaultPassword: string; selectedIdentityType: string = "unset"; identities: { [key: string]: Identity } = {}; constructor(id: string) { this.id = id; } connectUsername(): string { if (this.defaultUsername && this.defaultUsername !== "Another TeaSpeak user") { return this.defaultUsername; } let selected = this.selectedIdentity(); let name = selected ? selected.fallback_name() : undefined; return name || "Another TeaSpeak user"; } selectedIdentity(current_type?: IdentitifyType): Identity { if (!current_type) current_type = this.selectedType(); if (current_type === undefined) return undefined; 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()]; } return undefined; } selectedType(): IdentitifyType | undefined { return this.selectedIdentityType ? IdentitifyType[this.selectedIdentityType.toUpperCase()] : undefined; } setIdentity(type: IdentitifyType, identity: Identity) { this.identities[IdentitifyType[type].toLowerCase()] = identity; } spawnIdentityHandshakeHandler(connection: AbstractServerConnection): HandshakeIdentityHandler | undefined { const identity = this.selectedIdentity(); 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.defaultUsername, password: this.defaultPassword, profile_name: this.profileName, identity_type: this.selectedIdentityType, identity_data: identity_data, id: this.id }); } valid(): boolean { const identity = this.selectedIdentity(); return !!identity && identity.valid(); } } async function decodeProfile(payload: string): Promise { const data = JSON.parse(payload); if (data.version !== 1) { return "invalid version"; } const result: ConnectionProfile = new ConnectionProfile(data.id); 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)) { 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 availableProfiles_: ConnectionProfile[] = []; 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) { logError(LogCategory.IDENTITIES, 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 }; } })(); 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 decodeProfile(profile_data); if (typeof profile === "string") { logError(LogCategory.IDENTITIES, tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data); } else { availableProfiles_.push(profile as ConnectionProfile); } } } const defaultProfile = findConnectProfile("default"); if (!defaultProfile) { //Create a default profile and teaforo profile { const profile = createConnectProfile(tr("Default Profile"), "default"); profile.defaultPassword = ""; profile.defaultUsername = ""; profile.profileName = "Default Profile"; /* generate default identity */ try { 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!
Please manually generate the identity within your settings => profiles")).open(); } } { /* forum identity (works only when connected to the forum) */ const profile = createConnectProfile(tr("TeaSpeak Forum Profile"), "teaforo"); profile.defaultPassword = ""; profile.defaultUsername = ""; profile.setIdentity(IdentitifyType.TEAFORO, TeaForumIdentity.identity()); profile.selectedIdentityType = IdentitifyType[IdentitifyType.TEAFORO]; } save(); } } export function createConnectProfile(name: string, id?: string): ConnectionProfile { const profile = new ConnectionProfile(id || guid()); profile.profileName = name; profile.defaultUsername = ""; availableProfiles_.push(profile); return profile; } let _requires_save = false; export function save() { const profiles: string[] = []; for (const profile of availableProfiles_) { 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 availableConnectProfiles(): ConnectionProfile[] { return availableProfiles_; } 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 availableConnectProfiles()) if ((profile.profileName || "").toLowerCase() == name) return profile; return undefined; } export function defaultConnectProfile(): ConnectionProfile { return findConnectProfile("default"); } export function set_default_profile(profile: ConnectionProfile) { const old_default = defaultConnectProfile(); if (old_default && old_default != profile) { old_default.id = guid(); } profile.id = "default"; return old_default; } export function delete_profile(profile: ConnectionProfile) { availableProfiles_.remove(profile); } window.addEventListener("beforeunload", event => { if(requires_save()) { save(); } }); loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { name: "Identity setup", function: async () => { await loadConnectProfiles(); }, priority: 30 })