ios: SDK v2 pass one

- cleanup
- API simplification (single loadURL method)
- JitsiMeet singleton for linking helpers and globals
- Linking moved to app
This commit is contained in:
Saúl Ibarra Corretgé 2019-01-23 13:48:34 +01:00 committed by Saúl Ibarra Corretgé
parent 1c47720a08
commit dbc88b972e
14 changed files with 367 additions and 365 deletions

View File

@ -19,11 +19,10 @@
#import "FIRUtilities.h"
#import "Types.h"
#import <JitsiMeet/JitsiMeet.h>
@import Crashlytics;
@import Fabric;
@import Firebase;
@import JitsiMeet;
@implementation AppDelegate
@ -40,10 +39,10 @@
// Set the conference activity type defined in this application.
// This cannot be defined by the SDK.
JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
[JitsiMeet sharedInstance].conferenceActivityType = JitsiMeetConferenceActivityType;
[[JitsiMeet sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
return [JitsiMeetView application:application
didFinishLaunchingWithOptions:launchOptions];
return YES;
}
#pragma mark Linking delegate methods
@ -63,9 +62,9 @@
NSURL *dynamicLinkURL = dynamicLink.url;
if (dynamicLinkURL) {
userActivity.webpageURL = dynamicLinkURL;
[JitsiMeetView application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
[[JitsiMeet sharedInstance] application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
}];
@ -75,9 +74,9 @@
}
// 2. Default to plain old, non-Firebase-assisted Universal Links.
return [JitsiMeetView application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
return [[JitsiMeet sharedInstance] application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
- (BOOL)application:(UIApplication *)app
@ -100,9 +99,9 @@
}
}
return [JitsiMeetView application:app
openURL:openUrl
options:options];
return [[JitsiMeet sharedInstance] application:app
openURL:openUrl
options:options];
}
@end

View File

@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,18 +16,14 @@
*/
#import <Availability.h>
#import <CoreSpotlight/CoreSpotlight.h>
#import <MobileCoreServices/MobileCoreServices.h>
@import CoreSpotlight;
@import MobileCoreServices;
@import Intents; // Needed for NSUserActivity suggestedInvocationPhrase
#import "Types.h"
#import "ViewController.h"
// Needed for NSUserActivity suggestedInvocationPhrase
@import Intents;
@interface ViewController ()
@end
@implementation ViewController
@ -42,11 +39,9 @@
// anyway.
view.welcomePageEnabled = YES;
[view loadURL:nil];
[view loadURL:[[JitsiMeet sharedInstance] getInitialURL]];
}
// JitsiMeetViewDelegate
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name

View File

@ -13,7 +13,6 @@
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */; };
0B49424620AD8DBD00BD2DE0 /* outgoingRinging.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */; };
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */; };
@ -45,6 +44,8 @@
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */; };
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535321FB1BF800011A3A /* JitsiMeet.m */; };
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535521FB2E8300011A3A /* ReactUtils.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -55,7 +56,6 @@
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingStart.wav; path = ../../sounds/outgoingStart.wav; sourceTree = "<group>"; };
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingRinging.wav; path = ../../sounds/outgoingRinging.wav; sourceTree = "<group>"; };
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
@ -93,6 +93,10 @@
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocaleDetector.m; sourceTree = "<group>"; };
DEFE535321FB1BF800011A3A /* JitsiMeet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeet.m; sourceTree = "<group>"; };
DEFE535521FB2E8300011A3A /* ReactUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactUtils.m; sourceTree = "<group>"; };
DEFE535721FB2E9E00011A3A /* ReactUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactUtils.h; sourceTree = "<group>"; };
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeet+Private.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -158,16 +162,19 @@
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
DEFE535321FB1BF800011A3A /* JitsiMeet.m */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
C6A3426B204F127900E062DD /* picture-in-picture */,
0BCA495D1EC4B6C600B793EE /* POSIX.m */,
0BCA495E1EC4B6C600B793EE /* Proximity.m */,
DEFE535721FB2E9E00011A3A /* ReactUtils.h */,
DEFE535521FB2E8300011A3A /* ReactUtils.m */,
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
);
@ -447,7 +454,6 @@
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
@ -455,11 +461,13 @@
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
A480429C21EE335600289B73 /* AmplitudeModule.m in Sources */,
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,23 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "JitsiMeet.h"
@interface JitsiMeet ()
- (RCTBridge *)getReactBridge;
@end

