blob: 15ff23cd63a263b274a20348d286c9bf6e855c3d [file] [log] [blame]
Issam E. Maghnif796a092022-10-09 20:25:26 +00001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050018import { createRequire } from 'node:module';
19
Issam E. Maghni0432cb72022-11-12 06:09:26 +000020import {
21 AccountDetails,
idillon3e378fd2022-12-23 11:48:12 -050022 ComposingStatus,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050023 ContactDetails,
24 ConversationInfos,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000025 ConversationMessage,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050026 Devices,
27 LookupResult,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000028 Message,
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -050029 RegisteredNameFoundState,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000030 VolatileDetails,
31 WebSocketMessage,
32 WebSocketMessageType,
33} from 'jami-web-common';
Issam E. Maghnif796a092022-10-09 20:25:26 +000034import log from 'loglevel';
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -040035import { filter, firstValueFrom, map, Subject } from 'rxjs';
Issam E. Maghnif796a092022-10-09 20:25:26 +000036import { Service } from 'typedi';
37
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050038import { WebSocketServer } from '../websocket/websocket-server.js';
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050039import { ConversationMemberInfos } from './conversation-member-infos.js';
40import { ConversationRequestMetadata } from './conversation-request-metadata.js';
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040041import { JamiSignal } from './jami-signal.js';
42import {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040043 AccountDetailsChanged,
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050044 AccountMessageStatusChanged,
idillon3e378fd2022-12-23 11:48:12 -050045 ComposingStatusChanged,
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050046 ContactAdded,
47 ContactRemoved,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040048 ConversationLoaded,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050049 ConversationMemberEvent,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040050 ConversationReady,
51 ConversationRemoved,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050052 ConversationRequestReceived,
Charlieb62c6782022-10-30 15:14:56 -040053 IncomingAccountMessage,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040054 KnownDevicesChanged,
55 MessageReceived,
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040056 NameRegistrationEnded,
57 RegisteredNameFound,
58 RegistrationStateChanged,
59 VolatileDetailsChanged,
60} from './jami-signal-interfaces.js';
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040061import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray, vectMapToRecordArray } from './jami-swig.js';
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -050062import {
63 ConversationMemberEventType,
64 MessageState,
65 NameRegistrationEndedState,
66 RegistrationState,
67} from './state-enums.js';
Issam E. Maghnif796a092022-10-09 20:25:26 +000068
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050069const require = createRequire(import.meta.url);
70
Issam E. Maghnif796a092022-10-09 20:25:26 +000071@Service()
72export class Jamid {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050073 private jamiSwig: JamiSwig;
74 private usernamesToAccountIds = new Map<string, string>();
idillon18283ac2023-01-07 12:06:42 -050075 readonly events;
Issam E. Maghnif796a092022-10-09 20:25:26 +000076
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050077 constructor(private webSocketServer: WebSocketServer) {
78 this.jamiSwig = require('../../jamid.node') as JamiSwig;
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040079
80 // Setup signal handlers
Issam E. Maghnif796a092022-10-09 20:25:26 +000081 const handlers: Record<string, unknown> = {};
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040082
83 // Add default handler for all signals
84 const createDefaultHandler = (signal: string) => {
85 return (...args: unknown[]) => log.warn('Unhandled', signal, args);
Issam E. Maghnif796a092022-10-09 20:25:26 +000086 };
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040087 for (const signal in JamiSignal) {
88 handlers[signal] = createDefaultHandler(signal);
89 }
90
91 // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
92 const onAccountsChanged = new Subject<void>();
93 handlers.AccountsChanged = () => onAccountsChanged.next();
94
95 const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
96 handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
97 onAccountDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000098
99 const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400100 handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
101 onVolatileDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +0000102
103 const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500104 handlers.RegistrationStateChanged = (accountId: string, state: RegistrationState, code: number, details: string) =>
Issam E. Maghnif796a092022-10-09 20:25:26 +0000105 onRegistrationStateChanged.next({ accountId, state, code, details });
106
107 const onNameRegistrationEnded = new Subject<NameRegistrationEnded>();
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500108 handlers.NameRegistrationEnded = (accountId: string, state: NameRegistrationEndedState, username: string) =>
Issam E. Maghnif796a092022-10-09 20:25:26 +0000109 onNameRegistrationEnded.next({ accountId, state, username });
110
111 const onRegisteredNameFound = new Subject<RegisteredNameFound>();
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500112 handlers.RegisteredNameFound = (
113 accountId: string,
114 state: RegisteredNameFoundState,
115 address: string,
116 username: string
117 ) => onRegisteredNameFound.next({ accountId, state, address, username });
Issam E. Maghnif796a092022-10-09 20:25:26 +0000118
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400119 const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500120 handlers.KnownDevicesChanged = (accountId: string, devices: Devices) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400121 onKnownDevicesChanged.next({ accountId, devices });
122
Charlieb62c6782022-10-30 15:14:56 -0400123 const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500124 handlers.IncomingAccountMessage = (accountId: string, from: string, payload: Record<string, string>) =>
125 onIncomingAccountMessage.next({ accountId, from, payload });
126
127 const onAccountMessageStatusChanged = new Subject<AccountMessageStatusChanged>();
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500128 handlers.AccountMessageStatusChanged = (accountId: string, messageId: string, peer: string, state: MessageState) =>
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500129 onAccountMessageStatusChanged.next({ accountId, messageId, peer, state });
130
131 const onContactAdded = new Subject<ContactAdded>();
132 handlers.ContactAdded = (accountId: string, contactId: string, confirmed: boolean) =>
133 onContactAdded.next({ accountId, contactId, confirmed });
134
135 const onContactRemoved = new Subject<ContactRemoved>();
136 handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
137 onContactRemoved.next({ accountId, contactId, banned });
Charlieb62c6782022-10-30 15:14:56 -0400138
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500139 const onConversationRequestReceived = new Subject<ConversationRequestReceived>();
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500140 handlers.ConversationRequestReceived = (
141 accountId: string,
142 conversationId: string,
143 metadata: ConversationRequestMetadata
144 ) => onConversationRequestReceived.next({ accountId, conversationId, metadata });
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500145
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400146 const onConversationReady = new Subject<ConversationReady>();
147 handlers.ConversationReady = (accountId: string, conversationId: string) =>
148 onConversationReady.next({ accountId, conversationId });
149
150 const onConversationRemoved = new Subject<ConversationRemoved>();
151 handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
152 onConversationRemoved.next({ accountId, conversationId });
153
154 const onConversationLoaded = new Subject<ConversationLoaded>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400155 handlers.ConversationLoaded = (id: number, accountId: string, conversationId: string, messages: Message[]) =>
156 onConversationLoaded.next({ id, accountId, conversationId, messages });
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400157
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500158 const onConversationMemberEvent = new Subject<ConversationMemberEvent>();
159 handlers.ConversationMemberEvent = (
160 accountId: string,
161 conversationId: string,
162 memberUri: string,
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500163 event: ConversationMemberEventType
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500164 ) => {
165 onConversationMemberEvent.next({ accountId, conversationId, memberUri, event });
166 };
167
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400168 const onMessageReceived = new Subject<MessageReceived>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400169 handlers.MessageReceived = (accountId: string, conversationId: string, message: Message) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400170 onMessageReceived.next({ accountId, conversationId, message });
171
idillon3e378fd2022-12-23 11:48:12 -0500172 const onComposingStatusChanged = new Subject<ComposingStatusChanged>();
173 handlers.ComposingStatusChanged = (accountId: string, conversationId: string, from: string, status: number) =>
174 onComposingStatusChanged.next({ accountId, conversationId, from, status });
175
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400176 // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000177 this.events = {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400178 onAccountsChanged: onAccountsChanged.asObservable(),
179 onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000180 onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
181 onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
182 onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
183 onRegisteredNameFound: onRegisteredNameFound.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400184 onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
Charlieb62c6782022-10-30 15:14:56 -0400185 onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500186 onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
187 onContactAdded: onContactAdded.asObservable(),
188 onContactRemoved: onContactRemoved.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500189 onConversationRequestReceived: onConversationRequestReceived.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400190 onConversationReady: onConversationReady.asObservable(),
191 onConversationRemoved: onConversationRemoved.asObservable(),
192 onConversationLoaded: onConversationLoaded.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500193 onConversationMemberEvent: onConversationMemberEvent.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400194 onMessageReceived: onMessageReceived.asObservable(),
idillon3e378fd2022-12-23 11:48:12 -0500195 onComposingStatusChanged: onComposingStatusChanged.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000196 };
197
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400198 this.setupSignalHandlers();
Issam E. Maghnif796a092022-10-09 20:25:26 +0000199
idillon559ce282022-12-22 15:18:32 -0500200 if (process.env.JAMI_WEB_MONITOR === 'true') {
201 this.jamiSwig.monitor(true);
202 }
203
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400204 // RxJS Subjects are used as signal handlers for the following reasons:
205 // 1. You cannot change event handlers after calling jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000206 // 2. You cannot specify multiple handlers for the same event
207 // 3. You cannot specify a default handler
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400208 this.jamiSwig.init(handlers);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000209 }
210
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400211 stop(): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400212 this.jamiSwig.fini();
Misha Krieger-Raynauld62a0da92022-10-22 13:46:59 -0400213 }
214
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400215 getVolatileAccountDetails(accountId: string): VolatileDetails {
216 return stringMapToRecord(this.jamiSwig.getVolatileAccountDetails(accountId)) as unknown as VolatileDetails;
Issam E. Maghnif796a092022-10-09 20:25:26 +0000217 }
218
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400219 getAccountDetails(accountId: string): AccountDetails {
220 return stringMapToRecord(this.jamiSwig.getAccountDetails(accountId)) as unknown as AccountDetails;
221 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000222
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400223 setAccountDetails(accountId: string, accountDetails: AccountDetails): void {
simon43da57b2022-10-26 18:22:22 -0400224 const accountDetailsStringMap: StringMap = new this.jamiSwig.StringMap();
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400225 for (const [key, value] of Object.entries(accountDetails)) {
226 accountDetailsStringMap.set(key, value);
227 }
228 this.jamiSwig.setAccountDetails(accountId, accountDetailsStringMap);
229 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000230
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000231 async addAccount(accountDetails: Partial<AccountDetails>): Promise<RegistrationStateChanged> {
232 accountDetails['Account.type'] = 'RING';
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400233
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500234 const accountDetailsStringMap: StringMap = new this.jamiSwig.StringMap();
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000235 for (const [key, value] of Object.entries(accountDetails)) {
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500236 accountDetailsStringMap.set(key, value.toString());
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400237 }
238
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500239 const accountId = this.jamiSwig.addAccount(accountDetailsStringMap);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000240 return firstValueFrom(
241 this.events.onRegistrationStateChanged.pipe(
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400242 filter((value) => value.accountId === accountId),
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500243 filter(
244 (value) => value.state === RegistrationState.Registered || value.state === RegistrationState.ErrorGeneric
245 )
Issam E. Maghnif796a092022-10-09 20:25:26 +0000246 )
247 );
248 }
249
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400250 removeAccount(accountId: string): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400251 this.jamiSwig.removeAccount(accountId);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000252 }
253
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400254 getAccountIds(): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400255 return stringVectToArray(this.jamiSwig.getAccountList());
Issam E. Maghnif796a092022-10-09 20:25:26 +0000256 }
257
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500258 sendAccountTextMessage(accountId: string, contactId: string, message: string): void {
Charlieb62c6782022-10-30 15:14:56 -0400259 const messageStringMap: StringMap = new this.jamiSwig.StringMap();
Charlie2bc0d672022-11-04 11:53:44 -0400260 messageStringMap.set('application/json', message);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500261 this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
Charlieb62c6782022-10-30 15:14:56 -0400262 }
263
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500264 async lookupUsername(username: string, accountId?: string): Promise<LookupResult> {
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400265 const hasRingNs = this.jamiSwig.lookupName(accountId || '', '', username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000266 if (!hasRingNs) {
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500267 throw new Error('Jami does not have a nameserver');
Issam E. Maghnif796a092022-10-09 20:25:26 +0000268 }
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400269 return firstValueFrom(
270 this.events.onRegisteredNameFound.pipe(
271 filter((value) => value.username === username),
272 map(({ accountId: _, ...response }) => response) // Remove accountId from response
273 )
274 );
275 }
276
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500277 async lookupAddress(address: string, accountId?: string): Promise<LookupResult> {
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400278 const hasRingNs = this.jamiSwig.lookupAddress(accountId || '', '', address);
idillon18283ac2023-01-07 12:06:42 -0500279
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400280 if (!hasRingNs) {
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500281 throw new Error('Jami does not have a nameserver');
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400282 }
283 return firstValueFrom(
284 this.events.onRegisteredNameFound.pipe(
285 filter((value) => value.address === address),
286 map(({ accountId: _, ...response }) => response) // Remove accountId from response
287 )
288 );
Issam E. Maghnif796a092022-10-09 20:25:26 +0000289 }
290
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500291 async registerUsername(accountId: string, username: string, password: string): Promise<NameRegistrationEndedState> {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400292 const hasRingNs = this.jamiSwig.registerName(accountId, password, username);
293 if (!hasRingNs) {
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500294 throw new Error('Jami does not have a nameserver');
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400295 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400296 return firstValueFrom(
297 this.events.onNameRegistrationEnded.pipe(
298 filter((value) => value.accountId === accountId),
299 map((value) => value.state)
300 )
301 );
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400302 }
303
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500304 getDevices(accountId: string): Devices {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400305 return stringMapToRecord(this.jamiSwig.getKnownRingDevices(accountId));
306 }
307
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400308 addContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400309 this.jamiSwig.addContact(accountId, contactId);
310 }
311
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500312 sendTrustRequest(accountId: string, contactId: string): void {
313 this.jamiSwig.sendTrustRequest(accountId, contactId, new this.jamiSwig.Blob());
314 }
315
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400316 removeContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400317 this.jamiSwig.removeContact(accountId, contactId, false);
318 }
319
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400320 blockContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400321 this.jamiSwig.removeContact(accountId, contactId, true);
322 }
323
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500324 getContacts(accountId: string): ContactDetails[] {
325 return vectMapToRecordArray(this.jamiSwig.getContacts(accountId)) as unknown as ContactDetails[];
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400326 }
327
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500328 getContactDetails(accountId: string, contactId: string): ContactDetails {
329 return stringMapToRecord(this.jamiSwig.getContactDetails(accountId, contactId)) as unknown as ContactDetails;
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400330 }
331
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400332 getDefaultModeratorUris(accountId: string): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400333 return stringVectToArray(this.jamiSwig.getDefaultModerators(accountId));
334 }
335
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400336 addDefaultModerator(accountId: string, contactId: string): void {
337 this.jamiSwig.setDefaultModerator(accountId, contactId, true);
338 }
339
340 removeDefaultModerator(accountId: string, contactId: string): void {
341 this.jamiSwig.setDefaultModerator(accountId, contactId, false);
342 }
343
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400344 getConversationIds(accountId: string): string[] {
345 return stringVectToArray(this.jamiSwig.getConversations(accountId));
346 }
347
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500348 getConversationInfos(accountId: string, conversationId: string): ConversationInfos {
349 return stringMapToRecord(
350 this.jamiSwig.conversationInfos(accountId, conversationId)
351 ) as unknown as ConversationInfos;
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400352 }
353
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500354 getConversationMembers(accountId: string, conversationId: string): ConversationMemberInfos[] {
355 return vectMapToRecordArray(
356 this.jamiSwig.getConversationMembers(accountId, conversationId)
357 ) as unknown as ConversationMemberInfos[];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400358 }
359
idillon07d31cc2022-12-06 22:40:14 -0500360 async getConversationMessages(
361 accountId: string,
362 conversationId: string,
363 fromMessage = '',
364 n = 32
365 ): Promise<Message[]> {
366 const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage, n);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400367 return firstValueFrom(
368 this.events.onConversationLoaded.pipe(
369 filter((value) => value.id === requestId),
370 map((value) => value.messages)
371 )
372 );
373 }
374
idillon07d31cc2022-12-06 22:40:14 -0500375 removeConversation(accountId: string, conversationId: string) {
376 this.jamiSwig.removeConversation(accountId, conversationId);
377 }
378
idillon18283ac2023-01-07 12:06:42 -0500379 getConversationRequests(accountId: string): ConversationRequestMetadata[] {
380 return vectMapToRecordArray(
381 this.jamiSwig.getConversationRequests(accountId)
382 ) as unknown as ConversationRequestMetadata[];
383 }
384
385 acceptConversationRequest(accountId: string, conversationId: string): Promise<ConversationReady> {
386 this.jamiSwig.acceptConversationRequest(accountId, conversationId);
387 return firstValueFrom(
388 this.events.onConversationReady.pipe(
389 filter((value) => value.accountId === accountId),
390 filter((value) => value.conversationId === conversationId)
391 )
392 );
393 }
394
395 declineConversationRequest(accountId: string, conversationId: string): void {
396 this.jamiSwig.declineConversationRequest(accountId, conversationId);
397 }
398
Issam E. Maghni2cb239b2022-11-18 22:39:32 +0000399 sendConversationMessage(
400 accountId: string,
401 conversationId: string,
402 message: string,
403 replyTo?: string,
404 flag?: number
405 ): void {
406 this.jamiSwig.sendMessage(accountId, conversationId, message, replyTo ?? '', flag ?? 0);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400407 }
408
idillon3e378fd2022-12-23 11:48:12 -0500409 setIsComposing(accountId: string, conversationId: string, isWriting: boolean) {
410 this.jamiSwig.setIsComposing(accountId, conversationId, isWriting);
411 }
412
Misha Krieger-Raynauld7f959332022-11-04 15:12:53 -0400413 getCallIds(accountId: string): string[] {
414 return stringVectToArray(this.jamiSwig.getCallList(accountId));
415 }
416
417 // TODO: Replace Record with interface
418 getCallDetails(accountId: string, callId: string): Record<string, string> {
419 return stringMapToRecord(this.jamiSwig.getCallDetails(accountId, callId));
420 }
421
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400422 getAccountIdFromUsername(username: string): string | undefined {
423 return this.usernamesToAccountIds.get(username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000424 }
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400425
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400426 private setupSignalHandlers(): void {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400427 this.events.onAccountsChanged.subscribe(() => {
428 log.debug('Received AccountsChanged');
429 });
430
431 this.events.onAccountDetailsChanged.subscribe((signal) => {
432 log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
433 });
434
435 this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400436 const username = details['Account.registeredName'];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400437 log.debug(
438 `Received VolatileDetailsChanged: {"accountId":"${accountId}",` +
439 `"details":{"Account.registeredName":"${username}", ...}}`
440 );
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500441
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400442 if (username) {
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500443 // Keep map of usernames to account IDs
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400444 this.usernamesToAccountIds.set(username, accountId);
445 }
446 });
447
448 this.events.onRegistrationStateChanged.subscribe((signal) => {
449 log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
450 });
451
452 this.events.onNameRegistrationEnded.subscribe((signal) => {
453 log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
454 });
455
456 this.events.onRegisteredNameFound.subscribe((signal) => {
457 log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
458 });
459
460 this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
461 log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
462 });
463
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500464 this.events.onIncomingAccountMessage.subscribe(<T extends WebSocketMessageType>(signal: IncomingAccountMessage) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400465 log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500466
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500467 const message: Partial<WebSocketMessage<T>> = JSON.parse(signal.payload['application/json']);
Charlie2bc0d672022-11-04 11:53:44 -0400468
Misha Krieger-Raynauld99e5a702022-11-30 22:36:52 -0500469 if (typeof message !== 'object' || message === null) {
470 log.warn('Account message is not an object');
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500471 return;
472 }
473
474 if (message.type === undefined || message.data === undefined) {
475 log.warn('Account message is not a valid WebSocketMessage (missing type or data fields)');
Charlie2bc0d672022-11-04 11:53:44 -0400476 return;
477 }
478
479 if (!Object.values(WebSocketMessageType).includes(message.type)) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500480 log.warn(`Invalid WebSocket message type: ${message.type}`);
Charlie2bc0d672022-11-04 11:53:44 -0400481 return;
482 }
483
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500484 this.webSocketServer.send(signal.accountId, message.type, message.data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400485 });
486
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500487 this.events.onAccountMessageStatusChanged.subscribe((signal) => {
488 log.debug('Received AccountMessageStatusChanged:', JSON.stringify(signal));
489 });
490
491 this.events.onContactAdded.subscribe((signal) => {
492 log.debug('Received ContactAdded:', JSON.stringify(signal));
493 });
494
495 this.events.onContactRemoved.subscribe((signal) => {
496 log.debug('Received ContactRemoved:', JSON.stringify(signal));
497 });
498
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500499 this.events.onConversationRequestReceived.subscribe((signal) => {
500 log.debug('Received ConversationRequestReceived:', JSON.stringify(signal));
idillon18283ac2023-01-07 12:06:42 -0500501 //this.webSocketServer.send(signal.accountId, WebSocketMessageType.ConversationRequest, data);
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500502 });
503
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400504 this.events.onConversationReady.subscribe((signal) => {
505 log.debug('Received ConversationReady:', JSON.stringify(signal));
506 });
507
508 this.events.onConversationRemoved.subscribe((signal) => {
509 log.debug('Received ConversationRemoved:', JSON.stringify(signal));
510 });
511
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400512 this.events.onConversationLoaded.subscribe(({ id, accountId, conversationId }) => {
513 log.debug(
514 `Received ConversationLoaded: {"id":"${id}","accountId":"${accountId}",` +
idillon9e542ca2022-12-15 17:54:07 -0500515 `"conversationId":"${conversationId}"}`
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400516 );
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400517 });
518
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500519 this.events.onConversationMemberEvent.subscribe((signal) => {
520 log.debug('Received onConversationMemberEvent:', JSON.stringify(signal));
521 });
522
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400523 this.events.onMessageReceived.subscribe((signal) => {
524 log.debug('Received MessageReceived:', JSON.stringify(signal));
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500525
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000526 const data: ConversationMessage = {
527 conversationId: signal.conversationId,
528 message: signal.message,
529 };
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500530 this.webSocketServer.send(signal.accountId, WebSocketMessageType.ConversationMessage, data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400531 });
idillon3e378fd2022-12-23 11:48:12 -0500532
533 this.events.onComposingStatusChanged.subscribe((signal) => {
534 log.debug('Received ComposingStatusChanged:', JSON.stringify(signal));
535
536 const data: ComposingStatus = {
537 contactId: signal.from,
538 conversationId: signal.conversationId,
539 isWriting: signal.status === 1,
540 };
541 this.webSocketServer.send(signal.accountId, WebSocketMessageType.OnComposingStatusChanged, data);
542 });
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400543 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000544}