Handle remaining important daemon signals
Changes:
- Create new Jami signal interfaces based on SWIG interface files
- Add handlers for signals which are currently used in the old server
- Add explanatory comments
- Remove unneeded methods from JamiSwig
Change-Id: Iecd7284a833238f790b8ee6f1987bb36eee140c8
diff --git a/server/src/jamid/jami-signal-interfaces.ts b/server/src/jamid/jami-signal-interfaces.ts
index f868150..189cb53 100644
--- a/server/src/jamid/jami-signal-interfaces.ts
+++ b/server/src/jamid/jami-signal-interfaces.ts
@@ -15,9 +15,20 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
+import { AccountDetails, VolatileDetails } from 'jami-web-common';
+
+// These interfaces are used to hold all the parameters for signal handlers
+// These parameters' names and types can be found in daemon/bin/nodejs/callback.h
+// or in their relevant SWIG interface files (.i) in daemon/bin/nodejs
+
+export interface AccountDetailsChanged {
+ accountId: string;
+ details: AccountDetails;
+}
+
export interface VolatileDetailsChanged {
accountId: string;
- details: Map<string, string>;
+ details: VolatileDetails;
}
export interface RegistrationStateChanged {
@@ -40,8 +51,36 @@
username: string;
}
+export interface KnownDevicesChanged {
+ accountId: string;
+ devices: Record<string, string>;
+}
+
export interface IncomingAccountMessage {
accountId: string;
from: string;
message: Record<string, string>;
}
+
+export interface ConversationReady {
+ accountId: string;
+ conversationId: string;
+}
+
+export interface ConversationRemoved {
+ accountId: string;
+ conversationId: string;
+}
+
+export interface ConversationLoaded {
+ id: number;
+ accountId: string;
+ conversationId: string;
+ messages: Record<string, string>[];
+}
+
+export interface MessageReceived {
+ accountId: string;
+ conversationId: string;
+ message: Record<string, string>;
+}
diff --git a/server/src/jamid/jami-signal.ts b/server/src/jamid/jami-signal.ts
index f76127b..9455fcf 100644
--- a/server/src/jamid/jami-signal.ts
+++ b/server/src/jamid/jami-signal.ts
@@ -15,8 +15,13 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
+/**
+ * Signals emitted by the Jami daemon.
+ *
+ * The definition of signals can be found in `daemon/bin/nodejs/callback.h`.
+ */
export enum JamiSignal {
- // using DRing::ConfigurationSignal;
+ // libjami::ConfigurationSignal
AccountsChanged = 'AccountsChanged',
AccountDetailsChanged = 'AccountDetailsChanged',
RegistrationStateChanged = 'RegistrationStateChanged',
@@ -30,14 +35,14 @@
IncomingAccountMessage = 'IncomingAccountMessage',
AccountMessageStatusChanged = 'AccountMessageStatusChanged',
- // using DRing::CallSignal;
+ // libjami::CallSignal
StateChange = 'StateChange',
IncomingMessage = 'IncomingMessage',
IncomingCall = 'IncomingCall',
IncomingCallWithMedia = 'IncomingCallWithMedia',
MediaChangeRequested = 'MediaChangeRequested',
- // using DRing::ConversationSignal;
+ // libjami::ConversationSignal
ConversationLoaded = 'ConversationLoaded',
MessagesFound = 'MessagesFound',
MessageReceived = 'MessageReceived',
diff --git a/server/src/jamid/jami-swig.ts b/server/src/jamid/jami-swig.ts
index 26ac694..b072328 100644
--- a/server/src/jamid/jami-swig.ts
+++ b/server/src/jamid/jami-swig.ts
@@ -53,6 +53,11 @@
export const stringMapToMap = (sm: StringMap) => itToMap(swigMapToIt(sm));
// export const vectMapToArrayMap = (vm: VectMap) => itToArr(itMap(swigVecToIt(vm), stringMapToMap));
+/**
+ * Non-exhaustive list of properties for JamiSwig.
+ *
+ * The full list of methods can be found in SWIG interface files (`.i`) in `daemon/bin/nodejs`.
+ */
export interface JamiSwig {
init(args: Record<string, unknown>): void;
fini(): void;
@@ -75,11 +80,6 @@
getKnownRingDevices(accountId: string): StringMap;
- getAudioOutputDeviceList(): StringVect;
-
- getVolume(device: string): number;
- setVolume(device: string, value: number): void;
-
addContact(accountId: string, contactId: string): void;
removeContact(accountId: string, contactId: string, ban: boolean): void;
getContacts(accountId: string): VectMap;
diff --git a/server/src/jamid/jamid.ts b/server/src/jamid/jamid.ts
index ab1963f..8676875 100644
--- a/server/src/jamid/jamid.ts
+++ b/server/src/jamid/jamid.ts
@@ -22,7 +22,13 @@
import { JamiSignal } from './jami-signal.js';
import {
+ AccountDetailsChanged,
+ ConversationLoaded,
+ ConversationReady,
+ ConversationRemoved,
IncomingAccountMessage,
+ KnownDevicesChanged,
+ MessageReceived,
NameRegistrationEnded,
RegisteredNameFound,
RegistrationStateChanged,
@@ -31,6 +37,8 @@
import { JamiSwig, StringMap, stringMapToRecord, stringVectToArray } from './jami-swig.js';
import { require } from './utils.js';
+// TODO: Mechanism to map account IDs to a list of WebSockets
+
@Service()
export class Jamid {
private readonly jamiSwig: JamiSwig;
@@ -41,15 +49,30 @@
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.jamiSwig = require('../../jamid.node') as JamiSwig; // TODO: we should put the path in the .env
+ this.usernamesToAccountIds = new Map<string, string>();
+
+ // Setup signal handlers
const handlers: Record<string, unknown> = {};
- const handler = (sig: string) => {
- return (...args: unknown[]) => log.warn('Unhandled', sig, args);
+
+ // Add default handler for all signals
+ const createDefaultHandler = (signal: string) => {
+ return (...args: unknown[]) => log.warn('Unhandled', signal, args);
};
- Object.keys(JamiSignal).forEach((sig) => (handlers[sig] = handler(sig)));
+ for (const signal in JamiSignal) {
+ handlers[signal] = createDefaultHandler(signal);
+ }
+
+ // Overwrite handlers for handled signals using RxJS Subjects, converting multiple arguments to objects
+ const onAccountsChanged = new Subject<void>();
+ handlers.AccountsChanged = () => onAccountsChanged.next();
+
+ const onAccountDetailsChanged = new Subject<AccountDetailsChanged>();
+ handlers.AccountDetailsChanged = (accountId: string, details: AccountDetails) =>
+ onAccountDetailsChanged.next({ accountId, details });
const onVolatileDetailsChanged = new Subject<VolatileDetailsChanged>();
- handlers.VolatileDetailsChanged = (accountId: string, details: Record<string, string>) =>
- onVolatileDetailsChanged.next({ accountId, details: new Map(Object.entries(details)) });
+ handlers.VolatileDetailsChanged = (accountId: string, details: VolatileDetails) =>
+ onVolatileDetailsChanged.next({ accountId, details });
const onRegistrationStateChanged = new Subject<RegistrationStateChanged>();
handlers.RegistrationStateChanged = (accountId: string, state: string, code: number, details: string) =>
@@ -63,42 +86,56 @@
handlers.RegisteredNameFound = (accountId: string, state: number, address: string, username: string) =>
onRegisteredNameFound.next({ accountId, state, address, username });
+ const onKnownDevicesChanged = new Subject<KnownDevicesChanged>();
+ handlers.KnownDevicesChanged = (accountId: string, devices: Record<string, string>) =>
+ onKnownDevicesChanged.next({ accountId, devices });
+
const onIncomingAccountMessage = new Subject<IncomingAccountMessage>();
handlers.IncomingAccountMessage = (accountId: string, from: string, message: Record<string, string>) =>
onIncomingAccountMessage.next({ accountId, from, message });
+ const onConversationReady = new Subject<ConversationReady>();
+ handlers.ConversationReady = (accountId: string, conversationId: string) =>
+ onConversationReady.next({ accountId, conversationId });
+
+ const onConversationRemoved = new Subject<ConversationRemoved>();
+ handlers.ConversationRemoved = (accountId: string, conversationId: string) =>
+ onConversationRemoved.next({ accountId, conversationId });
+
+ const onConversationLoaded = new Subject<ConversationLoaded>();
+ handlers.ConversationLoaded = (
+ id: number,
+ accountId: string,
+ conversationId: string,
+ messages: Record<string, string>[]
+ ) => onConversationLoaded.next({ id, accountId, conversationId, messages });
+
+ const onMessageReceived = new Subject<MessageReceived>();
+ handlers.MessageReceived = (accountId: string, conversationId: string, message: Record<string, string>) =>
+ onMessageReceived.next({ accountId, conversationId, message });
+
+ // Expose all signals in an events object to allow other handlers to subscribe after jamiSwig.init()
this.events = {
+ onAccountsChanged: onAccountsChanged.asObservable(),
+ onAccountDetailsChanged: onAccountDetailsChanged.asObservable(),
onVolatileDetailsChanged: onVolatileDetailsChanged.asObservable(),
onRegistrationStateChanged: onRegistrationStateChanged.asObservable(),
onNameRegistrationEnded: onNameRegistrationEnded.asObservable(),
onRegisteredNameFound: onRegisteredNameFound.asObservable(),
+ onKnownDevicesChanged: onKnownDevicesChanged.asObservable(),
onIncomingAccountMessage: onIncomingAccountMessage.asObservable(),
+ onConversationReady: onConversationReady.asObservable(),
+ onConversationRemoved: onConversationRemoved.asObservable(),
+ onConversationLoaded: onConversationLoaded.asObservable(),
+ onMessageReceived: onMessageReceived.asObservable(),
};
- this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
- log.debug('[1] Received onVolatileDetailsChanged with', { accountId, details });
- // Keep map of usernames to account IDs as Jamid cannot do this by itself (AFAIK)
- const username = details.get('Account.registeredName');
- if (username) {
- this.usernamesToAccountIds.set(username, accountId);
- }
- });
- this.events.onRegistrationStateChanged.subscribe((ctx) =>
- log.debug('[1] Received onRegistrationStateChanged with', ctx)
- );
- this.events.onNameRegistrationEnded.subscribe((ctx) => log.debug('[1] Received onNameRegistrationEnded with', ctx));
- this.events.onRegisteredNameFound.subscribe((ctx) => log.debug('[1] Received onRegisteredNameFound with', ctx));
- this.events.onIncomingAccountMessage.subscribe((ctx) =>
- log.debug('[1] Received onIncomingAccountMessage with', ctx)
- );
+ this.setupSignalHandlers();
- this.usernamesToAccountIds = new Map<string, string>();
-
- // 1. You cannot change event handlers after init
+ // RxJS Subjects are used as signal handlers for the following reasons:
+ // 1. You cannot change event handlers after calling jamiSwig.init()
// 2. You cannot specify multiple handlers for the same event
// 3. You cannot specify a default handler
- // So we rely on Subject() instead of Observable()
- // Also, handlers receive multiple argument instead of tuple or object!
this.jamiSwig.init(handlers);
}
@@ -202,4 +239,60 @@
getAccountIdFromUsername(username: string): string | undefined {
return this.usernamesToAccountIds.get(username);
}
+
+ private setupSignalHandlers() {
+ this.events.onAccountsChanged.subscribe(() => {
+ log.debug('Received AccountsChanged');
+ });
+
+ this.events.onAccountDetailsChanged.subscribe((signal) => {
+ log.debug('Received AccountsDetailsChanged', JSON.stringify(signal));
+ });
+
+ this.events.onVolatileDetailsChanged.subscribe(({ accountId, details }) => {
+ log.debug(`Received VolatileDetailsChanged: {"accountId":"${accountId}", ...}`);
+ // Keep map of usernames to account IDs
+ const username = details['Account.registeredName'];
+ if (username) {
+ this.usernamesToAccountIds.set(username, accountId);
+ }
+ });
+
+ this.events.onRegistrationStateChanged.subscribe((signal) => {
+ log.debug('Received RegistrationStateChanged:', JSON.stringify(signal));
+ });
+
+ this.events.onNameRegistrationEnded.subscribe((signal) => {
+ log.debug('Received NameRegistrationEnded:', JSON.stringify(signal));
+ });
+
+ this.events.onRegisteredNameFound.subscribe((signal) => {
+ log.debug('Received RegisteredNameFound:', JSON.stringify(signal));
+ });
+
+ this.events.onKnownDevicesChanged.subscribe(({ accountId }) => {
+ log.debug(`Received KnownDevicesChanged: {"accountId":"${accountId}", ...}`);
+ });
+
+ this.events.onIncomingAccountMessage.subscribe((signal) => {
+ log.debug('Received IncomingAccountMessage:', JSON.stringify(signal));
+ });
+
+ this.events.onConversationReady.subscribe((signal) => {
+ log.debug('Received ConversationReady:', JSON.stringify(signal));
+ });
+
+ this.events.onConversationRemoved.subscribe((signal) => {
+ log.debug('Received ConversationRemoved:', JSON.stringify(signal));
+ });
+
+ this.events.onConversationLoaded.subscribe((signal) => {
+ log.debug('Received ConversationLoaded:', JSON.stringify(signal));
+ });
+
+ this.events.onMessageReceived.subscribe((signal) => {
+ log.debug('Received MessageReceived:', JSON.stringify(signal));
+ // TODO: Send message to client using WebSocket
+ });
+ }
}
diff --git a/server/src/routers/auth-router.ts b/server/src/routers/auth-router.ts
index 14559fe..d0fc6f0 100644
--- a/server/src/routers/auth-router.ts
+++ b/server/src/routers/auth-router.ts
@@ -21,7 +21,6 @@
import { ParamsDictionary, Request } from 'express-serve-static-core';
import { HttpStatusCode } from 'jami-web-common';
import { SignJWT } from 'jose';
-import log from 'loglevel';
import { Container } from 'typedi';
import { Creds } from '../creds.js';
@@ -104,8 +103,6 @@
return;
}
- log.debug(jamid.getAccountDetails(accountId));
-
const isPasswordVerified = await argon2.verify(hashedPassword, password);
if (!isPasswordVerified) {
res.sendStatus(HttpStatusCode.Unauthorized);