View File

@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,6 +15,32 @@
* limitations under the License.
*/
// JitsiMeetView
#import <JitsiMeet/JitsiMeetView.h>
#import <JitsiMeet/JitsiMeetViewDelegate.h>
@interface JitsiMeet : NSObject
@property (copy, nonatomic, nullable) NSString *conferenceActivityType;
#pragma mak - This class is a singleton
+ (instancetype)sharedInstance;
#pragma mark - Methods that the App delegate must call
- (BOOL)application:(UIApplication *_Nonnull)application
didFinishLaunchingWithOptions:(NSDictionary *_Nonnull)launchOptions;
- (BOOL)application:(UIApplication * _Nonnull)application
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler;
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
#pragma mark - Utility methods
- (NSDictionary *)getInitialURL;
@end

161
ios/sdk/src/JitsiMeet.m Normal file
View File

@ -0,0 +1,161 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Intents/Intents.h>
#import "Dropbox.h"
#import "JitsiMeet+Private.h"
#import "JitsiMeetView+Private.h"
#import "RCTBridgeWrapper.h"
#import "ReactUtils.h"
#import <RNGoogleSignin/RNGoogleSignin.h>
@implementation JitsiMeet {
RCTBridgeWrapper *_bridgeWrapper;
NSDictionary *_launchOptions;
}
#pragma mak - This class is a singleton
+ (instancetype)sharedInstance {
static JitsiMeet *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
if (self = [super init]) {
// Initialize the on and only bridge for interfacing with React Native.
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
// Register a fatal error handler for React.
registerReactFatalErrorHandler();
}
return self;
}
#pragma mark - Methods that the App delegate must call
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_launchOptions = [launchOptions copy];
[Dropbox setAppKey];
return YES;
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
id url = [self urlFromUserActivity:userActivity];
return url && [JitsiMeetView loadURLInViews:url];
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([Dropbox application:app openURL:url options:options]) {
return YES;
}
if ([RNGoogleSignin application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]) {
return YES;
}
return [JitsiMeetView loadURLInViews:@{ @"url" : url.absoluteString }];
}
#pragma mark - Utility methods
- (NSDictionary *)getInitialURL {
if (_launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL *url = _launchOptions[UIApplicationLaunchOptionsURLKey];
return @{ @"url" : url.absoluteString };
} else {
NSDictionary *userActivityDictionary
= _launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity
= [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil) {
return [self urlFromUserActivity:userActivity];
}
}
return nil;
}
- (NSDictionary *)urlFromUserActivity:(NSUserActivity *)userActivity {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
return @{ @"url" : userActivity.webpageURL.absoluteString };
} else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
// App was started by a CallKit Intent
INIntent *intent = userActivity.interaction.intent;
NSArray<INPerson *> *contacts;
NSString *url;
BOOL startAudioOnly = NO;
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
contacts = ((INStartAudioCallIntent *) intent).contacts;
startAudioOnly = YES;
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
contacts = ((INStartVideoCallIntent *) intent).contacts;
}
if (contacts && (url = contacts.firstObject.personHandle.value)) {
return @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
}
} else if (self.conferenceActivityType && [activityType isEqualToString:self.conferenceActivityType]) {
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
NSString *url;
if ((url = userActivity.userInfo[@"url"])) {
return @{ @"url" : url };
}
}
return nil;
}
#pragma mark - Private API methods
- (RCTBridge *)getReactBridge {
return _bridgeWrapper.bridge;
}
@end

View File

@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +19,7 @@
@interface JitsiMeetView ()
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
+ (BOOL)loadURLInViews:(NSDictionary *)urlObject;
@end

View File

@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,26 +34,6 @@
@property (nonatomic) BOOL welcomePageEnabled;
+ (BOOL)application:(UIApplication *_Nonnull)application
didFinishLaunchingWithOptions:(NSDictionary *_Nonnull)launchOptions;
+ (BOOL)application:(UIApplication * _Nonnull)application
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler;
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
+ (BOOL)application:(UIApplication * _Nonnull)application
openURL:(NSURL * _Nonnull)URL
sourceApplication:(NSString * _Nullable)sourceApplication
annotation:(id _Nullable)annotation __deprecated;
- (void)loadURL:(NSURL * _Nullable)url;
- (void)loadURLObject:(NSDictionary * _Nullable)urlObject;
- (void)loadURLString:(NSString * _Nullable)urlString;
- (void)loadURL:(NSDictionary * _Nullable)urlObject;
@end

