UNPKG

@jsxc/jsxc

Version:

Real-time XMPP chat application with video calls, file transfer and encrypted communication

355 lines (269 loc) 9.72 kB
import Contact from './Contact'; import JID from './JID'; import MultiUserChatWindow from './ui/MultiUserChatWindow'; import PersistentMap from './util/PersistentMap'; import { Presence } from './connection/AbstractConnection'; import Form from './connection/Form'; import Account from './Account'; import { ContactSubscription } from './Contact.interface'; import { IMUCService } from '@connection/Connection.interface'; import { IJID } from './JID.interface'; export const enum AFFILIATION { ADMIN = 'admin', MEMBER = 'member', OUTCAST = 'outcast', OWNER = 'owner', NONE = 'none', } export const enum ROLE { MODERATOR = 'moderator', PARTICIPANT = 'participant', VISITOR = 'visitor', NONE = 'none', } // const enum ROOMSTATE { // INIT, // ENTERED, // EXITED, // AWAIT_DESTRUCTION, // DESTROYED, // }; export const enum ROOMCONFIG { INSTANT = 'instant', } export default class MultiUserContact extends Contact { public static INSTANT_ROOMCONFIG = ROOMCONFIG.INSTANT; public static TYPE = 'groupchat'; private members: PersistentMap<{ affiliation: AFFILIATION; role: ROLE; jid: string }>; constructor(account: Account, jid: IJID, name?: string); constructor(account: Account, id: string); constructor() { super(arguments[0], arguments[1], arguments[2]); this.data.set('type', MultiUserContact.TYPE); } private getMembers(): PersistentMap { if (!this.members) { this.members = new PersistentMap(this.account.getStorage(), 'members', this.getId()); } return this.members; } private getService(): IMUCService { return this.account.getConnection().getMUCService(); } public async invite(jid: JID, reason?: string) { let discoInfo = await this.account.getDiscoInfoRepository().requestDiscoInfo(this.getJid()); let isMembersOnly = discoInfo.hasFeature('muc_membersonly'); if (isMembersOnly) { this.getService().sendMediatedMultiUserInvitation(jid, this.getJid(), reason); return; } let contact = this.account.getContact(jid); let resources = jid.resource ? [jid.resource] : contact.getResources(); if (resources.length > 0) { for (let resource of resources) { if (contact.hasFeatureByResource(resource, 'jabber:x:conference')) { let password = this.data.get('password'); this.getService().sendDirectMultiUserInvitation(jid, this.getJid(), reason, password); } } return; } throw new Error('No invitation method available'); } public changeTopic(topic: string) { return this.getService().changeTopic(this.getJid(), topic); } public changeNickname(nickname: string) { return this.getService().changeNickname(this.getJid(), nickname); } public kick(nickname: string, reason?: string) { return this.getService().kickUser(this.getJid(), nickname, reason); } public ban(target: IJID, reason?: string) { return this.getService().banUser(this.getJid(), target, reason); } public changeAffiliation(target: IJID, affiliation: string) { return this.getService().changeAffiliation(this.getJid(), target, affiliation); } public changeRole(nickname: string, role: string) { return this.getService().changeRole(this.getJid(), nickname, role); } public join() { this.data.set('joinDate', new Date()); this.data.set('memberListComplete', false); this.removeAllMembers(); this.refreshFeatures(); return this.getService().joinMultiUserRoom(new JID(this.jid.bare, this.getNickname()), this.data.get('password')); } public leave() { return this.getService().leaveMultiUserRoom(this.getJid()); } public destroy() { return this.getService().destroyMultiUserRoom(this.getJid()); } public createInstantRoom() { return this.getService().createInstantRoom(this.jid); } public createPreconfiguredRoom() { if (!this.hasRoomConfiguration()) { return Promise.reject('No saved room configuration'); } let form = Form.fromJSON(this.getRoomConfiguration()); return this.getService().submitRoomConfiguration(this.getJid(), form); } public async refreshFeatures(): Promise<string[]> { const stanza = await this.account.getConnection().getDiscoService().getDiscoInfo(new JID(this.jid.bare)); const features = $(stanza) .find('feature') .map((_, element) => $(element).attr('var')) .get() .filter(feature => feature && feature.startsWith('muc_')) .map(feature => feature.replace(/^muc_/, '')); this.data.set('features', features); return features; } public getFeatures(): string[] { return this.data.get('features') || []; } public isMembersOnly(): boolean { return this.getFeatures().includes('membersonly'); } public isModerated(): boolean { return this.getFeatures().includes('moderated'); } public isNonAnonymous(): boolean { return this.getFeatures().includes('nonanonymous'); } public isOpen(): boolean { return this.getFeatures().includes('open'); } public isPasswordProtected(): boolean { return this.getFeatures().includes('passwordprotected'); } public isPersistent(): boolean { return this.getFeatures().includes('persistent'); } public isPublic(): boolean { return this.getFeatures().includes('public'); } public isSemiAnonymous(): boolean { return this.getFeatures().includes('semianonymous'); } public isTemporary(): boolean { return this.getFeatures().includes('temporary'); } public isUnmoderated(): boolean { return this.getFeatures().includes('unmoderated'); } public isUnsecured(): boolean { return this.getFeatures().includes('unsecured'); } public setNickname(nickname: string) { //@TODO update ui according to affiliation and role this.data.set('nickname', nickname); this.setResource(nickname); //@REVIEW do we need the nickname field? } public getNickname(): string { return this.data.get('nickname'); } public getChatWindow(): MultiUserChatWindow { if (!this.chatWindow) { this.chatWindow = new MultiUserChatWindow(this); } return this.chatWindow; } public getMember(nickname: string): { affiliation?: AFFILIATION; role?: ROLE; jid?: JID } { let data = this.getMembers().get(nickname) || {}; data.jid = data.jid ? new JID(data.jid.full || data.jid) : undefined; return data; } public addMember(nickname: string, affiliation?: AFFILIATION, role?: ROLE, jid?: JID): boolean { let isNewMember = !this.getMembers().get(nickname); this.getMembers().set(nickname, { affiliation, role, jid: jid?.full, }); return isNewMember; } public removeMember(nickname: string) { this.getMembers().remove(nickname); if (nickname === this.getNickname()) { this.shutdown(); } } public getMemberIds(): string[] { return this.getMembers().getAllKeys(); } public getSubscription(): ContactSubscription { return ContactSubscription.BOTH; } public setSubject(subject: string) { this.data.set('subject', subject); } public getSubject(): string { return this.data.get('subject'); } public getPassword(): string { return this.data.get('password'); } public setPassword(password: string) { this.data.set('password', password); } public setAutoJoin(autoJoin: boolean) { this.data.set('autoJoin', autoJoin); } public isAutoJoin(): boolean { return !!this.data.get('autoJoin'); } public setBookmark(bookmark: boolean) { this.data.set('bookmark', bookmark); } public isBookmarked(): boolean { return !!this.data.get('bookmark'); } public setRoomConfiguration(roomConfig) { this.data.set('roomConfig', roomConfig); } public getRoomConfiguration() { let roomConfig = this.data.get('roomConfig'); return roomConfig !== ROOMCONFIG.INSTANT ? roomConfig : undefined; } public hasRoomConfiguration() { let roomConfig = this.data.get('roomConfig'); return typeof roomConfig === 'object' && roomConfig !== null; } public isInstantRoom(): boolean { return this.data.get('roomConfig') === ROOMCONFIG.INSTANT; } public isMemberListComplete(): boolean { return this.data.get('memberListComplete'); } public setMemberListComplete() { this.data.set('memberListComplete', true); } public getJoinDate(): Date { let dateString = this.data.get('joinDate'); return dateString ? new Date(dateString) : undefined; } public registerMemberHook(id: string, func: (newValue: any, oldValue: any, key: string) => void); public registerMemberHook(func: (newValue: any, oldValue: any, key: string) => void); public registerMemberHook() { this.getMembers().registerHook.apply(this.getMembers(), arguments); } public registerNewMemberHook(func: (value: any, nickname: string) => void) { this.getMembers().registerNewHook(func); } public registerRemoveMemberHook(func: (nickname: string) => void) { this.getMembers().registerRemoveHook(func); } public shutdown() { this.data.set('resources', {}); this.data.set('presence', Presence.offline); this.setNickname(null); this.removeAllMembers(); } private removeAllMembers() { this.getMembers().empty(); } }