blob: 30439ef574600642e5554fc804ae0d2c09e62777 [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,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000022 ConversationMessage,
23 Message,
24 VolatileDetails,
25 WebSocketMessage,
26 WebSocketMessageType,
27} from 'jami-web-common';
Issam E. Maghnif796a092022-10-09 20:25:26 +000028import log from 'loglevel';
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -040029import { filter, firstValueFrom, map, Subject } from 'rxjs';
Issam E. Maghnif796a092022-10-09 20:25:26 +000030import { Service } from 'typedi';
31
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050032import { WebSocketServer } from '../websocket/websocket-server.js';
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040033import { JamiSignal } from './jami-signal.js';
34import {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040035 AccountDetailsChanged,
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050036 AccountMessageStatusChanged,
37 ContactAdded,
38 ContactRemoved,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040039 ConversationLoaded,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050040 ConversationMemberEvent,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040041 ConversationReady,
42 ConversationRemoved,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050043 ConversationRequestReceived,
Charlieb62c6782022-10-30 15:14:56 -040044 IncomingAccountMessage,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040045 KnownDevicesChanged,
46 MessageReceived,
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040047 NameRegistrationEnded,
48 RegisteredNameFound,
49 RegistrationStateChanged,
50 VolatileDetailsChanged,
51} from './jami-signal-interfaces.js';
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040052import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray, vectMapToRecordArray } from './jami-swig.js';
Issam E. Maghnif796a092022-10-09 20:25:26 +000053
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050054const require = createRequire(import.meta.url);
55
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040056// TODO: Convert Records to interfaces and replace them in common/ (e.g. Contact)
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040057
Issam E. Maghnif796a092022-10-09 20:25:26 +000058@Service()
59export class Jamid {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050060 private jamiSwig: JamiSwig;
61 private usernamesToAccountIds = new Map<string, string>();
Issam E. Maghnif796a092022-10-09 20:25:26 +000062 private readonly events;
63
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050064 constructor(private webSocketServer: WebSocketServer) {
65 this.jamiSwig = require('../../jamid.node') as JamiSwig;
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040066
67 // Setup signal handlers
Issam E. Maghnif796a092022-10-09 20:25:26 +000068 const handlers: Record<string, unknown> = {};
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040069
70 // Add default handler for all signals
71 const createDefaultHandler = (signal: string) => {
72 return (...args: unknown[]) => log.warn('Unhandled', signal, args);
Issam E. Maghnif796a092022-10-09 20:25:26 +000073 };
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040074 for (const signal in JamiSignal) {
75 handlers[signal] = createDefaultHandler(signal);
76 }
77
78 // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
79 const onAccountsChanged = new Subject<void>();
80 handlers.AccountsChanged = () => onAccountsChanged.next();
81
82 const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
83 handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
84 onAccountDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000085
86 const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040087 handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
88 onVolatileDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000089
90 const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
91 handlers.RegistrationStateChanged = (accountId: string, state: string, code: number, details: string) =>
92 onRegistrationStateChanged.next({ accountId, state, code, details });
93
94 const onNameRegistrationEnded = new Subject<NameRegistrationEnded>();
95 handlers.NameRegistrationEnded = (accountId: string, state: number, username: string) =>
96 onNameRegistrationEnded.next({ accountId, state, username });
97
98 const onRegisteredNameFound = new Subject<RegisteredNameFound>();
99 handlers.RegisteredNameFound = (accountId: string, state: number, address: string, username: string) =>
100 onRegisteredNameFound.next({ accountId, state, address, username });
101
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400102 const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
103 handlers.KnownDevicesChanged = (accountId: string, devices: Record<string, string>) =>
104 onKnownDevicesChanged.next({ accountId, devices });
105
Charlieb62c6782022-10-30 15:14:56 -0400106 const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500107 handlers.IncomingAccountMessage = (accountId: string, from: string, payload: Record<string, string>) =>
108 onIncomingAccountMessage.next({ accountId, from, payload });
109
110 const onAccountMessageStatusChanged = new Subject<AccountMessageStatusChanged>();
111 handlers.AccountMessageStatusChanged = (accountId: string, messageId: string, peer: string, state: number) =>
112 onAccountMessageStatusChanged.next({ accountId, messageId, peer, state });
113
114 const onContactAdded = new Subject<ContactAdded>();
115 handlers.ContactAdded = (accountId: string, contactId: string, confirmed: boolean) =>
116 onContactAdded.next({ accountId, contactId, confirmed });
117
118 const onContactRemoved = new Subject<ContactRemoved>();
119 handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
120 onContactRemoved.next({ accountId, contactId, banned });
Charlieb62c6782022-10-30 15:14:56 -0400121
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500122 const onConversationRequestReceived = new Subject<ConversationRequestReceived>();
123 handlers.ConversationRequestReceived = (accountId: string, conversationId: string, metadata: StringMap) =>
124 onConversationRequestReceived.next({ accountId, conversationId, metadata });
125
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400126 const onConversationReady = new Subject<ConversationReady>();
127 handlers.ConversationReady = (accountId: string, conversationId: string) =>
128 onConversationReady.next({ accountId, conversationId });
129
130 const onConversationRemoved = new Subject<ConversationRemoved>();
131 handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
132 onConversationRemoved.next({ accountId, conversationId });
133
134 const onConversationLoaded = new Subject<ConversationLoaded>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400135 handlers.ConversationLoaded = (id: number, accountId: string, conversationId: string, messages: Message[]) =>
136 onConversationLoaded.next({ id, accountId, conversationId, messages });
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400137
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500138 const onConversationMemberEvent = new Subject<ConversationMemberEvent>();
139 handlers.ConversationMemberEvent = (
140 accountId: string,
141 conversationId: string,
142 memberUri: string,
143 event: number
144 ) => {
145 onConversationMemberEvent.next({ accountId, conversationId, memberUri, event });
146 };
147
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400148 const onMessageReceived = new Subject<MessageReceived>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400149 handlers.MessageReceived = (accountId: string, conversationId: string, message: Message) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400150 onMessageReceived.next({ accountId, conversationId, message });
151
152 // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000153 this.events = {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400154 onAccountsChanged: onAccountsChanged.asObservable(),
155 onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000156 onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
157 onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
158 onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
159 onRegisteredNameFound: onRegisteredNameFound.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400160 onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
Charlieb62c6782022-10-30 15:14:56 -0400161 onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500162 onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
163 onContactAdded: onContactAdded.asObservable(),
164 onContactRemoved: onContactRemoved.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500165 onConversationRequestReceived: onConversationRequestReceived.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400166 onConversationReady: onConversationReady.asObservable(),
167 onConversationRemoved: onConversationRemoved.asObservable(),
168 onConversationLoaded: onConversationLoaded.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500169 onConversationMemberEvent: onConversationMemberEvent.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400170 onMessageReceived: onMessageReceived.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000171 };
172
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400173 this.setupSignalHandlers();
Issam E. Maghnif796a092022-10-09 20:25:26 +0000174
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400175 // RxJS Subjects are used as signal handlers for the following reasons:
176 // 1. You cannot change event handlers after calling jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000177 // 2. You cannot specify multiple handlers for the same event
178 // 3. You cannot specify a default handler
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400179 this.jamiSwig.init(handlers);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000180 }
181
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400182 stop(): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400183 this.jamiSwig.fini();
Misha Krieger-Raynauld62a0da92022-10-22 13:46:59 -0400184 }
185
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400186 getVolatileAccountDetails(accountId: string): VolatileDetails {
187 return stringMapToRecord(this.jamiSwig.getVolatileAccountDetails(accountId)) as unknown as VolatileDetails;
Issam E. Maghnif796a092022-10-09 20:25:26 +0000188 }
189
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400190 getAccountDetails(accountId: string): AccountDetails {
191 return stringMapToRecord(this.jamiSwig.getAccountDetails(accountId)) as unknown as AccountDetails;
192 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000193
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400194 setAccountDetails(accountId: string, accountDetails: AccountDetails): void {
simon43da57b2022-10-26 18:22:22 -0400195 const accountDetailsStringMap: StringMap = new this.jamiSwig.StringMap();
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400196 for (const [key, value] of Object.entries(accountDetails)) {
197 accountDetailsStringMap.set(key, value);
198 }
199 this.jamiSwig.setAccountDetails(accountId, accountDetailsStringMap);
200 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000201
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000202 async addAccount(accountDetails: Partial<AccountDetails>): Promise<RegistrationStateChanged> {
203 accountDetails['Account.type'] = 'RING';
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400204
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000205 const detailsStringMap: StringMap = new this.jamiSwig.StringMap();
206 for (const [key, value] of Object.entries(accountDetails)) {
207 detailsStringMap.set(key, value.toString());
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400208 }
209
210 const accountId = this.jamiSwig.addAccount(detailsStringMap);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000211 return firstValueFrom(
212 this.events.onRegistrationStateChanged.pipe(
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400213 filter((value) => value.accountId === accountId),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000214 // TODO: is it the only state?
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400215 // TODO: Replace with string enum in common/
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000216 filter((value) => value.state === 'REGISTERED' || value.state === 'ERROR_GENERIC')
Issam E. Maghnif796a092022-10-09 20:25:26 +0000217 )
218 );
219 }
220
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400221 removeAccount(accountId: string): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400222 this.jamiSwig.removeAccount(accountId);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000223 }
224
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400225 getAccountIds(): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400226 return stringVectToArray(this.jamiSwig.getAccountList());
Issam E. Maghnif796a092022-10-09 20:25:26 +0000227 }
228
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500229 sendAccountTextMessage(accountId: string, contactId: string, message: string): void {
Charlieb62c6782022-10-30 15:14:56 -0400230 const messageStringMap: StringMap = new this.jamiSwig.StringMap();
Charlie2bc0d672022-11-04 11:53:44 -0400231 messageStringMap.set('application/json', message);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500232 this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
Charlieb62c6782022-10-30 15:14:56 -0400233 }
234
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400235 // TODO: Add interface for returned type
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400236 async lookupUsername(username: string, accountId?: string) {
237 const hasRingNs = this.jamiSwig.lookupName(accountId || '', '', username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000238 if (!hasRingNs) {
Issam E. Maghnif796a092022-10-09 20:25:26 +0000239 throw new Error('Jami does not have NS');
240 }
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400241 return firstValueFrom(
242 this.events.onRegisteredNameFound.pipe(
243 filter((value) => value.username === username),
244 map(({ accountId: _, ...response }) => response) // Remove accountId from response
245 )
246 );
247 }
248
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400249 // TODO: Add interface for returned type
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400250 async lookupAddress(address: string, accountId?: string) {
251 const hasRingNs = this.jamiSwig.lookupAddress(accountId || '', '', address);
252 if (!hasRingNs) {
253 throw new Error('Jami does not have NS');
254 }
255 return firstValueFrom(
256 this.events.onRegisteredNameFound.pipe(
257 filter((value) => value.address === address),
258 map(({ accountId: _, ...response }) => response) // Remove accountId from response
259 )
260 );
Issam E. Maghnif796a092022-10-09 20:25:26 +0000261 }
262
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400263 // TODO: Create enum for state and return that rather than a number
264 async registerUsername(accountId: string, username: string, password: string): Promise<number> {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400265 const hasRingNs = this.jamiSwig.registerName(accountId, password, username);
266 if (!hasRingNs) {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400267 throw new Error('Jami does not have NS');
268 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400269 return firstValueFrom(
270 this.events.onNameRegistrationEnded.pipe(
271 filter((value) => value.accountId === accountId),
272 map((value) => value.state)
273 )
274 );
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400275 }
276
277 getDevices(accountId: string): Record<string, string> {
278 return stringMapToRecord(this.jamiSwig.getKnownRingDevices(accountId));
279 }
280
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400281 addContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400282 this.jamiSwig.addContact(accountId, contactId);
283 }
284
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500285 sendTrustRequest(accountId: string, contactId: string): void {
286 this.jamiSwig.sendTrustRequest(accountId, contactId, new this.jamiSwig.Blob());
287 }
288
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400289 removeContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400290 this.jamiSwig.removeContact(accountId, contactId, false);
291 }
292
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400293 blockContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400294 this.jamiSwig.removeContact(accountId, contactId, true);
295 }
296
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400297 // TODO: Replace Record with interface
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400298 getContacts(accountId: string): Record<string, string>[] {
299 return vectMapToRecordArray(this.jamiSwig.getContacts(accountId));
300 }
301
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400302 // TODO: Replace Record with interface
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400303 getContactDetails(accountId: string, contactId: string): Record<string, string> {
304 return stringMapToRecord(this.jamiSwig.getContactDetails(accountId, contactId));
305 }
306
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400307 getDefaultModeratorUris(accountId: string): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400308 return stringVectToArray(this.jamiSwig.getDefaultModerators(accountId));
309 }
310
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400311 addDefaultModerator(accountId: string, contactId: string): void {
312 this.jamiSwig.setDefaultModerator(accountId, contactId, true);
313 }
314
315 removeDefaultModerator(accountId: string, contactId: string): void {
316 this.jamiSwig.setDefaultModerator(accountId, contactId, false);
317 }
318
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400319 getConversationIds(accountId: string): string[] {
320 return stringVectToArray(this.jamiSwig.getConversations(accountId));
321 }
322
323 // TODO: Replace Record with interface
324 getConversationInfos(accountId: string, conversationId: string): Record<string, string> {
325 return stringMapToRecord(this.jamiSwig.conversationInfos(accountId, conversationId));
326 }
327
328 // TODO: Replace Record with interface
329 getConversationMembers(accountId: string, conversationId: string): Record<string, string>[] {
330 return vectMapToRecordArray(this.jamiSwig.getConversationMembers(accountId, conversationId));
331 }
332
333 async getConversationMessages(accountId: string, conversationId: string, fromMessage?: string): Promise<Message[]> {
334 const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
335 return firstValueFrom(
336 this.events.onConversationLoaded.pipe(
337 filter((value) => value.id === requestId),
338 map((value) => value.messages)
339 )
340 );
341 }
342
Issam E. Maghni2cb239b2022-11-18 22:39:32 +0000343 sendConversationMessage(
344 accountId: string,
345 conversationId: string,
346 message: string,
347 replyTo?: string,
348 flag?: number
349 ): void {
350 this.jamiSwig.sendMessage(accountId, conversationId, message, replyTo ?? '', flag ?? 0);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400351 }
352
Misha Krieger-Raynauld7f959332022-11-04 15:12:53 -0400353 getCallIds(accountId: string): string[] {
354 return stringVectToArray(this.jamiSwig.getCallList(accountId));
355 }
356
357 // TODO: Replace Record with interface
358 getCallDetails(accountId: string, callId: string): Record<string, string> {
359 return stringMapToRecord(this.jamiSwig.getCallDetails(accountId, callId));
360 }
361
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400362 getAccountIdFromUsername(username: string): string | undefined {
363 return this.usernamesToAccountIds.get(username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000364 }
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400365
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400366 private setupSignalHandlers(): void {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400367 this.events.onAccountsChanged.subscribe(() => {
368 log.debug('Received AccountsChanged');
369 });
370
371 this.events.onAccountDetailsChanged.subscribe((signal) => {
372 log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
373 });
374
375 this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400376 const username = details['Account.registeredName'];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400377 log.debug(
378 `Received VolatileDetailsChanged: {"accountId":"${accountId}",` +
379 `"details":{"Account.registeredName":"${username}", ...}}`
380 );
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500381
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400382 if (username) {
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500383 // Keep map of usernames to account IDs
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400384 this.usernamesToAccountIds.set(username, accountId);
385 }
386 });
387
388 this.events.onRegistrationStateChanged.subscribe((signal) => {
389 log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
390 });
391
392 this.events.onNameRegistrationEnded.subscribe((signal) => {
393 log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
394 });
395
396 this.events.onRegisteredNameFound.subscribe((signal) => {
397 log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
398 });
399
400 this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
401 log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
402 });
403
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500404 this.events.onIncomingAccountMessage.subscribe(<T extends WebSocketMessageType>(signal: IncomingAccountMessage) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400405 log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500406
407 const message: WebSocketMessage<T> = 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
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500424 this.webSocketServer.send(signal.accountId, message.type, message.data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400425 });
426
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500427 this.events.onAccountMessageStatusChanged.subscribe((signal) => {
428 log.debug('Received AccountMessageStatusChanged:', JSON.stringify(signal));
429 });
430
431 this.events.onContactAdded.subscribe((signal) => {
432 log.debug('Received ContactAdded:', JSON.stringify(signal));
433 });
434
435 this.events.onContactRemoved.subscribe((signal) => {
436 log.debug('Received ContactRemoved:', JSON.stringify(signal));
437 });
438
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500439 this.events.onConversationRequestReceived.subscribe((signal) => {
440 log.debug('Received ConversationRequestReceived:', JSON.stringify(signal));
441
442 // TODO: Prompt user to accept conversation request on client
443 // Currently, we auto-accept all incoming conversation requests. In future, we
444 // need to ask the user if they accept the conversation request or not. Part of
445 // it can be done by sending a WebSocket event.
446 // See other implementations e.g. block contact / decline request / accept request.
447 this.jamiSwig.acceptConversationRequest(signal.accountId, signal.conversationId);
448 });
449
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400450 this.events.onConversationReady.subscribe((signal) => {
451 log.debug('Received ConversationReady:', JSON.stringify(signal));
452 });
453
454 this.events.onConversationRemoved.subscribe((signal) => {
455 log.debug('Received ConversationRemoved:', JSON.stringify(signal));
456 });
457
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400458 this.events.onConversationLoaded.subscribe(({ id, accountId, conversationId }) => {
459 log.debug(
460 `Received ConversationLoaded: {"id":"${id}","accountId":"${accountId}",` +
461 `"conversationId":"${conversationId}","messages":[...]}`
462 );
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400463 });
464
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500465 this.events.onConversationMemberEvent.subscribe((signal) => {
466 log.debug('Received onConversationMemberEvent:', JSON.stringify(signal));
467 });
468
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400469 this.events.onMessageReceived.subscribe((signal) => {
470 log.debug('Received MessageReceived:', JSON.stringify(signal));
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500471
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000472 const data: ConversationMessage = {
473 conversationId: signal.conversationId,
474 message: signal.message,
475 };
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500476 this.webSocketServer.send(signal.accountId, WebSocketMessageType.ConversationMessage, data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400477 });
478 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000479}