blob: 843d7cfe429dce536d30b2ade7bb4cb3dec7e064 [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,
22 AccountTextMessage,
23 ConversationMessage,
24 Message,
25 VolatileDetails,
26 WebSocketMessage,
27 WebSocketMessageType,
28} from 'jami-web-common';
Issam E. Maghnif796a092022-10-09 20:25:26 +000029import log from 'loglevel';
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -040030import { filter, firstValueFrom, map, Subject } from 'rxjs';
Issam E. Maghnif796a092022-10-09 20:25:26 +000031import { Service } from 'typedi';
32
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050033import { WebSocketServer } from '../websocket/websocket-server.js';
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040034import { JamiSignal } from './jami-signal.js';
35import {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040036 AccountDetailsChanged,
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050037 AccountMessageStatusChanged,
38 ContactAdded,
39 ContactRemoved,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040040 ConversationLoaded,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050041 ConversationMemberEvent,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040042 ConversationReady,
43 ConversationRemoved,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050044 ConversationRequestReceived,
Charlieb62c6782022-10-30 15:14:56 -040045 IncomingAccountMessage,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040046 KnownDevicesChanged,
47 MessageReceived,
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040048 NameRegistrationEnded,
49 RegisteredNameFound,
50 RegistrationStateChanged,
51 VolatileDetailsChanged,
52} from './jami-signal-interfaces.js';
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040053import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray, vectMapToRecordArray } from './jami-swig.js';
Issam E. Maghnif796a092022-10-09 20:25:26 +000054
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050055const require = createRequire(import.meta.url);
56
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040057// TODO: Convert Records to interfaces and replace them in common/ (e.g. Contact)
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040058
Issam E. Maghnif796a092022-10-09 20:25:26 +000059@Service()
60export class Jamid {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050061 private jamiSwig: JamiSwig;
62 private usernamesToAccountIds = new Map<string, string>();
Issam E. Maghnif796a092022-10-09 20:25:26 +000063 private readonly events;
64
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050065 constructor(private webSocketServer: WebSocketServer) {
66 this.jamiSwig = require('../../jamid.node') as JamiSwig;
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040067
68 // Setup signal handlers
Issam E. Maghnif796a092022-10-09 20:25:26 +000069 const handlers: Record<string, unknown> = {};
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040070
71 // Add default handler for all signals
72 const createDefaultHandler = (signal: string) => {
73 return (...args: unknown[]) => log.warn('Unhandled', signal, args);
Issam E. Maghnif796a092022-10-09 20:25:26 +000074 };
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040075 for (const signal in JamiSignal) {
76 handlers[signal] = createDefaultHandler(signal);
77 }
78
79 // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
80 const onAccountsChanged = new Subject<void>();
81 handlers.AccountsChanged = () => onAccountsChanged.next();
82
83 const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
84 handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
85 onAccountDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000086
87 const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040088 handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
89 onVolatileDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000090
91 const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
92 handlers.RegistrationStateChanged = (accountId: string, state: string, code: number, details: string) =>
93 onRegistrationStateChanged.next({ accountId, state, code, details });
94
95 const onNameRegistrationEnded = new Subject<NameRegistrationEnded>();
96 handlers.NameRegistrationEnded = (accountId: string, state: number, username: string) =>
97 onNameRegistrationEnded.next({ accountId, state, username });
98
99 const onRegisteredNameFound = new Subject<RegisteredNameFound>();
100 handlers.RegisteredNameFound = (accountId: string, state: number, address: string, username: string) =>
101 onRegisteredNameFound.next({ accountId, state, address, username });
102
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400103 const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
104 handlers.KnownDevicesChanged = (accountId: string, devices: Record<string, string>) =>
105 onKnownDevicesChanged.next({ accountId, devices });
106
Charlieb62c6782022-10-30 15:14:56 -0400107 const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500108 handlers.IncomingAccountMessage = (accountId: string, from: string, payload: Record<string, string>) =>
109 onIncomingAccountMessage.next({ accountId, from, payload });
110
111 const onAccountMessageStatusChanged = new Subject<AccountMessageStatusChanged>();
112 handlers.AccountMessageStatusChanged = (accountId: string, messageId: string, peer: string, state: number) =>
113 onAccountMessageStatusChanged.next({ accountId, messageId, peer, state });
114
115 const onContactAdded = new Subject<ContactAdded>();
116 handlers.ContactAdded = (accountId: string, contactId: string, confirmed: boolean) =>
117 onContactAdded.next({ accountId, contactId, confirmed });
118
119 const onContactRemoved = new Subject<ContactRemoved>();
120 handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
121 onContactRemoved.next({ accountId, contactId, banned });
Charlieb62c6782022-10-30 15:14:56 -0400122
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500123 const onConversationRequestReceived = new Subject<ConversationRequestReceived>();
124 handlers.ConversationRequestReceived = (accountId: string, conversationId: string, metadata: StringMap) =>
125 onConversationRequestReceived.next({ accountId, conversationId, metadata });
126
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400127 const onConversationReady = new Subject<ConversationReady>();
128 handlers.ConversationReady = (accountId: string, conversationId: string) =>
129 onConversationReady.next({ accountId, conversationId });
130
131 const onConversationRemoved = new Subject<ConversationRemoved>();
132 handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
133 onConversationRemoved.next({ accountId, conversationId });
134
135 const onConversationLoaded = new Subject<ConversationLoaded>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400136 handlers.ConversationLoaded = (id: number, accountId: string, conversationId: string, messages: Message[]) =>
137 onConversationLoaded.next({ id, accountId, conversationId, messages });
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400138
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500139 const onConversationMemberEvent = new Subject<ConversationMemberEvent>();
140 handlers.ConversationMemberEvent = (
141 accountId: string,
142 conversationId: string,
143 memberUri: string,
144 event: number
145 ) => {
146 onConversationMemberEvent.next({ accountId, conversationId, memberUri, event });
147 };
148
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400149 const onMessageReceived = new Subject<MessageReceived>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400150 handlers.MessageReceived = (accountId: string, conversationId: string, message: Message) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400151 onMessageReceived.next({ accountId, conversationId, message });
152
153 // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000154 this.events = {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400155 onAccountsChanged: onAccountsChanged.asObservable(),
156 onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000157 onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
158 onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
159 onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
160 onRegisteredNameFound: onRegisteredNameFound.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400161 onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
Charlieb62c6782022-10-30 15:14:56 -0400162 onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500163 onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
164 onContactAdded: onContactAdded.asObservable(),
165 onContactRemoved: onContactRemoved.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500166 onConversationRequestReceived: onConversationRequestReceived.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400167 onConversationReady: onConversationReady.asObservable(),
168 onConversationRemoved: onConversationRemoved.asObservable(),
169 onConversationLoaded: onConversationLoaded.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500170 onConversationMemberEvent: onConversationMemberEvent.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400171 onMessageReceived: onMessageReceived.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000172 };
173
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400174 this.setupSignalHandlers();
Issam E. Maghnif796a092022-10-09 20:25:26 +0000175
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400176 // RxJS Subjects are used as signal handlers for the following reasons:
177 // 1. You cannot change event handlers after calling jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000178 // 2. You cannot specify multiple handlers for the same event
179 // 3. You cannot specify a default handler
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400180 this.jamiSwig.init(handlers);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000181 }
182
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400183 stop(): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400184 this.jamiSwig.fini();
Misha Krieger-Raynauld62a0da92022-10-22 13:46:59 -0400185 }
186
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400187 getVolatileAccountDetails(accountId: string): VolatileDetails {
188 return stringMapToRecord(this.jamiSwig.getVolatileAccountDetails(accountId)) as unknown as VolatileDetails;
Issam E. Maghnif796a092022-10-09 20:25:26 +0000189 }
190
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400191 getAccountDetails(accountId: string): AccountDetails {
192 return stringMapToRecord(this.jamiSwig.getAccountDetails(accountId)) as unknown as AccountDetails;
193 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000194
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400195 setAccountDetails(accountId: string, accountDetails: AccountDetails): void {
simon43da57b2022-10-26 18:22:22 -0400196 const accountDetailsStringMap: StringMap = new this.jamiSwig.StringMap();
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400197 for (const [key, value] of Object.entries(accountDetails)) {
198 accountDetailsStringMap.set(key, value);
199 }
200 this.jamiSwig.setAccountDetails(accountId, accountDetailsStringMap);
201 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000202
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400203 async addAccount(details: Map<string, string | number | boolean>): Promise<string> {
simon43da57b2022-10-26 18:22:22 -0400204 const detailsStringMap: StringMap = new this.jamiSwig.StringMap();
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400205
206 detailsStringMap.set('Account.type', 'RING');
207 for (const [key, value] of details.entries()) {
208 detailsStringMap.set('Account.' + key, value.toString());
209 }
210
211 const accountId = this.jamiSwig.addAccount(detailsStringMap);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000212 return firstValueFrom(
213 this.events.onRegistrationStateChanged.pipe(
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400214 filter((value) => value.accountId === accountId),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000215 // TODO: is it the only state?
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400216 // TODO: Replace with string enum in common/
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400217 filter((value) => value.state === 'REGISTERED'),
218 map((value) => value.accountId)
Issam E. Maghnif796a092022-10-09 20:25:26 +0000219 )
220 );
221 }
222
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400223 removeAccount(accountId: string): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400224 this.jamiSwig.removeAccount(accountId);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000225 }
226
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400227 getAccountIds(): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400228 return stringVectToArray(this.jamiSwig.getAccountList());
Issam E. Maghnif796a092022-10-09 20:25:26 +0000229 }
230
Charlie2bc0d672022-11-04 11:53:44 -0400231 sendAccountTextMessage(from: string, to: string, message: string): void {
Charlieb62c6782022-10-30 15:14:56 -0400232 const messageStringMap: StringMap = new this.jamiSwig.StringMap();
Charlie2bc0d672022-11-04 11:53:44 -0400233 messageStringMap.set('application/json', message);
234 this.jamiSwig.sendAccountTextMessage(from, to, messageStringMap);
Charlieb62c6782022-10-30 15:14:56 -0400235 }
236
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400237 // TODO: Add interface for returned type
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400238 async lookupUsername(username: string, accountId?: string) {
239 const hasRingNs = this.jamiSwig.lookupName(accountId || '', '', username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000240 if (!hasRingNs) {
Issam E. Maghnif796a092022-10-09 20:25:26 +0000241 throw new Error('Jami does not have NS');
242 }
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400243 return firstValueFrom(
244 this.events.onRegisteredNameFound.pipe(
245 filter((value) => value.username === username),
246 map(({ accountId: _, ...response }) => response) // Remove accountId from response
247 )
248 );
249 }
250
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400251 // TODO: Add interface for returned type
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400252 async lookupAddress(address: string, accountId?: string) {
253 const hasRingNs = this.jamiSwig.lookupAddress(accountId || '', '', address);
254 if (!hasRingNs) {
255 throw new Error('Jami does not have NS');
256 }
257 return firstValueFrom(
258 this.events.onRegisteredNameFound.pipe(
259 filter((value) => value.address === address),
260 map(({ accountId: _, ...response }) => response) // Remove accountId from response
261 )
262 );
Issam E. Maghnif796a092022-10-09 20:25:26 +0000263 }
264
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400265 // TODO: Create enum for state and return that rather than a number
266 async registerUsername(accountId: string, username: string, password: string): Promise<number> {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400267 const hasRingNs = this.jamiSwig.registerName(accountId, password, username);
268 if (!hasRingNs) {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400269 throw new Error('Jami does not have NS');
270 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400271 return firstValueFrom(
272 this.events.onNameRegistrationEnded.pipe(
273 filter((value) => value.accountId === accountId),
274 map((value) => value.state)
275 )
276 );
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400277 }
278
279 getDevices(accountId: string): Record<string, string> {
280 return stringMapToRecord(this.jamiSwig.getKnownRingDevices(accountId));
281 }
282
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400283 addContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400284 this.jamiSwig.addContact(accountId, contactId);
285 }
286
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500287 sendTrustRequest(accountId: string, contactId: string): void {
288 this.jamiSwig.sendTrustRequest(accountId, contactId, new this.jamiSwig.Blob());
289 }
290
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400291 removeContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400292 this.jamiSwig.removeContact(accountId, contactId, false);
293 }
294
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400295 blockContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400296 this.jamiSwig.removeContact(accountId, contactId, true);
297 }
298
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400299 // TODO: Replace Record with interface
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400300 getContacts(accountId: string): Record<string, string>[] {
301 return vectMapToRecordArray(this.jamiSwig.getContacts(accountId));
302 }
303
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400304 // TODO: Replace Record with interface
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400305 getContactDetails(accountId: string, contactId: string): Record<string, string> {
306 return stringMapToRecord(this.jamiSwig.getContactDetails(accountId, contactId));
307 }
308
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400309 getDefaultModeratorUris(accountId: string): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400310 return stringVectToArray(this.jamiSwig.getDefaultModerators(accountId));
311 }
312
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400313 addDefaultModerator(accountId: string, contactId: string): void {
314 this.jamiSwig.setDefaultModerator(accountId, contactId, true);
315 }
316
317 removeDefaultModerator(accountId: string, contactId: string): void {
318 this.jamiSwig.setDefaultModerator(accountId, contactId, false);
319 }
320
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400321 getConversationIds(accountId: string): string[] {
322 return stringVectToArray(this.jamiSwig.getConversations(accountId));
323 }
324
325 // TODO: Replace Record with interface
326 getConversationInfos(accountId: string, conversationId: string): Record<string, string> {
327 return stringMapToRecord(this.jamiSwig.conversationInfos(accountId, conversationId));
328 }
329
330 // TODO: Replace Record with interface
331 getConversationMembers(accountId: string, conversationId: string): Record<string, string>[] {
332 return vectMapToRecordArray(this.jamiSwig.getConversationMembers(accountId, conversationId));
333 }
334
335 async getConversationMessages(accountId: string, conversationId: string, fromMessage?: string): Promise<Message[]> {
336 const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
337 return firstValueFrom(
338 this.events.onConversationLoaded.pipe(
339 filter((value) => value.id === requestId),
340 map((value) => value.messages)
341 )
342 );
343 }
344
Issam E. Maghni2cb239b2022-11-18 22:39:32 +0000345 sendConversationMessage(
346 accountId: string,
347 conversationId: string,
348 message: string,
349 replyTo?: string,
350 flag?: number
351 ): void {
352 this.jamiSwig.sendMessage(accountId, conversationId, message, replyTo ?? '', flag ?? 0);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400353 }
354
Misha Krieger-Raynauld7f959332022-11-04 15:12:53 -0400355 getCallIds(accountId: string): string[] {
356 return stringVectToArray(this.jamiSwig.getCallList(accountId));
357 }
358
359 // TODO: Replace Record with interface
360 getCallDetails(accountId: string, callId: string): Record<string, string> {
361 return stringMapToRecord(this.jamiSwig.getCallDetails(accountId, callId));
362 }
363
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400364 getAccountIdFromUsername(username: string): string | undefined {
365 return this.usernamesToAccountIds.get(username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000366 }
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400367
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400368 private setupSignalHandlers(): void {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400369 this.events.onAccountsChanged.subscribe(() => {
370 log.debug('Received AccountsChanged');
371 });
372
373 this.events.onAccountDetailsChanged.subscribe((signal) => {
374 log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
375 });
376
377 this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400378 const username = details['Account.registeredName'];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400379 log.debug(
380 `Received VolatileDetailsChanged: {"accountId":"${accountId}",` +
381 `"details":{"Account.registeredName":"${username}", ...}}`
382 );
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400383 if (username) {
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500384 // Keep map of usernames to account IDs
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400385 this.usernamesToAccountIds.set(username, accountId);
386 }
387 });
388
389 this.events.onRegistrationStateChanged.subscribe((signal) => {
390 log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
391 });
392
393 this.events.onNameRegistrationEnded.subscribe((signal) => {
394 log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
395 });
396
397 this.events.onRegisteredNameFound.subscribe((signal) => {
398 log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
399 });
400
401 this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
402 log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
403 });
404
405 this.events.onIncomingAccountMessage.subscribe((signal) => {
406 log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000407 const message: WebSocketMessage<any> = JSON.parse(signal.payload['application/json']);
Charlie2bc0d672022-11-04 11:53:44 -0400408
409 if (message === undefined) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500410 log.warn('Undefined account message');
411 return;
412 }
413
414 if (message.type === undefined || message.data === undefined) {
415 log.warn('Account message is not a valid WebSocketMessage (missing type or data fields)');
Charlie2bc0d672022-11-04 11:53:44 -0400416 return;
417 }
418
419 if (!Object.values(WebSocketMessageType).includes(message.type)) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500420 log.warn(`Invalid WebSocket message type: ${message.type}`);
Charlie2bc0d672022-11-04 11:53:44 -0400421 return;
422 }
423
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000424 const data: AccountTextMessage<unknown> = {
425 from: signal.from,
426 to: signal.accountId,
427 message: message.data.message,
428 };
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000429
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500430 this.webSocketServer.send(signal.accountId, message.type, data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400431 });
432
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500433 this.events.onAccountMessageStatusChanged.subscribe((signal) => {
434 log.debug('Received AccountMessageStatusChanged:', JSON.stringify(signal));
435 });
436
437 this.events.onContactAdded.subscribe((signal) => {
438 log.debug('Received ContactAdded:', JSON.stringify(signal));
439 });
440
441 this.events.onContactRemoved.subscribe((signal) => {
442 log.debug('Received ContactRemoved:', JSON.stringify(signal));
443 });
444
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500445 this.events.onConversationRequestReceived.subscribe((signal) => {
446 log.debug('Received ConversationRequestReceived:', JSON.stringify(signal));
447
448 // TODO: Prompt user to accept conversation request on client
449 // Currently, we auto-accept all incoming conversation requests. In future, we
450 // need to ask the user if they accept the conversation request or not. Part of
451 // it can be done by sending a WebSocket event.
452 // See other implementations e.g. block contact / decline request / accept request.
453 this.jamiSwig.acceptConversationRequest(signal.accountId, signal.conversationId);
454 });
455
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400456 this.events.onConversationReady.subscribe((signal) => {
457 log.debug('Received ConversationReady:', JSON.stringify(signal));
458 });
459
460 this.events.onConversationRemoved.subscribe((signal) => {
461 log.debug('Received ConversationRemoved:', JSON.stringify(signal));
462 });
463
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400464 this.events.onConversationLoaded.subscribe(({ id, accountId, conversationId }) => {
465 log.debug(
466 `Received ConversationLoaded: {"id":"${id}","accountId":"${accountId}",` +
467 `"conversationId":"${conversationId}","messages":[...]}`
468 );
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400469 });
470
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500471 this.events.onConversationMemberEvent.subscribe((signal) => {
472 log.debug('Received onConversationMemberEvent:', JSON.stringify(signal));
473 });
474
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400475 this.events.onMessageReceived.subscribe((signal) => {
476 log.debug('Received MessageReceived:', JSON.stringify(signal));
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500477
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000478 const data: ConversationMessage = {
479 conversationId: signal.conversationId,
480 message: signal.message,
481 };
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500482 this.webSocketServer.send(signal.accountId, WebSocketMessageType.ConversationMessage, data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400483 });
484 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000485}