[RN] Fix app startup from a CallKit intent
Story time. Currently the app can be started in 4 ways: - just tapping on the icon - via a deep link - via a universal link - via the phone's recent calls list The last 3 options will make the app join the specified room upon launch. React Native's Linking module implements the necessary bits to handle deep or universal linking, but CallKit is out of its scope. In order to blend any type of app startup mode, a new LaunchOptions module (iOS only) exports a getInitialURL function, akin to the one in the Linking module, but taking CallKit instents into consideration. This function is then used to make app startup with a URL consistent across all different modes.
This commit is contained in:
parent
d481c6f736
commit
bd301403c4
|
@ -11,6 +11,7 @@
|
|||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
|
||||
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
|
||||
|
@ -34,6 +35,7 @@
|
|||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
|
||||
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; 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>"; };
|
||||
|
@ -106,6 +108,7 @@
|
|||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
|
@ -305,6 +308,7 @@
|
|||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 <Intents/Intents.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) {
|
||||
NSString *activityType = userActivity.activityType;
|
||||
|
||||
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
||||
// App was started by opening a URL in the browser
|
||||
initialURL = 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)) {
|
||||
initialURL
|
||||
= @{
|
||||
@"config": @{@"startAudioOnly":@(startAudioOnly)},
|
||||
@"url": url
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(initialURL != nil ? initialURL : (id)kCFNull);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -14,7 +14,7 @@ import './features/base/react/prop-types-polyfill';
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { AppRegistry, Linking } from 'react-native';
|
||||
import { AppRegistry, Linking, NativeModules } from 'react-native';
|
||||
|
||||
import { App } from './features/app';
|
||||
import { equals } from './features/base/redux';
|
||||
|
@ -77,7 +77,7 @@ class Root extends Component {
|
|||
// Handle the URL, if any, with which the app was launched. But props
|
||||
// have precedence.
|
||||
if (typeof this.props.url === 'undefined') {
|
||||
Linking.getInitialURL()
|
||||
this._getInitialURL()
|
||||
.then(url => {
|
||||
if (typeof this.state.url === 'undefined') {
|
||||
this.setState({ url });
|
||||
|
@ -95,6 +95,25 @@ class Root extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initial URL the app was launched with. This can be a universal
|
||||
* (or deep) link, or a CallKit intent in iOS. Since the native
|
||||
* {@code Linking} module doesn't provide a way to access intents in iOS,
|
||||
* those are handled with the {@code LaunchOptions} module, which
|
||||
* essentially provides a replacement which takes that into consideration.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise} - A promise which will be fulfilled with the URL that
|
||||
* the app was launched with.
|
||||
*/
|
||||
_getInitialURL() {
|
||||
if (NativeModules.LaunchOptions) {
|
||||
return NativeModules.LaunchOptions.getInitialURL();
|
||||
}
|
||||
|
||||
return Linking.getInitialURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentWillReceiveProps()}.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue