Convert js files in `model/` to Typescript
Convert `JamiDaemon.js` to Typescript
Add `model/util.ts` containing some utility types.
Gitlab: #30
Change-Id: Ia5d120330011e89a0be28732965ae9814ab68d19
diff --git a/JamiDaemon.js b/JamiDaemon.ts
similarity index 78%
rename from JamiDaemon.js
rename to JamiDaemon.ts
index 44211d1..d40cc63 100755
--- a/JamiDaemon.js
+++ b/JamiDaemon.ts
@@ -22,21 +22,29 @@
import { createRequire } from 'module';
import path from 'path';
-import Account from './model/Account.js';
-import Conversation from './model/Conversation.js';
+import Account, { RegistrationState } from './model/Account';
+import AccountDetails, { AccountConfig, VolatileDetails } from './model/AccountDetails';
+import Contact from './model/Contact';
+import Conversation, { Message } from './model/Conversation';
+import { Lookup, LookupResolveValue, PromiseExecutor } from './model/util';
const require = createRequire(import.meta.url);
class JamiDaemon {
- constructor(onMessage) {
+ private accounts: Account[];
+ private readonly lookups: Lookup[];
+ private readonly tempAccounts: Record<string, PromiseExecutor<string>>;
+ private dring: Record<string, any>;
+
+ constructor(onMessage: (account: Account, conversation: Conversation, message: Message) => void) {
this.accounts = [];
this.lookups = [];
- this.tempAccounts = [];
+ this.tempAccounts = {};
this.dring = require(path.join(process.cwd(), 'jamid.node'));
this.dring.init({
AccountsChanged: () => {
console.log('AccountsChanged');
- const newAccounts = [];
+ const newAccounts: Account[] = [];
JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
for (const account of this.accounts) {
if (account.getId() === accountId) {
@@ -47,14 +55,14 @@
newAccounts.push(
new Account(
accountId,
- JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
- JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
+ JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)) as AccountDetails,
+ JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId)) as VolatileDetails
)
);
});
this.accounts = newAccounts;
},
- AccountDetailsChanged: (accountId, details) => {
+ AccountDetailsChanged: (accountId: string, details: AccountDetails) => {
console.log(`AccountDetailsChanged ${accountId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -63,7 +71,7 @@
}
account.details = details;
},
- VolatileDetailsChanged: (accountId, details) => {
+ VolatileDetailsChanged: (accountId: string, details: VolatileDetails) => {
console.log(`VolatileDetailsChanged ${accountId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -72,7 +80,7 @@
}
account.volatileDetails = details;
},
- IncomingAccountMessage: (accountId, from, message) => {
+ IncomingAccountMessage: (accountId: string, from: Account, message: Message) => {
console.log(`Received message: ${accountId} ${from} ${message['text/plain']}`);
/*
if (parser.validate(message["text/plain"]) === true) {
@@ -85,18 +93,19 @@
//io.emit('receivedMessage', message["text/plain"])
}*/
},
- RegistrationStateChanged: (accountId, state, code, detail) => {
+ RegistrationStateChanged: (accountId: string, state: RegistrationState, code: number, detail: string) => {
console.log('RegistrationStateChanged: ' + accountId + ' ' + state + ' ' + code + ' ' + detail);
- const account = this.getAccount(accountId);
+ const account: Account | undefined = this.getAccount(accountId);
if (account) {
account.registrationState = state;
} else {
console.log(`Unknown account ${accountId}`);
+ return;
}
const ctx = this.tempAccounts[accountId];
if (ctx) {
if (state === 'REGISTERED') {
- account.details = JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
+ account.details = JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)) as AccountDetails;
ctx.resolve(accountId);
delete this.tempAccounts[accountId];
} else if (state === 'ERROR_AUTH') {
@@ -106,7 +115,7 @@
}
}
},
- RegisteredNameFound: (accountId, state, address, name) => {
+ RegisteredNameFound: (accountId: string, state: number, address: string, name: string) => {
console.log(`RegisteredNameFound: ${accountId} ${state} ${address} ${name}`);
let lookups;
if (accountId) {
@@ -133,7 +142,7 @@
index -= 1;
}
},
- NameRegistrationEnded: (accountId, state, name) => {
+ NameRegistrationEnded: (accountId: string, state: number, name: string) => {
console.log(`NameRegistrationEnded: ${accountId} ${state} ${name}`);
const account = this.getAccount(accountId);
if (account) {
@@ -147,7 +156,7 @@
}
},
// Conversations
- ConversationReady: (accountId, conversationId) => {
+ ConversationReady: (accountId: string, conversationId: string) => {
console.log(`conversationReady: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -162,7 +171,7 @@
account.addConversation(conversation);
}
},
- ConversationRemoved: (accountId, conversationId) => {
+ ConversationRemoved: (accountId: string, conversationId: string) => {
console.log(`conversationRemoved: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -171,7 +180,7 @@
}
account.removeConversation(conversationId);
},
- ConversationLoaded: (id, accountId, conversationId, messages) => {
+ ConversationLoaded: (id: number, accountId: string, conversationId: string, messages: Message[]) => {
console.log(`conversationLoaded: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -187,7 +196,7 @@
}
}
},
- MessageReceived: (accountId, conversationId, message) => {
+ MessageReceived: (accountId: string, conversationId: string, message: Message) => {
console.log(`messageReceived: ${accountId} ${conversationId}`);
console.log(message);
const account = this.getAccount(accountId);
@@ -201,7 +210,7 @@
if (onMessage) onMessage(account, conversation, message);
}
},
- ConversationRequestReceived: (accountId, conversationId) => {
+ ConversationRequestReceived: (accountId: string, conversationId: string, message: Message) => {
console.log(`conversationRequestReceived: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -209,7 +218,7 @@
return;
}
},
- ConversationMemberEvent: (accountId, conversationId) => {
+ ConversationMemberEvent: (accountId: string, conversationId: string, memberUri: string, event: number) => {
console.log(`conversationMemberEvent: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -217,7 +226,7 @@
return;
}
},
- OnConversationError: (accountId, conversationId) => {
+ OnConversationError: (accountId: string, conversationId: string, code: number, what: string) => {
console.log(`onConversationError: ${accountId} ${conversationId}`);
const account = this.getAccount(accountId);
if (!account) {
@@ -226,22 +235,22 @@
}
},
// Calls
- CallStateChanged: (callId, state, code) => {
- console.log(`CallStateChanged: ${callId} ${state} ${code}`);
+ StateChange: (callId: string, state: string, code: number) => {
+ console.log(`CallStateChange: ${callId} ${state} ${code}`);
},
- IncomingCall: (accountId, callId, peerUri) => {
+ IncomingCall: (accountId: string, callId: string, peerUri: string) => {
console.log(`IncomingCall: ${accountId} ${callId} ${peerUri}`);
},
- ConferenceCreated: (confId) => {
+ ConferenceCreated: (confId: string) => {
console.log(`ConferenceCreated: ${confId}`);
},
- ConferenceChanged: (confId) => {
+ ConferenceChanged: (accountId: string, confId: string, state: string) => {
console.log(`ConferenceChanged: ${confId}`);
},
- ConferenceRemoved: (confId) => {
+ ConferenceRemoved: (confId: string) => {
console.log(`ConferenceRemoved: ${confId}`);
},
- OnConferenceInfosUpdated: (confId) => {
+ OnConferenceInfosUpdated: (confId: string) => {
console.log(`onConferenceInfosUpdated: ${confId}`);
},
});
@@ -249,11 +258,11 @@
JamiDaemon.vectToJs(this.dring.getAccountList()).forEach((accountId) => {
const account = new Account(
accountId,
- JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
- JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
+ JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)) as AccountDetails,
+ JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId)) as VolatileDetails
);
- account.contacts = JamiDaemon.vectMapToJs(this.dring.getContacts(accountId));
+ account.contacts = JamiDaemon.vectMapToJs(this.dring.getContacts(accountId)) as Contact[];
JamiDaemon.vectToJs(this.dring.getConversations(accountId)).forEach((conversationId) => {
const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId));
@@ -264,41 +273,40 @@
if (!member.uri) return;
console.log(`lookupAddress ${accountId} ${member.uri}`, member);
member.contact.setRegisteredName(
- new Promise((resolve, reject) => account.lookups.push({ address: member.uri, resolve, reject })).then(
- (result) => {
- if (result.state == 0) return result.name;
- else if (result.state == 1) return undefined;
- else return null;
- }
- )
+ new Promise((resolve: (value: LookupResolveValue) => void, reject) =>
+ account.lookups.push({ address: member.uri, resolve, reject })
+ ).then((result) => {
+ if (result.state == 0) return result.name;
+ else if (result.state == 1) return undefined;
+ else return null;
+ })
);
this.dring.lookupAddress(accountId, '', member.uri);
}
});
const conversation = new Conversation(conversationId, accountId, members);
- conversation.setInfos(JamiDaemon.mapToJs(this.dring.conversationInfos(accountId, conversationId)));
+ conversation.infos = JamiDaemon.mapToJs(this.dring.conversationInfos(accountId, conversationId));
account.addConversation(conversation);
});
- account.setDevices();
this.accounts.push(account);
});
}
- addAccount(accountConfig) {
+ addAccount(accountConfig: AccountConfig) {
const params = this.accountDetailsToNative(accountConfig);
params.set('Account.type', 'RING');
- return new Promise((resolve, reject) => {
+ return new Promise<string>((resolve, reject) => {
const accountId = this.dring.addAccount(params);
this.tempAccounts[accountId] = { resolve, reject };
});
}
- getDevices(accountId) {
+ getDevices(accountId: string) {
return JamiDaemon.mapToJs(this.dring.getKnownRingDevices(accountId));
}
- getAccount(accountId) {
+ getAccount(accountId: string) {
for (let i = 0; i < this.accounts.length; i++) {
const account = this.accounts[i];
if (account.getId() === accountId) return account;
@@ -308,7 +316,7 @@
getAccountList() {
return this.accounts;
}
- registerName(accountId, password, name) {
+ registerName(accountId: string, password: string, name: string) {
return new Promise((resolve, reject) => {
if (!name) return reject(new Error('Invalid name'));
const account = this.getAccount(accountId);
@@ -320,28 +328,28 @@
});
}
- getConversation(accountId, conversationId) {
+ getConversation(accountId: string, conversationId: string) {
const account = this.getAccount(accountId);
if (account) return account.getConversation(conversationId);
return null;
}
- getAccountDetails(accountId) {
+ getAccountDetails(accountId: string) {
return JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId));
}
- setAccountDetails(accountId, details) {
+ setAccountDetails(accountId: string, details: AccountDetails) {
this.dring.setAccountDetails(accountId, this.mapToNative(details));
}
getAudioOutputDeviceList() {
return JamiDaemon.vectToJs(this.dring.getAudioOutputDeviceList());
}
- getVolume(deviceName) {
+ getVolume(deviceName: string): number {
return this.dring.getVolume(deviceName);
}
- setVolume(deviceName, volume) {
+ setVolume(deviceName: string, volume: number) {
return this.dring.setVolume(deviceName, volume);
}
- lookupName(accountId, name) {
+ lookupName(accountId: string, name: string) {
const p = new Promise((resolve, reject) => {
if (accountId) {
const account = this.getAccount(accountId);
@@ -358,7 +366,7 @@
return p;
}
- lookupAddress(accountId, address) {
+ lookupAddress(accountId: string, address: string) {
console.log(`lookupAddress ${accountId} ${address}`);
const p = new Promise((resolve, reject) => {
if (accountId) {
@@ -380,7 +388,7 @@
this.dring.fini();
}
- addContact(accountId, contactId) {
+ addContact(accountId: string, contactId: string) {
this.dring.addContact(accountId, contactId);
const details = JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
if (details.conversationId) {
@@ -398,21 +406,21 @@
return details;
}
- removeContact(accountId, contactId) {
+ removeContact(accountId: string, contactId: string) {
//bool ban false
this.dring.removeContact(accountId, contactId, false);
}
- blockContact(accountId, contactId) {
+ blockContact(accountId: string, contactId: string) {
//bool ban true
this.dring.removeContact(accountId, contactId, true);
}
- getContactDetails(accountId, contactId) {
+ getContactDetails(accountId: string, contactId: string) {
return JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId));
}
- getDefaultModerators(accountId) {
+ getDefaultModerators(accountId: string) {
const account = this.getAccount(accountId);
if (!account) {
console.log(`Unknown account ${accountId}`);
@@ -423,19 +431,19 @@
);
}
- addDefaultModerator(accountId, uri) {
+ addDefaultModerator(accountId: string, uri: string) {
this.dring.setDefaultModerator(accountId, uri, true);
}
- removeDefaultModerator(accountId, uri) {
+ removeDefaultModerator(accountId: string, uri: string) {
this.dring.setDefaultModerator(accountId, uri, false);
}
- sendMessage(accountId, conversationId, message) {
+ sendMessage(accountId: string, conversationId: string, message: string) {
this.dring.sendMessage(accountId, conversationId, message, '');
}
- loadMessages(accountId, conversationId, fromMessage) {
+ loadMessages(accountId: string, conversationId: string, fromMessage?: string) {
const account = this.getAccount(accountId);
if (!account) throw new Error('Unknown account');
const conversation = account.getConversation(conversationId);
@@ -448,11 +456,11 @@
});
}
- boolToStr(bool) {
+ boolToStr(bool: boolean) {
return bool ? 'true' : 'false';
}
- accountDetailsToNative(account) {
+ accountDetailsToNative(account: AccountConfig) {
const params = new this.dring.StringMap();
if (account.managerUri) params.set('Account.managerUri', account.managerUri);
if (account.managerUsername) params.set('Account.managerUsername', account.managerUsername);
@@ -490,25 +498,25 @@
if (account.upnpEnabled !== undefined) params.set('Account.upnpEnabled', this.boolToStr(account.upnpEnabled));
return params;
}
- static vectToJs(vect) {
+ static vectToJs(vect: any) {
const len = vect.size();
const outputArr = new Array(len);
for (let i = 0; i < len; i++) outputArr[i] = vect.get(i);
return outputArr;
}
- static mapToJs(m) {
- const outputObj = {};
+ static mapToJs(m: any): Record<string, any> {
+ const outputObj: Record<string, any> = {};
JamiDaemon.vectToJs(m.keys()).forEach((k) => (outputObj[k] = m.get(k)));
return outputObj;
}
- static vectMapToJs(vectMap) {
+ static vectMapToJs(vectMap: any) {
const len = vectMap.size();
const outputArr = new Array(len);
for (let i = 0; i < len; i++) outputArr[i] = JamiDaemon.mapToJs(vectMap.get(i));
return outputArr;
}
- mapToNative(map) {
+ mapToNative(map: any) {
const ret = new this.dring.StringMap();
for (const [key, value] of Object.entries(map)) ret.set(key, value);
return ret;
diff --git a/app.ts b/app.ts
index c8a5331..42e81a8 100644
--- a/app.ts
+++ b/app.ts
@@ -10,13 +10,14 @@
import { promises as fs } from 'fs';
import http from 'http';
import passport from 'passport';
-import { Strategy as LocalStrategy } from 'passport-local';
+import { IVerifyOptions, Strategy as LocalStrategy } from 'passport-local';
import path from 'path';
import { Server, Socket } from 'socket.io';
import { ExtendedError } from 'socket.io/dist/namespace';
-import JamiDaemon from './JamiDaemon.js';
+import JamiDaemon from './JamiDaemon';
import Account from './model/Account';
+import { Session } from './model/util';
//import { createRequire } from 'module';
//const require = createRequire(import.meta.url);
//const redis = require('redis-url').connect()
@@ -31,7 +32,15 @@
//const sessionStore = new RedisStore({ client: redis })
const sessionStore = new session.MemoryStore();
+interface User {
+ id: string;
+ config: UserConfig;
+ username: string;
+ accountFilter?: (account: any) => boolean;
+}
+
interface UserConfig {
+ accountId?: string;
accounts: string;
password?: string;
username?: string;
@@ -40,7 +49,7 @@
interface AppConfig {
users: Record<string, UserConfig>;
- authMethods: any[];
+ authMethods: unknown[];
}
const loadConfig = async (filePath: string): Promise<AppConfig> => {
@@ -78,8 +87,14 @@
web socket call
*/
-const tempAccounts: Record<string, any> = {};
-const connectedUsers: Record<string, any> = {};
+const tempAccounts: Record<
+ string,
+ {
+ newUser: Express.User;
+ done: (error: any, user?: any, options?: IVerifyOptions) => void;
+ }
+> = {};
+const connectedUsers: Record<string, UserConfig> = {};
const createServer = async (appConfig: AppConfig) => {
const app = express();
@@ -160,7 +175,7 @@
return 'admin' in appConfig.users;
};
- const accountFilter = (filter: string | any[]) => {
+ const accountFilter = (filter: string | string[]) => {
if (typeof filter === 'string') {
if (filter === '*') return undefined;
else return (account: Account) => account.getId() === filter;
@@ -181,6 +196,7 @@
};
passport.serializeUser((user: any, done) => {
+ user = user as User;
connectedUsers[user.id] = user.config;
console.log('=============================SerializeUser called ' + user.id);
console.log(user);
@@ -237,7 +253,8 @@
res.status(401).end();
};
const securedRedirect = (req: Request, res: Response, next: NextFunction) => {
- if (req.user && (req.user as any)?.accountId) {
+ const user = req.user as UserConfig | undefined;
+ if (user?.accountId) {
return next();
}
(req.session as any).returnTo = req.originalUrl;
@@ -264,12 +281,12 @@
res.json({ loggedin: true });
});
app.post('/auth/local', passport.authenticate('local'), (req, res) => {
- res.json({ loggedin: true, user: (req.user as any)?.id });
+ res.json({ loggedin: true, user: (req.user as User | undefined)?.id });
});
const getState = (req: Request) => {
if (req.user) {
- const user = (req.user || {}) as UserConfig;
+ const user = req.user as UserConfig;
return { loggedin: true, username: user.username, type: user.type };
} else if (isSetupComplete()) {
return {};
@@ -322,7 +339,7 @@
});
io.on('connect', (socket) => {
console.log(`new connection ${socket.id}`);
- const session = (socket.request as any).session;
+ const session: Session = (socket.request as any).session;
console.log(`saving sid ${socket.id} in session ${session.id}`);
session.socketId = socket.id;
session.save();
@@ -333,15 +350,21 @@
if (session.conversation) {
console.log(`disconnect from old conversation ${session.conversation.conversationId}`);
const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId);
- delete conversation.listeners[socket.id];
+ if (conversation) {
+ delete conversation.listeners[socket.id];
+ }
}
session.conversation = { accountId: data.accountId, conversationId: data.conversationId };
const conversation = jami.getConversation(data.accountId, data.conversationId);
- if (!conversation.listeners) conversation.listeners = {};
- conversation.listeners[socket.id] = {
- socket,
- session,
- };
+ if (conversation) {
+ if (!conversation.listeners) {
+ conversation.listeners = {};
+ }
+ conversation.listeners[socket.id] = {
+ socket,
+ session,
+ };
+ }
session.save();
});
});
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 9e31f8f..9af3397 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -5,5 +5,6 @@
"jsx": "react-jsx",
/* Specify an output folder for all emitted files. */
"outDir": "./dist/"
- }
+ },
+ "include": ["src/"]
}
diff --git a/model/Account.js b/model/Account.js
deleted file mode 100644
index aa1e957..0000000
--- a/model/Account.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import Contact from './Contact.js';
-
-class Account {
- constructor(id, details, volatileDetails) {
- this.id = id;
- this.details = details;
- this.volatileDetails = volatileDetails;
- this.contactCache = {};
- this.contacts = {};
- this.conversations = {};
- this.lookups = [];
- this.devices = {};
- }
-
- static from(object) {
- const account = new Account(object.id, object.details, object.volatileDetails);
- if (object.defaultModerators) account.defaultModerators = object.defaultModerators.map((m) => Contact.from(m));
- return account;
- }
-
- update(data) {
- this.details = data.details;
- this.volatileDetails = data.volatileDetails;
- }
-
- async getObject() {
- const hasModerators = this.defaultModerators && this.defaultModerators.length;
- return {
- id: this.id,
- details: this.details,
- defaultModerators: hasModerators
- ? await Promise.all(this.defaultModerators.map(async (c) => await c.getObject()))
- : undefined,
- volatileDetails: this.volatileDetails,
- };
- }
-
- getId() {
- return this.id;
- }
-
- getType() {
- return this.details['Account.type'];
- }
-
- getUri() {
- return this.details['Account.username'];
- }
-
- getRegisteredName() {
- return this.volatileDetails['Account.registeredName'];
- }
-
- isRendezVous() {
- return this.details['Account.rendezVous'] === Account.BOOL_TRUE;
- }
-
- isPublicIn() {
- return this.details['DHT.PublicInCalls'] === Account.BOOL_TRUE;
- }
-
- setDetail(detail, value) {
- this.details[detail] = value;
- }
-
- updateDetails(details) {
- return Object.assign(this.details, details);
- }
-
- getDetails() {
- return this.details;
- }
-
- getSummary() {
- return this.getObject();
- }
-
- getDisplayName() {
- return this.details['Account.displayName'] || this.getDisplayUri();
- }
-
- getDisplayUri() {
- return this.getRegisteredName() || this.getUri();
- }
-
- getDisplayNameNoFallback() {
- return this.details['Account.displayName'] || this.getRegisteredName();
- }
-
- getConversationIds() {
- return Object.keys(this.conversations);
- }
- getConversations() {
- return this.conversations;
- }
-
- getConversation(conversationId) {
- return this.conversations[conversationId];
- }
-
- addConversation(conversation) {
- this.conversations[conversation.getId()] = conversation;
- }
-
- removeConversation(conversationId) {
- delete this.conversations[conversationId];
- }
-
- getContactFromCache(uri) {
- let contact = this.contactCache[uri];
- if (!contact) {
- contact = new Contact(uri);
- this.contactCache[uri] = contact;
- }
- return contact;
- }
-
- getContacts() {
- return this.contacts;
- }
-
- getDefaultModerators() {
- return this.defaultModerators;
- }
-
- setDevices(devices) {
- this.devices = { ...devices };
- }
- getDevices() {
- return this.devices;
- }
-}
-
-Account.TYPE_JAMI = 'RING';
-Account.TYPE_SIP = 'SIP';
-
-Account.BOOL_TRUE = 'true';
-Account.BOOL_FALSE = 'false';
-
-export default Account;
diff --git a/model/Account.ts b/model/Account.ts
new file mode 100644
index 0000000..ce529c0
--- /dev/null
+++ b/model/Account.ts
@@ -0,0 +1,216 @@
+import AccountDetails, { VolatileDetails } from './AccountDetails';
+import Contact from './Contact';
+import Conversation from './Conversation';
+import { Lookup, PromiseExecutor } from './util';
+
+type Devices = Record<string, string>;
+
+export type RegistrationState =
+ | 'UNREGISTERED'
+ | 'TRYING'
+ | 'REGISTERED'
+ | 'ERROR_GENERIC'
+ | 'ERROR_AUTH'
+ | 'ERROR_NETWORK'
+ | 'ERROR_HOST'
+ | 'ERROR_SERVICE_UNAVAILABLE'
+ | 'ERROR_NEED_MIGRATION'
+ | 'INITIALIZING';
+
+interface AccountRegisteringName extends PromiseExecutor<number> {
+ name: string;
+}
+
+class Account {
+ private readonly id: string;
+ private _details: AccountDetails;
+ private _volatileDetails: VolatileDetails;
+ private contactCache: Record<string, Contact> = {};
+ private _contacts: Contact[] = [];
+ private conversations: Record<string, Conversation> = {};
+ private defaultModerators: Contact[] = [];
+ private _lookups: Lookup[] = [];
+ private devices: Devices = {};
+ private _registrationState: RegistrationState | undefined = undefined;
+ private _registeringName: AccountRegisteringName | undefined = undefined;
+
+ static TYPE_JAMI: string;
+ static TYPE_SIP: string;
+ static BOOL_TRUE: string;
+ static BOOL_FALSE: string;
+
+ constructor(id: string, details: AccountDetails, volatileDetails: VolatileDetails) {
+ this.id = id;
+ this._details = details;
+ this._volatileDetails = volatileDetails;
+ }
+
+ static from(object: Account) {
+ const account = new Account(object.id, object._details, object._volatileDetails);
+ if (object.defaultModerators) account.defaultModerators = object.defaultModerators.map((m) => Contact.from(m));
+ return account;
+ }
+
+ update(data: Account) {
+ this._details = data._details;
+ this._volatileDetails = data._volatileDetails;
+ }
+
+ async getObject() {
+ const hasModerators = this.defaultModerators && this.defaultModerators.length;
+ return {
+ id: this.id,
+ details: this._details,
+ defaultModerators: hasModerators
+ ? await Promise.all(this.defaultModerators.map(async (c) => await c.getObject()))
+ : undefined,
+ volatileDetails: this._volatileDetails,
+ };
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getType() {
+ return this._details['Account.type'];
+ }
+
+ getUri() {
+ return this._details['Account.username'];
+ }
+
+ getRegisteredName() {
+ return this._volatileDetails['Account.registeredName'];
+ }
+
+ isRendezVous() {
+ return this._details['Account.rendezVous'] === Account.BOOL_TRUE;
+ }
+
+ isPublicIn() {
+ return this._details['DHT.PublicInCalls'] === Account.BOOL_TRUE;
+ }
+
+ setDetail(detail: keyof AccountDetails, value: string) {
+ this._details[detail] = value;
+ }
+
+ updateDetails(details: Partial<AccountDetails>) {
+ return Object.assign(this._details, details);
+ }
+
+ getDetails() {
+ return this._details;
+ }
+
+ getSummary() {
+ return this.getObject();
+ }
+
+ getDisplayName() {
+ return this._details['Account.displayName'] || this.getDisplayUri();
+ }
+
+ getDisplayUri() {
+ return this.getRegisteredName() || this.getUri();
+ }
+
+ getDisplayNameNoFallback() {
+ return this._details['Account.displayName'] || this.getRegisteredName();
+ }
+
+ getConversationIds() {
+ return Object.keys(this.conversations);
+ }
+
+ getConversations() {
+ return this.conversations;
+ }
+
+ getConversation(conversationId: string) {
+ return this.conversations[conversationId];
+ }
+
+ addConversation(conversation: Conversation) {
+ const conversationId = conversation.getId();
+ if (conversationId != null) {
+ this.conversations[conversationId] = conversation;
+ } else {
+ throw new Error('Conversation ID cannot be undefined');
+ }
+ }
+
+ removeConversation(conversationId: string) {
+ delete this.conversations[conversationId];
+ }
+
+ getContactFromCache(uri: string) {
+ let contact = this.contactCache[uri];
+ if (!contact) {
+ contact = new Contact(uri);
+ this.contactCache[uri] = contact;
+ }
+ return contact;
+ }
+
+ getContacts() {
+ return this._contacts;
+ }
+
+ set contacts(contacts: Contact[]) {
+ this._contacts = contacts;
+ }
+
+ getDefaultModerators() {
+ return this.defaultModerators;
+ }
+
+ set details(value: AccountDetails) {
+ this._details = value;
+ }
+
+ set volatileDetails(value: VolatileDetails) {
+ this._volatileDetails = value;
+ }
+
+ get lookups(): Lookup[] {
+ return this._lookups;
+ }
+
+ set lookups(lookups: Lookup[]) {
+ this._lookups = lookups;
+ }
+
+ setDevices(devices: Devices) {
+ this.devices = { ...devices };
+ }
+
+ getDevices() {
+ return this.devices;
+ }
+
+ get registrationState(): RegistrationState | undefined {
+ return this._registrationState;
+ }
+
+ set registrationState(registrationState: RegistrationState | undefined) {
+ this._registrationState = registrationState;
+ }
+
+ get registeringName(): AccountRegisteringName | undefined {
+ return this._registeringName;
+ }
+
+ set registeringName(registeringName: AccountRegisteringName | undefined) {
+ this._registeringName = registeringName;
+ }
+}
+
+Account.TYPE_JAMI = 'RING';
+Account.TYPE_SIP = 'SIP';
+
+Account.BOOL_TRUE = 'true';
+Account.BOOL_FALSE = 'false';
+
+export default Account;
diff --git a/model/AccountDetails.ts b/model/AccountDetails.ts
new file mode 100644
index 0000000..e9514aa
--- /dev/null
+++ b/model/AccountDetails.ts
@@ -0,0 +1,169 @@
+/**
+ * Account parameters
+ *
+ * See `jami-daemon/src/account_schema.h`
+ */
+export default interface AccountDetails {
+ // Common account parameters
+ 'Account.type': string;
+ 'Account.alias': string;
+ 'Account.displayName': string;
+ 'Account.mailbox': string;
+ 'Account.enable': string;
+ 'Account.autoAnswer': string;
+ 'Account.sendReadReceipt': string;
+ 'Account.rendezVous': string;
+ 'Account.registrationExpire': string;
+ 'Account.dtmfType': string;
+ 'Account.ringtonePath': string;
+ 'Account.ringtoneEnabled': string;
+ 'Account.videoEnabled': string;
+ 'Account.keepAliveEnabled': string;
+ 'Account.presenceEnabled': string;
+ 'Account.presencePublishSupported': string;
+ 'Account.presenceSubscribeSupported': string;
+ 'Account.presenceStatus': string;
+ 'Account.presenceNote': string;
+
+ 'Account.hostname': string;
+ 'Account.username': string;
+ 'Account.routeset': string;
+ 'Account.allowIPAutoRewrite': string;
+ 'Account.password': string;
+ 'Account.realm': string;
+ 'Account.useragent': string;
+ 'Account.hasCustomUserAgent': string;
+ 'Account.audioPortMin': string;
+ 'Account.audioPortMax': string;
+ 'Account.videoPortMin': string;
+ 'Account.videoPortMax': string;
+
+ 'Account.bindAddress': string;
+ 'Account.localInterface': string;
+ 'Account.publishedSameAsLocal': string;
+ 'Account.localPort': string;
+ 'Account.publishedPort': string;
+ 'Account.publishedAddress': string;
+ 'Account.upnpEnabled': string;
+ 'Account.defaultModerators': string;
+ 'Account.localModeratorsEnabled': string;
+ 'Account.allModeratorEnabled': string;
+
+ // SIP specific parameters
+ 'STUN.server': string;
+ 'STUN.enable': string;
+ 'TURN.server': string;
+ 'TURN.enable': string;
+ 'TURN.username': string;
+ 'TURN.password': string;
+ 'TURN.realm': string;
+
+ // SRTP specific parameters
+ 'SRTP.enable': string;
+ 'SRTP.keyExchange': string;
+ 'SRTP.rtpFallback': string;
+
+ 'TLS.listenerPort': string;
+ 'TLS.enable': string;
+ 'TLS.certificateListFile': string;
+ 'TLS.certificateFile': string;
+ 'TLS.privateKeyFile': string;
+ 'TLS.password': string;
+ 'TLS.method': string;
+ 'TLS.ciphers': string;
+ 'TLS.serverName': string;
+ 'TLS.verifyServer': string;
+ 'TLS.verifyClient': string;
+ 'TLS.requireClientCertificate': string;
+ 'TLS.negotiationTimeoutSec': string;
+
+ // DHT specific parameters
+ 'DHT.port': string;
+ 'DHT.PublicInCalls': string;
+
+ // Volatile parameters
+ 'Account.registrationStatus': string;
+ 'Account.registrationCode': string;
+ 'Account.registrationDescription': string;
+ 'Transport.statusCode': string;
+ 'Transport.statusDescription': string;
+}
+
+/**
+ * Volatile properties
+ *
+ * See `jami-daemon/src/jami/account_const.h`
+ */
+export interface VolatileDetails {
+ 'Account.active': string;
+ 'Account.deviceAnnounced': string;
+ 'Account.registeredName': string;
+}
+
+/**
+ * See `ConfProperties` in `jami-daemon/src/jami/account_const.h
+ */
+export interface AccountConfig {
+ id?: string;
+ type?: string;
+ alias?: string;
+ displayName?: string;
+ enable?: boolean;
+ mailbox?: string;
+ dtmfType?: string;
+ autoAnswer?: boolean;
+ sendReadReceipt?: string;
+ rendezVous?: boolean;
+ activeCallLimit?: string;
+ hostname?: string;
+ username?: string;
+ bindAddress?: string;
+ routeset?: string;
+ password?: string;
+ realm?: string;
+ localInterface?: string;
+ publishedSameAsLocal?: boolean;
+ localPort?: string;
+ publishedPort?: string;
+ publishedAddress?: string;
+ useragent?: string;
+ upnpEnabled?: boolean;
+ hasCustomUserAgent?: string;
+ allowCertFromHistory?: string;
+ allowCertFromContact?: string;
+ allowCertFromTrusted?: string;
+ archivePassword?: string;
+ archiveHasPassword?: string;
+ archivePath?: string;
+ archivePIN?: string;
+ deviceID?: string;
+ deviceName?: string;
+ proxyEnabled?: boolean;
+ proxyServer?: string;
+ proxyPushToken?: string;
+ keepAliveEnabled?: boolean;
+ peerDiscovery?: string;
+ accountDiscovery?: string;
+ accountPublish?: string;
+ managerUri?: string;
+ managerUsername?: string;
+ bootstrapListUrl?: string;
+ dhtProxyListUrl?: string;
+ defaultModerators?: string;
+ localModeratorsEnabled?: boolean;
+ allModeratorsEnabled?: boolean;
+ allowIPAutoRewrite?: string;
+
+ // Audio
+ audioPortMax?: string;
+ audioPortMin?: string;
+
+ // Video
+ videoEnabled?: boolean;
+ videoPortMax?: boolean;
+ videoPortMin?: string;
+
+ // Ringtone
+ ringtonePath?: string;
+ ringtoneEnabled?: boolean;
+}
diff --git a/model/Contact.js b/model/Contact.ts
similarity index 73%
rename from model/Contact.js
rename to model/Contact.ts
index cc8e386..01d4a02 100644
--- a/model/Contact.js
+++ b/model/Contact.ts
@@ -1,11 +1,13 @@
class Contact {
- constructor(uri) {
+ private readonly uri: string;
+ private displayName: string | undefined = undefined;
+ private registeredName: string | undefined = undefined;
+
+ constructor(uri: string) {
this.uri = uri;
- this.displayName = undefined;
- this.registeredName = undefined;
}
- static from(object) {
+ static from(object: Contact) {
const contact = new Contact(object.uri);
if (object.registeredName) contact.setRegisteredName(object.registeredName);
return contact;
@@ -19,7 +21,7 @@
return this.registeredName;
}
- setRegisteredName(name) {
+ setRegisteredName(name: string | undefined) {
this.registeredName = name;
}
diff --git a/model/Conversation.js b/model/Conversation.js
deleted file mode 100644
index 174ba03..0000000
--- a/model/Conversation.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import Contact from './Contact.js';
-
-class Conversation {
- constructor(id, accountId, members) {
- this.id = id;
- this.accountId = accountId;
- this.members = members || [];
- this.messages = [];
- this.infos = {};
- }
-
- static from(accountId, object) {
- const conversation = new Conversation(
- object.id,
- accountId,
- object.members.map((member) => {
- member.contact = Contact.from(member.contact);
- return member;
- })
- );
- conversation.messages = object.messages;
- return conversation;
- }
- static fromSingleContact(accountId, contact) {
- return new Conversation(undefined, accountId, [{ contact }]);
- }
-
- getId() {
- return this.id;
- }
-
- getAccountId() {
- return this.accountId;
- }
-
- getDisplayName() {
- if (this.members.length !== 0) {
- return this.members[0].contact.getDisplayName();
- }
- return this.getDisplayUri();
- }
-
- getDisplayNameNoFallback() {
- if (this.members.length !== 0) {
- return this.members[0].contact.getDisplayNameNoFallback();
- }
- }
-
- async getObject(params) {
- const members = params.memberFilter ? this.members.filter(params.memberFilter) : this.members;
- return {
- id: this.id,
- messages: this.messages,
- members: await Promise.all(
- members.map(async (member) => {
- const copiedMember = { role: member.role }; //Object.assign({}, member);
- copiedMember.contact = await member.contact.getObject();
- return copiedMember;
- })
- ),
- };
- }
-
- getSummary() {
- return this.getObject();
- }
-
- getDisplayUri() {
- return this.getId() || this.getFirstMember().contact.getUri();
- }
-
- getFirstMember() {
- return this.members[0];
- }
-
- getMembers() {
- return this.members;
- }
-
- addMessage(message) {
- if (this.messages.length === 0) this.messages.push(message);
- else if (message.id === this.messages[this.messages.length - 1].linearizedParent) {
- this.messages.push(message);
- } else if (message.linearizedParent === this.messages[0].id) {
- this.messages.unshift(message);
- } else {
- console.log("Can't insert message " + message.id);
- }
- }
-
- addLoadedMessages(messages) {
- messages.forEach((message) => this.addMessage(message));
- }
-
- getMessages() {
- return this.messages;
- }
-
- setInfos(infos) {
- this.infos = infos;
- }
-}
-
-export default Conversation;
diff --git a/model/Conversation.ts b/model/Conversation.ts
new file mode 100644
index 0000000..3a75567
--- /dev/null
+++ b/model/Conversation.ts
@@ -0,0 +1,151 @@
+import { Socket } from 'socket.io';
+
+import Contact from './Contact';
+import { PromiseExecutor, Session } from './util';
+
+export interface ConversationMember {
+ contact: Contact;
+ role?: 'admin' | 'member' | 'invited' | 'banned' | 'left';
+}
+
+type ConversationInfos = Record<string, unknown>;
+
+export type Message = Record<string, string>;
+type ConversationRequest = PromiseExecutor<Message[]>;
+
+type ConversationListeners = Record<
+ string,
+ {
+ socket: Socket;
+ session: Session;
+ }
+>;
+
+class Conversation {
+ private readonly id: string | undefined;
+ private readonly accountId: string;
+ private readonly members: ConversationMember[];
+ private messages: Message[] = [];
+ private _infos: ConversationInfos = {};
+ private _requests: Record<string, ConversationRequest> = {};
+ private _listeners: ConversationListeners = {};
+
+ constructor(id: string | undefined, accountId: string, members?: ConversationMember[]) {
+ this.id = id;
+ this.accountId = accountId;
+ this.members = members || [];
+ }
+
+ static from(accountId: string, object: Conversation) {
+ const conversation = new Conversation(
+ object.id,
+ accountId,
+ object.members.map((member) => {
+ member.contact = Contact.from(member.contact);
+ return member;
+ })
+ );
+ conversation.messages = object.messages;
+ return conversation;
+ }
+ static fromSingleContact(accountId: string, contact: Contact) {
+ return new Conversation(undefined, accountId, [{ contact }]);
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getAccountId() {
+ return this.accountId;
+ }
+
+ getDisplayName() {
+ if (this.members.length !== 0) {
+ return this.members[0].contact.getDisplayName();
+ }
+ return this.getDisplayUri();
+ }
+
+ getDisplayNameNoFallback() {
+ if (this.members.length !== 0) {
+ return this.members[0].contact.getDisplayNameNoFallback();
+ }
+ }
+
+ async getObject(params?: {
+ memberFilter: (value: ConversationMember, index: number, array: ConversationMember[]) => boolean;
+ }) {
+ const members = params?.memberFilter ? this.members.filter(params.memberFilter) : this.members;
+ return {
+ id: this.id,
+ messages: this.messages,
+ members: await Promise.all(
+ members.map(async (member) => {
+ //Object.assign({}, member);
+ return {
+ role: member.role,
+ contact: await member.contact.getObject(),
+ };
+ })
+ ),
+ };
+ }
+
+ getSummary() {
+ return this.getObject();
+ }
+
+ getDisplayUri() {
+ return this.getId() || this.getFirstMember().contact.getUri();
+ }
+
+ getFirstMember() {
+ return this.members[0];
+ }
+
+ getMembers() {
+ return this.members;
+ }
+
+ addMessage(message: Message) {
+ if (this.messages.length === 0) this.messages.push(message);
+ else if (message.id === this.messages[this.messages.length - 1].linearizedParent) {
+ this.messages.push(message);
+ } else if (message.linearizedParent === this.messages[0].id) {
+ this.messages.unshift(message);
+ } else {
+ console.log("Can't insert message " + message.id);
+ }
+ }
+
+ addLoadedMessages(messages: Message[]) {
+ messages.forEach((message) => this.addMessage(message));
+ }
+
+ getMessages() {
+ return this.messages;
+ }
+
+ set infos(infos: ConversationInfos) {
+ this._infos = infos;
+ }
+
+ get requests(): Record<string, ConversationRequest> {
+ return this._requests;
+ }
+
+ set requests(value: Record<string, ConversationRequest>) {
+ this._requests = value;
+ }
+
+ get listeners(): ConversationListeners {
+ return this._listeners;
+ }
+
+ set listeners(listeners: ConversationListeners) {
+ this._listeners = listeners;
+ }
+}
+
+export default Conversation;
diff --git a/model/util.ts b/model/util.ts
new file mode 100644
index 0000000..630b358
--- /dev/null
+++ b/model/util.ts
@@ -0,0 +1,22 @@
+import { Session as ISession } from 'express-session';
+
+export interface PromiseExecutor<T> {
+ resolve: (value: T) => void;
+ reject: (reason?: any) => void;
+}
+
+export interface LookupResolveValue {
+ address: string;
+ name: string;
+ state: number;
+}
+
+export interface Lookup extends PromiseExecutor<LookupResolveValue> {
+ name?: string;
+ address?: string;
+}
+
+export interface Session extends ISession {
+ socketId: string;
+ conversation: any;
+}