diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift
index 2e3e889..10053b8 100644
--- a/ios/AppDelegate.swift
+++ b/ios/AppDelegate.swift
@@ -35,6 +35,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
return true
}
+
+ func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
+ BigBlueButtonSDK.handleDeepLink(app, open: url, options: options)
+ return true
+ }
+
+
func sourceURL(for bridge: RCTBridge!) -> URL! {
//#if DEBUG
diff --git a/ios/BigBlueButton/Info.plist b/ios/BigBlueButton/Info.plist
index 57c6222..398f3e1 100644
--- a/ios/BigBlueButton/Info.plist
+++ b/ios/BigBlueButton/Info.plist
@@ -20,6 +20,19 @@
1.0
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ com.bigbluebutton.mobile
+ CFBundleURLSchemes
+
+ bigbluebutton
+
+
+
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
ITSAppUsesNonExemptEncryption
diff --git a/react-native/app/emitter/emitter.ts b/react-native/app/emitter/emitter.ts
new file mode 100644
index 0000000..1320ddb
--- /dev/null
+++ b/react-native/app/emitter/emitter.ts
@@ -0,0 +1,14 @@
+import {
+ NativeModules,
+ NativeEventEmitter,
+} from 'react-native';
+
+const {
+ ReactNativeEventEmitter,
+} = NativeModules;
+
+
+export const emitter: NativeEventEmitter = new NativeEventEmitter(
+ ReactNativeEventEmitter
+)
+
diff --git a/react-native/app/pages/utils/createNewPortal.ts b/react-native/app/pages/utils/createNewPortal.ts
index 93e8b3d..ef40ac1 100644
--- a/react-native/app/pages/utils/createNewPortal.ts
+++ b/react-native/app/pages/utils/createNewPortal.ts
@@ -1,25 +1,26 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
-import {ICreatePortal, IPortal, IPortalProp, IPortalToAdd} from './types';
+import { IPortal, IPortalProp, IPortalToAdd} from './types';
export async function createNewPortal({
name,
url,
-}: ICreatePortal): Promise {
+ temporary = false
+}: IPortal): Promise {
let portalsStorage: IPortalProp = await getPortals();
if (portalsStorage === null || portalsStorage === '[]')
- return await createFromEmptyStorage(name, url);
+ return await createFromEmptyStorage({name, url, temporary});
portalsStorage = parseString(portalsStorage);
- return await addPortalToStorage({portals: portalsStorage, name, url});
+ return await addPortalToStorage({portals: portalsStorage, name, url, temporary});
}
-async function createFromEmptyStorage(name: string, url: string) {
+async function createFromEmptyStorage({name, url, temporary}:IPortal) {
await createStorageEmpty();
let portalsStorage = await getPortals();
const portalStorage = parseString(portalsStorage!);
- await addPortalToStorage({portals: portalStorage, name, url});
+ await addPortalToStorage({portals: portalStorage, name, url, temporary});
return portalStorage;
}
@@ -39,8 +40,9 @@ async function addPortalToStorage({
portals,
name,
url,
+ temporary
}: IPortalToAdd): Promise {
- portals.push({name, url});
+ portals.push({name, url, temporary});
await AsyncStorage.setItem('portal', JSON.stringify(portals));
return portals;
}
diff --git a/react-native/app/pages/utils/types.ts b/react-native/app/pages/utils/types.ts
index 01870a6..f48ecb4 100644
--- a/react-native/app/pages/utils/types.ts
+++ b/react-native/app/pages/utils/types.ts
@@ -1,17 +1,14 @@
-export type ICreatePortal = {
- name: string;
- url: string;
-};
-
export type IPortal = {
name: string;
url: string;
+ temporary: boolean
};
export type IPortalToAdd = {
portals: Array;
name: string;
url: string;
+ temporary: boolean;
};
export type IPortalProp = string | null | IPortal[];
diff --git a/react-native/app/routes/component.js b/react-native/app/routes/component.js
deleted file mode 100644
index b908958..0000000
--- a/react-native/app/routes/component.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from 'react';
-import {NavigationContainer} from '@react-navigation/native';
-import {ListPortals} from '../pages/list_portals/component';
-import {usePortal} from '../contexts/portals/hook';
-import AsyncStorage from '@react-native-async-storage/async-storage';
-import i18next from 'i18next';
-
-import {createDrawerNavigator} from '@react-navigation/drawer';
-import SdkContainer from '../../bootstrap/sdk/container';
-import {initTranslation} from '../translations/index';
-
-const Drawer = createDrawerNavigator();
-export const Routes = () => {
- initTranslation();
-
- const {portals, setPortals} = usePortal();
- async function getPortals() {
- try {
- let items = await AsyncStorage.getAllKeys();
- if (items.includes('portal')) {
- let portalsStorage = await AsyncStorage.getItem('portal');
- portalsStorage = JSON.parse(portalsStorage);
- setPortals(portalsStorage);
- }
- } catch (e) {
- console.log('error', e);
- return null;
- }
- }
-
- React.useEffect(() => {
- getPortals();
- }, []);
-
- return (
-
-
-
- {portals && portals.length
- ? portals.map(item => {
- return (
- }
- />
- );
- })
- : null}
-
-
- );
-};
diff --git a/react-native/app/routes/component.tsx b/react-native/app/routes/component.tsx
new file mode 100644
index 0000000..965bcb5
--- /dev/null
+++ b/react-native/app/routes/component.tsx
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import { NavigationContainer, useNavigation} from '@react-navigation/native';
+import {ListPortals} from '../pages/list_portals/component';
+import {usePortal} from '../contexts/portals/hook';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import i18next from 'i18next';
+
+import {createDrawerNavigator} from '@react-navigation/drawer';
+import SdkContainer from '../../bootstrap/sdk/container';
+import {initTranslation} from '../translations/index';
+import { Alert, Linking } from 'react-native';
+import { createNewPortal } from '../pages/utils/createNewPortal';
+import { emitter } from '../emitter/emitter';
+import { IPortal } from '../pages/utils/types';
+
+const DeepLink = ()=>{
+ initTranslation();
+
+ const SCHEME = 'bigbluebutton://';
+ const SCHEME_DEFAULT = 'https://'
+ var NAME_PORTALS_DEEP_LINK = i18next.t('mobileApp.portals.namePortal.deepLink');
+
+ const navigate = useNavigation();
+ const {portals, setPortals} = usePortal();
+
+ async function createTemporaryPortalFromDeepLink(link: string){
+ const linkWithoutScheme = link.replace(SCHEME, '');
+
+ if(linkWithoutScheme === ''){
+ navigate.navigate(i18next.t('mobileApp.portals.drawerNavigation.button.label'))
+ return Alert.alert(i18next.t('mobileApp.portals.handleWithoutURL'))
+ }
+ const roomName = linkWithoutScheme.match(/^\w+/)
+ if(roomName != 'bigbluebutton'){
+ NAME_PORTALS_DEEP_LINK = roomName[0]
+ }
+
+ const linkWhitoutSchemeAndName = linkWithoutScheme.replace(/^\w+[\/]/, '')
+ const portalToAdd:IPortal = {
+ name: NAME_PORTALS_DEEP_LINK,
+ url: SCHEME_DEFAULT+linkWhitoutSchemeAndName,
+ temporary: true
+ }
+
+ // Adding LinkedPortal to AsyncStorage
+ const newPortals = await createNewPortal(portalToAdd)
+ // Adding to context
+ setPortals(newPortals)
+ // Navigation to portal
+ navigate.navigate(NAME_PORTALS_DEEP_LINK)
+ }
+
+ async function checkIfHaveTemporaryPortal(){
+ const portalsFromStorage = await AsyncStorage.getItem('portal')
+ const portalsParsed = JSON.parse(portalsFromStorage)
+ const portalsWithoutTemporary = portalsParsed.filter((portal: IPortal)=>{
+ if(portal.temporary == true) return false
+ if(portal.name == NAME_PORTALS_DEEP_LINK) return false
+ return portal
+ })
+ await AsyncStorage.setItem('portal', JSON.stringify(portalsWithoutTemporary))
+ setPortals(portalsWithoutTemporary)
+
+ getLinkFromAppClosed() //To app running in backgrond then open throug deep link
+ Linking.addEventListener('url', (url)=>{
+ // if app is open when it's minimized (not running in background)
+ createTemporaryPortalFromDeepLink(url.url)
+ })
+
+ }
+
+ async function getLinkFromAppClosed(){
+ const linkFromAppClosed = await Linking.getInitialURL()
+ if(linkFromAppClosed === null) return
+ createTemporaryPortalFromDeepLink(linkFromAppClosed)
+ }
+
+ React.useEffect(() => {
+ checkIfHaveTemporaryPortal()
+
+ }, []);
+ return null
+}
+
+const Drawer = createDrawerNavigator();
+export const Routes = () => {
+ initTranslation();
+ const {portals, setPortals} = usePortal();
+ async function getPortals() {
+ try {
+ let items = await AsyncStorage.getAllKeys();
+ if (items.includes('portal')) {
+ let portalsStorage = await AsyncStorage.getItem('portal');
+ portalsStorage = JSON.parse(portalsStorage);
+ setPortals(portalsStorage);
+ }
+ } catch (e) {
+ console.log('error', e);
+ return null;
+ }
+ }
+
+ React.useEffect(() => {
+ getPortals();
+ }, []);
+
+ return (
+
+
+
+
+ {portals && portals.length
+ ? portals.map((item: { name: React.Key | null | undefined; url: string; }) => {
+ return (
+ }
+ />
+ );
+ })
+ : null}
+
+
+ );
+};
diff --git a/react-native/app/translations/resources/en.ts b/react-native/app/translations/resources/en.ts
index 88435b9..4020e66 100644
--- a/react-native/app/translations/resources/en.ts
+++ b/react-native/app/translations/resources/en.ts
@@ -9,5 +9,7 @@ export default {
"mobileApp.portals.drawerNavigation.button.label": "Portals",
"mobileApp.portals.addPortalPopup.validation.emptyFields": "Required Fields",
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Name already in use",
- "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Error trying to load the page - check URL and network connection"
+ "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Error trying to load the page - check URL and network connection",
+ "mobileApp.portals.namePortal.deepLink": "Linked Portal",
+ "mobileApp.portals.handleWithoutURL": "Dont have link, you was redirected to list portals home.",
}