View File

@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,62 +15,15 @@
* limitations under the License.
*/
#import <Intents/Intents.h>
#include <mach/mach_time.h>
#import <React/RCTAssert.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTRootView.h>
#import <RNGoogleSignin/RNGoogleSignin.h>
#import "Dropbox.h"
#import "JitsiMeet+Private.h"
#import "JitsiMeetView+Private.h"
#import "RCTBridgeWrapper.h"
/**
* A `RCTFatalHandler` implementation which swallows JavaScript errors. In the
* Release configuration, React Native will (intentionally) raise an unhandled
* `NSException` for an unhandled JavaScript error. This will effectively kill
* the application. `_RCTFatal` is suitable to be in accord with the Web i.e.
* not kill the application.
*/
RCTFatalHandler _RCTFatal = ^(NSError *error) {
id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
@try {
NSString *name
= [NSString stringWithFormat:@"%@: %@",
RCTFatalExceptionName,
error.localizedDescription];
NSString *message
= RCTFormatError(error.localizedDescription, jsStackTrace, 75);
[NSException raise:name format:@"%@", message];
} @catch (NSException *e) {
if (!jsStackTrace) {
@throw;
}
}
};
/**
* Helper function to register a fatal error handler for React. Our handler
* won't kill the process, it will swallow JS errors and print stack traces
* instead.
*/
void registerFatalErrorHandler() {
#if !DEBUG
// In the Release configuration, React Native will (intentionally) raise an
// unhandled `NSException` for an unhandled JavaScript error. This will
// effectively kill the application. In accord with the Web, do not kill the
// application.
if (!RCTGetFatalHandler()) {
RCTSetFatalHandler(_RCTFatal);
}
#endif
}
@interface JitsiMeetView() {
@implementation JitsiMeetView {
/**
* The unique identifier of this `JitsiMeetView` within the process for the
* purposes of `ExternalAPI`. The name scope was inspired by postis which we
@ -78,98 +32,24 @@ void registerFatalErrorHandler() {
NSString *externalAPIScope;
RCTRootView *rootView;
}
@end
@implementation JitsiMeetView {
NSNumber *_pictureInPictureEnabled;
}
@dynamic pictureInPictureEnabled;
static NSString *_conferenceActivityType;
static RCTBridgeWrapper *bridgeWrapper;
/**
* Copy of the `launchOptions` dictionary that the application was started with.
* It is required for the initial URL to be used if a (Universal) link was used
* to launch a new instance of the application.
*/
static NSDictionary *_launchOptions;
/**
* The `JitsiMeetView`s associated with their `ExternalAPI` scopes (i.e. unique
* identifiers within the process).
*/
static NSMapTable<NSString *, JitsiMeetView *> *views;
+ (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Store launch options, will be used when we create the bridge.
_launchOptions = [launchOptions copy];
[Dropbox setAppKey];
return YES;
}
#pragma mark Linking delegate helpers
// https://facebook.github.io/react-native/docs/linking.html
+ (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
{
// XXX At least twice we received bug reports about malfunctioning loadURL
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
// expected in our testing. But that was to be expected because the app does
// not exercise loadURL. In order to increase the test coverage of loadURL,
// channel Universal linking through loadURL.
id url = [self conferenceURLFromUserActivity:userActivity];
if (url && [self loadURLObjectInViews:url]) {
return YES;
}
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([Dropbox application:app openURL:url options:options]) {
return YES;
}
if ([RNGoogleSignin application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]) {
return YES;
}
// XXX At least twice we received bug reports about malfunctioning loadURL
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
// expected in our testing. But that was to be expected because the app does
// not exercise loadURL. In order to increase the test coverage of loadURL,
// channel Universal linking through loadURL.
if ([self loadURLInViews:url]) {
return YES;
}
return [RCTLinkingManager application:app openURL:url options:options];
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
return [self application:application openURL:url options:@{}];
/**
* This gets called automagically when the program starts.
*/
__attribute__((constructor))
static void initializeViewsMap() {
views = [NSMapTable strongToWeakObjectsMapTable];
}
#pragma mark Initializers
@ -201,19 +81,26 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
return self;
}
#pragma mark API
/**
* Loads a specific `NSURL` which may identify a conference to join. If the
* specified `NSURL` is `nil` and the Welcome page is enabled, the Welcome page
* is displayed instead.
* Internal initialization:
*
* @param url The `NSURL` to load which may identify a conference to join.
* - sets the background color
* - initializes the external API scope
*/
- (void)loadURL:(NSURL *)url {
[self loadURLString:url ? url.absoluteString : nil];
- (void)initWithXXX {
// Hook this JitsiMeetView into ExternalAPI.
externalAPIScope = [NSUUID UUID].UUIDString;
[views setObject:self forKey:externalAPIScope];
// Set a background color which is in accord with the JavaScript and Android
// parts of the application and causes less perceived visual flicker than
// the default background color.
self.backgroundColor
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
}
#pragma mark API
/**
* Loads a specific URL which may identify a conference to join. The URL is
* specified in the form of an `NSDictionary` of properties which (1)
@ -224,7 +111,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
*
* @param urlObject The URL to load which may identify a conference to join.
*/
- (void)loadURLObject:(NSDictionary *)urlObject {
- (void)loadURL:(NSDictionary *_Nullable)urlObject {
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
if (self.defaultURL) {
@ -242,14 +129,14 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
props[@"url"] = urlObject;
}
// XXX The method loadURLObject: is supposed to be imperative i.e. a second
// XXX The method loadURL: is supposed to be imperative i.e. a second
// invocation with one and the same URL is expected to join the respective
// conference again if the first invocation was followed by leaving the
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented bellow introduces imperativeness
// in React Component props by defining a unique value per loadURLObject:
// in React Component props by defining a unique value per loadURL:
// invocation.
props[@"timestamp"] = @(mach_absolute_time());
@ -257,8 +144,9 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
// Update props with the new URL.
rootView.appProperties = props;
} else {
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
rootView
= [[RCTRootView alloc] initWithBridge:bridgeWrapper.bridge
= [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"App"
initialProperties:props];
rootView.backgroundColor = self.backgroundColor;
@ -272,28 +160,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
}
}
/**
* Loads a specific URL `NSString` which may identify a conference to
* join. If the specified URL `NSString` is `nil` and the Welcome page is
* enabled, the Welcome page is displayed instead.
*
* @param urlString The URL `NSString` to load which may identify a conference
* to join.
*/
- (void)loadURLString:(NSString *)urlString {
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
}
#pragma conferenceActivityType getter / setter
+ (NSString *)conferenceActivityType {
return _conferenceActivityType;
}
+ (void) setConferenceActivityType:(NSString *)conferenceActivityType {
_conferenceActivityType = conferenceActivityType;
}
#pragma pictureInPictureEnabled getter / setter
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
@ -318,19 +184,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
#pragma mark Private methods
/**
* Loads a specific `NSURL` in all existing `JitsiMeetView`s.
*
* @param url The `NSURL` to load in all existing `JitsiMeetView`s.
* @return `YES` if the specified `url` was submitted for loading in at least
* one `JitsiMeetView`; otherwise, `NO`.
*/
+ (BOOL)loadURLInViews:(NSURL *)url {
return
[self loadURLObjectInViews:url ? @{ @"url": url.absoluteString } : nil];
}
+ (BOOL)loadURLObjectInViews:(NSDictionary *)urlObject {
+ (BOOL)loadURLInViews:(NSDictionary *)urlObject {
BOOL handled = NO;
if (views) {
@ -339,7 +193,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
= [self viewForExternalAPIScope:externalAPIScope];
if (view) {
[view loadURLObject:urlObject];
[view loadURL:urlObject];
handled = YES;
}
}
@ -348,79 +202,8 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
return handled;
}
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
return @{ @"url" : userActivity.webpageURL.absoluteString };
} else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
// App was started by a CallKit Intent
INIntent *intent = userActivity.interaction.intent;
NSArray<INPerson *> *contacts;
NSString *url;
BOOL startAudioOnly = NO;
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
contacts = ((INStartAudioCallIntent *) intent).contacts;
startAudioOnly = YES;
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
contacts = ((INStartVideoCallIntent *) intent).contacts;
}
if (contacts && (url = contacts.firstObject.personHandle.value)) {
return @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
}
} else if (_conferenceActivityType && [activityType isEqualToString:_conferenceActivityType]) {
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
NSString *url;
if ((url = userActivity.userInfo[@"url"])) {
return @{ @"url" : url };
}
}
return nil;
}
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
return [views objectForKey:externalAPIScope];
}
/**
* Internal initialization:
*
* - sets the background color
* - creates the React bridge
* - loads the necessary custom fonts
* - registers a custom fatal error error handler for React
*/
- (void)initWithXXX {
static dispatch_once_t dispatchOncePredicate;
dispatch_once(&dispatchOncePredicate, ^{
// Initialize the static state of JitsiMeetView.
bridgeWrapper
= [[RCTBridgeWrapper alloc] initWithLaunchOptions:_launchOptions];
views = [NSMapTable strongToWeakObjectsMapTable];
// Register a fatal error handler for React.
registerFatalErrorHandler();
});
// Hook this JitsiMeetView into ExternalAPI.
externalAPIScope = [NSUUID UUID].UUIDString;
[views setObject:self forKey:externalAPIScope];
// Set a background color which is in accord with the JavaScript and Android
// parts of the application and causes less perceived visual flicker than
// the default background color.
self.backgroundColor
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
}
@end

View File

@ -1,56 +0,0 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "JitsiMeetView+Private.h"
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
@interface LaunchOptions : NSObject<RCTBridgeModule>
@property (nonatomic, weak) RCTBridge *bridge;
@end
@implementation LaunchOptions
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject) {
id initialURL = nil;
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL *url = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
initialURL = url.absoluteString;
} else {
NSDictionary *userActivityDictionary
= self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity
= [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil) {
initialURL = [JitsiMeetView conferenceURLFromUserActivity:userActivity];
}
}
resolve(initialURL != nil ? initialURL : (id)kCFNull);
}
@end

