blob: bb47b4e2e3b2ba501c84ab7d8c3cec5d1e2c91b1 [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,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050022 ContactDetails,
23 ConversationInfos,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000024 ConversationMessage,
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050025 Devices,
26 LookupResult,
Issam E. Maghni0432cb72022-11-12 06:09:26 +000027 Message,
28 VolatileDetails,
29 WebSocketMessage,
30 WebSocketMessageType,
31} from 'jami-web-common';
Issam E. Maghnif796a092022-10-09 20:25:26 +000032import log from 'loglevel';
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -040033import { filter, firstValueFrom, map, Subject } from 'rxjs';
Issam E. Maghnif796a092022-10-09 20:25:26 +000034import { Service } from 'typedi';
35
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050036import { WebSocketServer } from '../websocket/websocket-server.js';
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050037import { ConversationMemberInfos } from './conversation-member-infos.js';
38import { ConversationRequestMetadata } from './conversation-request-metadata.js';
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040039import { JamiSignal } from './jami-signal.js';
40import {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040041 AccountDetailsChanged,
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -050042 AccountMessageStatusChanged,
43 ContactAdded,
44 ContactRemoved,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040045 ConversationLoaded,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050046 ConversationMemberEvent,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040047 ConversationReady,
48 ConversationRemoved,
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -050049 ConversationRequestReceived,
Charlieb62c6782022-10-30 15:14:56 -040050 IncomingAccountMessage,
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040051 KnownDevicesChanged,
52 MessageReceived,
Misha Krieger-Raynauldaddd6fe2022-10-22 12:46:04 -040053 NameRegistrationEnded,
54 RegisteredNameFound,
55 RegistrationStateChanged,
56 VolatileDetailsChanged,
57} from './jami-signal-interfaces.js';
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -040058import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray, vectMapToRecordArray } from './jami-swig.js';
Issam E. Maghnif796a092022-10-09 20:25:26 +000059
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050060const require = createRequire(import.meta.url);
61
Issam E. Maghnif796a092022-10-09 20:25:26 +000062@Service()
63export class Jamid {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050064 private jamiSwig: JamiSwig;
65 private usernamesToAccountIds = new Map<string, string>();
Issam E. Maghnif796a092022-10-09 20:25:26 +000066 private readonly events;
67
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -050068 constructor(private webSocketServer: WebSocketServer) {
69 this.jamiSwig = require('../../jamid.node') as JamiSwig;
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040070
71 // Setup signal handlers
Issam E. Maghnif796a092022-10-09 20:25:26 +000072 const handlers: Record<string, unknown> = {};
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040073
74 // Add default handler for all signals
75 const createDefaultHandler = (signal: string) => {
76 return (...args: unknown[]) => log.warn('Unhandled', signal, args);
Issam E. Maghnif796a092022-10-09 20:25:26 +000077 };
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040078 for (const signal in JamiSignal) {
79 handlers[signal] = createDefaultHandler(signal);
80 }
81
82 // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
83 const onAccountsChanged = new Subject<void>();
84 handlers.AccountsChanged = () => onAccountsChanged.next();
85
86 const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
87 handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
88 onAccountDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000089
90 const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -040091 handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
92 onVolatileDetailsChanged.next({ accountId, details });
Issam E. Maghnif796a092022-10-09 20:25:26 +000093
94 const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
95 handlers.RegistrationStateChanged = (accountId: string, state: string, code: number, details: string) =>
96 onRegistrationStateChanged.next({ accountId, state, code, details });
97
98 const onNameRegistrationEnded = new Subject<NameRegistrationEnded>();
99 handlers.NameRegistrationEnded = (accountId: string, state: number, username: string) =>
100 onNameRegistrationEnded.next({ accountId, state, username });
101
102 const onRegisteredNameFound = new Subject<RegisteredNameFound>();
103 handlers.RegisteredNameFound = (accountId: string, state: number, address: string, username: string) =>
104 onRegisteredNameFound.next({ accountId, state, address, username });
105
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400106 const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500107 handlers.KnownDevicesChanged = (accountId: string, devices: Devices) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400108 onKnownDevicesChanged.next({ accountId, devices });
109
Charlieb62c6782022-10-30 15:14:56 -0400110 const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500111 handlers.IncomingAccountMessage = (accountId: string, from: string, payload: Record<string, string>) =>
112 onIncomingAccountMessage.next({ accountId, from, payload });
113
114 const onAccountMessageStatusChanged = new Subject<AccountMessageStatusChanged>();
115 handlers.AccountMessageStatusChanged = (accountId: string, messageId: string, peer: string, state: number) =>
116 onAccountMessageStatusChanged.next({ accountId, messageId, peer, state });
117
118 const onContactAdded = new Subject<ContactAdded>();
119 handlers.ContactAdded = (accountId: string, contactId: string, confirmed: boolean) =>
120 onContactAdded.next({ accountId, contactId, confirmed });
121
122 const onContactRemoved = new Subject<ContactRemoved>();
123 handlers.ContactRemoved = (accountId: string, contactId: string, banned: boolean) =>
124 onContactRemoved.next({ accountId, contactId, banned });
Charlieb62c6782022-10-30 15:14:56 -0400125
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500126 const onConversationRequestReceived = new Subject<ConversationRequestReceived>();
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500127 handlers.ConversationRequestReceived = (
128 accountId: string,
129 conversationId: string,
130 metadata: ConversationRequestMetadata
131 ) => onConversationRequestReceived.next({ accountId, conversationId, metadata });
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500132
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400133 const onConversationReady = new Subject<ConversationReady>();
134 handlers.ConversationReady = (accountId: string, conversationId: string) =>
135 onConversationReady.next({ accountId, conversationId });
136
137 const onConversationRemoved = new Subject<ConversationRemoved>();
138 handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
139 onConversationRemoved.next({ accountId, conversationId });
140
141 const onConversationLoaded = new Subject<ConversationLoaded>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400142 handlers.ConversationLoaded = (id: number, accountId: string, conversationId: string, messages: Message[]) =>
143 onConversationLoaded.next({ id, accountId, conversationId, messages });
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400144
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500145 const onConversationMemberEvent = new Subject<ConversationMemberEvent>();
146 handlers.ConversationMemberEvent = (
147 accountId: string,
148 conversationId: string,
149 memberUri: string,
150 event: number
151 ) => {
152 onConversationMemberEvent.next({ accountId, conversationId, memberUri, event });
153 };
154
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400155 const onMessageReceived = new Subject<MessageReceived>();
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400156 handlers.MessageReceived = (accountId: string, conversationId: string, message: Message) =>
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400157 onMessageReceived.next({ accountId, conversationId, message });
158
159 // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000160 this.events = {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400161 onAccountsChanged: onAccountsChanged.asObservable(),
162 onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000163 onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
164 onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
165 onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
166 onRegisteredNameFound: onRegisteredNameFound.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400167 onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
Charlieb62c6782022-10-30 15:14:56 -0400168 onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500169 onAccountMessageStatusChanged: onAccountMessageStatusChanged.asObservable(),
170 onContactAdded: onContactAdded.asObservable(),
171 onContactRemoved: onContactRemoved.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500172 onConversationRequestReceived: onConversationRequestReceived.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400173 onConversationReady: onConversationReady.asObservable(),
174 onConversationRemoved: onConversationRemoved.asObservable(),
175 onConversationLoaded: onConversationLoaded.asObservable(),
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500176 onConversationMemberEvent: onConversationMemberEvent.asObservable(),
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400177 onMessageReceived: onMessageReceived.asObservable(),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000178 };
179
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400180 this.setupSignalHandlers();
Issam E. Maghnif796a092022-10-09 20:25:26 +0000181
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400182 // RxJS Subjects are used as signal handlers for the following reasons:
183 // 1. You cannot change event handlers after calling jamiSwig.init()
Issam E. Maghnif796a092022-10-09 20:25:26 +0000184 // 2. You cannot specify multiple handlers for the same event
185 // 3. You cannot specify a default handler
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400186 this.jamiSwig.init(handlers);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000187 }
188
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400189 stop(): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400190 this.jamiSwig.fini();
Misha Krieger-Raynauld62a0da92022-10-22 13:46:59 -0400191 }
192
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400193 getVolatileAccountDetails(accountId: string): VolatileDetails {
194 return stringMapToRecord(this.jamiSwig.getVolatileAccountDetails(accountId)) as unknown as VolatileDetails;
Issam E. Maghnif796a092022-10-09 20:25:26 +0000195 }
196
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400197 getAccountDetails(accountId: string): AccountDetails {
198 return stringMapToRecord(this.jamiSwig.getAccountDetails(accountId)) as unknown as AccountDetails;
199 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000200
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400201 setAccountDetails(accountId: string, accountDetails: AccountDetails): void {
simon43da57b2022-10-26 18:22:22 -0400202 const accountDetailsStringMap: StringMap = new this.jamiSwig.StringMap();
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400203 for (const [key, value] of Object.entries(accountDetails)) {
204 accountDetailsStringMap.set(key, value);
205 }
206 this.jamiSwig.setAccountDetails(accountId, accountDetailsStringMap);
207 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000208
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000209 async addAccount(accountDetails: Partial<AccountDetails>): Promise<RegistrationStateChanged> {
210 accountDetails['Account.type'] = 'RING';
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400211
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000212 const detailsStringMap: StringMap = new this.jamiSwig.StringMap();
213 for (const [key, value] of Object.entries(accountDetails)) {
214 detailsStringMap.set(key, value.toString());
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400215 }
216
217 const accountId = this.jamiSwig.addAccount(detailsStringMap);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000218 return firstValueFrom(
219 this.events.onRegistrationStateChanged.pipe(
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400220 filter((value) => value.accountId === accountId),
Issam E. Maghnif796a092022-10-09 20:25:26 +0000221 // TODO: is it the only state?
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400222 // TODO: Replace with string enum in common/
Issam E. Maghnib05ad992022-11-20 23:50:48 +0000223 filter((value) => value.state === 'REGISTERED' || value.state === 'ERROR_GENERIC')
Issam E. Maghnif796a092022-10-09 20:25:26 +0000224 )
225 );
226 }
227
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400228 removeAccount(accountId: string): void {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400229 this.jamiSwig.removeAccount(accountId);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000230 }
231
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400232 getAccountIds(): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400233 return stringVectToArray(this.jamiSwig.getAccountList());
Issam E. Maghnif796a092022-10-09 20:25:26 +0000234 }
235
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500236 sendAccountTextMessage(accountId: string, contactId: string, message: string): void {
Charlieb62c6782022-10-30 15:14:56 -0400237 const messageStringMap: StringMap = new this.jamiSwig.StringMap();
Charlie2bc0d672022-11-04 11:53:44 -0400238 messageStringMap.set('application/json', message);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500239 this.jamiSwig.sendAccountTextMessage(accountId, contactId, messageStringMap);
Charlieb62c6782022-10-30 15:14:56 -0400240 }
241
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500242 async lookupUsername(username: string, accountId?: string): Promise<LookupResult> {
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400243 const hasRingNs = this.jamiSwig.lookupName(accountId || '', '', username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000244 if (!hasRingNs) {
Issam E. Maghnif796a092022-10-09 20:25:26 +0000245 throw new Error('Jami does not have NS');
246 }
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400247 return firstValueFrom(
248 this.events.onRegisteredNameFound.pipe(
249 filter((value) => value.username === username),
250 map(({ accountId: _, ...response }) => response) // Remove accountId from response
251 )
252 );
253 }
254
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500255 async lookupAddress(address: string, accountId?: string): Promise<LookupResult> {
Misha Krieger-Raynauld6f9c7ae2022-10-28 11:41:45 -0400256 const hasRingNs = this.jamiSwig.lookupAddress(accountId || '', '', address);
257 if (!hasRingNs) {
258 throw new Error('Jami does not have NS');
259 }
260 return firstValueFrom(
261 this.events.onRegisteredNameFound.pipe(
262 filter((value) => value.address === address),
263 map(({ accountId: _, ...response }) => response) // Remove accountId from response
264 )
265 );
Issam E. Maghnif796a092022-10-09 20:25:26 +0000266 }
267
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400268 // TODO: Create enum for state and return that rather than a number
269 async registerUsername(accountId: string, username: string, password: string): Promise<number> {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400270 const hasRingNs = this.jamiSwig.registerName(accountId, password, username);
271 if (!hasRingNs) {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400272 throw new Error('Jami does not have NS');
273 }
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400274 return firstValueFrom(
275 this.events.onNameRegistrationEnded.pipe(
276 filter((value) => value.accountId === accountId),
277 map((value) => value.state)
278 )
279 );
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400280 }
281
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500282 getDevices(accountId: string): Devices {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400283 return stringMapToRecord(this.jamiSwig.getKnownRingDevices(accountId));
284 }
285
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400286 addContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400287 this.jamiSwig.addContact(accountId, contactId);
288 }
289
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500290 sendTrustRequest(accountId: string, contactId: string): void {
291 this.jamiSwig.sendTrustRequest(accountId, contactId, new this.jamiSwig.Blob());
292 }
293
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400294 removeContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400295 this.jamiSwig.removeContact(accountId, contactId, false);
296 }
297
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400298 blockContact(accountId: string, contactId: string): void {
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400299 this.jamiSwig.removeContact(accountId, contactId, true);
300 }
301
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500302 getContacts(accountId: string): ContactDetails[] {
303 return vectMapToRecordArray(this.jamiSwig.getContacts(accountId)) as unknown as ContactDetails[];
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400304 }
305
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500306 getContactDetails(accountId: string, contactId: string): ContactDetails {
307 return stringMapToRecord(this.jamiSwig.getContactDetails(accountId, contactId)) as unknown as ContactDetails;
Misha Krieger-Raynauldbfed1732022-11-01 20:49:35 -0400308 }
309
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400310 getDefaultModeratorUris(accountId: string): string[] {
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400311 return stringVectToArray(this.jamiSwig.getDefaultModerators(accountId));
312 }
313
Misha Krieger-Raynauld153a1482022-11-05 12:00:41 -0400314 addDefaultModerator(accountId: string, contactId: string): void {
315 this.jamiSwig.setDefaultModerator(accountId, contactId, true);
316 }
317
318 removeDefaultModerator(accountId: string, contactId: string): void {
319 this.jamiSwig.setDefaultModerator(accountId, contactId, false);
320 }
321
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400322 getConversationIds(accountId: string): string[] {
323 return stringVectToArray(this.jamiSwig.getConversations(accountId));
324 }
325
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500326 getConversationInfos(accountId: string, conversationId: string): ConversationInfos {
327 return stringMapToRecord(
328 this.jamiSwig.conversationInfos(accountId, conversationId)
329 ) as unknown as ConversationInfos;
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400330 }
331
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500332 getConversationMembers(accountId: string, conversationId: string): ConversationMemberInfos[] {
333 return vectMapToRecordArray(
334 this.jamiSwig.getConversationMembers(accountId, conversationId)
335 ) as unknown as ConversationMemberInfos[];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400336 }
337
338 async getConversationMessages(accountId: string, conversationId: string, fromMessage?: string): Promise<Message[]> {
339 const requestId = this.jamiSwig.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
340 return firstValueFrom(
341 this.events.onConversationLoaded.pipe(
342 filter((value) => value.id === requestId),
343 map((value) => value.messages)
344 )
345 );
346 }
347
Issam E. Maghni2cb239b2022-11-18 22:39:32 +0000348 sendConversationMessage(
349 accountId: string,
350 conversationId: string,
351 message: string,
352 replyTo?: string,
353 flag?: number
354 ): void {
355 this.jamiSwig.sendMessage(accountId, conversationId, message, replyTo ?? '', flag ?? 0);
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400356 }
357
Misha Krieger-Raynauld7f959332022-11-04 15:12:53 -0400358 getCallIds(accountId: string): string[] {
359 return stringVectToArray(this.jamiSwig.getCallList(accountId));
360 }
361
362 // TODO: Replace Record with interface
363 getCallDetails(accountId: string, callId: string): Record<string, string> {
364 return stringMapToRecord(this.jamiSwig.getCallDetails(accountId, callId));
365 }
366
Misha Krieger-Raynauldb6f1c322022-10-23 20:42:57 -0400367 getAccountIdFromUsername(username: string): string | undefined {
368 return this.usernamesToAccountIds.get(username);
Issam E. Maghnif796a092022-10-09 20:25:26 +0000369 }
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400370
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400371 private setupSignalHandlers(): void {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400372 this.events.onAccountsChanged.subscribe(() => {
373 log.debug('Received AccountsChanged');
374 });
375
376 this.events.onAccountDetailsChanged.subscribe((signal) => {
377 log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
378 });
379
380 this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400381 const username = details['Account.registeredName'];
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400382 log.debug(
383 `Received VolatileDetailsChanged: {"accountId":"${accountId}",` +
384 `"details":{"Account.registeredName":"${username}", ...}}`
385 );
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500386
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400387 if (username) {
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500388 // Keep map of usernames to account IDs
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400389 this.usernamesToAccountIds.set(username, accountId);
390 }
391 });
392
393 this.events.onRegistrationStateChanged.subscribe((signal) => {
394 log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
395 });
396
397 this.events.onNameRegistrationEnded.subscribe((signal) => {
398 log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
399 });
400
401 this.events.onRegisteredNameFound.subscribe((signal) => {
402 log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
403 });
404
405 this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
406 log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
407 });
408
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500409 this.events.onIncomingAccountMessage.subscribe(<T extends WebSocketMessageType>(signal: IncomingAccountMessage) => {
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400410 log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500411
412 const message: WebSocketMessage<T> = JSON.parse(signal.payload['application/json']);
Charlie2bc0d672022-11-04 11:53:44 -0400413
414 if (message === undefined) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500415 log.warn('Undefined account message');
416 return;
417 }
418
419 if (message.type === undefined || message.data === undefined) {
420 log.warn('Account message is not a valid WebSocketMessage (missing type or data fields)');
Charlie2bc0d672022-11-04 11:53:44 -0400421 return;
422 }
423
424 if (!Object.values(WebSocketMessageType).includes(message.type)) {
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500425 log.warn(`Invalid WebSocket message type: ${message.type}`);
Charlie2bc0d672022-11-04 11:53:44 -0400426 return;
427 }
428
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500429 this.webSocketServer.send(signal.accountId, message.type, message.data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400430 });
431
Misha Krieger-Raynauldcb11bba2022-11-11 18:08:33 -0500432 this.events.onAccountMessageStatusChanged.subscribe((signal) => {
433 log.debug('Received AccountMessageStatusChanged:', JSON.stringify(signal));
434 });
435
436 this.events.onContactAdded.subscribe((signal) => {
437 log.debug('Received ContactAdded:', JSON.stringify(signal));
438 });
439
440 this.events.onContactRemoved.subscribe((signal) => {
441 log.debug('Received ContactRemoved:', JSON.stringify(signal));
442 });
443
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500444 this.events.onConversationRequestReceived.subscribe((signal) => {
445 log.debug('Received ConversationRequestReceived:', JSON.stringify(signal));
446
447 // TODO: Prompt user to accept conversation request on client
448 // Currently, we auto-accept all incoming conversation requests. In future, we
449 // need to ask the user if they accept the conversation request or not. Part of
450 // it can be done by sending a WebSocket event.
451 // See other implementations e.g. block contact / decline request / accept request.
452 this.jamiSwig.acceptConversationRequest(signal.accountId, signal.conversationId);
453 });
454
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400455 this.events.onConversationReady.subscribe((signal) => {
456 log.debug('Received ConversationReady:', JSON.stringify(signal));
457 });
458
459 this.events.onConversationRemoved.subscribe((signal) => {
460 log.debug('Received ConversationRemoved:', JSON.stringify(signal));
461 });
462
Misha Krieger-Raynauld8a381da2022-11-03 17:37:51 -0400463 this.events.onConversationLoaded.subscribe(({ id, accountId, conversationId }) => {
464 log.debug(
465 `Received ConversationLoaded: {"id":"${id}","accountId":"${accountId}",` +
466 `"conversationId":"${conversationId}","messages":[...]}`
467 );
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400468 });
469
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500470 this.events.onConversationMemberEvent.subscribe((signal) => {
471 log.debug('Received onConversationMemberEvent:', JSON.stringify(signal));
472 });
473
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400474 this.events.onMessageReceived.subscribe((signal) => {
475 log.debug('Received MessageReceived:', JSON.stringify(signal));
Michelle Sepkap Simedd82cbf2022-11-17 23:31:49 -0500476
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000477 const data: ConversationMessage = {
478 conversationId: signal.conversationId,
479 message: signal.message,
480 };
Misha Krieger-Raynauldb933fbb2022-11-15 15:11:09 -0500481 this.webSocketServer.send(signal.accountId, WebSocketMessageType.ConversationMessage, data);
Misha Krieger-Raynauld68a9b562022-10-28 19:47:46 -0400482 });
483 }
Issam E. Maghnif796a092022-10-09 20:25:26 +0000484}