Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
9a74aba
Partial implementation; media signaling and media calls collection
pierre-lehnen-rc Jul 11, 2025
f1436bf
started work on media-signaling-server
pierre-lehnen-rc Jul 16, 2025
8b44642
split channels into a new model, send offer request to caller
pierre-lehnen-rc Jul 17, 2025
5c911f3
call acceptance
pierre-lehnen-rc Jul 21, 2025
c062c57
split files
pierre-lehnen-rc Jul 22, 2025
8ed0635
service, endpoints and streams
pierre-lehnen-rc Jul 22, 2025
866d7d2
dependencies
pierre-lehnen-rc Jul 22, 2025
e6fb9d9
client call state
pierre-lehnen-rc Jul 22, 2025
abfbd3f
integrating
pierre-lehnen-rc Jul 23, 2025
e9938bd
integrating
pierre-lehnen-rc Jul 25, 2025
9271940
webrtc processor, some fixes and reorganization
pierre-lehnen-rc Jul 26, 2025
0374b5a
accept calls, exchange offers and answers
pierre-lehnen-rc Jul 28, 2025
9046b76
first working state
pierre-lehnen-rc Jul 29, 2025
1f2642c
call by extension
pierre-lehnen-rc Jul 29, 2025
b5f5cba
missed a file
pierre-lehnen-rc Jul 29, 2025
abc9062
simplifying everything
pierre-lehnen-rc Jul 31, 2025
4d4b53c
moving test code to a separate branch
pierre-lehnen-rc Jul 31, 2025
fc8fd1a
rebase conflict oopsie
pierre-lehnen-rc Jul 31, 2025
df797ec
rejection and handling of temporary data
pierre-lehnen-rc Aug 1, 2025
8339874
client side timeouts
pierre-lehnen-rc Aug 1, 2025
eb4a7b7
handle actor signals through agents and contracts
pierre-lehnen-rc Aug 6, 2025
566f9a7
use a signal to request calls too
pierre-lehnen-rc Aug 7, 2025
378abb9
report webrtc states
pierre-lehnen-rc Aug 7, 2025
7553d66
house cleaning
pierre-lehnen-rc Aug 7, 2025
8ed2343
serialization and type validation
pierre-lehnen-rc Aug 8, 2025
a1c6907
Multi session
pierre-lehnen-rc Aug 8, 2025
834fd5a
active state
pierre-lehnen-rc Aug 9, 2025
4afb2af
organized global media signaling server functions
pierre-lehnen-rc Aug 11, 2025
ebf940e
renamed package
pierre-lehnen-rc Aug 11, 2025
2c3f20e
expire old calls
pierre-lehnen-rc Aug 11, 2025
739304a
update comment
pierre-lehnen-rc Aug 11, 2025
eab5358
unneeded dependency
pierre-lehnen-rc Aug 12, 2025
f0731b6
dev dependencies
pierre-lehnen-rc Aug 12, 2025
d5d3673
removing comments
pierre-lehnen-rc Aug 12, 2025
b3bb38a
missed a couple
pierre-lehnen-rc Aug 12, 2025
2f606ec
rename and move token function
pierre-lehnen-rc Aug 13, 2025
79b882b
clarify cronjob interval with a comment
pierre-lehnen-rc Aug 13, 2025
5ea0a46
remove token function
pierre-lehnen-rc Aug 13, 2025
2a1e9dd
remove unnecessary warn
pierre-lehnen-rc Aug 13, 2025
fb08a4d
use setTimeout instead of cronjob
pierre-lehnen-rc Aug 13, 2025
af06988
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Aug 13, 2025
2ddda61
starting
pierre-lehnen-rc Aug 14, 2025
c34e723
creating providers after all
pierre-lehnen-rc Aug 15, 2025
48d9afd
sip calls coming in
pierre-lehnen-rc Aug 15, 2025
af98f4f
answering call
pierre-lehnen-rc Aug 15, 2025
bf0de5f
make calls?
pierre-lehnen-rc Aug 18, 2025
2c33f86
refactor
pierre-lehnen-rc Aug 21, 2025
222f80e
working again
pierre-lehnen-rc Aug 26, 2025
8ba970e
removed some duplicated stuff
pierre-lehnen-rc Aug 26, 2025
79cb7fa
file organization
pierre-lehnen-rc Aug 26, 2025
9a06186
settings
pierre-lehnen-rc Aug 26, 2025
bad1bf5
logs
pierre-lehnen-rc Aug 26, 2025
25d9eb1
small adjustments
pierre-lehnen-rc Aug 27, 2025
65f6f50
reorganization to better accomodate sip integration
pierre-lehnen-rc Aug 27, 2025
23cc986
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 27, 2025
c71572d
ice configuration
pierre-lehnen-rc Aug 27, 2025
2c65b97
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 27, 2025
d0e2279
ice gathering timeout on session
pierre-lehnen-rc Aug 27, 2025
7e0a724
Merge remote-tracking branch 'origin/feat/media-calls' into feat/medi…
pierre-lehnen-rc Aug 27, 2025
98c11e6
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Aug 27, 2025
f2a1487
habemos audio
pierre-lehnen-rc Aug 28, 2025
e42bca5
support accepting a call after the webrtc negotiation is complete
pierre-lehnen-rc Aug 28, 2025
bb9cec7
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 28, 2025
79025c8
removing some dead code
pierre-lehnen-rc Aug 28, 2025
c3b547d
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 28, 2025
86f1192
simplifying
pierre-lehnen-rc Aug 28, 2025
f3b9666
small fixes
pierre-lehnen-rc Aug 28, 2025
808db2f
importing fixes from sip branch
pierre-lehnen-rc Aug 28, 2025
1bdf18f
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 28, 2025
0176bc0
better error handling
pierre-lehnen-rc Aug 29, 2025
08e298f
better error handling
pierre-lehnen-rc Aug 29, 2025
cfdd6fa
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Aug 29, 2025
41832bb
fix merge mess up
pierre-lehnen-rc Aug 29, 2025
b2c9d71
hangup, cancel and rejection
pierre-lehnen-rc Aug 29, 2025
fc161d6
patched drachtio-srf to fix types
pierre-lehnen-rc Sep 1, 2025
d370c03
webrtc improvements
pierre-lehnen-rc Sep 2, 2025
9ed9ca5
renamed `onHold` to `held` and improved some comments
pierre-lehnen-rc Sep 4, 2025
1cf44bb
allow passing the inputTrack as param when creating a new call;
pierre-lehnen-rc Sep 4, 2025
246f70d
feat: webrtc renegotiation (#36874)
pierre-lehnen-rc Sep 8, 2025
0cfb569
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Sep 8, 2025
b8f1097
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Sep 8, 2025
0274eb6
feat: media call transfer (#36898)
pierre-lehnen-rc Sep 11, 2025
4c8c523
Merge branch 'feat/media-calls-backend' into feat/media-calls
pierre-lehnen-rc Sep 11, 2025
4a85e27
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Sep 11, 2025
fb3abc3
improving code quality + applying some suggestions from code review
pierre-lehnen-rc Sep 12, 2025
69e4896
better state and presence management
pierre-lehnen-rc Sep 12, 2025
e5b269d
changes from code review
pierre-lehnen-rc Sep 15, 2025
05c4be8
strip sensitive data from logs
pierre-lehnen-rc Sep 15, 2025
6a3831e
code review
pierre-lehnen-rc Sep 15, 2025
d41cca7
dockerfiles
pierre-lehnen-rc Sep 15, 2025
21a1a28
unregisterPeerEvents
pierre-lehnen-rc Sep 15, 2025
1f3e97b
hangup call on unavailable response from a signed client
pierre-lehnen-rc Sep 15, 2025
6fed7a8
code review
pierre-lehnen-rc Sep 15, 2025
acf08fa
better handling of call expiration
pierre-lehnen-rc Sep 15, 2025
fabc48b
improved client side state handling
pierre-lehnen-rc Sep 15, 2025
b5a91fe
logs
pierre-lehnen-rc Sep 15, 2025
3c5258c
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Sep 15, 2025
dcfd74e
handle situation where ICE Gathering time outs with no candidates
pierre-lehnen-rc Sep 15, 2025
3e0a01d
ice candidate count
pierre-lehnen-rc Sep 15, 2025
433d3ce
endSession
pierre-lehnen-rc Sep 15, 2025
0eeb087
code review
pierre-lehnen-rc Sep 15, 2025
e29335b
fix: getUserMedia being called too early
pierre-lehnen-rc Sep 15, 2025
2711aed
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Sep 16, 2025
90c5795
basic changes to reflect merged code
pierre-lehnen-rc Sep 16, 2025
e95286b
improve handling of input tracks
pierre-lehnen-rc Sep 16, 2025
f9abc9f
bug fixes
pierre-lehnen-rc Sep 16, 2025
1973027
fix presence check on transfered calls
pierre-lehnen-rc Sep 16, 2025
5601d9e
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Sep 17, 2025
fb0dbe0
working on sip transfers and renegotiations
pierre-lehnen-rc Sep 19, 2025
a207464
Merge branch 'develop' into feat/media-calls-backend
pierre-lehnen-rc Sep 19, 2025
5f9f7e3
Merge branch 'develop' into feat/media-calls
pierre-lehnen-rc Sep 19, 2025
40b9514
permissions
pierre-lehnen-rc Sep 19, 2025
75eac3d
Merge branch 'feat/media-calls-backend' into feat/media-calls-sip
pierre-lehnen-rc Sep 20, 2025
72eb15c
transfer incoming calls
pierre-lehnen-rc Sep 20, 2025
6645d60
dtmf
pierre-lehnen-rc Sep 22, 2025
66e625f
Merge branch 'feat/media-calls' into feat/media-calls-sip
pierre-lehnen-rc Sep 22, 2025
5b69163
fix request type
pierre-lehnen-rc Sep 22, 2025
8a73fb3
await DTMF
pierre-lehnen-rc Sep 22, 2025
8646b5d
private settings
pierre-lehnen-rc Sep 22, 2025
faa17d3
removing comments
pierre-lehnen-rc Sep 22, 2025
0394760
cleanup
pierre-lehnen-rc Sep 22, 2025
2201225
Merge branch 'develop' into feat/media-calls-backend
pierre-lehnen-rc Sep 22, 2025
3667697
make media call director non-static
pierre-lehnen-rc Sep 22, 2025
5fce441
fix merge error
pierre-lehnen-rc Sep 22, 2025
ca91b95
fix freeswitch parseChannelUsername regex
pierre-lehnen-rc Sep 22, 2025
7c17e53
fix yarn patch?
pierre-lehnen-rc Sep 22, 2025
913b644
callee negotiation
pierre-lehnen-rc Sep 22, 2025
eb340ca
yarn patch again
pierre-lehnen-rc Sep 22, 2025
854fa42
caller renegotiation
pierre-lehnen-rc Sep 22, 2025
231532c
changes from code review
pierre-lehnen-rc Sep 22, 2025
d2c849a
maybe this works?
pierre-lehnen-rc Sep 22, 2025
02da243
back to home
pierre-lehnen-rc Sep 22, 2025
880a07c
removed comment
pierre-lehnen-rc Sep 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .yarn/patches/drachtio-srf-npm-5.0.12-b0b1afaad6.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
diff --git a/lib/@types/index.d.ts b/lib/@types/index.d.ts
index f71a82f458c1432202be8d4585fc70ba94bee4a4..b874ee9e1fa8a051f06d4824fc12161acfadb78a 100644
--- a/lib/@types/index.d.ts
+++ b/lib/@types/index.d.ts
@@ -119,12 +119,12 @@ declare namespace Srf {
on(messageType: "modify", callback: (req: SrfRequest, res: SrfResponse) => void): void;
once(messageType: string, callback: (msg: SrfResponse) => void): void;
listeners(messageType: string): any[];
- request(opts?: SrfRequest): Promise<SrfResponse>;
- request(opts: SrfRequest, callback?: (err: any, msg: SrfResponse) => void): void;
+ request(opts?: Partial<SrfRequest>): Promise<SrfResponse>;
+ request(opts: Partial<SrfRequest>, callback?: (err: any, msg: SrfResponse) => void): void;
}

export interface CreateUASOptions {
- localSdp: string;
+ localSdp: string | (() => string | Promise<string>);
headers?: SipMessageHeaders;
}

@@ -135,6 +135,8 @@ declare namespace Srf {
localSdp?: string;
proxy?: string;
auth?: { username: string; password: string; };
+ callingName?: string;
+ callingNumber?: string;
}

Comment on lines +1 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if all we're changing is types, we could get away with module / namespace / interface augmentation. 🤔

export interface CreateB2BUAOptions {
@@ -155,7 +157,7 @@ declare class Srf extends EventEmitter {
constructor();
constructor(tags: string | string[]);
connect(config?: Srf.SrfConfig): Promise<void>;
- disconnect(): void;
+ disconnect(socket?: Socket): void;
use(callback: (req: Srf.SrfRequest, res: Srf.SrfResponse, next: Function) => void): void;
use(messageType: string, callback: (req: Srf.SrfRequest, res: Srf.SrfResponse, next: Function) => void): void;
invite(callback: (req: Srf.SrfRequest, res: Srf.SrfResponse) => void): void;
@@ -164,12 +166,12 @@ declare class Srf extends EventEmitter {
proxyRequest(req: Srf.SrfRequest, destination: string | string[], opts?: Srf.ProxyRequestOptions, callback?: (err: any, results: string) => void): void;
createUAS(req: Srf.SrfRequest, res: Srf.SrfResponse, opts: Srf.CreateUASOptions): Promise<Srf.Dialog>;
createUAS(req: Srf.SrfRequest, res: Srf.SrfResponse, opts: Srf.CreateUASOptions, callback?: (err: any, dialog: Srf.Dialog) => void): this;
- createUAC(uri: string | Srf.CreateUACOptions, opts?: Srf.CreateUACOptions, progressCallbacks?: { cbRequest?: (req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Srf.SrfResponse) => void; }): Promise<Srf.Dialog>;
- createUAC(uri: string | Srf.CreateUACOptions, opts?: Srf.CreateUACOptions, progressCallbacks?: { cbRequest?: (req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Srf.SrfResponse) => void; }, callback?: (err: any, dialog: Srf.Dialog) => void): this;
- createB2BUA(req: Srf.SrfRequest, res: Srf.SrfResponse, uri: string, opts: Srf.CreateB2BUAOptions, progressCallbacks?: { cbRequest?: (req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Response) => void; cbFinalizedUac?: (uac: Srf.Dialog) => void; }): Promise<{ uas: Srf.Dialog; uac: Srf.Dialog }>;
- createB2BUA(req: Srf.SrfRequest, res: Srf.SrfResponse, uri: string, opts: Srf.CreateB2BUAOptions, progressCallbacks?: { cbRequest?: (req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Response) => void; cbFinalizedUac?: (uac: Srf.Dialog) => void; }, callback?: (err: any, dialog: Srf.Dialog) => void): this;
+ createUAC(uri: string | Srf.CreateUACOptions, opts?: Srf.CreateUACOptions, progressCallbacks?: { cbRequest?: (error: unknown, req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Srf.SrfResponse) => void; }): Promise<Srf.Dialog>;
+ createUAC(uri: string | Srf.CreateUACOptions, opts?: Srf.CreateUACOptions, progressCallbacks?: { cbRequest?: (error: unknown, req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Srf.SrfResponse) => void; }, callback?: (err: any, dialog: Srf.Dialog) => void): this;
+ createB2BUA(req: Srf.SrfRequest, res: Srf.SrfResponse, uri: string, opts: Srf.CreateB2BUAOptions, progressCallbacks?: { cbRequest?: (error: unknown, req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Response) => void; cbFinalizedUac?: (uac: Srf.Dialog) => void; }): Promise<{ uas: Srf.Dialog; uac: Srf.Dialog }>;
+ createB2BUA(req: Srf.SrfRequest, res: Srf.SrfResponse, uri: string, opts: Srf.CreateB2BUAOptions, progressCallbacks?: { cbRequest?: (error: unknown, req: Srf.SrfRequest) => void; cbProvisional?: (provisionalRes: Response) => void; cbFinalizedUac?: (uac: Srf.Dialog) => void; }, callback?: (err: any, dialog: Srf.Dialog) => void): this;
on(event: 'connect', listener: (err: Error, hostPort: string) => void): this;
- on(event: 'error', listener: (err: Error) => void): this;
+ on(event: 'error', listener: (err: Error, socket?: Socket) => void): this;
on(event: 'disconnect', listener: () => void): this;
on(event: 'message', listener: (req: Srf.SrfRequest, res: Srf.SrfResponse) => void): this;
on(event: 'request', listener: (req: Srf.SrfRequest, res: Srf.SrfResponse) => void): this;
4 changes: 4 additions & 0 deletions apps/meteor/app/authorization/server/constant/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ export const permissions = [
// Allow viewing details of an extension
{ _id: 'view-voip-extension-details', roles: ['admin', 'user'] },

// New Media calls permissions
{ _id: 'allow-internal-voice-calls', roles: ['admin', 'user'] },
{ _id: 'allow-external-voice-calls', roles: ['admin', 'user'] },

{ _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] },
{ _id: 'manage-apps', roles: ['admin'] },
{ _id: 'post-readonly', roles: ['admin', 'owner', 'moderator'] },
Expand Down
131 changes: 92 additions & 39 deletions apps/meteor/ee/server/settings/voip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,106 @@ export function addSettings(): Promise<void> {
alert: 'VoIP_TeamCollab_Beta_Alert',
});

await this.add('VoIP_TeamCollab_FreeSwitch_Host', '', {
type: 'string',
public: true,
invalidValue: '',
enableQuery,
});
await this.section('VoIP_TeamCollab_WebRTC', async function () {
await this.add('VoIP_TeamCollab_Ice_Servers', 'stun:stun.l.google.com:19302', {
type: 'string',
public: true,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_FreeSwitch_Port', 8021, {
type: 'int',
public: true,
invalidValue: 8021,
enableQuery,
await this.add('VoIP_TeamCollab_Ice_Gathering_Timeout', 5000, {
type: 'int',
public: true,
invalidValue: 5000,
enableQuery,
});
});

await this.add('VoIP_TeamCollab_FreeSwitch_Password', '', {
type: 'password',
secret: true,
invalidValue: '',
enableQuery,
});
await this.section('VoIP_TeamCollab_SIP_Integration', async function () {
await this.add('VoIP_TeamCollab_SIP_Integration_Enabled', false, {
type: 'boolean',
public: true,
invalidValue: false,
});

await this.add('VoIP_TeamCollab_FreeSwitch_Timeout', 3000, {
type: 'int',
public: true,
invalidValue: 3000,
enableQuery,
});
await this.add('VoIP_TeamCollab_SIP_Integration_For_Internal_Calls', false, {
type: 'boolean',
public: true,
invalidValue: false,
});

await this.add('VoIP_TeamCollab_FreeSwitch_WebSocket_Path', '', {
type: 'string',
public: true,
invalidValue: '',
enableQuery,
});
await this.add('VoIP_TeamCollab_Drachtio_Host', '', {
type: 'string',
public: false,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_Ice_Servers', 'stun:stun.l.google.com:19302', {
type: 'string',
public: true,
invalidValue: '',
enableQuery,
await this.add('VoIP_TeamCollab_Drachtio_Port', 9022, {
type: 'int',
public: false,
invalidValue: 9022,
enableQuery,
});

await this.add('VoIP_TeamCollab_Drachtio_Password', '', {
type: 'password',
secret: true,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_SIP_Server_Host', '', {
type: 'string',
public: false,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_SIP_Server_Port', 5080, {
type: 'int',
public: false,
invalidValue: 5080,
enableQuery,
});
});

await this.add('VoIP_TeamCollab_Ice_Gathering_Timeout', 5000, {
type: 'int',
public: true,
invalidValue: 5000,
enableQuery,
await this.section('VoIP_TeamCollab_FreeSwitch', async function () {
await this.add('VoIP_TeamCollab_FreeSwitch_Host', '', {
type: 'string',
public: false,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_FreeSwitch_Port', 8021, {
type: 'int',
public: false,
invalidValue: 8021,
enableQuery,
});

await this.add('VoIP_TeamCollab_FreeSwitch_Password', '', {
type: 'password',
secret: true,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_FreeSwitch_Timeout', 3000, {
type: 'int',
public: true,
invalidValue: 3000,
enableQuery,
});

await this.add('VoIP_TeamCollab_FreeSwitch_WebSocket_Path', '', {
type: 'string',
public: true,
invalidValue: '',
enableQuery,
});
});
},
);
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
"date.js": "~0.3.3",
"debug": "~4.3.7",
"dompurify": "^3.2.6",
"drachtio-srf": "patch:drachtio-srf@npm%3A5.0.12#~/.yarn/patches/drachtio-srf-npm-5.0.12-b0b1afaad6.patch",
"ejson": "^2.2.3",
"emailreplyparser": "^0.0.5",
"emoji-toolkit": "^7.0.1",
Expand Down
28 changes: 25 additions & 3 deletions apps/meteor/server/services/media-call/service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { api, ServiceClassInternal, type IMediaCallService } from '@rocket.chat/core-services';
import { api, ServiceClassInternal, type IMediaCallService, Authorization } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { callServer, type IMediaCallServerSettings } from '@rocket.chat/media-calls';
Expand All @@ -15,6 +15,8 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall
constructor() {
super();
callServer.emitter.on('signalRequest', ({ toUid, signal }) => this.sendSignal(toUid, signal));
callServer.emitter.on('callUpdated', (params) => api.broadcast('media-call.updated', params));
this.onEvent('media-call.updated', (params) => callServer.receiveCallUpdate(params));

this.onEvent('watch.settings', async ({ setting }): Promise<void> => {
if (setting._id.startsWith('VoIP_TeamCollab_')) {
Expand Down Expand Up @@ -70,8 +72,8 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall

private getMediaServerSettings(): IMediaCallServerSettings {
const enabled = settings.get<boolean>('VoIP_TeamCollab_Enabled') ?? false;
const sipEnabled = false;
const forceSip = false;
const sipEnabled = enabled && (settings.get<boolean>('VoIP_TeamCollab_SIP_Integration_Enabled') ?? false);
const forceSip = sipEnabled && (settings.get<boolean>('VoIP_TeamCollab_SIP_Integration_For_Internal_Calls') ?? false);

return {
enabled,
Expand All @@ -81,10 +83,30 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall
},
sip: {
enabled: sipEnabled,
drachtio: {
host: settings.get<string>('VoIP_TeamCollab_Drachtio_Host') ?? '',
port: settings.get<number>('VoIP_TeamCollab_Drachtio_Port') ?? 9022,
secret: settings.get<string>('VoIP_TeamCollab_Drachtio_Password') ?? '',
},
sipServer: {
host: settings.get<string>('VoIP_TeamCollab_SIP_Server_Host') ?? '',
port: settings.get<number>('VoIP_TeamCollab_SIP_Server_Port') ?? 5080,
},
},
permissionCheck: (uid, callType) => this.userHasMediaCallPermission(uid, callType),
};
}

private async userHasMediaCallPermission(uid: IUser['_id'], callType: 'internal' | 'external' | 'any'): Promise<boolean> {
if (callType === 'any') {
return Authorization.hasAtLeastOnePermission(uid, ['allow-internal-voice-calls', 'allow-external-voice-calls']);
}

const permissionId = `allow-${callType}-voice-calls`;

return Authorization.hasPermission(uid, permissionId);
}

private async deserializeClientSignal(serialized: string): Promise<ClientMediaSignal> {
try {
const signal = JSON.parse(serialized);
Expand Down
3 changes: 2 additions & 1 deletion ee/packages/media-calls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@rocket.chat/emitter": "~0.31.25",
"@rocket.chat/logger": "workspace:^",
"@rocket.chat/media-signaling": "workspace:^",
"@rocket.chat/models": "workspace:^"
"@rocket.chat/models": "workspace:^",
"drachtio-srf": "patch:drachtio-srf@npm%3A5.0.12#~/.yarn/patches/drachtio-srf-npm-5.0.12-b0b1afaad6.patch"
}
}
2 changes: 2 additions & 0 deletions ee/packages/media-calls/src/base/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export abstract class BaseMediaCallAgent implements IMediaCallAgent {

public abstract onCallTransferred(callId: string): Promise<void>;

public abstract onDTMF(callId: string, dtmf: string, duration: number): Promise<void>;

protected async createOrUpdateChannel(call: IMediaCall, contractId: string): Promise<IMediaCallChannel> {
if (!contractId) {
throw new Error('error-invalid-contract');
Expand Down
7 changes: 7 additions & 0 deletions ee/packages/media-calls/src/base/BaseCallProvider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { IMediaCall } from '@rocket.chat/core-typings';
import type { ClientMediaSignalBody } from '@rocket.chat/media-signaling';

import { logger } from '../logger';

export class BaseCallProvider {
public get callId(): string {
return this.call._id;
}

constructor(public readonly call: IMediaCall) {}

public async reactToCallChanges(params: { dtmf?: ClientMediaSignalBody<'dtmf'> }): Promise<void> {
logger.debug({ msg: 'BaseCallProvider.reactToCallChanges', callId: this.callId, params });
}
}
6 changes: 4 additions & 2 deletions ee/packages/media-calls/src/definition/IMediaCallAgent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMediaCall, MediaCallActor, MediaCallActorType } from '@rocket.chat/core-typings';
import type { IMediaCall, MediaCallActor, MediaCallActorType, MediaCallContact } from '@rocket.chat/core-typings';
import type { CallRole } from '@rocket.chat/media-signaling';

export interface IMediaCallAgent {
Expand All @@ -18,7 +18,9 @@ export interface IMediaCallAgent {
/* Called when the sdp of the other actor is available, regardless of call state, or when this actor must provide an offer */
onRemoteDescriptionChanged(callId: string, negotiationId: string): Promise<void>;

onDTMF(callId: string, tone: string, duration: number): Promise<void>;

onCallTransferred(callId: string): Promise<void>;

getMyCallActor(call: IMediaCall): MediaCallActor;
getMyCallActor(call: IMediaCall): MediaCallContact;
}
16 changes: 15 additions & 1 deletion ee/packages/media-calls/src/definition/IMediaCallServer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { Emitter } from '@rocket.chat/emitter';
import type { ClientMediaSignal, ServerMediaSignal } from '@rocket.chat/media-signaling';
import type { ClientMediaSignal, ClientMediaSignalBody, ServerMediaSignal } from '@rocket.chat/media-signaling';

import type { InternalCallParams } from './common';

export type MediaCallServerEvents = {
callUpdated: { callId: string; dtmf?: ClientMediaSignalBody<'dtmf'> };
signalRequest: { toUid: IUser['_id']; signal: ServerMediaSignal };
};

Expand All @@ -18,17 +19,30 @@ export interface IMediaCallServerSettings {

sip: {
enabled: boolean;
drachtio: {
host: string;
port: number;
secret: string;
};
sipServer: {
host: string;
port: number;
};
};

permissionCheck: (uid: IUser['_id'], callType: 'internal' | 'external' | 'any') => Promise<boolean>;
}

export interface IMediaCallServer {
emitter: Emitter<MediaCallServerEvents>;

// functions that trigger events
sendSignal(toUid: IUser['_id'], signal: ServerMediaSignal): void;
reportCallUpdate(params: { callId: string; dtmf?: ClientMediaSignalBody<'dtmf'> }): void;

// functions that are run on events
receiveSignal(fromUid: IUser['_id'], signal: ClientMediaSignal): void;
receiveCallUpdate(params: { callId: string; dtmf?: ClientMediaSignalBody<'dtmf'> }): void;

// extra functions available to the service
hangupExpiredCalls(): Promise<void>;
Expand Down
12 changes: 2 additions & 10 deletions ee/packages/media-calls/src/definition/common.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import type {
AtLeast,
IMediaCall,
IUser,
MediaCallActorType,
MediaCallContact,
MediaCallSignedActor,
MediaCallSignedContact,
} from '@rocket.chat/core-typings';
import type { AtLeast, IMediaCall, IUser, MediaCallActorType, MediaCallContact, MediaCallSignedContact } from '@rocket.chat/core-typings';
import type { CallRejectedReason, CallService } from '@rocket.chat/media-signaling';

export type MinimalUserData = Pick<IUser, '_id' | 'username' | 'name' | 'freeSwitchExtension'>;
Expand All @@ -22,7 +14,7 @@ export type InternalCallParams = {
requestedCallId?: string;
requestedService?: CallService;
parentCallId?: string;
requestedBy?: MediaCallSignedActor;
requestedBy?: MediaCallSignedContact;
};

export type MediaCallHeader = AtLeast<IMediaCall, '_id' | 'caller' | 'callee'>;
Expand Down
Loading
Loading