Separate common interfaces from client-specific classes
Changes:
- Move client-specific classes to client/src/models
- Extract common interface to standalone files in common/interfaces
- Remove unused features from client-specific classes
- These are features which were once used on the old server, but are now no longer needed
- Remove getObject() method for Account, Contact, and Conversation
- Remove lookups, registrationState and registeringName from Account
- Remove resolving logic from Contact
- Remove requests and listeners from Conversation (once used for Socket.IO and promises on server)
- Rename services/Conversation.ts to services/conversationQueries.ts
- Update imports
Future work:
- Create interface versions of Account, Contact, and Conversation
- Create new interfaces to replace Records on server
GitLab: #94
Change-Id: Ia51fe6ebeda44a30887d851a5564569dc290e5ed
diff --git a/client/src/models/Account.ts b/client/src/models/Account.ts
new file mode 100644
index 0000000..7471e5e
--- /dev/null
+++ b/client/src/models/Account.ts
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { AccountDetails, VolatileDetails } from 'jami-web-common';
+
+import { Contact } from './Contact.js';
+import { Conversation } from './Conversation.js';
+
+export class Account {
+ private readonly id: string;
+ private _details: AccountDetails;
+ private _volatileDetails: VolatileDetails;
+ private _contacts: Contact[];
+ private readonly conversations: Record<string, Conversation>;
+ private defaultModerators: Contact[];
+ private devices: Record<string, string>;
+
+ static readonly TYPE_JAMI: string = 'RING';
+ static readonly TYPE_SIP: string = 'SIP';
+
+ constructor(id: string, details: AccountDetails, volatileDetails: VolatileDetails) {
+ this.id = id;
+ this._details = details || {};
+ this._volatileDetails = volatileDetails || {};
+ this._contacts = [];
+ this.conversations = {};
+ this.defaultModerators = [];
+ this.devices = {};
+ }
+
+ static from(object: any) {
+ const account = new Account(object.id, object.details, object.volatileDetails);
+ if (object.defaultModerators) account.defaultModerators = object.defaultModerators.map((m: any) => Contact.from(m));
+ return account;
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getType() {
+ return this._details['Account.type'];
+ }
+
+ getUri() {
+ return this._details['Account.username'];
+ }
+
+ getRegisteredName() {
+ return this._volatileDetails['Account.registeredName'];
+ }
+
+ isRendezVous() {
+ return this._details['Account.rendezVous'] === 'true';
+ }
+
+ isPublicIn() {
+ return this._details['DHT.PublicInCalls'] === 'true';
+ }
+
+ setDetail(detail: keyof AccountDetails, value: string) {
+ this._details[detail] = value;
+ }
+
+ updateDetails(details: Partial<AccountDetails>) {
+ return Object.assign(this._details, details);
+ }
+
+ getDetails() {
+ return this._details;
+ }
+
+ getDisplayName() {
+ return this._details['Account.displayName'] || this.getDisplayUri();
+ }
+
+ getDisplayUri() {
+ return this.getRegisteredName() || this.getUri();
+ }
+
+ getDisplayNameNoFallback() {
+ return this._details['Account.displayName'] || this.getRegisteredName();
+ }
+
+ getConversationIds() {
+ return Object.keys(this.conversations);
+ }
+
+ getConversations() {
+ return this.conversations;
+ }
+
+ getConversation(conversationId: string) {
+ return this.conversations[conversationId];
+ }
+
+ addConversation(conversation: Conversation) {
+ const conversationId = conversation.getId();
+ if (conversationId != null) {
+ this.conversations[conversationId] = conversation;
+ } else {
+ throw new Error('Conversation ID cannot be undefined');
+ }
+ }
+
+ removeConversation(conversationId: string) {
+ delete this.conversations[conversationId];
+ }
+
+ getContacts() {
+ return this._contacts;
+ }
+
+ set contacts(contacts: Contact[]) {
+ this._contacts = contacts;
+ }
+
+ getDefaultModerators() {
+ return this.defaultModerators;
+ }
+
+ set details(value: AccountDetails) {
+ this._details = value;
+ }
+
+ set volatileDetails(value: VolatileDetails) {
+ this._volatileDetails = value;
+ }
+
+ setDevices(devices: Record<string, string>) {
+ this.devices = { ...devices };
+ }
+
+ getDevices() {
+ return this.devices;
+ }
+}
diff --git a/client/src/models/Contact.ts b/client/src/models/Contact.ts
new file mode 100644
index 0000000..bd526ae
--- /dev/null
+++ b/client/src/models/Contact.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+export class Contact {
+ private readonly uri: string;
+ private readonly displayName: string | undefined;
+ private registeredName: string | undefined;
+
+ constructor(uri: string) {
+ this.uri = uri;
+ this.displayName = undefined;
+ this.registeredName = undefined;
+ }
+
+ static from(object: any) {
+ const contact = new Contact(object.uri);
+ if (object.registeredName) contact.setRegisteredName(object.registeredName);
+ return contact;
+ }
+
+ getUri() {
+ return this.uri;
+ }
+
+ getRegisteredName() {
+ return this.registeredName;
+ }
+
+ setRegisteredName(name: string | undefined) {
+ this.registeredName = name;
+ }
+
+ getDisplayName() {
+ return this.getDisplayNameNoFallback() || this.getUri();
+ }
+
+ getDisplayNameNoFallback() {
+ return this.displayName || this.getRegisteredName();
+ }
+}
diff --git a/client/src/models/Conversation.ts b/client/src/models/Conversation.ts
new file mode 100644
index 0000000..fa72af9
--- /dev/null
+++ b/client/src/models/Conversation.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Message } from 'jami-web-common';
+
+import { Contact } from './Contact';
+
+export interface ConversationMember {
+ contact: Contact;
+ role?: 'admin' | 'member' | 'invited' | 'banned' | 'left';
+}
+
+type ConversationInfos = Record<string, unknown>;
+
+export class Conversation {
+ private readonly id: string | undefined;
+ private readonly accountId: string;
+ private readonly members: ConversationMember[];
+ private messages: Message[];
+ private _infos: ConversationInfos;
+
+ constructor(id: string | undefined, accountId: string, members?: ConversationMember[]) {
+ this.id = id;
+ this.accountId = accountId;
+ this.members = members || [];
+
+ this.messages = [];
+ this._infos = {};
+ }
+
+ static from(accountId: string, object: any) {
+ const conversation = new Conversation(
+ object.id,
+ accountId,
+ object.members.map((member: any) => {
+ member.contact = Contact.from(member.contact);
+ return member;
+ })
+ );
+ conversation.messages = object.messages;
+ conversation.infos = object.infos;
+ return conversation;
+ }
+ static fromSingleContact(accountId: string, contact: Contact) {
+ return new Conversation(undefined, accountId, [{ contact }]);
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getAccountId() {
+ return this.accountId;
+ }
+
+ getDisplayName() {
+ if (this.members.length !== 0) {
+ return this.members[0].contact.getDisplayName();
+ }
+ return this.getDisplayUri();
+ }
+
+ getDisplayNameNoFallback() {
+ if (this.members.length !== 0) {
+ return this.members[0].contact.getDisplayNameNoFallback();
+ }
+ }
+
+ getDisplayUri() {
+ return this.getId() || this.getFirstMember().contact.getUri();
+ }
+
+ getFirstMember() {
+ return this.members[0];
+ }
+
+ getMembers() {
+ return this.members;
+ }
+
+ addMessage(message: Message) {
+ if (this.messages.length === 0) this.messages.push(message);
+ else if (message.id === this.messages[this.messages.length - 1].linearizedParent) {
+ this.messages.push(message);
+ } else if (message.linearizedParent === this.messages[0].id) {
+ this.messages.unshift(message);
+ } else {
+ console.log("Can't insert message " + message.id);
+ }
+ }
+
+ addLoadedMessages(messages: Message[]) {
+ messages.forEach((message) => this.addMessage(message));
+ }
+
+ getMessages() {
+ return this.messages;
+ }
+
+ get infos() {
+ return this._infos;
+ }
+
+ set infos(infos: ConversationInfos) {
+ this._infos = infos;
+ }
+}