[IMPROVEMENT] Add deep link to Jitsi calls (#2223)
* [WIP] Jitsi Deep Links * [WIP] Add app links * save uniqueID servers database * add serverInfoKey of uniqueID * search server by call url * open jitsi deeplink poc * improve jitsi url * fix * improve comment * add missing android scheme * handle host not found * Allow app links to be matched on parseDeepLinking * Fix push notification of a call * Minor fix Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
8cd38d5e19
commit
cb5c914570
|
@ -37,8 +37,10 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" android:host="go.rocket.chat" />
|
<data android:scheme="https" android:host="go.rocket.chat" />
|
||||||
|
<data android:scheme="https" android:host="jitsi.rocket.chat" />
|
||||||
<data android:scheme="rocketchat" android:host="room" />
|
<data android:scheme="rocketchat" android:host="room" />
|
||||||
<data android:scheme="rocketchat" android:host="auth" />
|
<data android:scheme="rocketchat" android:host="auth" />
|
||||||
|
<data android:scheme="rocketchat" android:host="jitsi.rocket.chat" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
|
|
@ -50,6 +50,13 @@ const parseDeepLinking = (url) => {
|
||||||
return parseQuery(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;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,4 +25,6 @@ export default class Server extends Model {
|
||||||
@field('auto_lock_time') autoLockTime;
|
@field('auto_lock_time') autoLockTime;
|
||||||
|
|
||||||
@field('biometry') biometry;
|
@field('biometry') biometry;
|
||||||
|
|
||||||
|
@field('unique_id') uniqueID;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,17 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 5,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'servers',
|
||||||
|
columns: [
|
||||||
|
{ name: 'unique_id', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 4,
|
version: 5,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -28,7 +28,8 @@ export default appSchema({
|
||||||
{ name: 'last_local_authenticated_session', type: 'number', isOptional: true },
|
{ name: 'last_local_authenticated_session', type: 'number', isOptional: true },
|
||||||
{ name: 'auto_lock', type: 'boolean', isOptional: true },
|
{ name: 'auto_lock', type: 'boolean', isOptional: true },
|
||||||
{ name: 'auto_lock_time', type: 'number', 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 }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,41 +1,39 @@
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
import Navigation from '../Navigation';
|
import Navigation from '../Navigation';
|
||||||
|
|
||||||
const jitsiBaseUrl = ({
|
async function jitsiURL({ rid }) {
|
||||||
Jitsi_Enabled, Jitsi_SSL, Jitsi_Domain, Jitsi_URL_Room_Prefix, uniqueID
|
const { settings } = reduxStore.getState();
|
||||||
}) => {
|
const { Jitsi_Enabled } = settings;
|
||||||
|
|
||||||
if (!Jitsi_Enabled) {
|
if (!Jitsi_Enabled) {
|
||||||
return '';
|
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 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 = '';
|
let queryString = '';
|
||||||
const { settings } = reduxStore.getState();
|
|
||||||
const { Jitsi_Enabled_TokenAuth } = settings;
|
|
||||||
|
|
||||||
if (Jitsi_Enabled_TokenAuth) {
|
if (Jitsi_Enabled_TokenAuth) {
|
||||||
try {
|
try {
|
||||||
accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid);
|
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid);
|
||||||
} catch (e) {
|
queryString = `?jwt=${ accessToken }`;
|
||||||
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessToken) {
|
return `${ protocol }${ domain }${ prefix }${ uniqueIdentifier }${ rid }${ queryString }`;
|
||||||
queryString = `?jwt=${ accessToken }`;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
export default callJitsi;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import database from '../database';
|
import database from '../database';
|
||||||
|
import store from '../createStore';
|
||||||
|
|
||||||
const restTypes = {
|
const restTypes = {
|
||||||
channel: 'channels', direct: 'im', group: 'groups'
|
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 {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const subsCollection = db.collections.get('subscriptions');
|
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) {
|
if (rid) {
|
||||||
try {
|
try {
|
||||||
|
@ -75,8 +82,10 @@ export default async function canOpenRoom({ rid, path }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [type, name] = path.split('/');
|
||||||
try {
|
try {
|
||||||
return await open.call(this, { type, rid, name });
|
const result = await open.call(this, { type, rid, name });
|
||||||
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import protectedFunction from './helpers/protectedFunction';
|
||||||
import fetch from '../../utils/fetch';
|
import fetch from '../../utils/fetch';
|
||||||
import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication';
|
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
|
// these settings are used only on onboarding process
|
||||||
const loginSettings = [
|
const loginSettings = [
|
||||||
|
@ -68,6 +68,9 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
||||||
return { ...allSettings, autoLockTime: setting.valueAsNumber };
|
return { ...allSettings, autoLockTime: setting.valueAsNumber };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (setting._id === 'uniqueID') {
|
||||||
|
return { ...allSettings, uniqueID: setting.valueAsString };
|
||||||
|
}
|
||||||
return allSettings;
|
return allSettings;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const onNotification = (notification) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
rid, name, sender, type, host
|
rid, name, sender, type, host, messageType
|
||||||
} = EJSON.parse(data.ejson);
|
} = EJSON.parse(data.ejson);
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
|
@ -24,7 +24,8 @@ export const onNotification = (notification) => {
|
||||||
const params = {
|
const params = {
|
||||||
host,
|
host,
|
||||||
rid,
|
rid,
|
||||||
path: `${ types[type] }/${ roomName }`
|
path: `${ types[type] }/${ roomName }`,
|
||||||
|
isCall: messageType === 'jitsi_call_started'
|
||||||
};
|
};
|
||||||
store.dispatch(deepLinkingOpen(params));
|
store.dispatch(deepLinkingOpen(params));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import EventEmitter from '../utils/events';
|
||||||
import { appStart, ROOT_INSIDE, ROOT_NEW_SERVER } from '../actions/app';
|
import { appStart, ROOT_INSIDE, ROOT_NEW_SERVER } from '../actions/app';
|
||||||
import { localAuthenticate } from '../utils/localAuthentication';
|
import { localAuthenticate } from '../utils/localAuthentication';
|
||||||
import { goRoom } from '../utils/goRoom';
|
import { goRoom } from '../utils/goRoom';
|
||||||
|
import callJitsi from '../lib/methods/callJitsi';
|
||||||
|
|
||||||
const roomTypes = {
|
const roomTypes = {
|
||||||
channel: 'c', direct: 'd', group: 'p', channels: 'l'
|
channel: 'c', direct: 'd', group: 'p', channels: 'l'
|
||||||
|
@ -48,7 +49,11 @@ const navigate = function* navigate({ params }) {
|
||||||
roomUserId: RocketChat.getUidDirectMessage(room),
|
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||||
...room
|
...room
|
||||||
};
|
};
|
||||||
goRoom({ item, isMasterDetail });
|
yield goRoom({ item, isMasterDetail });
|
||||||
|
|
||||||
|
if (params.isCall) {
|
||||||
|
callJitsi(item.rid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yield handleInviteLink({ params });
|
yield handleInviteLink({ params });
|
||||||
|
@ -57,11 +62,23 @@ const navigate = function* navigate({ params }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpen = function* handleOpen({ params }) {
|
const handleOpen = function* handleOpen({ params }) {
|
||||||
if (!params.host) {
|
const serversDB = database.servers;
|
||||||
return;
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
}
|
|
||||||
|
|
||||||
let { host } = params;
|
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)) {
|
if (!/^(http|https)/.test(host)) {
|
||||||
host = `https://${ params.host }`;
|
host = `https://${ params.host }`;
|
||||||
}
|
}
|
||||||
|
@ -87,8 +104,6 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
yield navigate({ params });
|
yield navigate({ params });
|
||||||
} else {
|
} else {
|
||||||
// search if deep link's server already exists
|
// search if deep link's server already exists
|
||||||
const serversDB = database.servers;
|
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
|
||||||
try {
|
try {
|
||||||
const servers = yield serversCollection.find(host);
|
const servers = yield serversCollection.find(host);
|
||||||
if (servers && user) {
|
if (servers && user) {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
<array>
|
<array>
|
||||||
<string>applinks:go.rocket.chat</string>
|
<string>applinks:go.rocket.chat</string>
|
||||||
|
<string>applinks:jitsi.rocket.chat</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
|
|
Loading…
Reference in New Issue