View File

@ -34,6 +34,4 @@
@property (nonatomic, readonly, strong) RCTBridge *bridge;
- (instancetype)initWithLaunchOptions:(NSDictionary *)launchOptions;
@end

View File

@ -22,12 +22,12 @@
*/
@implementation RCTBridgeWrapper
- (instancetype)initWithLaunchOptions:(NSDictionary *)launchOptions {
- (instancetype)init {
self = [super init];
if (self) {
_bridge
= [[RCTBridge alloc] initWithDelegate:self
launchOptions:launchOptions];
launchOptions:nil];
}
return self;

22
ios/sdk/src/ReactUtils.h Normal file
View File

@ -0,0 +1,22 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ReactUtils_h
#define ReactUtils_h
void registerReactFatalErrorHandler(void);
#endif /* ReactUtils_h */

60
ios/sdk/src/ReactUtils.m Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <React/RCTAssert.h>
#import "ReactUtils.h"
/**
* A `RCTFatalHandler` implementation which swallows JavaScript errors. In the
* Release configuration, React Native will (intentionally) raise an unhandled
* `NSException` for an unhandled JavaScript error. This will effectively kill
* the application. `_RCTFatal` is suitable to be in accord with the Web i.e.
* not kill the application.
*/
RCTFatalHandler _RCTFatal = ^(NSError *error) {
id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
@try {
NSString *name
= [NSString stringWithFormat:@"%@: %@",
RCTFatalExceptionName,
error.localizedDescription];
NSString *message
= RCTFormatError(error.localizedDescription, jsStackTrace, 75);
[NSException raise:name format:@"%@", message];
} @catch (NSException *e) {
if (!jsStackTrace) {
@throw;
}
}
};
/**
* Helper function to register a fatal error handler for React. Our handler
* won't kill the process, it will swallow JS errors and print stack traces
* instead.
*/
void registerReactFatalErrorHandler() {
#if !DEBUG
// In the Release configuration, React Native will (intentionally) raise an
// unhandled `NSException` for an unhandled JavaScript error. This will
// effectively kill the application. In accord with the Web, do not kill the
// application.
if (!RCTGetFatalHandler()) {
RCTSetFatalHandler(_RCTFatal);
}
#endif
}