From 5fddfcc99ea6d17eb63cd33772dc7388a1d7fba8 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 21 Oct 2024 10:27:29 -0300 Subject: [PATCH] refactor: Migrate more modules to TypeScript (#33636) --- .../startup/{responses.js => responses.ts} | 23 +- .../app/e2e/client/{events.js => events.ts} | 2 +- .../app/e2e/client/{helper.js => helper.ts} | 64 ++- ...hat.e2e.room.js => rocketchat.e2e.room.ts} | 110 ++-- apps/meteor/app/e2e/client/rocketchat.e2e.ts | 2 +- .../lib/{emojiCustom.js => emojiCustom.ts} | 80 +-- .../emoji-custom/client/lib/function-isSet.js | 9 - .../client/{emojiParser.js => emojiParser.ts} | 16 +- apps/meteor/app/emoji/lib/rocketchat.ts | 32 +- .../server/handler/{index.js => index.ts} | 18 +- .../localHandlers/{index.js => index.ts} | 0 .../peerHandlers/{index.js => index.ts} | 0 .../irc/server/servers/{index.js => index.ts} | 0 .../client/{OAuthProxy.js => OAuthProxy.ts} | 4 +- .../client/collections/LivechatInquiry.js | 3 - .../client/collections/LivechatInquiry.ts | 4 + .../client/lib/stream/queueManager.ts | 6 +- ...client.js => slackbridge_import.client.ts} | 2 +- .../client/lib/normalizeThreadTitle.ts | 4 +- .../ui-master/server/{index.js => index.ts} | 29 +- apps/meteor/app/ui-master/server/inject.ts | 4 +- .../client/{WebRTCClass.js => WebRTCClass.ts} | 473 +++++++++++------- apps/meteor/app/webrtc/client/adapter.js | 6 - apps/meteor/app/webrtc/client/adapter.ts | 7 + .../client/{screenShare.js => screenShare.ts} | 21 +- .../meteor/client/components/MarkdownText.tsx | 2 +- apps/meteor/client/definitions/global.d.ts | 75 +++ .../client/lib/utils/renderMessageEmoji.ts | 2 +- .../client/providers/OmnichannelProvider.tsx | 13 +- apps/meteor/client/views/meet/CallPage.tsx | 44 +- apps/meteor/definition/externals/global.d.ts | 24 - .../meteor/meteorhacks-inject-initial.d.ts | 1 + .../apps/storage/{index.js => index.ts} | 0 apps/meteor/package.json | 1 + packages/base64/src/base64.ts | 18 +- packages/ddp-client/src/types/streams.ts | 77 ++- yarn.lock | 18 + 37 files changed, 742 insertions(+), 452 deletions(-) rename apps/meteor/app/canned-responses/client/startup/{responses.js => responses.ts} (72%) rename apps/meteor/app/e2e/client/{events.js => events.ts} (83%) rename apps/meteor/app/e2e/client/{helper.js => helper.ts} (64%) rename apps/meteor/app/e2e/client/{rocketchat.e2e.room.js => rocketchat.e2e.room.ts} (88%) rename apps/meteor/app/emoji-custom/client/lib/{emojiCustom.js => emojiCustom.ts} (62%) delete mode 100644 apps/meteor/app/emoji-custom/client/lib/function-isSet.js rename apps/meteor/app/emoji/client/{emojiParser.js => emojiParser.ts} (74%) rename apps/meteor/app/federation/server/handler/{index.js => index.ts} (80%) rename apps/meteor/app/irc/server/irc-bridge/localHandlers/{index.js => index.ts} (100%) rename apps/meteor/app/irc/server/irc-bridge/peerHandlers/{index.js => index.ts} (100%) rename apps/meteor/app/irc/server/servers/{index.js => index.ts} (100%) rename apps/meteor/app/lib/client/{OAuthProxy.js => OAuthProxy.ts} (86%) delete mode 100644 apps/meteor/app/livechat/client/collections/LivechatInquiry.js create mode 100644 apps/meteor/app/livechat/client/collections/LivechatInquiry.ts rename apps/meteor/app/slackbridge/client/{slackbridge_import.client.js => slackbridge_import.client.ts} (85%) rename apps/meteor/app/ui-master/server/{index.js => index.ts} (81%) rename apps/meteor/app/webrtc/client/{WebRTCClass.js => WebRTCClass.ts} (66%) delete mode 100644 apps/meteor/app/webrtc/client/adapter.js create mode 100644 apps/meteor/app/webrtc/client/adapter.ts rename apps/meteor/app/webrtc/client/{screenShare.js => screenShare.ts} (77%) rename apps/meteor/ee/server/apps/storage/{index.js => index.ts} (100%) diff --git a/apps/meteor/app/canned-responses/client/startup/responses.js b/apps/meteor/app/canned-responses/client/startup/responses.ts similarity index 72% rename from apps/meteor/app/canned-responses/client/startup/responses.js rename to apps/meteor/app/canned-responses/client/startup/responses.ts index 595945283261..6d761adb890d 100644 --- a/apps/meteor/app/canned-responses/client/startup/responses.js +++ b/apps/meteor/app/canned-responses/client/startup/responses.ts @@ -6,13 +6,6 @@ import { settings } from '../../../settings/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; import { CannedResponse } from '../collections/CannedResponse'; -const events = { - changed: ({ type, ...response }) => { - CannedResponse.upsert({ _id: response._id }, response); - }, - removed: (response) => CannedResponse.remove({ _id: response._id }), -}; - Meteor.startup(() => { Tracker.autorun(async (c) => { if (!Meteor.userId()) { @@ -27,12 +20,24 @@ Meteor.startup(() => { Tracker.afterFlush(() => { try { // TODO: check options - sdk.stream('canned-responses', ['canned-responses'], (response, options) => { + sdk.stream('canned-responses', ['canned-responses'], (...[response, options]) => { const { agentsId } = options || {}; if (Array.isArray(agentsId) && !agentsId.includes(Meteor.userId())) { return; } - events[response.type](response); + + switch (response.type) { + case 'changed': { + const { type, ...fields } = response; + CannedResponse.upsert({ _id: response._id }, fields); + break; + } + + case 'removed': { + CannedResponse.remove({ _id: response._id }); + break; + } + } }); } catch (error) { console.log(error); diff --git a/apps/meteor/app/e2e/client/events.js b/apps/meteor/app/e2e/client/events.ts similarity index 83% rename from apps/meteor/app/e2e/client/events.js rename to apps/meteor/app/e2e/client/events.ts index c59b20594b85..9ccef3d7b28d 100644 --- a/apps/meteor/app/e2e/client/events.js +++ b/apps/meteor/app/e2e/client/events.ts @@ -3,5 +3,5 @@ import { Accounts } from 'meteor/accounts-base'; import { e2e } from './rocketchat.e2e'; Accounts.onLogout(() => { - e2e.stopClient(); + void e2e.stopClient(); }); diff --git a/apps/meteor/app/e2e/client/helper.js b/apps/meteor/app/e2e/client/helper.ts similarity index 64% rename from apps/meteor/app/e2e/client/helper.js rename to apps/meteor/app/e2e/client/helper.ts index 25d9e9407801..66ca3bf1cc2e 100644 --- a/apps/meteor/app/e2e/client/helper.js +++ b/apps/meteor/app/e2e/client/helper.ts @@ -1,24 +1,20 @@ import { Random } from '@rocket.chat/random'; import ByteBuffer from 'bytebuffer'; -// eslint-disable-next-line no-proto -const StaticArrayBufferProto = new ArrayBuffer().__proto__; - -export function toString(thing) { +export function toString(thing: any) { if (typeof thing === 'string') { return thing; } - // eslint-disable-next-line new-cap - return new ByteBuffer.wrap(thing).toString('binary'); + + return ByteBuffer.wrap(thing).toString('binary'); } -export function toArrayBuffer(thing) { +export function toArrayBuffer(thing: any) { if (thing === undefined) { return undefined; } - if (thing === Object(thing)) { - // eslint-disable-next-line no-proto - if (thing.__proto__ === StaticArrayBufferProto) { + if (typeof thing === 'object') { + if (Object.getPrototypeOf(thing) === ArrayBuffer.prototype) { return thing; } } @@ -26,11 +22,11 @@ export function toArrayBuffer(thing) { if (typeof thing !== 'string') { throw new Error(`Tried to convert a non-string of type ${typeof thing} to an array buffer`); } - // eslint-disable-next-line new-cap - return new ByteBuffer.wrap(thing, 'binary').toArrayBuffer(); + + return ByteBuffer.wrap(thing, 'binary').toArrayBuffer(); } -export function joinVectorAndEcryptedData(vector, encryptedData) { +export function joinVectorAndEcryptedData(vector: any, encryptedData: any) { const cipherText = new Uint8Array(encryptedData); const output = new Uint8Array(vector.length + cipherText.length); output.set(vector, 0); @@ -38,30 +34,30 @@ export function joinVectorAndEcryptedData(vector, encryptedData) { return output; } -export function splitVectorAndEcryptedData(cipherText) { +export function splitVectorAndEcryptedData(cipherText: any) { const vector = cipherText.slice(0, 16); const encryptedData = cipherText.slice(16); return [vector, encryptedData]; } -export async function encryptRSA(key, data) { +export async function encryptRSA(key: any, data: any) { return crypto.subtle.encrypt({ name: 'RSA-OAEP' }, key, data); } -export async function encryptAES(vector, key, data) { +export async function encryptAES(vector: any, key: any, data: any) { return crypto.subtle.encrypt({ name: 'AES-CBC', iv: vector }, key, data); } -export async function encryptAESCTR(vector, key, data) { +export async function encryptAESCTR(vector: any, key: any, data: any) { return crypto.subtle.encrypt({ name: 'AES-CTR', counter: vector, length: 64 }, key, data); } -export async function decryptRSA(key, data) { +export async function decryptRSA(key: any, data: any) { return crypto.subtle.decrypt({ name: 'RSA-OAEP' }, key, data); } -export async function decryptAES(vector, key, data) { +export async function decryptAES(vector: any, key: any, data: any) { return crypto.subtle.decrypt({ name: 'AES-CBC', iv: vector }, key, data); } @@ -86,54 +82,54 @@ export async function generateRSAKey() { ); } -export async function exportJWKKey(key) { +export async function exportJWKKey(key: any) { return crypto.subtle.exportKey('jwk', key); } -export async function importRSAKey(keyData, keyUsages = ['encrypt', 'decrypt']) { +export async function importRSAKey(keyData: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { return crypto.subtle.importKey( - 'jwk', + 'jwk' as any, keyData, { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' }, - }, + } as any, true, keyUsages, ); } -export async function importAESKey(keyData, keyUsages = ['encrypt', 'decrypt']) { +export async function importAESKey(keyData: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { return crypto.subtle.importKey('jwk', keyData, { name: 'AES-CBC' }, true, keyUsages); } -export async function importRawKey(keyData, keyUsages = ['deriveKey']) { +export async function importRawKey(keyData: any, keyUsages: ReadonlyArray = ['deriveKey']) { return crypto.subtle.importKey('raw', keyData, { name: 'PBKDF2' }, false, keyUsages); } -export async function deriveKey(salt, baseKey, keyUsages = ['encrypt', 'decrypt']) { +export async function deriveKey(salt: any, baseKey: any, keyUsages: ReadonlyArray = ['encrypt', 'decrypt']) { const iterations = 1000; const hash = 'SHA-256'; return crypto.subtle.deriveKey({ name: 'PBKDF2', salt, iterations, hash }, baseKey, { name: 'AES-CBC', length: 256 }, true, keyUsages); } -export async function readFileAsArrayBuffer(file) { - return new Promise((resolve, reject) => { +export async function readFileAsArrayBuffer(file: any) { + return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = function (evt) { - resolve(evt.target.result); + reader.onload = (evt) => { + resolve(evt.target?.result); }; - reader.onerror = function (evt) { + reader.onerror = (evt) => { reject(evt); }; reader.readAsArrayBuffer(file); }); } -export async function generateMnemonicPhrase(n, sep = ' ') { +export async function generateMnemonicPhrase(n: any, sep = ' ') { const { default: wordList } = await import('./wordList'); const result = new Array(n); let len = wordList.length; @@ -147,14 +143,14 @@ export async function generateMnemonicPhrase(n, sep = ' ') { return result.join(sep); } -export async function createSha256HashFromText(data) { +export async function createSha256HashFromText(data: any) { const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data)); return Array.from(new Uint8Array(hash)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } -export async function sha256HashFromArrayBuffer(arrayBuffer) { +export async function sha256HashFromArrayBuffer(arrayBuffer: any) { const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', arrayBuffer))); return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); } diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts similarity index 88% rename from apps/meteor/app/e2e/client/rocketchat.e2e.room.js rename to apps/meteor/app/e2e/client/rocketchat.e2e.room.ts index 4c9de837dce0..ff1841a7ef86 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.js +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts @@ -34,7 +34,7 @@ import { e2e } from './rocketchat.e2e'; const KEY_ID = Symbol('keyID'); const PAUSED = Symbol('PAUSED'); -const permitedMutations = { +const permitedMutations: any = { [E2ERoomState.NOT_STARTED]: [E2ERoomState.ESTABLISHING, E2ERoomState.DISABLED, E2ERoomState.KEYS_RECEIVED], [E2ERoomState.READY]: [E2ERoomState.DISABLED, E2ERoomState.CREATING_KEYS, E2ERoomState.WAITING_KEYS], [E2ERoomState.ERROR]: [E2ERoomState.KEYS_RECEIVED, E2ERoomState.NOT_STARTED], @@ -49,7 +49,7 @@ const permitedMutations = { ], }; -const filterMutation = (currentState, nextState) => { +const filterMutation = (currentState: any, nextState: any): any => { if (currentState === nextState) { return nextState === E2ERoomState.ERROR; } @@ -66,11 +66,29 @@ const filterMutation = (currentState, nextState) => { }; export class E2ERoom extends Emitter { - state = undefined; + state: any = undefined; - [PAUSED] = undefined; + [PAUSED]: boolean | undefined = undefined; - constructor(userId, room) { + [KEY_ID]: any; + + userId: any; + + roomId: any; + + typeOfRoom: any; + + roomKeyId: any; + + groupSessionKey: any; + + oldKeys: any; + + sessionKeyExportedString: string | undefined; + + sessionKeyExported: any; + + constructor(userId: any, room: any) { super(); this.userId = userId; @@ -93,11 +111,11 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.NOT_STARTED); } - log(...msg) { + log(...msg: unknown[]) { log(`E2E ROOM { state: ${this.state}, rid: ${this.roomId} }`, ...msg); } - error(...msg) { + error(...msg: unknown[]) { logError(`E2E ROOM { state: ${this.state}, rid: ${this.roomId} }`, ...msg); } @@ -109,7 +127,7 @@ export class E2ERoom extends Emitter { return this.state; } - setState(requestedState) { + setState(requestedState: any) { const currentState = this.state; const nextState = filterMutation(currentState, requestedState); @@ -120,7 +138,7 @@ export class E2ERoom extends Emitter { this.state = nextState; this.log(currentState, '->', nextState); - this.emit('STATE_CHANGED', currentState, nextState, this); + this.emit('STATE_CHANGED', currentState); this.emit(nextState, this); } @@ -160,7 +178,7 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.KEYS_RECEIVED); } - async shouldConvertSentMessages(message) { + async shouldConvertSentMessages(message: any) { if (!this.isReady() || this[PAUSED]) { return false; } @@ -197,7 +215,7 @@ export class E2ERoom extends Emitter { async decryptSubscription() { const subscription = Subscriptions.findOne({ rid: this.roomId }); - if (subscription.lastMessage?.t !== 'e2e') { + if (subscription?.lastMessage?.t !== 'e2e') { this.log('decryptSubscriptions nothing to do'); return; } @@ -245,7 +263,7 @@ export class E2ERoom extends Emitter { this.log('decryptOldRoomKeys Done'); } - async exportOldRoomKeys(oldKeys) { + async exportOldRoomKeys(oldKeys: any) { this.log('exportOldRoomKeys starting'); if (!oldKeys || oldKeys.length === 0) { this.log('exportOldRoomKeys nothing to do'); @@ -294,7 +312,7 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.ESTABLISHING); try { - const groupKey = Subscriptions.findOne({ rid: this.roomId }).E2EKey; + const groupKey = Subscriptions.findOne({ rid: this.roomId })?.E2EKey; if (groupKey) { await this.importGroupKey(groupKey); this.setState(E2ERoomState.READY); @@ -307,7 +325,7 @@ export class E2ERoom extends Emitter { } try { - const room = ChatRoom.findOne({ _id: this.roomId }); + const room = ChatRoom.findOne({ _id: this.roomId })!; // Only room creator can set keys for room if (!room.e2eKeyId && this.userShouldCreateKeys(room)) { this.setState(E2ERoomState.CREATING_KEYS); @@ -325,7 +343,7 @@ export class E2ERoom extends Emitter { } } - userShouldCreateKeys(room) { + userShouldCreateKeys(room: any) { // On DMs, we'll allow any user to set the keys if (room.t === 'd') { return true; @@ -334,15 +352,15 @@ export class E2ERoom extends Emitter { return room.u._id === this.userId; } - isSupportedRoomType(type) { + isSupportedRoomType(type: any) { return roomCoordinator.getRoomDirectives(type).allowRoomSettingChange({}, RoomSettingsEnum.E2E); } - async decryptSessionKey(key) { + async decryptSessionKey(key: any) { return importAESKey(JSON.parse(await this.exportSessionKey(key))); } - async exportSessionKey(key) { + async exportSessionKey(key: any) { key = key.slice(12); key = Base64.decode(key); @@ -350,7 +368,7 @@ export class E2ERoom extends Emitter { return toString(decryptedKey); } - async importGroupKey(groupKey) { + async importGroupKey(groupKey: any) { this.log('Importing room key ->', this.roomId); // Get existing group key // const keyID = groupKey.slice(0, 12); @@ -374,7 +392,7 @@ export class E2ERoom extends Emitter { // Import session key for use. try { - const key = await importAESKey(JSON.parse(this.sessionKeyExportedString)); + const key = await importAESKey(JSON.parse(this.sessionKeyExportedString!)); // Key has been obtained. E2E is now in session. this.groupSessionKey = key; } catch (error) { @@ -402,8 +420,8 @@ export class E2ERoom extends Emitter { await sdk.rest.post('/v1/e2e.updateGroupKey', { rid: this.roomId, uid: this.userId, - key: await this.encryptGroupKeyForParticipant(e2e.publicKey), - }); + key: await this.encryptGroupKeyForParticipant(e2e.publicKey!), + } as any); await this.encryptKeyForOtherParticipants(); } catch (error) { this.error('Error exporting group key: ', error); @@ -434,7 +452,7 @@ export class E2ERoom extends Emitter { } } - onRoomKeyReset(keyID) { + onRoomKeyReset(keyID: any) { this.log(`Room keyID was reset. New keyID: ${keyID} Previous keyID: ${this.keyID}`); this.setState(E2ERoomState.WAITING_KEYS); this.keyID = keyID; @@ -455,10 +473,10 @@ export class E2ERoom extends Emitter { return; } - const usersSuggestedGroupKeys = { [this.roomId]: [] }; + const usersSuggestedGroupKeys = { [this.roomId]: [] as any[] }; for await (const user of users) { - const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e.public_key); - const oldKeys = await this.encryptOldKeysForParticipant(user.e2e.public_key, decryptedOldGroupKeys); + const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e!.public_key!); + const oldKeys = await this.encryptOldKeysForParticipant(user.e2e?.public_key, decryptedOldGroupKeys); usersSuggestedGroupKeys[this.roomId].push({ _id: user._id, key: encryptedGroupKey, ...(oldKeys && { oldKeys }) }); } @@ -469,7 +487,7 @@ export class E2ERoom extends Emitter { } } - async encryptOldKeysForParticipant(public_key, oldRoomKeys) { + async encryptOldKeysForParticipant(publicKey: any, oldRoomKeys: any) { if (!oldRoomKeys || oldRoomKeys.length === 0) { return; } @@ -477,7 +495,7 @@ export class E2ERoom extends Emitter { let userKey; try { - userKey = await importRSAKey(JSON.parse(public_key), ['encrypt']); + userKey = await importRSAKey(JSON.parse(publicKey), ['encrypt']); } catch (error) { return this.error('Error importing user key: ', error); } @@ -499,10 +517,10 @@ export class E2ERoom extends Emitter { } } - async encryptGroupKeyForParticipant(public_key) { + async encryptGroupKeyForParticipant(publicKey: string) { let userKey; try { - userKey = await importRSAKey(JSON.parse(public_key), ['encrypt']); + userKey = await importRSAKey(JSON.parse(publicKey), ['encrypt']); } catch (error) { return this.error('Error importing user key: ', error); } @@ -519,7 +537,7 @@ export class E2ERoom extends Emitter { } // Encrypts files before upload. I/O is in arraybuffers. - async encryptFile(file) { + async encryptFile(file: any) { // if (!this.isSupportedRoomType(this.typeOfRoom)) { // return; // } @@ -554,7 +572,7 @@ export class E2ERoom extends Emitter { } // Decrypt uploaded encrypted files. I/O is in arraybuffers. - async decryptFile(file, key, iv) { + async decryptFile(file: any, key: any, iv: any) { const ivArray = Base64.decode(iv); const cryptoKey = await window.crypto.subtle.importKey('jwk', key, { name: 'AES-CTR' }, true, ['encrypt', 'decrypt']); @@ -562,7 +580,7 @@ export class E2ERoom extends Emitter { } // Encrypts messages - async encryptText(data) { + async encryptText(data: any) { const vector = crypto.getRandomValues(new Uint8Array(16)); try { @@ -575,7 +593,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of content - async encryptMessageContent(contentToBeEncrypted) { + async encryptMessageContent(contentToBeEncrypted: any) { const data = new TextEncoder().encode(EJSON.stringify(contentToBeEncrypted)); return { @@ -585,7 +603,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of content - async encryptMessage(message) { + async encryptMessage(message: any) { const { msg, attachments, ...rest } = message; const content = await this.encryptMessageContent({ msg, attachments }); @@ -599,7 +617,7 @@ export class E2ERoom extends Emitter { } // Helper function for encryption of messages - encrypt(message) { + encrypt(message: any) { if (!this.isSupportedRoomType(this.typeOfRoom)) { return; } @@ -610,7 +628,7 @@ export class E2ERoom extends Emitter { const ts = new Date(); - const data = new TextEncoder('UTF-8').encode( + const data = new TextEncoder().encode( EJSON.stringify({ _id: message._id, text: message.msg, @@ -622,7 +640,7 @@ export class E2ERoom extends Emitter { return this.encryptText(data); } - async decryptContent(data) { + async decryptContent(data: any) { if (data.content && data.content.algorithm === 'rc.v1.aes-sha2') { const content = await this.decrypt(data.content.ciphertext); Object.assign(data, content); @@ -632,7 +650,7 @@ export class E2ERoom extends Emitter { } // Decrypt messages - async decryptMessage(message) { + async decryptMessage(message: any) { if (message.t !== 'e2e' || message.e2e === 'done') { return message; } @@ -653,12 +671,12 @@ export class E2ERoom extends Emitter { }; } - async doDecrypt(vector, key, cipherText) { + async doDecrypt(vector: any, key: any, cipherText: any) { const result = await decryptAES(vector, key, cipherText); return EJSON.parse(new TextDecoder('UTF-8').decode(new Uint8Array(result))); } - async decrypt(message) { + async decrypt(message: any) { const keyID = message.slice(0, 12); message = message.slice(12); @@ -666,7 +684,7 @@ export class E2ERoom extends Emitter { let oldKey = ''; if (keyID !== this.keyID) { - const oldRoomKey = this.oldKeys?.find((key) => key.e2eKeyId === keyID); + const oldRoomKey = this.oldKeys?.find((key: any) => key.e2eKeyId === keyID); // Messages already contain a keyID stored with them // That means that if we cannot find a keyID for the key the message has preppended to // The message is indecipherable. @@ -691,21 +709,21 @@ export class E2ERoom extends Emitter { } } - provideKeyToUser(keyId) { + provideKeyToUser(keyId: any) { if (this.keyID !== keyId) { return; } - this.encryptKeyForOtherParticipants(); + void this.encryptKeyForOtherParticipants(); this.setState(E2ERoomState.READY); } - onStateChange(cb) { + onStateChange(cb: any) { this.on('STATE_CHANGED', cb); return () => this.off('STATE_CHANGED', cb); } - async encryptGroupKeyForParticipantsWaitingForTheKeys(users) { + async encryptGroupKeyForParticipantsWaitingForTheKeys(users: any[]) { if (!this.isReady()) { return; } diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 3b2fd01621e4..824afc3aa2d5 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -44,7 +44,7 @@ import { import { log, logError } from './logger'; import { E2ERoom } from './rocketchat.e2e.room'; -import './events.js'; +import './events'; let failedToDecodeKey = false; diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts similarity index 62% rename from apps/meteor/app/emoji-custom/client/lib/emojiCustom.js rename to apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts index 64f1df9bd932..ee186ebd4e15 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.ts @@ -1,3 +1,4 @@ +import type { IEmoji } from '@rocket.chat/core-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; @@ -6,52 +7,61 @@ import { emoji, updateRecent } from '../../../emoji/client'; import { CachedCollectionManager } from '../../../ui-cached-collection/client'; import { getURL } from '../../../utils/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; -import { isSetNotNull } from './function-isSet'; -export const getEmojiUrlFromName = function (name, extension) { +const isSetNotNull = (fn: () => unknown) => { + let value; + try { + value = fn(); + } catch (e) { + value = null; + } + return value !== null && value !== undefined; +}; + +const getEmojiUrlFromName = (name: string, extension: string) => { if (name == null) { return; } - const key = `emoji_random_${name}`; + const key = `emoji_random_${name}` as const; - const random = isSetNotNull(() => Session.keys[key]) ? Session.keys[key] : 0; + const random = (Session as unknown as { keys: Record }).keys[key] ?? 0; return getURL(`/emoji-custom/${encodeURIComponent(name)}.${extension}?_dc=${random}`); }; -export const deleteEmojiCustom = function (emojiData) { +export const deleteEmojiCustom = (emojiData: IEmoji) => { delete emoji.list[`:${emojiData.name}:`]; const arrayIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(emojiData.name); if (arrayIndex !== -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.splice(arrayIndex, 1); } - const arrayIndexList = emoji.packages.emojiCustom.list.indexOf(`:${emojiData.name}:`); + const arrayIndexList = emoji.packages.emojiCustom.list?.indexOf(`:${emojiData.name}:`) ?? -1; if (arrayIndexList !== -1) { - emoji.packages.emojiCustom.list.splice(arrayIndexList, 1); + emoji.packages.emojiCustom.list?.splice(arrayIndexList, 1); } - if (isSetNotNull(() => emojiData.aliases)) { + if (emojiData.aliases) { for (const alias of emojiData.aliases) { delete emoji.list[`:${alias}:`]; - const aliasIndex = emoji.packages.emojiCustom.list.indexOf(`:${alias}:`); + const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1; if (aliasIndex !== -1) { - emoji.packages.emojiCustom.list.splice(aliasIndex, 1); + emoji.packages.emojiCustom.list?.splice(aliasIndex, 1); } } } - updateRecent('rocket'); + updateRecent(['rocket']); }; -export const updateEmojiCustom = function (emojiData) { +export const updateEmojiCustom = (emojiData: IEmoji) => { const previousExists = isSetNotNull(() => emojiData.previousName); const currentAliases = isSetNotNull(() => emojiData.aliases); if (previousExists && isSetNotNull(() => emoji.list[`:${emojiData.previousName}:`].aliases)) { - for (const alias of emoji.list[`:${emojiData.previousName}:`].aliases) { + for (const alias of emoji.list[`:${emojiData.previousName}:`].aliases ?? []) { delete emoji.list[`:${alias}:`]; - const aliasIndex = emoji.packages.emojiCustom.list.indexOf(`:${alias}:`); + const aliasIndex = emoji.packages.emojiCustom.list?.indexOf(`:${alias}:`) ?? -1; if (aliasIndex !== -1) { - emoji.packages.emojiCustom.list.splice(aliasIndex, 1); + emoji.packages.emojiCustom.list?.splice(aliasIndex, 1); } } } @@ -61,9 +71,9 @@ export const updateEmojiCustom = function (emojiData) { if (arrayIndex !== -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.splice(arrayIndex, 1); } - const arrayIndexList = emoji.packages.emojiCustom.list.indexOf(`:${emojiData.previousName}:`); + const arrayIndexList = emoji.packages.emojiCustom.list?.indexOf(`:${emojiData.previousName}:`) ?? -1; if (arrayIndexList !== -1) { - emoji.packages.emojiCustom.list.splice(arrayIndexList, 1); + emoji.packages.emojiCustom.list?.splice(arrayIndexList, 1); } delete emoji.list[`:${emojiData.previousName}:`]; } @@ -71,23 +81,24 @@ export const updateEmojiCustom = function (emojiData) { const categoryIndex = emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(`${emojiData.name}`); if (categoryIndex === -1) { emoji.packages.emojiCustom.emojisByCategory.rocket.push(`${emojiData.name}`); - emoji.packages.emojiCustom.list.push(`:${emojiData.name}:`); + emoji.packages.emojiCustom.list?.push(`:${emojiData.name}:`); } emoji.list[`:${emojiData.name}:`] = Object.assign({ emojiPackage: 'emojiCustom' }, emoji.list[`:${emojiData.name}:`], emojiData); if (currentAliases) { for (const alias of emojiData.aliases) { - emoji.packages.emojiCustom.list.push(`:${alias}:`); - emoji.list[`:${alias}:`] = {}; - emoji.list[`:${alias}:`].emojiPackage = 'emojiCustom'; - emoji.list[`:${alias}:`].aliasOf = emojiData.name; + emoji.packages.emojiCustom.list?.push(`:${alias}:`); + emoji.list[`:${alias}:`] = { + emojiPackage: 'emojiCustom', + aliasOf: emojiData.name, + }; } } - updateRecent('rocket'); + updateRecent(['rocket']); }; -const customRender = (html) => { - const emojisMatchGroup = emoji.packages.emojiCustom.list.map(escapeRegExp).join('|'); +const customRender = (html: string) => { + const emojisMatchGroup = emoji.packages.emojiCustom.list?.map(escapeRegExp).join('|'); if (emojisMatchGroup !== emoji.packages.emojiCustom._regexpSignature) { emoji.packages.emojiCustom._regexpSignature = emojisMatchGroup; emoji.packages.emojiCustom._regexp = new RegExp( @@ -96,22 +107,22 @@ const customRender = (html) => { ); } - html = html.replace(emoji.packages.emojiCustom._regexp, (shortname) => { - if (typeof shortname === 'undefined' || shortname === '' || emoji.packages.emojiCustom.list.indexOf(shortname) === -1) { + html = html.replace(emoji.packages.emojiCustom._regexp!, (shortname) => { + if (typeof shortname === 'undefined' || shortname === '' || (emoji.packages.emojiCustom.list?.indexOf(shortname) ?? -1) === -1) { return shortname; } let emojiAlias = shortname.replace(/:/g, ''); let dataCheck = emoji.list[shortname]; - if (dataCheck.hasOwnProperty('aliasOf')) { + if (dataCheck.aliasOf) { emojiAlias = dataCheck.aliasOf; dataCheck = emoji.list[`:${emojiAlias}:`]; } return `${shortname}`; }); @@ -125,7 +136,7 @@ emoji.packages.emojiCustom = { list: [], _regexpSignature: null, _regexp: null, - + emojisByCategory: {}, render: customRender, renderPicker: customRender, }; @@ -135,16 +146,15 @@ Meteor.startup(() => try { const { emojis: { update: emojis }, - } = await sdk.rest.get('/v1/emoji-custom.list'); + } = await sdk.rest.get('/v1/emoji-custom.list', { query: '' }); emoji.packages.emojiCustom.emojisByCategory = { rocket: [] }; for (const currentEmoji of emojis) { emoji.packages.emojiCustom.emojisByCategory.rocket.push(currentEmoji.name); - emoji.packages.emojiCustom.list.push(`:${currentEmoji.name}:`); - emoji.list[`:${currentEmoji.name}:`] = currentEmoji; - emoji.list[`:${currentEmoji.name}:`].emojiPackage = 'emojiCustom'; + emoji.packages.emojiCustom.list?.push(`:${currentEmoji.name}:`); + emoji.list[`:${currentEmoji.name}:`] = { ...currentEmoji, emojiPackage: 'emojiCustom' } as any; for (const alias of currentEmoji.aliases) { - emoji.packages.emojiCustom.list.push(`:${alias}:`); + emoji.packages.emojiCustom.list?.push(`:${alias}:`); emoji.list[`:${alias}:`] = { emojiPackage: 'emojiCustom', aliasOf: currentEmoji.name, diff --git a/apps/meteor/app/emoji-custom/client/lib/function-isSet.js b/apps/meteor/app/emoji-custom/client/lib/function-isSet.js deleted file mode 100644 index 0ccf1abe02ab..000000000000 --- a/apps/meteor/app/emoji-custom/client/lib/function-isSet.js +++ /dev/null @@ -1,9 +0,0 @@ -export const isSetNotNull = function (fn) { - let value; - try { - value = fn(); - } catch (e) { - value = null; - } - return value !== null && value !== undefined; -}; diff --git a/apps/meteor/app/emoji/client/emojiParser.js b/apps/meteor/app/emoji/client/emojiParser.ts similarity index 74% rename from apps/meteor/app/emoji/client/emojiParser.js rename to apps/meteor/app/emoji/client/emojiParser.ts index 0b3b722aaebd..08ec99b06958 100644 --- a/apps/meteor/app/emoji/client/emojiParser.js +++ b/apps/meteor/app/emoji/client/emojiParser.ts @@ -3,10 +3,8 @@ import { emoji } from './lib'; /** * emojiParser is a function that will replace emojis - * @param {{ html: string }} message - The message object - * @return {{ html: string }} */ -export const emojiParser = ({ html }) => { +export const emojiParser = (html: string) => { html = html.trim(); // ' to apostrophe (') for emojis such as :') @@ -28,8 +26,12 @@ export const emojiParser = ({ html }) => { let hasText = false; if (!isIE11) { - const filter = (node) => { - if (node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('emojione') || node.classList.contains('emoji'))) { + const isElement = (node: Node): node is Element => node.nodeType === Node.ELEMENT_NODE; + + const isTextNode = (node: Node): node is Text => node.nodeType === Node.TEXT_NODE; + + const filter = (node: Node) => { + if (isElement(node) && (node.classList.contains('emojione') || node.classList.contains('emoji'))) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; @@ -38,7 +40,7 @@ export const emojiParser = ({ html }) => { const walker = document.createTreeWalker(checkEmojiOnly, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, filter); while (walker.nextNode()) { - if (walker.currentNode.nodeType === Node.TEXT_NODE && walker.currentNode.nodeValue.trim() !== '') { + if (isTextNode(walker.currentNode) && walker.currentNode.nodeValue.trim() !== '') { hasText = true; break; } @@ -60,5 +62,5 @@ export const emojiParser = ({ html }) => { // line breaks '
' back to '
' html = html.replace(/
/g, '
'); - return { html }; + return html; }; diff --git a/apps/meteor/app/emoji/lib/rocketchat.ts b/apps/meteor/app/emoji/lib/rocketchat.ts index 49d6ffbe41aa..f5d33cce3de0 100644 --- a/apps/meteor/app/emoji/lib/rocketchat.ts +++ b/apps/meteor/app/emoji/lib/rocketchat.ts @@ -9,6 +9,9 @@ export type EmojiPackage = { renderPicker: (emojiToRender: string) => string | undefined; ascii?: boolean; sprites?: unknown; + list?: string[]; + _regexpSignature?: string | null; + _regexp?: RegExp | null; }; export type EmojiPackages = { @@ -16,14 +19,25 @@ export type EmojiPackages = { [key: string]: EmojiPackage; }; list: { - [key: keyof NonNullable]: { - category: string; - emojiPackage: string; - shortnames: string[]; - uc_base: string; - uc_greedy: string; - uc_match: string; - uc_output: string; - }; + [key: keyof NonNullable]: + | { + category: string; + emojiPackage: string; + shortnames: string[]; + uc_base: string; + uc_greedy: string; + uc_match: string; + uc_output: string; + aliases?: string[]; + aliasOf?: undefined; + extension?: string; + } + | { + emojiPackage: string; + aliasOf: string; + extension?: undefined; + aliases?: undefined; + shortnames?: undefined; + }; }; }; diff --git a/apps/meteor/app/federation/server/handler/index.js b/apps/meteor/app/federation/server/handler/index.ts similarity index 80% rename from apps/meteor/app/federation/server/handler/index.js rename to apps/meteor/app/federation/server/handler/index.ts index c5b19856f19f..f7a3ae53ec29 100644 --- a/apps/meteor/app/federation/server/handler/index.js +++ b/apps/meteor/app/federation/server/handler/index.ts @@ -5,7 +5,7 @@ import { federationRequestToPeer } from '../lib/http'; import { isFederationEnabled } from '../lib/isFederationEnabled'; import { clientLogger } from '../lib/logger'; -export async function federationSearchUsers(query) { +export async function federationSearchUsers(query: string) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -23,7 +23,7 @@ export async function federationSearchUsers(query) { return users; } -export async function getUserByUsername(query) { +export async function getUserByUsername(query: string) { if (!isFederationEnabled()) { throw disabled('client.searchUsers'); } @@ -41,7 +41,13 @@ export async function getUserByUsername(query) { return user; } -export async function requestEventsFromLatest(domain, fromDomain, contextType, contextQuery, latestEventIds) { +export async function requestEventsFromLatest( + domain: string, + fromDomain: string, + contextType: unknown, + contextQuery: unknown, + latestEventIds: unknown, +) { if (!isFederationEnabled()) { throw disabled('client.requestEventsFromLatest'); } @@ -64,7 +70,7 @@ export async function requestEventsFromLatest(domain, fromDomain, contextType, c }); } -export async function dispatchEvents(domains, events) { +export async function dispatchEvents(domains: string[], events: unknown[]) { if (!isFederationEnabled()) { throw disabled('client.dispatchEvents'); } @@ -80,11 +86,11 @@ export async function dispatchEvents(domains, events) { } } -export async function dispatchEvent(domains, event) { +export async function dispatchEvent(domains: string[], event: unknown) { await dispatchEvents([...new Set(domains)], [event]); } -export async function getUpload(domain, fileId) { +export async function getUpload(domain: string, fileId: string) { const { data: { upload, buffer }, } = await federationRequestToPeer('GET', domain, `/api/v1/federation.uploads?${qs.stringify({ upload_id: fileId })}`); diff --git a/apps/meteor/app/irc/server/irc-bridge/localHandlers/index.js b/apps/meteor/app/irc/server/irc-bridge/localHandlers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/irc-bridge/localHandlers/index.js rename to apps/meteor/app/irc/server/irc-bridge/localHandlers/index.ts diff --git a/apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.js b/apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.js rename to apps/meteor/app/irc/server/irc-bridge/peerHandlers/index.ts diff --git a/apps/meteor/app/irc/server/servers/index.js b/apps/meteor/app/irc/server/servers/index.ts similarity index 100% rename from apps/meteor/app/irc/server/servers/index.js rename to apps/meteor/app/irc/server/servers/index.ts diff --git a/apps/meteor/app/lib/client/OAuthProxy.js b/apps/meteor/app/lib/client/OAuthProxy.ts similarity index 86% rename from apps/meteor/app/lib/client/OAuthProxy.js rename to apps/meteor/app/lib/client/OAuthProxy.ts index a5035783c676..ec9143528fc9 100644 --- a/apps/meteor/app/lib/client/OAuthProxy.js +++ b/apps/meteor/app/lib/client/OAuthProxy.ts @@ -6,12 +6,12 @@ OAuth.launchLogin = ((func) => function (options) { const proxy = settings.get('Accounts_OAuth_Proxy_services').replace(/\s/g, '').split(','); if (proxy.includes(options.loginService)) { - const redirect_uri = options.loginUrl.match(/(&redirect_uri=)([^&]+|$)/)[2]; + const redirectUri = options.loginUrl.match(/(&redirect_uri=)([^&]+|$)/)?.[2]; options.loginUrl = options.loginUrl.replace( /(&redirect_uri=)([^&]+|$)/, `$1${encodeURIComponent(settings.get('Accounts_OAuth_Proxy_host'))}/oauth_redirect`, ); - options.loginUrl = options.loginUrl.replace(/(&state=)([^&]+|$)/, `$1${redirect_uri}!$2`); + options.loginUrl = options.loginUrl.replace(/(&state=)([^&]+|$)/, `$1${redirectUri}!$2`); options.loginUrl = `${settings.get('Accounts_OAuth_Proxy_host')}/redirect/${encodeURIComponent(options.loginUrl)}`; } diff --git a/apps/meteor/app/livechat/client/collections/LivechatInquiry.js b/apps/meteor/app/livechat/client/collections/LivechatInquiry.js deleted file mode 100644 index c43a9cb31ca5..000000000000 --- a/apps/meteor/app/livechat/client/collections/LivechatInquiry.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -export const LivechatInquiry = new Mongo.Collection(null); diff --git a/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts b/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts new file mode 100644 index 000000000000..16b9533d1649 --- /dev/null +++ b/apps/meteor/app/livechat/client/collections/LivechatInquiry.ts @@ -0,0 +1,4 @@ +import type { ILivechatInquiryRecord } from '@rocket.chat/core-typings'; +import { Mongo } from 'meteor/mongo'; + +export const LivechatInquiry = new Mongo.Collection(null); diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts index 5ba2cf0d9791..c6a671e2883d 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts @@ -30,12 +30,12 @@ const events = { const invalidateRoomQueries = async (rid: string) => { await queryClient.invalidateQueries(['rooms', { reference: rid, type: 'l' }]); - await queryClient.removeQueries(['rooms', rid]); - await queryClient.removeQueries(['/v1/rooms.info', rid]); + queryClient.removeQueries(['rooms', rid]); + queryClient.removeQueries(['/v1/rooms.info', rid]); }; const removeInquiry = async (inquiry: ILivechatInquiryRecord) => { - await LivechatInquiry.remove(inquiry._id); + LivechatInquiry.remove(inquiry._id); return queryClient.invalidateQueries(['rooms', { reference: inquiry.rid, type: 'l' }]); }; diff --git a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js b/apps/meteor/app/slackbridge/client/slackbridge_import.client.ts similarity index 85% rename from apps/meteor/app/slackbridge/client/slackbridge_import.client.js rename to apps/meteor/app/slackbridge/client/slackbridge_import.client.ts index eebc07ddb72d..2138fc2a35f9 100644 --- a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js +++ b/apps/meteor/app/slackbridge/client/slackbridge_import.client.ts @@ -1,7 +1,7 @@ import { settings } from '../../settings/client'; import { slashCommands } from '../../utils/client/slashCommand'; -settings.onload('SlackBridge_Enabled', (key, value) => { +settings.onload('SlackBridge_Enabled', (_key, value) => { if (value) { slashCommands.add({ command: 'slackbridge-import', diff --git a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts index 70a2a6008e56..c3d10b531b6b 100644 --- a/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts +++ b/apps/meteor/app/threads/client/lib/normalizeThreadTitle.ts @@ -2,7 +2,7 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; -import { emojiParser } from '../../../emoji/client/emojiParser.js'; +import { emojiParser } from '../../../emoji/client/emojiParser'; import { filterMarkdown } from '../../../markdown/lib/markdown'; import { MentionsParser } from '../../../mentions/lib/MentionsParser'; import { Users } from '../../../models/client'; @@ -26,7 +26,7 @@ export function normalizeThreadTitle({ ...message }: Readonly) { userTemplate: ({ label }) => ` ${label} `, roomTemplate: ({ prefix, mention }) => `${prefix} ${mention} `, }); - const { html } = emojiParser({ html: filteredMessage }); + const html = emojiParser(filteredMessage); return instance.parse({ ...message, msg: filteredMessage, html }).html; } diff --git a/apps/meteor/app/ui-master/server/index.js b/apps/meteor/app/ui-master/server/index.ts similarity index 81% rename from apps/meteor/app/ui-master/server/index.js rename to apps/meteor/app/ui-master/server/index.ts index 2d4f3cc7de56..b4f15f211abc 100644 --- a/apps/meteor/app/ui-master/server/index.js +++ b/apps/meteor/app/ui-master/server/index.ts @@ -1,3 +1,4 @@ +import type { ISettingColor } from '@rocket.chat/core-typings'; import { Settings } from '@rocket.chat/models'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; @@ -15,11 +16,11 @@ export * from './inject'; Meteor.startup(() => { Tracker.autorun(() => { - const injections = Object.values(headInjections.all()); + const injections = Object.values(headInjections.all()).filter((injection): injection is NonNullable => !!injection); Inject.rawModHtml('headInjections', applyHeadInjections(injections)); }); - settings.watch('Default_Referrer_Policy', (value) => { + settings.watch('Default_Referrer_Policy', (value) => { if (!value) { return injectIntoHead('noreferrer', ''); } @@ -40,7 +41,7 @@ Meteor.startup(() => { ); } - settings.watch('Assets_SvgFavicon_Enable', (value) => { + settings.watch('Assets_SvgFavicon_Enable', (value) => { const standardFavicons = ` `; @@ -56,7 +57,7 @@ Meteor.startup(() => { } }); - settings.watch('theme-color-sidebar-background', (value) => { + settings.watch('theme-color-sidebar-background', (value) => { const escapedValue = escapeHTML(value); injectIntoHead( 'theme-color-sidebar-background', @@ -64,7 +65,7 @@ Meteor.startup(() => { ); }); - settings.watch('Site_Name', (value = 'Rocket.Chat') => { + settings.watch('Site_Name', (value = 'Rocket.Chat') => { const escapedValue = escapeHTML(value); injectIntoHead( 'Site_Name', @@ -74,7 +75,7 @@ Meteor.startup(() => { ); }); - settings.watch('Meta_language', (value = '') => { + settings.watch('Meta_language', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead( 'Meta_language', @@ -82,27 +83,27 @@ Meteor.startup(() => { ); }); - settings.watch('Meta_robots', (value = '') => { + settings.watch('Meta_robots', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_robots', ``); }); - settings.watch('Meta_msvalidate01', (value = '') => { + settings.watch('Meta_msvalidate01', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_msvalidate01', ``); }); - settings.watch('Meta_google-site-verification', (value = '') => { + settings.watch('Meta_google-site-verification', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_google-site-verification', ``); }); - settings.watch('Meta_fb_app_id', (value = '') => { + settings.watch('Meta_fb_app_id', (value = '') => { const escapedValue = escapeHTML(value); injectIntoHead('Meta_fb_app_id', ``); }); - settings.watch('Meta_custom', (value = '') => { + settings.watch('Meta_custom', (value = '') => { injectIntoHead('Meta_custom', value); }); @@ -127,7 +128,7 @@ const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => { // const variables = RocketChat.models.Settings.findOne({_id:'theme-custom-variables'}, {fields: { value: 1}}); const colors = await Settings.find({ _id: /theme-color-rc/i }, { projection: { value: 1, editor: 1 } }).toArray(); const css = colors - .filter((color) => color && color.value) + .filter((color): color is ISettingColor => !!color?.value) .map(({ _id, value, editor }) => { if (editor === 'expression') { return `--${_id.replace('theme-color-', '')}: var(--${value});`; @@ -138,7 +139,7 @@ const renderDynamicCssList = withDebouncing({ wait: 500 })(async () => { injectIntoBody('dynamic-variables', ``); }); -renderDynamicCssList(); +await renderDynamicCssList(); settings.watchByRegex(/theme-color-rc/i, renderDynamicCssList); @@ -160,4 +161,4 @@ injectIntoBody( `, ); -injectIntoBody('icons', await Assets.getTextAsync('public/icons.svg')); +injectIntoBody('icons', (await Assets.getTextAsync('public/icons.svg')) ?? ''); diff --git a/apps/meteor/app/ui-master/server/inject.ts b/apps/meteor/app/ui-master/server/inject.ts index 1e00a0e47433..47b63db4bb3f 100644 --- a/apps/meteor/app/ui-master/server/inject.ts +++ b/apps/meteor/app/ui-master/server/inject.ts @@ -16,7 +16,7 @@ type Injection = tag: string; }; -export const headInjections = new ReactiveDict(); +export const headInjections = new ReactiveDict>(); const callback: NextHandleFunction = (req, res, next) => { if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') { @@ -32,7 +32,7 @@ const callback: NextHandleFunction = (req, res, next) => { return; } - const injection = headInjections.get(pathname.replace(/^\//, '').split('_')[0]) as Injection | undefined; + const injection = headInjections.get(pathname.replace(/^\//, '').split('_')[0]); if (!injection || typeof injection === 'string') { next(); diff --git a/apps/meteor/app/webrtc/client/WebRTCClass.js b/apps/meteor/app/webrtc/client/WebRTCClass.ts similarity index 66% rename from apps/meteor/app/webrtc/client/WebRTCClass.js rename to apps/meteor/app/webrtc/client/WebRTCClass.ts index eb9772966575..6ce3b5cc442f 100644 --- a/apps/meteor/app/webrtc/client/WebRTCClass.js +++ b/apps/meteor/app/webrtc/client/WebRTCClass.ts @@ -1,3 +1,5 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; @@ -13,33 +15,128 @@ import { t } from '../../utils/lib/i18n'; import { WEB_RTC_EVENTS } from '../lib/constants'; import { ChromeScreenShare } from './screenShare'; -class WebRTCTransportClass extends Emitter { - constructor(webrtcInstance) { +// FIXME: there is a mix of obsolete definitions and incorrect field assignments + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface RTCPeerConnection { + /** @deprecated non-standard */ + createdAt: number; + /** @deprecated non-standard */ + remoteMedia: MediaStreamConstraints; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface RTCOfferOptions { + /** @deprecated non-standard */ + mandatory?: unknown; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaStream { + /** @deprecated non-standard */ + volume?: GainNode; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaStreamConstraints { + /** @deprecated non-standard */ + desktop?: boolean; + } + + /** @deprecated browser-specific global */ + const chrome: { + webstore: { + install(url: string, onSuccess: () => void, onError: (error: any) => void): void; + }; + }; + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + rocketchatscreenshare?: unknown; + audioContext?: AudioContext; + } +} + +type EventData, TType> = Extract< + StreamerCallbackArgs, + [type: TType, data: any] +>[1]; + +type StatusData = EventData<'notify-room', `${string}/webrtc`, 'status'>; +type CallData = EventData<'notify-room-users', `${string}/webrtc`, 'call'>; +type CandidateData = EventData<'notify-user', `${string}/webrtc`, 'candidate'>; +type DescriptionData = EventData<'notify-user', `${string}/webrtc`, 'description'>; +type JoinData = EventData<'notify-user', `${string}/webrtc`, 'join'>; + +type RemoteItem = { + id: string; + url: MediaStream; + state: RTCIceConnectionState; + stateText?: string; + connected?: boolean; +}; + +type RemoteConnection = { + id: string; + media: MediaStreamConstraints; +}; + +class WebRTCTransportClass extends Emitter<{ + status: StatusData; + call: CallData; + candidate: CandidateData; + description: DescriptionData; + join: JoinData; +}> { + public debug = false; + + constructor(public webrtcInstance: WebRTCClass) { super(); - this.debug = false; - this.webrtcInstance = webrtcInstance; sdk.stream('notify-room', [`${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { this.log('WebRTCTransportClass - onRoom', type, data); this.emit(type, data); }); } - log(...args) { + log(...args: unknown[]) { if (this.debug === true) { console.log(...args); } } - onUserStream(type, data) { + onUserStream(type: 'candidate', data: CandidateData): void; + + onUserStream(type: 'description', data: DescriptionData): void; + + onUserStream(type: 'join', data: JoinData): void; + + onUserStream( + ...[type, data]: + | [type: 'candidate', data: CandidateData] + | [type: 'description', data: DescriptionData] + | [type: 'join', data: JoinData] + ) { if (data.room !== this.webrtcInstance.room) { return; } this.log('WebRTCTransportClass - onUser', type, data); - this.emit(type, data); + + switch (type) { + case 'candidate': + this.emit('candidate', data); + break; + case 'description': + this.emit('description', data); + break; + case 'join': + this.emit('join', data); + break; + } } - startCall(data) { + startCall(data: CallData) { this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId); sdk.publish('notify-room-users', [ `${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`, @@ -53,7 +150,7 @@ class WebRTCTransportClass extends Emitter { ]); } - joinCall(data) { + joinCall(data: JoinData) { this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId); if (data.monitor === true) { sdk.publish('notify-user', [ @@ -80,75 +177,105 @@ class WebRTCTransportClass extends Emitter { } } - sendCandidate(data) { + sendCandidate(data: CandidateData) { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendCandidate', data); sdk.publish('notify-user', [`${data.to}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.CANDIDATE, data]); } - sendDescription(data) { + sendDescription(data: DescriptionData) { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendDescription', data); sdk.publish('notify-user', [`${data.to}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.DESCRIPTION, data]); } - sendStatus(data) { + sendStatus(data: StatusData) { this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room); data.from = this.webrtcInstance.selfId; sdk.publish('notify-room', [`${this.webrtcInstance.room}/${WEB_RTC_EVENTS.WEB_RTC}`, WEB_RTC_EVENTS.STATUS, data]); } - onRemoteCall(fn) { + onRemoteCall(fn: (data: CallData) => void) { return this.on(WEB_RTC_EVENTS.CALL, fn); } - onRemoteJoin(fn) { + onRemoteJoin(fn: (data: JoinData) => void) { return this.on(WEB_RTC_EVENTS.JOIN, fn); } - onRemoteCandidate(fn) { + onRemoteCandidate(fn: (data: CandidateData) => void) { return this.on(WEB_RTC_EVENTS.CANDIDATE, fn); } - onRemoteDescription(fn) { + onRemoteDescription(fn: (data: DescriptionData) => void) { return this.on(WEB_RTC_EVENTS.DESCRIPTION, fn); } - onRemoteStatus(fn) { + onRemoteStatus(fn: (data: StatusData) => void) { return this.on(WEB_RTC_EVENTS.STATUS, fn); } } class WebRTCClass { - /* - @param seldId {String} - @param room {String} - */ + transport: WebRTCTransportClass; + + config: { iceServers: RTCIceServer[] }; + + debug: boolean; + + TransportClass: typeof WebRTCTransportClass; + + peerConnections: Record = {}; + + remoteItems: ReactiveVar; + + remoteItemsById: ReactiveVar>; + + callInProgress: ReactiveVar; + + audioEnabled: ReactiveVar; + + videoEnabled: ReactiveVar; + + overlayEnabled: ReactiveVar; + + screenShareEnabled: ReactiveVar; + + localUrl: ReactiveVar; - constructor(selfId, room, autoAccept = false) { + active: boolean; + + remoteMonitoring: boolean; + + monitor: boolean; + + navigator: string | undefined; + + screenShareAvailable: boolean; + + media: MediaStreamConstraints; + + constructor(public selfId: string, public room: string, public autoAccept = false) { this.config = { iceServers: [], }; this.debug = false; this.TransportClass = WebRTCTransportClass; - this.selfId = selfId; - this.room = room; - let servers = settings.get('WebRTC_Servers'); + let servers = settings.get('WebRTC_Servers'); if (servers && servers.trim() !== '') { servers = servers.replace(/\s/g, ''); - servers = servers.split(','); - servers.forEach((server) => { - server = server.split('@'); - const serverConfig = { - urls: server.pop(), + servers.split(',').forEach((server) => { + const parts = server.split('@'); + const serverConfig: RTCIceServer = { + urls: parts.pop()!, }; - if (server.length === 1) { - server = server[0].split(':'); - serverConfig.username = decodeURIComponent(server[0]); - serverConfig.credential = decodeURIComponent(server[1]); + if (parts.length === 1) { + const [username, credential] = parts[0].split(':'); + serverConfig.username = decodeURIComponent(username); + serverConfig.credential = decodeURIComponent(credential); } this.config.iceServers.push(serverConfig); }); @@ -161,11 +288,10 @@ class WebRTCClass { this.videoEnabled = new ReactiveVar(false); this.overlayEnabled = new ReactiveVar(false); this.screenShareEnabled = new ReactiveVar(false); - this.localUrl = new ReactiveVar(); + this.localUrl = new ReactiveVar(undefined); this.active = false; this.remoteMonitoring = false; this.monitor = false; - this.autoAccept = autoAccept; this.navigator = undefined; const userAgent = navigator.userAgent.toLocaleLowerCase(); @@ -179,7 +305,7 @@ class WebRTCClass { this.navigator = 'safari'; } - this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator); + this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator!); this.media = { video: true, audio: true, @@ -194,18 +320,41 @@ class WebRTCClass { setInterval(this.checkPeerConnections.bind(this), 1000); } - onUserStream(...args) { - return this.transport.onUserStream(...args); + onUserStream(type: 'candidate', data: CandidateData): void; + + onUserStream(type: 'description', data: DescriptionData): void; + + onUserStream(type: 'join', data: JoinData): void; + + onUserStream( + ...[type, data]: + | [type: 'candidate', data: CandidateData] + | [type: 'description', data: DescriptionData] + | [type: 'join', data: JoinData] + ) { + switch (type) { + case 'candidate': + this.transport.onUserStream('candidate', data); + break; + + case 'description': + this.transport.onUserStream('description', data); + break; + + case 'join': + this.transport.onUserStream('join', data); + break; + } } - log(...args) { + log(...args: unknown[]) { if (this.debug === true) { - console.log.apply(console, args); + console.log(...args); } } - onError(...args) { - console.error.apply(console, args); + onError(...args: unknown[]) { + console.error(...args); } checkPeerConnections() { @@ -221,13 +370,13 @@ class WebRTCClass { } updateRemoteItems() { - const items = []; - const itemsById = {}; + const items: RemoteItem[] = []; + const itemsById: Record = {}; const { peerConnections } = this; Object.entries(peerConnections).forEach(([id, peerConnection]) => { peerConnection.getRemoteStreams().forEach((remoteStream) => { - const item = { + const item: RemoteItem = { id, url: remoteStream, state: peerConnection.iceConnectionState, @@ -266,9 +415,9 @@ class WebRTCClass { if (this.active !== true || this.monitor === true || this.remoteMonitoring === true) { return; } - const remoteConnections = []; + const remoteConnections: RemoteConnection[] = []; const { peerConnections } = this; - Object.keys(peerConnections).entries(([id, { remoteMedia: media }]) => { + Object.entries(peerConnections).forEach(([id, { remoteMedia: media }]) => { remoteConnections.push({ id, media, @@ -281,16 +430,9 @@ class WebRTCClass { }); } - /* - @param data {Object} - from {String} - media {Object} - remoteConnections {Array[Object]} - id {String} - media {Object} - */ + callInProgressTimeout: ReturnType | undefined = undefined; - onRemoteStatus(data) { + onRemoteStatus(data: StatusData) { // this.log(onRemoteStatus, arguments); this.callInProgress.set(true); clearTimeout(this.callInProgressTimeout); @@ -300,7 +442,7 @@ class WebRTCClass { } const remoteConnections = [ { - id: data.from, + id: data.from!, media: data.media, }, ...data.remoteConnections, @@ -317,11 +459,7 @@ class WebRTCClass { }); } - /* - @param id {String} - */ - - getPeerConnection(id) { + getPeerConnection(id: string) { if (this.peerConnections[id] != null) { return this.peerConnections[id]; } @@ -386,8 +524,10 @@ class WebRTCClass { return peerConnection; } - _getUserMedia(media, onSuccess, onError) { - const onSuccessLocal = (stream) => { + audioContext: AudioContext | undefined; + + _getUserMedia(media: MediaStreamConstraints, onSuccess: (stream: MediaStream) => void, onError: (error?: any) => void) { + const onSuccessLocal = (stream: MediaStream) => { if (AudioContext && stream.getAudioTracks().length > 0) { const audioContext = new AudioContext(); const source = audioContext.createMediaStreamSource(stream); @@ -403,23 +543,24 @@ class WebRTCClass { } onSuccess(stream); }; - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + + if (navigator.mediaDevices?.getUserMedia) { return navigator.mediaDevices.getUserMedia(media).then(onSuccessLocal).catch(onError); } - navigator.getUserMedia(media, onSuccessLocal, onError); + navigator.getUserMedia?.(media, onSuccessLocal, onError); } - getUserMedia(media, onSuccess, onError = this.onError) { + getUserMedia(media: MediaStreamConstraints, onSuccess: (stream: MediaStream) => void, onError: (error: any) => void = this.onError) { if (media.desktop !== true) { - this._getUserMedia(media, onSuccess, onError); + void this._getUserMedia(media, onSuccess, onError); return; } if (this.screenShareAvailable !== true) { console.log('Screen share is not avaliable'); return; } - const getScreen = (audioStream) => { + const getScreen = (audioStream?: MediaStream) => { const refresh = function () { imperativeModal.open({ component: GenericModal, @@ -466,7 +607,7 @@ class WebRTCClass { return onError(false); } - const getScreenSuccess = (stream) => { + const getScreenSuccess = (stream: MediaStream) => { if (audioStream != null) { stream.addTrack(audioStream.getAudioTracks()[0]); } @@ -480,9 +621,9 @@ class WebRTCClass { mediaSource: 'window', }, }; - this._getUserMedia(media, getScreenSuccess, onError); + void this._getUserMedia(media, getScreenSuccess, onError); } else { - ChromeScreenShare.getSourceId(this.navigator, (id) => { + ChromeScreenShare.getSourceId(this.navigator!, (id) => { media = { audio: false, video: { @@ -494,21 +635,21 @@ class WebRTCClass { }, }, }; - this._getUserMedia(media, getScreenSuccess, onError); + void this._getUserMedia(media, getScreenSuccess, onError); }); } }; if (this.navigator === 'firefox' || media.audio == null || media.audio === false) { getScreen(); } else { - const getAudioSuccess = (audioStream) => { + const getAudioSuccess = (audioStream: MediaStream) => { getScreen(audioStream); }; const getAudioError = () => { getScreen(); }; - this._getUserMedia( + void this._getUserMedia( { audio: media.audio, }, @@ -518,37 +659,29 @@ class WebRTCClass { } } - /* - @param callback {Function} - */ - - getLocalUserMedia(callback, ...args) { + getLocalUserMedia(callback: (...args: any[]) => void, ...args: unknown[]) { this.log('getLocalUserMedia', [callback, ...args]); if (this.localStream != null) { return callback(null, this.localStream); } - const onSuccess = (stream) => { + const onSuccess = (stream: MediaStream) => { this.localStream = stream; !this.audioEnabled.get() && this.disableAudio(); !this.videoEnabled.get() && this.disableVideo(); this.localUrl.set(stream); const { peerConnections } = this; Object.entries(peerConnections).forEach(([, peerConnection]) => peerConnection.addStream(stream)); - document.querySelector('video#localVideo').srcObject = stream; + document.querySelector('video#localVideo')!.srcObject = stream; callback(null, this.localStream); }; - const onError = (error) => { + const onError = (error: any) => { callback(false); this.onError(error); }; this.getUserMedia(this.media, onSuccess, onError); } - /* - @param id {String} - */ - - stopPeerConnection = (id) => { + stopPeerConnection = (id: string) => { const peerConnection = this.peerConnections[id]; if (peerConnection == null) { return; @@ -563,7 +696,7 @@ class WebRTCClass { Object.keys(peerConnections).forEach(this.stopPeerConnection); - window.audioContext && window.audioContext.close(); + void window.audioContext?.close(); // FIXME: probably should be `this.audioContext` } setAudioEnabled(enabled = true) { @@ -590,6 +723,8 @@ class WebRTCClass { return this.enableAudio(); } + localStream: MediaStream | undefined; + setVideoEnabled(enabled = true) { if (this.localStream != null) { this.localStream.getVideoTracks().forEach((video) => { @@ -649,13 +784,7 @@ class WebRTCClass { this.stopAllPeerConnections(); } - /* - @param media {Object} - audio {Boolean} - video {Boolean} - */ - - startCall(media = {}, ...args) { + startCall(media: MediaStreamConstraints = {}, ...args: unknown[]) { this.log('startCall', [media, ...args]); this.media = media; this.getLocalUserMedia(() => { @@ -666,7 +795,7 @@ class WebRTCClass { }); } - startCallAsMonitor(media = {}, ...args) { + startCallAsMonitor(media: MediaStreamConstraints = {}, ...args: unknown[]) { this.log('startCallAsMonitor', [media, ...args]); this.media = media; this.active = true; @@ -677,16 +806,7 @@ class WebRTCClass { }); } - /* - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - */ - - onRemoteCall(data) { + onRemoteCall(data: CallData) { if (this.autoAccept === true) { setTimeout(() => { this.joinCall({ @@ -700,31 +820,31 @@ class WebRTCClass { const user = Meteor.users.findOne(data.from); let fromUsername = undefined; - if (user && user.username) { + if (user?.username) { fromUsername = user.username; } const subscription = ChatSubscription.findOne({ rid: data.room, - }); + })!; let icon; let title; if (data.monitor === true) { - icon = 'eye'; + icon = 'eye' as const; title = t('WebRTC_monitor_call_from_%s', fromUsername); } else if (subscription && subscription.t === 'd') { - if (data.media && data.media.video) { - icon = 'videocam'; + if (data.media?.video) { + icon = 'video' as const; title = t('WebRTC_direct_video_call_from_%s', fromUsername); } else { - icon = 'phone'; + icon = 'phone' as const; title = t('WebRTC_direct_audio_call_from_%s', fromUsername); } - } else if (data.media && data.media.video) { - icon = 'videocam'; + } else if (data.media?.video) { + icon = 'video' as const; title = t('WebRTC_group_video_call_from_%s', subscription.name); } else { - icon = 'phone'; + icon = 'phone' as const; title = t('WebRTC_group_audio_call_from_%s', subscription.name); } @@ -737,7 +857,7 @@ class WebRTCClass { cancelText: t('No'), children: t('Do_you_want_to_accept'), onConfirm: () => { - goToRoomById(data.room); + void goToRoomById(data.room!); return this.joinCall({ to: data.from, monitor: data.monitor, @@ -750,32 +870,22 @@ class WebRTCClass { }); } - /* - @param data {Object} - to {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - */ - - joinCall(data = {}, ...args) { + joinCall(data: JoinData = {}, ...args: unknown[]) { data.media = this.media; this.log('joinCall', [data, ...args]); this.getLocalUserMedia(() => { - this.remoteMonitoring = data.monitor; + this.remoteMonitoring = data.monitor!; this.active = true; this.transport.joinCall(data); }); } - onRemoteJoin(data, ...args) { + onRemoteJoin(data: JoinData, ...args: unknown[]) { if (this.active !== true) { return; } this.log('onRemoteJoin', [data, ...args]); - let peerConnection = this.getPeerConnection(data.from); + let peerConnection = this.getPeerConnection(data.from!); // needsRefresh = false // if peerConnection.iceConnectionState isnt 'new' @@ -785,18 +895,18 @@ class WebRTCClass { // # if peerConnection.signalingState is "have-local-offer" or needsRefresh - if (peerConnection.signalingState !== 'checking') { - this.stopPeerConnection(data.from); - peerConnection = this.getPeerConnection(data.from); + if ((peerConnection.signalingState as RTCSignalingState | 'checking') !== 'checking') { + this.stopPeerConnection(data.from!); + peerConnection = this.getPeerConnection(data.from!); } if (peerConnection.iceConnectionState !== 'new') { return; } - peerConnection.remoteMedia = data.media; + peerConnection.remoteMedia = data.media!; if (this.localStream) { peerConnection.addStream(this.localStream); } - const onOffer = (offer) => { + const onOffer: RTCSessionDescriptionCallback = (offer) => { const onLocalDescription = () => { this.transport.sendDescription({ to: data.from, @@ -810,39 +920,39 @@ class WebRTCClass { }); }; - peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); + void peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); }; if (data.monitor === true) { - peerConnection.createOffer(onOffer, this.onError, { + void peerConnection.createOffer(onOffer, this.onError, { mandatory: { - OfferToReceiveAudio: data.media.audio, - OfferToReceiveVideo: data.media.video, + OfferToReceiveAudio: data.media?.audio, + OfferToReceiveVideo: data.media?.video, }, }); } else { - peerConnection.createOffer(onOffer, this.onError); + void peerConnection.createOffer(onOffer, this.onError); } } - onRemoteOffer(data, ...args) { + onRemoteOffer(data: Omit, ...args: unknown[]) { if (this.active !== true) { return; } this.log('onRemoteOffer', [data, ...args]); - let peerConnection = this.getPeerConnection(data.from); + let peerConnection = this.getPeerConnection(data.from!); if (['have-local-offer', 'stable'].includes(peerConnection.signalingState) && peerConnection.createdAt < data.ts) { - this.stopPeerConnection(data.from); - peerConnection = this.getPeerConnection(data.from); + this.stopPeerConnection(data.from!); + peerConnection = this.getPeerConnection(data.from!); } if (peerConnection.iceConnectionState !== 'new') { return; } - peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + void peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); try { if (this.localStream) { @@ -852,7 +962,7 @@ class WebRTCClass { console.log(error); } - const onAnswer = (answer) => { + const onAnswer: RTCSessionDescriptionCallback = (answer) => { const onLocalDescription = () => { this.transport.sendDescription({ to: data.from, @@ -865,20 +975,13 @@ class WebRTCClass { }); }; - peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); + void peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); }; - peerConnection.createAnswer(onAnswer, this.onError); + void peerConnection.createAnswer(onAnswer, this.onError); } - /* - @param data {Object} - to {String} - from {String} - candidate {RTCIceCandidate JSON encoded} - */ - - onRemoteCandidate(data, ...args) { + onRemoteCandidate(data: CandidateData, ...args: unknown[]) { if (this.active !== true) { return; } @@ -886,32 +989,19 @@ class WebRTCClass { return; } this.log('onRemoteCandidate', [data, ...args]); - const peerConnection = this.getPeerConnection(data.from); + const peerConnection = this.getPeerConnection(data.from!); if ( peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed' ) { - peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); + void peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); } - document.querySelector('video#remoteVideo').srcObject = this.remoteItems.get()[0]?.url; + document.querySelector('video#remoteVideo')!.srcObject = this.remoteItems.get()[0]?.url; } - /* - @param data {Object} - to {String} - from {String} - type {String} [offer, answer] - description {RTCSessionDescription JSON encoded} - ts {Integer} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - */ - - onRemoteDescription(data, ...args) { + onRemoteDescription(data: DescriptionData, ...args: unknown[]) { if (this.active !== true) { return; } @@ -919,7 +1009,7 @@ class WebRTCClass { return; } this.log('onRemoteDescription', [data, ...args]); - const peerConnection = this.getPeerConnection(data.from); + const peerConnection = this.getPeerConnection(data.from!); if (data.type === 'offer') { peerConnection.remoteMedia = data.media; this.onRemoteOffer({ @@ -928,17 +1018,19 @@ class WebRTCClass { description: data.description, }); } else { - peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + void peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); } } } const WebRTC = new (class { + instancesByRoomId: Record = {}; + constructor() { this.instancesByRoomId = {}; } - getInstanceByRoomId(rid, visitorId = null) { + getInstanceByRoomId(rid: IRoom['_id'], visitorId: string | null = null) { let enabled = false; if (!visitorId) { const subscription = ChatSubscription.findOne({ rid }); @@ -956,17 +1048,17 @@ const WebRTC = new (class { enabled = settings.get('WebRTC_Enable_Channel'); break; case 'l': - enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; } } else { - enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; + enabled = settings.get('Omnichannel_call_provider') === 'WebRTC'; } enabled = enabled && settings.get('WebRTC_Enabled'); if (enabled === false) { return; } if (this.instancesByRoomId[rid] == null) { - let uid = Meteor.userId(); + let uid = Meteor.userId()!; let autoAccept = false; if (visitorId) { uid = visitorId; @@ -980,13 +1072,26 @@ const WebRTC = new (class { Meteor.startup(() => { Tracker.autorun(() => { - if (Meteor.userId()) { - sdk.stream('notify-user', [`${Meteor.userId()}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { + const uid = Meteor.userId(); + + if (uid) { + sdk.stream('notify-user', [`${uid}/${WEB_RTC_EVENTS.WEB_RTC}`], (type, data) => { if (data.room == null) { return; } const webrtc = WebRTC.getInstanceByRoomId(data.room); - webrtc.onUserStream(type, data); + + switch (type) { + case 'candidate': + webrtc?.onUserStream('candidate', data); + break; + case 'description': + webrtc?.onUserStream('description', data); + break; + case 'join': + webrtc?.onUserStream('join', data); + break; + } }); } }); diff --git a/apps/meteor/app/webrtc/client/adapter.js b/apps/meteor/app/webrtc/client/adapter.js deleted file mode 100644 index 972e68e09f3c..000000000000 --- a/apps/meteor/app/webrtc/client/adapter.js +++ /dev/null @@ -1,6 +0,0 @@ -window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; -window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; -window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate; -window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; -window.AudioContext = window.AudioContext || window.mozAudioContext || window.webkitAudioContext; -navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; diff --git a/apps/meteor/app/webrtc/client/adapter.ts b/apps/meteor/app/webrtc/client/adapter.ts new file mode 100644 index 000000000000..f98ae7815c05 --- /dev/null +++ b/apps/meteor/app/webrtc/client/adapter.ts @@ -0,0 +1,7 @@ +// FIXME: probably outdated +window.RTCPeerConnection = window.RTCPeerConnection ?? window.mozRTCPeerConnection ?? window.webkitRTCPeerConnection; +window.RTCSessionDescription = window.RTCSessionDescription ?? window.mozRTCSessionDescription ?? window.webkitRTCSessionDescription; +window.RTCIceCandidate = window.RTCIceCandidate ?? window.mozRTCIceCandidate ?? window.webkitRTCIceCandidate; +window.RTCSessionDescription = window.RTCSessionDescription ?? window.mozRTCSessionDescription ?? window.webkitRTCSessionDescription; +window.AudioContext = window.AudioContext ?? window.mozAudioContext ?? window.webkitAudioContext; +navigator.getUserMedia = navigator.getUserMedia ?? navigator.mozGetUserMedia ?? navigator.webkitGetUserMedia; diff --git a/apps/meteor/app/webrtc/client/screenShare.js b/apps/meteor/app/webrtc/client/screenShare.ts similarity index 77% rename from apps/meteor/app/webrtc/client/screenShare.js rename to apps/meteor/app/webrtc/client/screenShare.ts index ecb6f93a51d0..3fac4a05bfea 100644 --- a/apps/meteor/app/webrtc/client/screenShare.js +++ b/apps/meteor/app/webrtc/client/screenShare.ts @@ -1,18 +1,20 @@ import { fireGlobalEvent } from '../../../client/lib/utils/fireGlobalEvent'; export const ChromeScreenShare = { - callbacks: {}, - installed: false, - init() { - this.callbacks['get-RocketChatScreenSharingExtensionVersion'] = (version) => { + callbacks: { + 'get-RocketChatScreenSharingExtensionVersion': (version: unknown) => { if (version) { - this.installed = true; + ChromeScreenShare.installed = true; } - }; + }, + 'getSourceId': (_sourceId: string): void => undefined, + }, + installed: false, + init() { window.postMessage('get-RocketChatScreenSharingExtensionVersion', '*'); }, - getSourceId(navigator, callback) { - if (callback == null) { + getSourceId(navigator: string, callback: (sourceId: string) => void) { + if (!callback) { throw new Error('"callback" parameter is mandatory.'); } this.callbacks.getSourceId = callback; @@ -36,8 +38,7 @@ window.addEventListener('message', (e) => { throw new Error('PermissionDeniedError'); } if (e.data.version != null) { - ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion'] && - ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion'](e.data.version); + ChromeScreenShare.callbacks['get-RocketChatScreenSharingExtensionVersion']?.(e.data.version); } else if (e.data.sourceId != null) { return typeof ChromeScreenShare.callbacks.getSourceId === 'function' && ChromeScreenShare.callbacks.getSourceId(e.data.sourceId); } diff --git a/apps/meteor/client/components/MarkdownText.tsx b/apps/meteor/client/components/MarkdownText.tsx index a116ad83ddd9..9ce44f8a9ff4 100644 --- a/apps/meteor/client/components/MarkdownText.tsx +++ b/apps/meteor/client/components/MarkdownText.tsx @@ -123,7 +123,7 @@ const MarkdownText = ({ // We are using the old emoji parser here. This could come // with additional processing use, but is the workaround available right now. // Should be replaced in the future with the new parser. - return renderMessageEmoji({ html: markedHtml }); + return renderMessageEmoji(markedHtml); } return markedHtml; diff --git a/apps/meteor/client/definitions/global.d.ts b/apps/meteor/client/definitions/global.d.ts index 8b20108e8e48..0916ef237119 100644 --- a/apps/meteor/client/definitions/global.d.ts +++ b/apps/meteor/client/definitions/global.d.ts @@ -4,5 +4,80 @@ declare global { // eslint-disable-next-line @typescript-eslint/naming-convention interface Window { RocketChatDesktop?: IRocketChatDesktop; + + /** @deprecated use `window.RTCPeerConnection` */ + mozRTCPeerConnection?: RTCPeerConnection; + /** @deprecated use `window.RTCPeerConnection` */ + webkitRTCPeerConnection?: RTCPeerConnection; + + /** @deprecated use `window.RTCSessionDescription` */ + mozRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCSessionDescription` */ + webkitRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCIceCandidate` */ + mozRTCIceCandidate?: RTCIceCandidate; + /** @deprecated use `window.RTCIceCandidate` */ + webkitRTCIceCandidate?: RTCIceCandidate; + /** @deprecated use `window.RTCSessionDescription` */ + mozRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.RTCSessionDescription` */ + webkitRTCSessionDescription?: RTCSessionDescription; + /** @deprecated use `window.AudioContext` */ + mozAudioContext?: AudioContext; + /** @deprecated use `window.AudioContext` */ + webkitAudioContext?: AudioContext; + } + + interface Navigator { + /** @deprecated */ + getUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + webkitGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + mozGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + /** @deprecated */ + msGetUserMedia?: ( + this: Navigator, + constraints?: MediaStreamConstraints | undefined, + onSuccess?: (stream: MediaStream) => void, + onError?: (error: any) => void, + ) => void; + } + + interface RTCPeerConnection { + /** @deprecated use `getReceivers() */ + getRemoteStreams(): MediaStream[]; + /** @deprecated */ + addStream(stream: MediaStream): void; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface MediaTrackConstraints { + /** @deprecated */ + mozMediaSource?: string; + /** @deprecated */ + mediaSource?: string; + /** @deprecated */ + mandatory?: { + chromeMediaSource: string; + chromeMediaSourceId: string; + maxWidth: number; + maxHeight: number; + }; } } diff --git a/apps/meteor/client/lib/utils/renderMessageEmoji.ts b/apps/meteor/client/lib/utils/renderMessageEmoji.ts index 7960ec1914e5..644187605ae7 100644 --- a/apps/meteor/client/lib/utils/renderMessageEmoji.ts +++ b/apps/meteor/client/lib/utils/renderMessageEmoji.ts @@ -1,3 +1,3 @@ import { emojiParser } from '../../../app/emoji/client/emojiParser'; -export const renderMessageEmoji = ({ html }: { html: string }): string => emojiParser({ html }).html; +export const renderMessageEmoji = (html: string) => emojiParser(html); diff --git a/apps/meteor/client/providers/OmnichannelProvider.tsx b/apps/meteor/client/providers/OmnichannelProvider.tsx index 9517a9d3b155..6e3c65610709 100644 --- a/apps/meteor/client/providers/OmnichannelProvider.tsx +++ b/apps/meteor/client/providers/OmnichannelProvider.tsx @@ -1,8 +1,9 @@ -import type { - IOmnichannelAgent, - OmichannelRoutingConfig, - OmnichannelSortingMechanismSettingType, - ILivechatInquiryRecord, +import { + type IOmnichannelAgent, + type OmichannelRoutingConfig, + type OmnichannelSortingMechanismSettingType, + type ILivechatInquiryRecord, + LivechatInquiryStatus, } from '@rocket.chat/core-typings'; import { useSafely } from '@rocket.chat/fuselage-hooks'; import { useUser, useSetting, usePermission, useMethod, useEndpoint, useStream } from '@rocket.chat/ui-contexts'; @@ -137,7 +138,7 @@ const OmnichannelProvider = ({ children }: OmnichannelProviderProps) => { } return LivechatInquiry.find( - { status: 'queued' }, + { status: LivechatInquiryStatus.QUEUED }, { sort: getOmniChatSortQuery(omnichannelSortingMechanism), limit: omnichannelPoolMaxIncoming, diff --git a/apps/meteor/client/views/meet/CallPage.tsx b/apps/meteor/client/views/meet/CallPage.tsx index d07e3dfd6831..97b5c93c3ded 100644 --- a/apps/meteor/client/views/meet/CallPage.tsx +++ b/apps/meteor/client/views/meet/CallPage.tsx @@ -60,35 +60,47 @@ const CallPage = ({ if (!visitorId) { throw new Error('Call Page - no visitor id'); } - const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId as any); + const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId); const isMobileDevice = (): boolean => { if (isLayoutEmbedded) { setCallInIframe(true); } if (window.innerWidth <= 450 && window.innerHeight >= 629 && window.innerHeight <= 900) { setIsLocalMobileDevice(true); - webrtcInstance.media = { - audio: true, - video: { - width: { ideal: 440 }, - height: { ideal: 580 }, - }, - }; + if (webrtcInstance) + webrtcInstance.media = { + audio: true, + video: { + width: { ideal: 440 }, + height: { ideal: 580 }, + }, + }; return true; } return false; }; - const unsubNotifyUser = subscribeNotifyUser(`${visitorId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type: any, data: any) => { + const unsubNotifyUser = subscribeNotifyUser(`${visitorId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type, data) => { if (data.room == null) { return; } - webrtcInstance.onUserStream(type, data); + + switch (type) { + case 'candidate': + webrtcInstance?.onUserStream('candidate', data); + break; + case 'description': + webrtcInstance?.onUserStream('description', data); + break; + case 'join': + webrtcInstance?.onUserStream('join', data); + break; + } }); const unsubNotifyRoom = subscribeNotifyRoom(`${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, (type: any, data: any) => { if (type === 'callStatus' && data.callStatus === 'ended') { - webrtcInstance.stop(); + webrtcInstance?.stop(); setStatus(data.callStatus); } else if (type === 'getDeviceType') { sdk.publish('notify-room', [ @@ -130,7 +142,7 @@ const CallPage = ({ if (status === 'inProgress') { sdk.publish('notify-room', [`${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, 'getDeviceType']); - webrtcInstance.startCall({ + webrtcInstance?.startCall({ audio: true, video: { width: { ideal: 1920 }, @@ -145,10 +157,10 @@ const CallPage = ({ if (type === 'callStatus') { switch (data.callStatus) { case 'ended': - webrtcInstance.stop(); + webrtcInstance?.stop(); break; case 'inProgress': - webrtcInstance.startCall({ + webrtcInstance?.startCall({ audio: true, video: { width: { ideal: 1920 }, @@ -168,10 +180,10 @@ const CallPage = ({ const toggleButton = (control: any): any => { if (control === 'mic') { - WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleAudio(); + WebRTC.getInstanceByRoomId(roomId, visitorToken)?.toggleAudio(); return setIsMicOn(!isMicOn); } - WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleVideo(); + WebRTC.getInstanceByRoomId(roomId, visitorToken)?.toggleVideo(); setIsCameraOn(!isCameraOn); sdk.publish('notify-room', [ `${roomId}/${WEB_RTC_EVENTS.WEB_RTC}`, diff --git a/apps/meteor/definition/externals/global.d.ts b/apps/meteor/definition/externals/global.d.ts index 52e9aa4b1f31..94ce146ae4d2 100644 --- a/apps/meteor/definition/externals/global.d.ts +++ b/apps/meteor/definition/externals/global.d.ts @@ -5,30 +5,6 @@ declare global { interface Navigator { /** @deprecated */ readonly userLanguage?: string; - getUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - webkitGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - mozGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; - msGetUserMedia?: ( - this: Navigator, - constraints?: MediaStreamConstraints | undefined, - onSuccess?: (stream: MediaStream) => void, - onError?: (error: any) => void, - ) => void; } const __meteor_runtime_config__: { diff --git a/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts b/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts index b0498aaf4a0d..df6118689a0d 100644 --- a/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts +++ b/apps/meteor/definition/externals/meteor/meteorhacks-inject-initial.d.ts @@ -1,5 +1,6 @@ declare module 'meteor/meteorhacks:inject-initial' { namespace Inject { function rawBody(key: string, value: string): void; + function rawModHtml(key: string, value: (html: string) => string): void; } } diff --git a/apps/meteor/ee/server/apps/storage/index.js b/apps/meteor/ee/server/apps/storage/index.ts similarity index 100% rename from apps/meteor/ee/server/apps/storage/index.js rename to apps/meteor/ee/server/apps/storage/index.ts diff --git a/apps/meteor/package.json b/apps/meteor/package.json index b7e7aab91573..585fefde1795 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -91,6 +91,7 @@ "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", "@types/busboy": "^1.5.4", + "@types/bytebuffer": "~5.0.49", "@types/chai": "~4.3.19", "@types/chai-as-promised": "^7.1.8", "@types/chai-datetime": "0.0.39", diff --git a/packages/base64/src/base64.ts b/packages/base64/src/base64.ts index c35c76254a10..8cc2ec80bc64 100644 --- a/packages/base64/src/base64.ts +++ b/packages/base64/src/base64.ts @@ -11,23 +11,7 @@ for (let i = 0; i < BASE_64_CHARS.length; i++) { BASE_64_VALS[getChar(i)] = i; } -// XXX This is a weird place for this to live, but it's used both by -// this package and 'ejson', and we can't put it in 'ejson' without -// introducing a circular dependency. It should probably be in its own -// package or as a helper in a package that both 'base64' and 'ejson' -// use. -const newBinary = (len: number) => { - if (typeof Uint8Array === 'undefined' || typeof ArrayBuffer === 'undefined') { - const ret = Object.assign( - Array.from({ length: len }, () => 0), - { - $Uint8ArrayPolyfill: true, - }, - ); - return ret; - } - return new Uint8Array(new ArrayBuffer(len)); -}; +const newBinary = (len: number) => new Uint8Array(new ArrayBuffer(len)); const encode = (array: ArrayLike | string) => { if (typeof array === 'string') { diff --git a/packages/ddp-client/src/types/streams.ts b/packages/ddp-client/src/types/streams.ts index 99aa9b77c7c9..122d37c9f539 100644 --- a/packages/ddp-client/src/types/streams.ts +++ b/packages/ddp-client/src/types/streams.ts @@ -64,7 +64,22 @@ export interface StreamerEvents { { key: `${string}/videoconf`; args: [id: string] }, { key: `${string}/messagesRead`; args: [{ until: Date; tmid?: string }] }, { key: `${string}/messagesImported`; args: [null] }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: [ + type: 'status', + data: { + from?: string; + room?: string; + to?: string; + media: MediaStreamConstraints; + remoteConnections: { + id: string; + media: MediaStreamConstraints; + }[]; + }, + ]; + }, /* @deprecated over videoconf*/ // { key: `${string}/${string}`; args: [id: string] }, ]; @@ -173,7 +188,51 @@ export interface StreamerEvents { { key: `${string}/userData`; args: [IUserDataEvent] }, { key: `${string}/updateInvites`; args: [unknown] }, { key: `${string}/departmentAgentData`; args: [unknown] }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: + | [ + type: 'candidate', + data: { + from?: string; + room?: string; + to?: string; + candidate: RTCIceCandidateInit; + }, + ] + | [ + type: 'description', + data: + | { + from?: string; + room?: string; + to?: string; + type: 'offer'; + ts: number; + media: MediaStreamConstraints; + description: RTCSessionDescriptionInit; + } + | { + from?: string; + room?: string; + to?: string; + type: 'answer'; + ts: number; + media?: undefined; + description: RTCSessionDescriptionInit; + }, + ] + | [ + type: 'join', + data: { + from?: string; + room?: string; + to?: string; + media?: MediaStreamConstraints; + monitor?: boolean; + }, + ]; + }, { key: `${string}/otr`; args: [ @@ -283,7 +342,19 @@ export interface StreamerEvents { key: `${string}/video-conference`; args: [{ action: string; params: { callId: VideoConference['_id']; uid: IUser['_id']; rid: IRoom['_id'] } }]; }, - { key: `${string}/webrtc`; args: unknown[] }, + { + key: `${string}/webrtc`; + args: [ + type: 'call', + data: { + from?: string; + room?: string; + to?: string; + media: MediaStreamConstraints; + monitor?: boolean; + }, + ]; + }, { key: `${string}/otr`; args: [ diff --git a/yarn.lock b/yarn.lock index 19d9ff0d5bb1..663944e2b63f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9002,6 +9002,7 @@ __metadata: "@types/bcrypt": "npm:^5.0.2" "@types/body-parser": "npm:^1.19.5" "@types/busboy": "npm:^1.5.4" + "@types/bytebuffer": "npm:~5.0.49" "@types/chai": "npm:~4.3.19" "@types/chai-as-promised": "npm:^7.1.8" "@types/chai-datetime": "npm:0.0.39" @@ -11539,6 +11540,16 @@ __metadata: languageName: node linkType: hard +"@types/bytebuffer@npm:~5.0.49": + version: 5.0.49 + resolution: "@types/bytebuffer@npm:5.0.49" + dependencies: + "@types/long": "npm:^3.0.0" + "@types/node": "npm:*" + checksum: 10/31eb2521d2710f256c3d17a3e8d87f04394f335b29f7276c31c054ddbf4795146f2663effa3b6e910442da69238e994d2db9f7d5918eead4313e3f9e29165932 + languageName: node + linkType: hard + "@types/chai-as-promised@npm:^7.1.8": version: 7.1.8 resolution: "@types/chai-as-promised@npm:7.1.8" @@ -12535,6 +12546,13 @@ __metadata: languageName: node linkType: hard +"@types/long@npm:^3.0.0": + version: 3.0.32 + resolution: "@types/long@npm:3.0.32" + checksum: 10/cc5422875a085b49b74ffeb5c60a8681d30f700859a8931012b4a58c5c6005cdacb4d3ce3e5af7a7f579ee20d5c2e442a773a83b3a4f7a2d39795a7a8e9a962d + languageName: node + linkType: hard + "@types/mailparser@npm:^3.4.4": version: 3.4.4 resolution: "@types/mailparser@npm:3.4.4"