Skip to content

Commit ed26458

Browse files
[mirotalk] - #285 refactoring
1 parent 4cb3ac2 commit ed26458

File tree

5 files changed

+117
-69
lines changed

5 files changed

+117
-69
lines changed

app/src/mattermost.js

+99-52
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const TokenManager = require('./tokenManager');
66
const Logger = require('./logs');
77
const log = new Logger('Mattermost');
88

9+
// Token operations
910
class TokenService {
1011
constructor(secret, expiresIn, encryptionKey) {
1112
this.tokenManager = new TokenManager(secret, expiresIn, encryptionKey);
@@ -20,119 +21,147 @@ class TokenService {
2021
}
2122
}
2223

23-
class MattermostService {
24-
constructor(config) {
25-
this.validateConfig(config);
26-
27-
this.token = config.token;
28-
this.serverUrl = config.server_url;
29-
this.username = config.username;
30-
this.password = config.password;
31-
this.disabledEndpoints = config.api_disabled || [];
32-
24+
// Mattermost authentication
25+
class MattermostAuthService {
26+
constructor({ serverUrl, username, password }) {
3327
this.client = new Client4();
34-
this.client.setUrl(this.serverUrl);
35-
36-
this.tokenService = new TokenService(
37-
this.token || 'fallback-secret-at-least-32-chars',
38-
config.roomTokenExpire || '15m',
39-
config.encryptionKey || 'fallback-encryption-key-32chars'
40-
);
41-
}
42-
43-
validateConfig(config) {
44-
if (!config.enabled || !config.server_url || !config.token || !config.username || !config.password) {
45-
throw new Error('Invalid Mattermost configuration');
46-
}
28+
this.client.setUrl(serverUrl);
29+
this.username = username;
30+
this.password = password;
4731
}
4832

49-
async authenticate() {
33+
async login() {
5034
try {
5135
const user = await this.client.login(this.username, this.password);
5236
log.debug('Logged into Mattermost as', user.username);
5337
} catch (error) {
5438
log.error('Failed to log into Mattermost:', error);
5539
}
5640
}
41+
}
42+
43+
// Meeting-related operations
44+
class MeetingService {
45+
constructor(tokenService, serverUrl, disabledEndpoints = [], secure = false) {
46+
this.tokenService = tokenService;
47+
this.serverUrl = serverUrl;
48+
this.disabledEndpoints = disabledEndpoints;
49+
this.secure = secure;
50+
}
5751

5852
isEndpointDisabled(endpoint) {
5953
return this.disabledEndpoints.includes(endpoint);
6054
}
6155

62-
createMeetingToken(userId) {
63-
const payload = {
56+
createTokenPayload(userId) {
57+
return {
6458
userId,
6559
roomId: uuidV4(),
6660
timestamp: Date.now(),
6761
};
62+
}
63+
64+
createMeetingURL(req) {
65+
return `${this.getBaseURL(req)}/join/${uuidV4()}`;
66+
}
6867

68+
createSecureMeetingURL(req, userId) {
69+
const payload = this.createTokenPayload(userId);
6970
const token = this.tokenService.createToken(payload);
70-
return { token, payload };
71+
return {
72+
url: `${this.getBaseURL(req)}/mattermost/join/${encodeURIComponent(token)}`,
73+
payload,
74+
};
7175
}
7276

73-
validateToken(token) {
77+
decodeToken(token) {
7478
return this.tokenService.decodeToken(token);
7579
}
7680

77-
getMeetingURL(req, roomToken) {
81+
getBaseURL(req) {
7882
const host = req.headers.host;
7983
const protocol = host.includes('localhost') ? 'http' : 'https';
80-
return `${protocol}://${host}/mattermost/join/${encodeURIComponent(roomToken)}`;
84+
return `${protocol}://${host}`;
8185
}
8286
}
8387

88+
// Just handles routing and delegates everything
8489
class MattermostController {
85-
constructor(app, mattermostCfg, htmlInjector, clientHtml) {
90+
constructor(app, config, htmlInjector, clientHtml) {
8691
try {
87-
this.service = new MattermostService(mattermostCfg);
92+
this.validateConfig(config);
93+
94+
const tokenService = new TokenService(
95+
config.token || 'fallback-secret-at-least-32-chars',
96+
config.roomTokenExpire || '15m',
97+
config.encryptionKey || 'fallback-encryption-key-32chars'
98+
);
99+
100+
this.authService = new MattermostAuthService({
101+
serverUrl: config.server_url,
102+
username: config.username,
103+
password: config.password,
104+
});
105+
106+
this.meetingService = new MeetingService(
107+
tokenService,
108+
config.server_url,
109+
config.api_disabled,
110+
config.security
111+
);
112+
113+
this.token = config.token;
114+
this.app = app;
115+
this.htmlInjector = htmlInjector;
116+
this.clientHtml = clientHtml;
117+
118+
this.authService.login();
119+
this.setupRoutes();
120+
88121
} catch (error) {
89122
log.error('MattermostController disabled due to config error:', error.message);
90-
return;
91123
}
124+
}
92125

93-
this.htmlInjector = htmlInjector;
94-
this.clientHtml = clientHtml;
95-
this.app = app;
96-
this.token = mattermostCfg.token;
97-
98-
this.service.authenticate();
99-
this.setupRoutes();
126+
validateConfig(cfg) {
127+
if (!cfg.enabled || !cfg.server_url || !cfg.token || !cfg.username || !cfg.password) {
128+
throw new Error('Invalid Mattermost configuration');
129+
}
100130
}
101131

102132
setupRoutes() {
103133
this.app.post('/mattermost', (req, res) => {
104-
if (this.service.isEndpointDisabled('mattermost')) {
134+
if (this.meetingService.isEndpointDisabled('mattermost')) {
105135
return res.end('`This endpoint has been disabled`. Please contact the administrator.');
106136
}
107137

108138
const { token, text, command, channel_id, user_id } = req.body;
109-
110139
if (token !== this.token) {
111140
log.error('Invalid token attempt', { token });
112141
return res.status(403).send('Invalid token');
113142
}
114143

115-
if (command?.trim() === '/p2p' || text?.trim() === '/p2p') {
144+
if (this.isP2PCommand(command, text)) {
116145
try {
117-
const { token: roomToken } = this.service.createMeetingToken(user_id);
118-
const meetingUrl = this.service.getMeetingURL(req, roomToken);
146+
const meetingUrl = this.generateMeetingUrl(req, user_id);
147+
const message = this.getMeetingResponseMessage(meetingUrl);
119148

120149
return res.json({
121150
response_type: 'in_channel',
122-
text: `🔗 [Click here to join your private meeting](${meetingUrl})`,
151+
text: message,
123152
channel_id,
124153
});
125154
} catch (error) {
126-
log.error('Token creation failed', error);
127-
return res.status(500).send('Error creating meeting');
155+
log.error('Meeting creation failed', { error, user_id });
156+
return res.status(500).json({ error: 'Failed to create meeting' });
128157
}
129158
}
130159

131160
return res.status(404).send('Command not recognized');
132161
});
133162

134163
this.app.get('/mattermost/join/:roomToken', (req, res) => {
135-
if (this.service.isEndpointDisabled('mattermost')) {
164+
if (this.meetingService.isEndpointDisabled('mattermost')) {
136165
return res.end('This endpoint has been disabled');
137166
}
138167

@@ -143,14 +172,13 @@ class MattermostController {
143172
}
144173

145174
try {
146-
const payload = this.service.validateToken(roomToken);
147-
log.debug('Decoded payload', payload);
148-
175+
const payload = this.meetingService.decodeToken(roomToken);
149176
if (!payload || !payload.userId || !payload.roomId) {
150177
log.error('Invalid or malformed token payload', payload);
151178
return res.status(400).send('Invalid token');
152179
}
153180

181+
log.debug('Decoded payload', payload);
154182
return this.htmlInjector.injectHtml(this.clientHtml, res);
155183
} catch (error) {
156184
log.error('Token processing error', {
@@ -161,6 +189,25 @@ class MattermostController {
161189
}
162190
});
163191
}
192+
193+
isP2PCommand(command, text) {
194+
const normalizedCommand = command?.trim();
195+
const normalizedText = text?.trim();
196+
return normalizedCommand === '/p2p' || normalizedText === '/p2p';
197+
}
198+
199+
generateMeetingUrl(req, userId) {
200+
if (this.meetingService.secure) {
201+
return this.meetingService.createSecureMeetingURL(req, userId).url;
202+
}
203+
return this.meetingService.createMeetingURL(req);
204+
}
205+
206+
getMeetingResponseMessage(meetingUrl) {
207+
return this.meetingService.secure
208+
? `🔒 [Join your secure private meeting](${meetingUrl})`
209+
: `🌐 Join meeting: ${meetingUrl}`;
210+
}
164211
}
165212

166213
module.exports = MattermostController;

app/src/server.js

+14-13
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies: {
4545
* @license For commercial use or closed source, contact us at [email protected] or purchase directly from CodeCanyon
4646
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
4747
* @author Miroslav Pejic - [email protected]
48-
* @version 1.5.04
48+
* @version 1.5.05
4949
*
5050
*/
5151

@@ -265,18 +265,6 @@ if (configChatGPT.enabled) {
265265
}
266266
}
267267

268-
// Mattermost config
269-
const mattermostCfg = {
270-
enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED),
271-
server_url: process.env.MATTERMOST_SERVER_URL,
272-
username: process.env.MATTERMOST_USERNAME,
273-
password: process.env.MATTERMOST_PASSWORD,
274-
token: process.env.MATTERMOST_TOKEN,
275-
roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE,
276-
encryptionKey: process.env.JWT_KEY,
277-
api_disabled: api_disabled,
278-
};
279-
280268
// IP Whitelist
281269
const ipWhitelist = {
282270
enabled: getEnvBoolean(process.env.IP_WHITELIST_ENABLED),
@@ -350,6 +338,19 @@ function OIDCAuth(req, res, next) {
350338
}
351339
}
352340

341+
// Mattermost config
342+
const mattermostCfg = {
343+
enabled: getEnvBoolean(process.env.MATTERMOST_ENABLED),
344+
server_url: process.env.MATTERMOST_SERVER_URL,
345+
username: process.env.MATTERMOST_USERNAME,
346+
password: process.env.MATTERMOST_PASSWORD,
347+
token: process.env.MATTERMOST_TOKEN,
348+
roomTokenExpire: process.env.MATTERMOST_ROOM_TOKEN_EXPIRE,
349+
encryptionKey: process.env.JWT_KEY,
350+
security: (hostCfg.protected || OIDC.enabled),
351+
api_disabled: api_disabled,
352+
};
353+
353354
// stats configuration
354355
const statsData = {
355356
enabled: process.env.STATS_ENABLED ? getEnvBoolean(process.env.STATS_ENABLED) : true,

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mirotalk",
3-
"version": "1.5.04",
3+
"version": "1.5.05",
44
"description": "A free WebRTC browser-based video call",
55
"main": "server.js",
66
"scripts": {

public/js/brand.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ let brand = {
7373
},
7474
about: {
7575
imageUrl: '../images/mirotalk-logo.gif',
76-
title: 'WebRTC P2P v1.5.04',
76+
title: 'WebRTC P2P v1.5.05',
7777
html: `
7878
<button
7979
id="support-button"

public/js/client.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* @license For commercial use or closed source, contact us at [email protected] or purchase directly from CodeCanyon
1616
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
1717
* @author Miroslav Pejic - [email protected]
18-
* @version 1.5.04
18+
* @version 1.5.05
1919
*
2020
*/
2121

@@ -11158,7 +11158,7 @@ function showAbout() {
1115811158
Swal.fire({
1115911159
background: swBg,
1116011160
position: 'center',
11161-
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.04',
11161+
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.05',
1116211162
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
1116311163
customClass: { image: 'img-about' },
1116411164
html: `

0 commit comments

Comments
 (0)