blob: c5d8fbe0205e7c0b454cc3d2fee0e70377bb7d49 [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 { createRequire } from 'module';
23import path from 'path';
simonc7d52452022-09-23 02:09:42 -040024
simon07b4eb02022-09-29 17:50:26 -040025import Account from './model/Account.js';
26import Conversation from './model/Conversation.js';
27
Adrien Béraude74741b2021-04-19 13:22:54 -040028const require = createRequire(import.meta.url);
Adrien Béraud6ecaa402021-04-06 17:37:25 -040029
Adrien Béraud6ecaa402021-04-06 17:37:25 -040030class JamiDaemon {
simond47ef9e2022-09-28 22:24:28 -040031 constructor(onMessage) {
32 this.accounts = [];
33 this.lookups = [];
34 this.tempAccounts = [];
35 this.dring = require(path.join(process.cwd(), 'jamid.node'));
36 this.dring.init({
37 AccountsChanged: () => {
38 console.log('AccountsChanged');
39 const newAccounts = [];
40 JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
41 for (const account of this.accounts) {
42 if (account.getId() === accountId) {
43 newAccounts.push(account);
44 return;
45 }
46 }
47 newAccounts.push(
48 new Account(
49 accountId,
50 JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
51 JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
52 )
53 );
54 });
55 this.accounts = newAccounts;
56 },
57 AccountDetailsChanged: (accountId, details) => {
58 console.log(`AccountDetailsChanged ${accountId}`);
59 const account = this.getAccount(accountId);
60 if (!account) {
61 console.log(`Unknown account ${accountId}`);
62 return;
63 }
64 account.details = details;
65 },
66 VolatileDetailsChanged: (accountId, details) => {
67 console.log(`VolatileDetailsChanged ${accountId}`);
68 const account = this.getAccount(accountId);
69 if (!account) {
70 console.log(`Unknown account ${accountId}`);
71 return;
72 }
73 account.volatileDetails = details;
74 },
75 IncomingAccountMessage: (accountId, from, message) => {
76 console.log(`Received message: ${accountId} ${from} ${message['text/plain']}`);
77 /*
Adrien Béraud6ecaa402021-04-06 17:37:25 -040078 if (parser.validate(message["text/plain"]) === true) {
Adrien Béraude74741b2021-04-19 13:22:54 -040079 console.log(message["text/plain"])
Adrien Béraud6ecaa402021-04-06 17:37:25 -040080 } else {
81
Adrien Béraude74741b2021-04-19 13:22:54 -040082 user = connectedUsers[accountId]
Adrien Béraud6ecaa402021-04-06 17:37:25 -040083 console.log(user.socketId)
Adrien Béraude74741b2021-04-19 13:22:54 -040084 io.to(user.socketId).emit('receivedMessage', message["text/plain"])
85 //io.emit('receivedMessage', message["text/plain"])
Adrien Béraud6ecaa402021-04-06 17:37:25 -040086 }*/
simond47ef9e2022-09-28 22:24:28 -040087 },
88 RegistrationStateChanged: (accountId, state, code, detail) => {
89 console.log('RegistrationStateChanged: ' + accountId + ' ' + state + ' ' + code + ' ' + detail);
90 const account = this.getAccount(accountId);
91 if (account) {
92 account.registrationState = state;
93 } else {
94 console.log(`Unknown account ${accountId}`);
Adrien Béraud6ecaa402021-04-06 17:37:25 -040095 }
simond47ef9e2022-09-28 22:24:28 -040096 const ctx = this.tempAccounts[accountId];
97 if (ctx) {
98 if (state === 'REGISTERED') {
99 account.details = JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
100 ctx.resolve(accountId);
101 delete this.tempAccounts[accountId];
102 } else if (state === 'ERROR_AUTH') {
103 this.dring.removeAccount(accountId);
104 ctx.reject(state);
105 delete this.tempAccounts[accountId];
106 }
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400107 }
simond47ef9e2022-09-28 22:24:28 -0400108 },
109 RegisteredNameFound: (accountId, state, address, name) => {
110 console.log(`RegisteredNameFound: ${accountId} ${state} ${address} ${name}`);
111 let lookups;
112 if (accountId) {
113 const account = this.getAccount(accountId);
114 if (!account) {
115 console.log(`Unknown account ${accountId}`);
116 return;
117 }
118 if (state == 0) {
119 const contact = account.getContactFromCache(address);
120 if (!contact.isRegisteredNameResolved()) contact.setRegisteredName(name);
121 }
122 lookups = account.lookups;
123 } else {
124 lookups = this.lookups;
125 }
126 let index = lookups.length - 1;
127 while (index >= 0) {
128 const lookup = lookups[index];
129 if ((lookup.address && lookup.address === address) || (lookup.name && lookup.name === name)) {
130 lookup.resolve({ address, name, state });
131 lookups.splice(index, 1);
132 }
133 index -= 1;
134 }
135 },
136 NameRegistrationEnded: (accountId, state, name) => {
137 console.log(`NameRegistrationEnded: ${accountId} ${state} ${name}`);
138 const account = this.getAccount(accountId);
139 if (account) {
140 if (state === 0) account.volatileDetails['Account.registeredName'] = name;
141 if (account.registeringName) {
142 account.registeringName.resolve(state);
143 delete account.registeringName;
144 }
145 } else {
146 console.log(`Unknown account ${accountId}`);
147 }
148 },
149 // Conversations
150 ConversationReady: (accountId, conversationId) => {
151 console.log(`conversationReady: ${accountId} ${conversationId}`);
152 const account = this.getAccount(accountId);
Adrien Béraud150b4782021-04-21 19:40:59 -0400153 if (!account) {
simond47ef9e2022-09-28 22:24:28 -0400154 console.log(`Unknown account ${accountId}`);
155 return;
Adrien Béraud150b4782021-04-21 19:40:59 -0400156 }
simond47ef9e2022-09-28 22:24:28 -0400157 let conversation = account.getConversation(conversationId);
158 if (!conversation) {
159 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId));
160 members.forEach((member) => (member.contact = account.getContactFromCache(member.uri)));
161 conversation = new Conversation(conversationId, accountId, members);
162 account.addConversation(conversation);
163 }
164 },
165 ConversationRemoved: (accountId, conversationId) => {
166 console.log(`conversationRemoved: ${accountId} ${conversationId}`);
167 const account = this.getAccount(accountId);
168 if (!account) {
169 console.log(`Unknown account ${accountId}`);
170 return;
171 }
172 account.removeConversation(conversationId);
173 },
174 ConversationLoaded: (id, accountId, conversationId, messages) => {
175 console.log(`conversationLoaded: ${accountId} ${conversationId}`);
176 const account = this.getAccount(accountId);
177 if (!account) {
178 console.log(`Unknown account ${accountId}`);
179 return;
180 }
181 const conversation = account.getConversation(conversationId);
182 if (conversation) {
183 //conversation.addLoadedMessages(messages)
184 const request = conversation.requests[id];
185 if (request) {
186 request.resolve(messages);
187 }
188 }
189 },
190 MessageReceived: (accountId, conversationId, message) => {
191 console.log(`messageReceived: ${accountId} ${conversationId}`);
192 console.log(message);
193 const account = this.getAccount(accountId);
194 if (!account) {
195 console.log(`Unknown account ${accountId}`);
196 return;
197 }
198 const conversation = account.getConversation(conversationId);
199 if (conversation) {
200 conversation.addMessage(message);
201 if (onMessage) onMessage(account, conversation, message);
202 }
203 },
204 ConversationRequestReceived: (accountId, conversationId, request) => {
205 console.log(`conversationRequestReceived: ${accountId} ${conversationId}`);
206 const account = this.getAccount(accountId);
207 if (!account) {
208 console.log(`Unknown account ${accountId}`);
209 return;
210 }
211 },
212 ConversationMemberEvent: (accountId, conversationId, member, event) => {
213 console.log(`conversationMemberEvent: ${accountId} ${conversationId}`);
214 const account = this.getAccount(accountId);
215 if (!account) {
216 console.log(`Unknown account ${accountId}`);
217 return;
218 }
219 },
220 OnConversationError: (accountId, conversationId, code, what) => {
221 console.log(`onConversationError: ${accountId} ${conversationId}`);
222 const account = this.getAccount(accountId);
223 if (!account) {
224 console.log(`Unknown account ${accountId}`);
225 return;
226 }
227 },
228 // Calls
229 CallStateChanged: (callId, state, code) => {
230 console.log(`CallStateChanged: ${callId} ${state} ${code}`);
231 },
232 IncomingCall: (accountId, callId, peerUri) => {
233 console.log(`IncomingCall: ${accountId} ${callId} ${peerUri}`);
234 },
235 ConferenceCreated: (confId) => {
236 console.log(`ConferenceCreated: ${confId}`);
237 },
238 ConferenceChanged: (confId, state) => {
239 console.log(`ConferenceChanged: ${confId}`);
240 },
241 ConferenceRemoved: (confId) => {
242 console.log(`ConferenceRemoved: ${confId}`);
243 },
244 OnConferenceInfosUpdated: (confId, info) => {
245 console.log(`onConferenceInfosUpdated: ${confId}`);
246 },
247 });
248
249 JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
250 const account = new Account(
251 accountId,
252 JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
253 JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
254 );
255
256 account.contacts = JamiDaemon.vectMapToJs(this.dring.getContacts(accountId));
257
258 JamiDaemon.vectToJs(this.dring.getConversations(accountId)).forEach((conversationId) => {
259 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId));
260 console.log('\n\nXMEMBERS: ', members);
261 members.forEach((member) => {
262 member.contact = account.getContactFromCache(member.uri);
263 if (!member.contact.isRegisteredNameResolved()) {
264 if (!member.uri) return;
265 console.log(`lookupAddress ${accountId} ${member.uri}`, member);
266 member.contact.setRegisteredName(
267 new Promise((resolve, reject) => account.lookups.push({ address: member.uri, resolve, reject })).then(
268 (result) => {
269 if (result.state == 0) return result.name;
270 else if (result.state == 1) return undefined;
271 else return null;
272 }
273 )
274 );
275 this.dring.lookupAddress(accountId, '', member.uri);
276 }
277 });
278 const conversation = new Conversation(conversationId, accountId, members);
279 conversation.setInfos(JamiDaemon.mapToJs(this.dring.conversationInfos(accountId, conversationId)));
280 account.addConversation(conversation);
281 });
282 account.setDevices();
283
284 this.accounts.push(account);
285 });
286 }
287
288 addAccount(accountConfig) {
289 const params = this.accountDetailsToNative(accountConfig);
290 params.set('Account.type', 'RING');
291 return new Promise((resolve, reject) => {
292 const accountId = this.dring.addAccount(params);
293 this.tempAccounts[accountId] = { resolve, reject };
294 });
295 }
296
297 getDevices(accountId) {
298 return JamiDaemon.mapToJs(this.dring.getKnownRingDevices(accountId));
299 }
300
301 getAccount(accountId) {
302 for (let i = 0; i < this.accounts.length; i++) {
303 const account = this.accounts[i];
304 if (account.getId() === accountId) return account;
Adrien Béraud150b4782021-04-21 19:40:59 -0400305 }
simond47ef9e2022-09-28 22:24:28 -0400306 return undefined;
307 }
308 getAccountList() {
309 return this.accounts;
310 }
311 registerName(accountId, password, name) {
312 return new Promise((resolve, reject) => {
313 if (!name) return reject(new Error('Invalid name'));
314 const account = this.getAccount(accountId);
315 if (!account) return reject(new Error("Can't find account"));
316 if (account.registeringName) return reject(new Error('Username already being registered'));
317 if (this.dring.registerName(accountId, password, name)) {
318 account.registeringName = { name, resolve, reject };
319 }
320 });
321 }
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400322
simond47ef9e2022-09-28 22:24:28 -0400323 getConversation(accountId, conversationId) {
324 const account = this.getAccount(accountId);
325 if (account) return account.getConversation(conversationId);
326 return null;
327 }
328 getAccountDetails(accountId) {
329 return JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
330 }
331 setAccountDetails(accountId, details) {
332 this.dring.setAccountDetails(accountId, this.mapToNative(details));
333 }
334 getAudioOutputDeviceList() {
335 return JamiDaemon.vectToJs(this.dring.getAudioOutputDeviceList());
336 }
337 getVolume(deviceName) {
338 return this.dring.getVolume(deviceName);
339 }
340 setVolume(deviceName, volume) {
341 return this.dring.setVolume(deviceName, volume);
342 }
343
344 lookupName(accountId, name) {
345 const p = new Promise((resolve, reject) => {
346 if (accountId) {
347 const account = this.getAccount(accountId);
348 if (!account) {
349 reject(new Error("Can't find account"));
350 } else {
351 account.lookups.push({ name, resolve, reject });
352 }
353 } else {
354 this.lookups.push({ name, resolve, reject });
355 }
356 });
357 this.dring.lookupName(accountId || '', '', name);
358 return p;
359 }
360
361 lookupAddress(accountId, address) {
362 console.log(`lookupAddress ${accountId} ${address}`);
363 const p = new Promise((resolve, reject) => {
364 if (accountId) {
365 const account = this.getAccount(accountId);
366 if (!account) {
367 reject(new Error("Can't find account"));
368 } else {
369 account.lookups.push({ address, resolve, reject });
370 }
371 } else {
372 this.lookups.push({ address, resolve, reject });
373 }
374 });
375 this.dring.lookupAddress(accountId || '', '', address);
376 return p;
377 }
378
379 stop() {
380 this.dring.fini();
381 }
382
383 addContact(accountId, contactId) {
384 this.dring.addContact(accountId, contactId);
385 const details = JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
386 if (details.conversationId) {
387 const account = this.getAccount(accountId);
388 if (account) {
389 let conversation = account.getConversation(details.conversationId);
390 if (!conversation) {
391 const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, details.conversationId));
392 members.forEach((member) => (member.contact = account.getContactFromCache(member.uri)));
393 conversation = new Conversation(details.conversationId, accountId, members);
394 account.addConversation(conversation);
395 }
396 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400397 }
simond47ef9e2022-09-28 22:24:28 -0400398 return details;
399 }
Adrien Béraud150b4782021-04-21 19:40:59 -0400400
simond47ef9e2022-09-28 22:24:28 -0400401 removeContact(accountId, contactId) {
402 //bool ban false
403 this.dring.removeContact(accountId, contactId, false);
404 }
405
406 blockContact(accountId, contactId) {
407 //bool ban true
408 this.dring.removeContact(accountId, contactId, true);
409 }
410
411 getContactDetails(accountId, contactId) {
412 return JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
413 }
414
415 getDefaultModerators(accountId) {
416 const account = this.getAccount(accountId);
417 if (!account) {
418 console.log(`Unknown account ${accountId}`);
419 return {};
Adrien Béraud150b4782021-04-21 19:40:59 -0400420 }
simond47ef9e2022-09-28 22:24:28 -0400421 return JamiDaemon.vectToJs(this.dring.getDefaultModerators(accountId)).map((contactId) =>
422 account.getContactFromCache(contactId)
423 );
424 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400425
simond47ef9e2022-09-28 22:24:28 -0400426 addDefaultModerator(accountId, uri) {
427 this.dring.setDefaultModerator(accountId, uri, true);
428 }
Adrien Béraud5e9e19b2021-04-22 01:38:53 -0400429
simond47ef9e2022-09-28 22:24:28 -0400430 removeDefaultModerator(accountId, uri) {
431 this.dring.setDefaultModerator(accountId, uri, false);
432 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400433
simond47ef9e2022-09-28 22:24:28 -0400434 sendMessage(accountId, conversationId, message) {
435 this.dring.sendMessage(accountId, conversationId, message, '');
436 }
Adrien Béraud4e287b92021-04-24 16:15:56 -0400437
simond47ef9e2022-09-28 22:24:28 -0400438 loadMessages(accountId, conversationId, fromMessage) {
439 const account = this.getAccount(accountId);
440 if (!account) throw new Error('Unknown account');
441 const conversation = account.getConversation(conversationId);
442 if (!conversation) throw new Error(`Unknown conversation ${conversationId}`);
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400443
simond47ef9e2022-09-28 22:24:28 -0400444 return new Promise((resolve, reject) => {
445 if (!conversation.requests) conversation.requests = {};
446 const requestId = this.dring.loadConversationMessages(accountId, conversationId, fromMessage || '', 32);
447 conversation.requests[requestId] = { resolve, reject };
448 });
449 }
450
451 boolToStr(bool) {
452 return bool ? 'true' : 'false';
453 }
454
455 accountDetailsToNative(account) {
456 const params = new this.dring.StringMap();
457 if (account.managerUri) params.set('Account.managerUri', account.managerUri);
458 if (account.managerUsername) params.set('Account.managerUsername', account.managerUsername);
459 if (account.archivePassword) {
460 params.set('Account.archivePassword', account.archivePassword);
461 } /* else {
Adrien Béraud35e7d7c2021-04-13 03:28:39 -0400462 console.log("archivePassword required")
Adrien Béraude74741b2021-04-19 13:22:54 -0400463 return
Adrien Béraud88a52442021-04-26 12:11:41 -0400464 }*/
simond47ef9e2022-09-28 22:24:28 -0400465 if (account.alias) params.set('Account.alias', account.alias);
466 if (account.displayName) params.set('Account.displayName', account.displayName);
467 if (account.enable !== undefined) params.set('Account.enable', this.boolToStr(account.enable));
468 if (account.autoAnswer !== undefined) params.set('Account.autoAnswer', this.boolToStr(account.autoAnswer));
469 if (account.autoAnswer !== undefined) params.set('Account.autoAnswer', this.boolToStr(account.autoAnswer));
470 if (account.ringtonePath) params.set('Account.ringtonePath', account.ringtonePath);
471 if (account.ringtoneEnabled !== undefined)
472 params.set('Account.ringtoneEnabled', this.boolToStr(account.ringtoneEnabled));
473 if (account.videoEnabled !== undefined) params.set('Account.videoEnabled', this.boolToStr(account.videoEnabled));
474 if (account.useragent) {
475 params.set('Account.useragent', account.useragent);
476 params.set('Account.hasCustomUserAgent', 'TRUE');
477 } else {
478 params.set('Account.hasCustomUserAgent', 'FALSE');
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400479 }
simond47ef9e2022-09-28 22:24:28 -0400480 if (account.audioPortMin) params.set('Account.audioPortMin', account.audioPortMin);
481 if (account.audioPortMax) params.set('Account.audioPortMax', account.audioPortMax);
482 if (account.videoPortMin) params.set('Account.videoPortMin', account.videoPortMin);
483 if (account.videoPortMax) params.set('Account.videoPortMax', account.videoPortMax);
484 if (account.localInterface) params.set('Account.localInterface', account.localInterface);
485 if (account.publishedSameAsLocal !== undefined)
486 params.set('Account.publishedSameAsLocal', this.boolToStr(account.publishedSameAsLocal));
487 if (account.localPort) params.set('Account.localPort', account.localPort);
488 if (account.publishedPort) params.set('Account.publishedPort', account.publishedPort);
489 if (account.rendezVous !== undefined) params.set('Account.rendezVous', this.boolToStr(account.rendezVous));
490 if (account.upnpEnabled !== undefined) params.set('Account.upnpEnabled', this.boolToStr(account.upnpEnabled));
491 return params;
492 }
493 static vectToJs(vect) {
494 const len = vect.size();
495 const outputArr = new Array(len);
496 for (let i = 0; i < len; i++) outputArr[i] = vect.get(i);
497 return outputArr;
498 }
499 static mapToJs(m) {
500 const outputObj = {};
501 JamiDaemon.vectToJs(m.keys()).forEach((k) => (outputObj[k] = m.get(k)));
502 return outputObj;
503 }
504 static vectMapToJs(vectMap) {
505 const len = vectMap.size();
506 const outputArr = new Array(len);
507 for (let i = 0; i < len; i++) outputArr[i] = JamiDaemon.mapToJs(vectMap.get(i));
508 return outputArr;
509 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400510
simond47ef9e2022-09-28 22:24:28 -0400511 mapToNative(map) {
512 const ret = new this.dring.StringMap();
513 for (const [key, value] of Object.entries(map)) ret.set(key, value);
514 return ret;
515 }
Adrien Béraud6ecaa402021-04-06 17:37:25 -0400516}
517
simond47ef9e2022-09-28 22:24:28 -0400518export default JamiDaemon;