diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a672c0e2..ebfb8c74 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -37,8 +37,10 @@ + + diff --git a/app/index.js b/app/index.js index 220f5083..e4d9e52c 100644 --- a/app/index.js +++ b/app/index.js @@ -50,6 +50,13 @@ const parseDeepLinking = (url) => { return parseQuery(url); } } + const call = /^(https:\/\/)?jitsi.rocket.chat\//; + if (url.match(call)) { + url = url.replace(call, '').trim(); + if (url) { + return { path: url, isCall: true }; + } + } } return null; }; diff --git a/app/lib/database/model/Server.js b/app/lib/database/model/Server.js index 5ff103fa..d30b3a3f 100644 --- a/app/lib/database/model/Server.js +++ b/app/lib/database/model/Server.js @@ -25,4 +25,6 @@ export default class Server extends Model { @field('auto_lock_time') autoLockTime; @field('biometry') biometry; + + @field('unique_id') uniqueID; } diff --git a/app/lib/database/model/serversMigrations.js b/app/lib/database/model/serversMigrations.js index 0163d3bd..8d74b043 100644 --- a/app/lib/database/model/serversMigrations.js +++ b/app/lib/database/model/serversMigrations.js @@ -26,6 +26,17 @@ export default schemaMigrations({ ] }) ] + }, + { + toVersion: 5, + steps: [ + addColumns({ + table: 'servers', + columns: [ + { name: 'unique_id', type: 'string', isOptional: true } + ] + }) + ] } ] }); diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 7557f8b1..b02859e1 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 4, + version: 5, tables: [ tableSchema({ name: 'users', @@ -28,7 +28,8 @@ export default appSchema({ { name: 'last_local_authenticated_session', type: 'number', isOptional: true }, { name: 'auto_lock', type: 'boolean', isOptional: true }, { name: 'auto_lock_time', type: 'number', isOptional: true }, - { name: 'biometry', type: 'boolean', isOptional: true } + { name: 'biometry', type: 'boolean', isOptional: true }, + { name: 'unique_id', type: 'string', isOptional: true } ] }) ] diff --git a/app/lib/methods/callJitsi.js b/app/lib/methods/callJitsi.js index 92f7bc44..c4968807 100644 --- a/app/lib/methods/callJitsi.js +++ b/app/lib/methods/callJitsi.js @@ -1,41 +1,39 @@ import reduxStore from '../createStore'; import Navigation from '../Navigation'; -const jitsiBaseUrl = ({ - Jitsi_Enabled, Jitsi_SSL, Jitsi_Domain, Jitsi_URL_Room_Prefix, uniqueID -}) => { +async function jitsiURL({ rid }) { + const { settings } = reduxStore.getState(); + const { Jitsi_Enabled } = settings; + if (!Jitsi_Enabled) { return ''; } - const uniqueIdentifier = uniqueID || 'undefined'; - const domain = Jitsi_Domain; + + const { + Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID + } = settings; + + const domain = `${ Jitsi_Domain }/`; const prefix = Jitsi_URL_Room_Prefix; + const uniqueIdentifier = uniqueID || 'undefined'; + const protocol = Jitsi_SSL ? 'https://' : 'http://'; - const urlProtocol = Jitsi_SSL ? 'https://' : 'http://'; - const urlDomain = `${ domain }/`; - - return `${ urlProtocol }${ urlDomain }${ prefix }${ uniqueIdentifier }`; -}; - -async function callJitsi(rid, onlyAudio = false) { - let accessToken; let queryString = ''; - const { settings } = reduxStore.getState(); - const { Jitsi_Enabled_TokenAuth } = settings; - if (Jitsi_Enabled_TokenAuth) { try { - accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid); - } catch (e) { + const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid); + queryString = `?jwt=${ accessToken }`; + } catch { // do nothing } } - if (accessToken) { - queryString = `?jwt=${ accessToken }`; - } + return `${ protocol }${ domain }${ prefix }${ uniqueIdentifier }${ rid }${ queryString }`; +} - Navigation.navigate('JitsiMeetView', { url: `${ jitsiBaseUrl(settings) }${ rid }${ queryString }`, onlyAudio, rid }); +async function callJitsi(rid, onlyAudio = false) { + const url = await jitsiURL.call(this, { rid }); + Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid }); } export default callJitsi; diff --git a/app/lib/methods/canOpenRoom.js b/app/lib/methods/canOpenRoom.js index 1578837a..b8572872 100644 --- a/app/lib/methods/canOpenRoom.js +++ b/app/lib/methods/canOpenRoom.js @@ -1,4 +1,5 @@ import database from '../database'; +import store from '../createStore'; const restTypes = { channel: 'channels', direct: 'im', group: 'groups' @@ -53,11 +54,17 @@ async function open({ type, rid, name }) { } } -export default async function canOpenRoom({ rid, path }) { +export default async function canOpenRoom({ rid, path, isCall }) { try { const db = database.active; const subsCollection = db.collections.get('subscriptions'); - const [type, name] = path.split('/'); + + if (isCall && !rid) { + // Extract rid from a Jitsi URL + // Eg.: [Jitsi_URL_Room_Prefix][uniqueID][rid][?jwt] + const { Jitsi_URL_Room_Prefix, uniqueID } = store.getState().settings; + rid = path.replace(`${ Jitsi_URL_Room_Prefix }${ uniqueID }`, '').replace(/\?(.*)/g, ''); + } if (rid) { try { @@ -75,8 +82,10 @@ export default async function canOpenRoom({ rid, path }) { } } + const [type, name] = path.split('/'); try { - return await open.call(this, { type, rid, name }); + const result = await open.call(this, { type, rid, name }); + return result; } catch (e) { return false; } diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index e635807e..2ebad1ed 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -11,7 +11,7 @@ import protectedFunction from './helpers/protectedFunction'; import fetch from '../../utils/fetch'; import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication'; -const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize', 'Force_Screen_Lock', 'Force_Screen_Lock_After']; +const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize', 'Force_Screen_Lock', 'Force_Screen_Lock_After', 'uniqueID']; // these settings are used only on onboarding process const loginSettings = [ @@ -68,6 +68,9 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => { return { ...allSettings, autoLockTime: setting.valueAsNumber }; } } + if (setting._id === 'uniqueID') { + return { ...allSettings, uniqueID: setting.valueAsString }; + } return allSettings; }, {}); diff --git a/app/notifications/push/index.js b/app/notifications/push/index.js index 865a6ad6..40ccbea1 100644 --- a/app/notifications/push/index.js +++ b/app/notifications/push/index.js @@ -10,7 +10,7 @@ export const onNotification = (notification) => { if (data) { try { const { - rid, name, sender, type, host + rid, name, sender, type, host, messageType } = EJSON.parse(data.ejson); const types = { @@ -24,7 +24,8 @@ export const onNotification = (notification) => { const params = { host, rid, - path: `${ types[type] }/${ roomName }` + path: `${ types[type] }/${ roomName }`, + isCall: messageType === 'jitsi_call_started' }; store.dispatch(deepLinkingOpen(params)); } catch (e) { diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index ffa04040..7c9c8e3b 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -13,6 +13,7 @@ import EventEmitter from '../utils/events'; import { appStart, ROOT_INSIDE, ROOT_NEW_SERVER } from '../actions/app'; import { localAuthenticate } from '../utils/localAuthentication'; import { goRoom } from '../utils/goRoom'; +import callJitsi from '../lib/methods/callJitsi'; const roomTypes = { channel: 'c', direct: 'd', group: 'p', channels: 'l' @@ -48,7 +49,11 @@ const navigate = function* navigate({ params }) { roomUserId: RocketChat.getUidDirectMessage(room), ...room }; - goRoom({ item, isMasterDetail }); + yield goRoom({ item, isMasterDetail }); + + if (params.isCall) { + callJitsi(item.rid); + } } } else { yield handleInviteLink({ params }); @@ -57,11 +62,23 @@ const navigate = function* navigate({ params }) { }; const handleOpen = function* handleOpen({ params }) { - if (!params.host) { - return; - } + const serversDB = database.servers; + const serversCollection = serversDB.collections.get('servers'); let { host } = params; + if (params.isCall && !host) { + const servers = yield serversCollection.query().fetch(); + // search from which server is that call + servers.forEach(({ uniqueID, id }) => { + if (params.path.includes(uniqueID)) { + host = id; + } + }); + } + + if (!host) { + return; + } if (!/^(http|https)/.test(host)) { host = `https://${ params.host }`; } @@ -87,8 +104,6 @@ const handleOpen = function* handleOpen({ params }) { yield navigate({ params }); } else { // search if deep link's server already exists - const serversDB = database.servers; - const serversCollection = serversDB.collections.get('servers'); try { const servers = yield serversCollection.find(host); if (servers && user) { diff --git a/ios/RocketChatRN/RocketChatRN.entitlements b/ios/RocketChatRN/RocketChatRN.entitlements index 70e74044..5f5c3bd2 100644 --- a/ios/RocketChatRN/RocketChatRN.entitlements +++ b/ios/RocketChatRN/RocketChatRN.entitlements @@ -11,6 +11,7 @@ com.apple.developer.associated-domains applinks:go.rocket.chat + applinks:jitsi.rocket.chat com.apple.security.application-groups