blob: 587a4777e8a3114b5f2a6626728118db4e4c62be [file] [log] [blame]
Adrien Béraud6ecaa402021-04-06 17:37:25 -04001/*
2 * Copyright (c) 2017-2021 Savoir-faire Linux Inc.
3 *
4 * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5 * Author: Asad Salman <me@asad.co>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
simond47ef9e2022-09-28 22:24:28 -040020'use strict';
Adrien Béraud947e8792021-04-15 18:32:44 -040021
simond47ef9e2022-09-28 22:24:28 -040022import Account from './model/Account.js';
23import Conversation from './model/Conversation.js';
24import { createRequire } from 'module';
25import path from 'path';
simonc7d52452022-09-23 02:09:42 -040026
Adrien Béraude74741b2021-04-19 13:22:54 -040027const require = createRequire(import.meta.url);
Adrien Béraud6ecaa402021-04-06 17:37:25 -040028
Adrien Béraud6ecaa402021-04-06 17:37:25 -040029class JamiDaemon {
simond47ef9e2022-09-28 22:24:28 -040030 constructor(onMessage) {
31 this.accounts = [];
32 this.lookups = [];
33 this.tempAccounts = [];
34 this.dring = require(path.join(process.cwd(), 'jamid.node'));
35 this.dring.init({
36 AccountsChanged: () => {
37 console.log('AccountsChanged');
38 const newAccounts = [];
39 JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
40 for (const account of this.accounts) {
41 if (account.getId() === accountId) {
42 newAccounts.push(account);
43 return;
44 }
45 }
46 newAccounts.push(
47 new Account(
48 accountId,
49 JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
50 JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
51 )
52 );
53 });
54 this.accounts = newAccounts;
55 },
56 AccountDetailsChanged: (accountId, details) => {
57 console.log(`AccountDetailsChanged ${accountId}`);
58 const account = this.getAccount(accountId);
59 if (!account) {
60 console.log(`Unknown account ${accountId}`);
61 return;
62 }
63 account.details = details;
64 },
65 VolatileDetailsChanged: (accountId, details) => {
66 console.log(`VolatileDetailsChanged ${accountId}`);
67 const account = this.getAccount(accountId);
68 if (!account) {
69 console.log(`Unknown account ${accountId}`);
70 return;
71 }
72 account.volatileDetails = details;
73 },
74 IncomingAccountMessage: (accountId, from, message) => {
75 console.log(`Received message: ${accountId} ${from} ${message['text/plain']}`);
76 /*
Adrien Béraud6ecaa402021-04-06 17:37:25 -040077 if (parser.validate(message["text/plain"]) === true) {
Adrien Béraude74741b2021-04-19 13:22:54 -040078 console.log(message["text/plain"])
Adrien Béraud6ecaa402021-04-06 17:37:25 -040079 } else {
80
Adrien Béraude74741b2021-04-19 13:22:54 -040081 user = connectedUsers[accountId]
Adrien Béraud6ecaa402021-04-06 17:37:25 -040082 console.log(user.socketId)
Adrien Béraude74741b2021-04-19 13:22:54 -040083 io.to(user.socketId).emit('receivedMessage', message["text/plain"])
84 //io.emit('receivedMessage', message["text/plain"])
Adrien Béraud6ecaa402021-04-06 17:37:25 -040085 }*/
simond47ef9e2022-09-28 22:24:28 -040086 },
87 RegistrationStateChanged: (accountId, state, code, detail) => {
88 console.log('RegistrationStateChanged: ' + accountId + ' ' + state + ' ' + code + ' ' + detail);
89 const account = this.getAccount(accountId);
90 if (account) {
91 account.registrationState = state;
92 } else {
93 console.log(`Unknown account ${accountId}`);
Adrien Béraud6ecaa402021-04-06 17:37:25 -040094 }
simond47ef9e2022-09-28 22:24:28 -040095 const ctx = this.tempAccounts[accountId];
96 if (ctx) {
97 if (state === 'REGISTERED') {
98 account.details = JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
99 ctx.resolve(accountId);
100 delete this.tempAccounts[accountId];
101 } else if (state === 'ERROR_AUTH') {
102 this.dring.removeAccount(accountId);
103 ctx.reject(state);
104 delete this.tempAccounts[accountId];
105 }
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400106 }
simond47ef9e2022-09-28 22:24:28 -0400107 },
108 RegisteredNameFound: (accountId, state, address, name) => {
109 console.log(`RegisteredNameFound: ${accountId} ${state} ${address} ${name}`);
110 let lookups;
111 if (accountId) {
112 const account = this.getAccount(accountId);
113 if (!account) {
114 console.log(`Unknown account ${accountId}`);
115 return;
116 }
117 if (state == 0) {
118 const contact = account.getContactFromCache(address);
119 if (!contact.isRegisteredNameResolved()) contact.setRegisteredName(name);
120 }
121 lookups = account.lookups;
122 } else {
123 lookups = this.lookups;
124 }
125 let index = lookups.length - 1;
126 while (index >= 0) {
127 const lookup = lookups[index];
128 if ((lookup.address && lookup.address === address) || (lookup.name && lookup.name === name)) {
129 lookup.resolve({ address, name, state });
130 lookups.splice(index, 1);
131 }
132 index -= 1;
133 }
134 },
135 NameRegistrationEnded: (accountId, state, name) => {
136 console.log(`NameRegistrationEnded: ${accountId} ${state} ${name}`);
137 const account = this.getAccount(accountId);
138 if (account) {
139 if (state === 0) account.volatileDetails['Account.registeredName'] = name;
140 if (account.registeringName) {
141 account.registeringName.resolve(state);
142 delete account.registeringName;
143 }
144 } else {
145 console.log(`Unknown account ${accountId}`);
146 }
147 },
148 // Conversations
149 ConversationReady: (accountId, conversationId) => {
150 console.log(`conversationReady: ${accountId} ${conversationId}`);
151 const account = this.getAccount(accountId);
Adrien Béraud150b4782021-04-21 19:40:59 -0400152 if (!account) {
simond47ef9e2022-09-28 22:24:28 -0400153 console.log(`Unknown account ${accountId}`);
154 return;
Adrien Béraud150b4782021-04-21 19:40:59 -0400155 }
simond47ef9e2022-09-28 22:24:28 -0400156 let conversation = account.getConversation(conversationId);
157 if (!conversation) {
158 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId));
159 members.forEach((member) => (member.contact = account.getContactFromCache(member.uri)));
160 conversation = new Conversation(conversationId, accountId, members);
161 account.addConversation(conversation);
162 }
163 },
164 ConversationRemoved: (accountId, conversationId) => {
165 console.log(`conversationRemoved: ${accountId} ${conversationId}`);
166 const account = this.getAccount(accountId);
167 if (!account) {
168 console.log(`Unknown account ${accountId}`);
169 return;
170 }
171 account.removeConversation(conversationId);
172 },
173 ConversationLoaded: (id, accountId, conversationId, messages) => {
174 console.log(`conversationLoaded: ${accountId} ${conversationId}`);
175 const account = this.getAccount(accountId);
176 if (!account) {
177 console.log(`Unknown account ${accountId}`);
178 return;
179 }
180 const conversation = account.getConversation(conversationId);
181 if (conversation) {
182 //conversation.addLoadedMessages(messages)
183 const request = conversation.requests[id];
184 if (request) {
185 request.resolve(messages);
186 }
187 }
188 },
189 MessageReceived: (accountId, conversationId, message) => {
190 console.log(`messageReceived: ${accountId} ${conversationId}`);
191 console.log(message);
192 const account = this.getAccount(accountId);
193 if (!account) {
194 console.log(`Unknown account ${accountId}`);
195 return;
196 }
197 const conversation = account.getConversation(conversationId);
198 if (conversation) {
199 conversation.addMessage(message);
200 if (onMessage) onMessage(account, conversation, message);
201 }
202 },
203 ConversationRequestReceived: (accountId, conversationId, request) => {
204 console.log(`conversationRequestReceived: ${accountId} ${conversationId}`);
205 const account = this.getAccount(accountId);
206 if (!account) {
207 console.log(`Unknown account ${accountId}`);
208 return;
209 }
210 },
211 ConversationMemberEvent: (accountId, conversationId, member, event) => {
212 console.log(`conversationMemberEvent: ${accountId} ${conversationId}`);
213 const account = this.getAccount(accountId);
214 if (!account) {
215 console.log(`Unknown account ${accountId}`);
216 return;
217 }
218 },
219 OnConversationError: (accountId, conversationId, code, what) => {
220 console.log(`onConversationError: ${accountId} ${conversationId}`);
221 const account = this.getAccount(accountId);
222 if (!account) {
223 console.log(`Unknown account ${accountId}`);
224 return;
225 }
226 },
227 // Calls
228 CallStateChanged: (callId, state, code) => {
229 console.log(`CallStateChanged: ${callId} ${state} ${code}`);
230 },
231 IncomingCall: (accountId, callId, peerUri) => {
232 console.log(`IncomingCall: ${accountId} ${callId} ${peerUri}`);
233 },
234 ConferenceCreated: (confId) => {
235 console.log(`ConferenceCreated: ${confId}`);
236 },
237 ConferenceChanged: (confId, state) => {
238 console.log(`ConferenceChanged: ${confId}`);
239 },
240 ConferenceRemoved: (confId) => {
241 console.log(`ConferenceRemoved: ${confId}`);
242 },
243 OnConferenceInfosUpdated: (confId, info) => {
244 console.log(`onConferenceInfosUpdated: ${confId}`);
245 },
246 });
247
248 JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
249 const account = new Account(
250 accountId,
251 JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
252 JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
253 );
254
255 account.contacts = JamiDaemon.vectMapToJs(this.dring.getContacts(accountId));
256
257 JamiDaemon.vectToJs(this.dring.getConversations(accountId)).forEach((conversationId) => {
258 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId));
259 console.log('\n\nXMEMBERS: ', members);
260 members.forEach((member) => {
261 member.contact = account.getContactFromCache(member.uri);
262 if (!member.contact.isRegisteredNameResolved()) {
263 if (!member.uri) return;
264 console.log(`lookupAddress ${accountId} ${member.uri}`, member);
265 member.contact.setRegisteredName(
266 new Promise((resolve, reject) => account.lookups.push({ address: member.uri, resolve, reject })).then(
267 (result) => {
268 if (result.state == 0) return result.name;
269 else if (result.state == 1) return undefined;
270 else return null;
271 }
272 )
273 );
274 this.dring.lookupAddress(accountId, '', member.uri);
275 }
276 });
277 const conversation = new Conversation(conversationId, accountId, members);
278 conversation.setInfos(JamiDaemon.mapToJs(this.dring.conversationInfos(accountId, conversationId)));
279 account.addConversation(conversation);
280 });
281 account.setDevices();
282
283 this.accounts.push(account);
284 });
285 }
286
287 addAccount(accountConfig) {
288 const params = this.accountDetailsToNative(accountConfig);
289 params.set('Account.type', 'RING');
290 return new Promise((resolve, reject) => {
291 const accountId = this.dring.addAccount(params);
292 this.tempAccounts[accountId] = { resolve, reject };
293 });
294 }
295
296 getDevices(accountId) {
297 return JamiDaemon.mapToJs(this.dring.getKnownRingDevices(accountId));
298 }
299
300 getAccount(accountId) {
301 for (let i = 0; i < this.accounts.length; i++) {
302 const account = this.accounts[i];
303 if (account.getId() === accountId) return account;
Adrien Béraud150b4782021-04-21 19:40:59 -0400304 }
simond47ef9e2022-09-28 22:24:28 -0400305 return undefined;
306 }
307 getAccountList() {
308 return this.accounts;
309 }
310 registerName(accountId, password, name) {
311 return new Promise((resolve, reject) => {
312 if (!name) return reject(new Error('Invalid name'));
313 const account = this.getAccount(accountId);
314 if (!account) return reject(new Error("Can't find account"));
315 if (account.registeringName) return reject(new Error('Username already being registered'));
316 if (this.dring.registerName(accountId, password, name)) {
317 account.registeringName = { name, resolve, reject };
318 }
319 });
320 }
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400321
simond47ef9e2022-09-28 22:24:28 -0400322 getConversation(accountId, conversationId) {
323 const account = this.getAccount(accountId);
324 if (account) return account.getConversation(conversationId);
325 return null;
326 }
327 getAccountDetails(accountId) {
328 return JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
329 }
330 setAccountDetails(accountId, details) {
331 this.dring.setAccountDetails(accountId, this.mapToNative(details));
332 }
333 getAudioOutputDeviceList() {
334 return JamiDaemon.vectToJs(this.dring.getAudioOutputDeviceList());
335 }
336 getVolume(deviceName) {
337 return this.dring.getVolume(deviceName);
338 }
339 setVolume(deviceName, volume) {
340 return this.dring.setVolume(deviceName, volume);
341 }
342
343 lookupName(accountId, name) {
344 const p = new Promise((resolve, reject) => {
345 if (accountId) {
346 const account = this.getAccount(accountId);
347 if (!account) {
348 reject(new Error("Can't find account"));
349 } else {
350 account.lookups.push({ name, resolve, reject });
351 }
352 } else {
353 this.lookups.push({ name, resolve, reject });
354 }
355 });
356 this.dring.lookupName(accountId || '', '', name);
357 return p;
358 }
359
360 lookupAddress(accountId, address) {
361 console.log(`lookupAddress ${accountId} ${address}`);
362 const p = new Promise((resolve, reject) => {
363 if (accountId) {
364 const account = this.getAccount(accountId);
365 if (!account) {
366 reject(new Error("Can't find account"));
367 } else {
368 account.lookups.push({ address, resolve, reject });
369 }
370 } else {
371 this.lookups.push({ address, resolve, reject });
372 }
373 });
374 this.dring.lookupAddress(accountId || '', '', address);
375 return p;
376 }
377
378 stop() {
379 this.dring.fini();
380 }
381
382 addContact(accountId, contactId) {
383 this.dring.addContact(accountId, contactId);
384 const details = JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
385 if (details.conversationId) {
386 const account = this.getAccount(accountId);
387 if (account) {
388 let conversation = account.getConversation(details.conversationId);
389 if (!conversation) {
390 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, details.conversationId));
391 members.forEach((member) => (member.contact = account.getContactFromCache(member.uri)));
392 conversation = new Conversation(details.conversationId, accountId, members);
393 account.addConversation(conversation);
394 }
395 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400396 }
simond47ef9e2022-09-28 22:24:28 -0400397 return details;
398 }
Adrien Béraud150b4782021-04-21 19:40:59 -0400399
simond47ef9e2022-09-28 22:24:28 -0400400 removeContact(accountId, contactId) {
401 //bool ban false
402 this.dring.removeContact(accountId, contactId, false);
403 }
404
405 blockContact(accountId, contactId) {
406 //bool ban true
407 this.dring.removeContact(accountId, contactId, true);
408 }
409
410 getContactDetails(accountId, contactId) {
411 return JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
412 }
413
414 getDefaultModerators(accountId) {
415 const account = this.getAccount(accountId);
416 if (!account) {
417 console.log(`Unknown account ${accountId}`);
418 return {};
Adrien Béraud150b4782021-04-21 19:40:59 -0400419 }
simond47ef9e2022-09-28 22:24:28 -0400420 return JamiDaemon.vectToJs(this.dring.getDefaultModerators(accountId)).map((contactId) =>
421 account.getContactFromCache(contactId)
422 );
423 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400424
simond47ef9e2022-09-28 22:24:28 -0400425 addDefaultModerator(accountId, uri) {
426 this.dring.setDefaultModerator(accountId, uri, true);
427 }
Adrien Béraud5e9e19b2021-04-22 01:38:53 -0400428
simond47ef9e2022-09-28 22:24:28 -0400429 removeDefaultModerator(accountId, uri) {
430 this.dring.setDefaultModerator(accountId, uri, false);
431 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400432
simond47ef9e2022-09-28 22:24:28 -0400433 sendMessage(accountId, conversationId, message) {
434 this.dring.sendMessage(accountId, conversationId, message, '');
435 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400436
simond47ef9e2022-09-28 22:24:28 -0400437 loadMessages(accountId, conversationId, fromMessage) {
438 const account = this.getAccount(accountId);
439 if (!account) throw new Error('Unknown account');
440 const conversation = account.getConversation(conversationId);
441 if (!conversation) throw new Error(`Unknown conversation ${conversationId}`);
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400442
simond47ef9e2022-09-28 22:24:28 -0400443 return new Promise((resolve, reject) => {
444 if (!conversation.requests) conversation.requests = {};
445 const requestId = this.dring.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
446 conversation.requests[requestId] = { resolve, reject };
447 });
448 }
449
450 boolToStr(bool) {
451 return bool ? 'true' : 'false';
452 }
453
454 accountDetailsToNative(account) {
455 const params = new this.dring.StringMap();
456 if (account.managerUri) params.set('Account.managerUri', account.managerUri);
457 if (account.managerUsername) params.set('Account.managerUsername', account.managerUsername);
458 if (account.archivePassword) {
459 params.set('Account.archivePassword', account.archivePassword);
460 } /* else {
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400461 console.log("archivePassword required")
Adrien Béraude74741b2021-04-19 13:22:54 -0400462 return
Adrien Béraud88a52442021-04-26 12:11:41 -0400463 }*/
simond47ef9e2022-09-28 22:24:28 -0400464 if (account.alias) params.set('Account.alias', account.alias);
465 if (account.displayName) params.set('Account.displayName', account.displayName);
466 if (account.enable !== undefined) params.set('Account.enable', this.boolToStr(account.enable));
467 if (account.autoAnswer !== undefined) params.set('Account.autoAnswer', this.boolToStr(account.autoAnswer));
468 if (account.autoAnswer !== undefined) params.set('Account.autoAnswer', this.boolToStr(account.autoAnswer));
469 if (account.ringtonePath) params.set('Account.ringtonePath', account.ringtonePath);
470 if (account.ringtoneEnabled !== undefined)
471 params.set('Account.ringtoneEnabled', this.boolToStr(account.ringtoneEnabled));
472 if (account.videoEnabled !== undefined) params.set('Account.videoEnabled', this.boolToStr(account.videoEnabled));
473 if (account.useragent) {
474 params.set('Account.useragent', account.useragent);
475 params.set('Account.hasCustomUserAgent', 'TRUE');
476 } else {
477 params.set('Account.hasCustomUserAgent', 'FALSE');
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400478 }
simond47ef9e2022-09-28 22:24:28 -0400479 if (account.audioPortMin) params.set('Account.audioPortMin', account.audioPortMin);
480 if (account.audioPortMax) params.set('Account.audioPortMax', account.audioPortMax);
481 if (account.videoPortMin) params.set('Account.videoPortMin', account.videoPortMin);
482 if (account.videoPortMax) params.set('Account.videoPortMax', account.videoPortMax);
483 if (account.localInterface) params.set('Account.localInterface', account.localInterface);
484 if (account.publishedSameAsLocal !== undefined)
485 params.set('Account.publishedSameAsLocal', this.boolToStr(account.publishedSameAsLocal));
486 if (account.localPort) params.set('Account.localPort', account.localPort);
487 if (account.publishedPort) params.set('Account.publishedPort', account.publishedPort);
488 if (account.rendezVous !== undefined) params.set('Account.rendezVous', this.boolToStr(account.rendezVous));
489 if (account.upnpEnabled !== undefined) params.set('Account.upnpEnabled', this.boolToStr(account.upnpEnabled));
490 return params;
491 }
492 static vectToJs(vect) {
493 const len = vect.size();
494 const outputArr = new Array(len);
495 for (let i = 0; i < len; i++) outputArr[i] = vect.get(i);
496 return outputArr;
497 }
498 static mapToJs(m) {
499 const outputObj = {};
500 JamiDaemon.vectToJs(m.keys()).forEach((k) => (outputObj[k] = m.get(k)));
501 return outputObj;
502 }
503 static vectMapToJs(vectMap) {
504 const len = vectMap.size();
505 const outputArr = new Array(len);
506 for (let i = 0; i < len; i++) outputArr[i] = JamiDaemon.mapToJs(vectMap.get(i));
507 return outputArr;
508 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400509
simond47ef9e2022-09-28 22:24:28 -0400510 mapToNative(map) {
511 const ret = new this.dring.StringMap();
512 for (const [key, value] of Object.entries(map)) ret.set(key, value);
513 return ret;
514 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400515}
516
simond47ef9e2022-09-28 22:24:28 -0400517export default JamiDaemon;