@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
355 lines (269 loc) • 9.72 kB
text/typescript
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();
}
}