瀏覽代碼

[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.
master
Saúl Ibarra Corretgé 7 年之前
父節點
當前提交
bd301403c4
共有 3 個檔案被更改,包括 108 行新增2 行删除
  1. 4
    0
      ios/sdk/sdk.xcodeproj/project.pbxproj
  2. 83
    0
      ios/sdk/src/LaunchOptions.m
  3. 21
    2
      react/index.native.js

+ 4
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj 查看文件

11
 		0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
11
 		0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
12
 		0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
12
 		0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
13
 		0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
13
 		0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
14
+		0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
14
 		0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
15
 		0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
15
 		0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
16
 		0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
16
 		0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
17
 		0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
34
 		0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
35
 		0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
35
 		0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
36
 		0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
36
 		0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
37
 		0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
38
+		0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
37
 		0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
39
 		0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
38
 		0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
40
 		0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
39
 		0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
41
 		0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
106
 				0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
108
 				0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
107
 				0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
109
 				0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
108
 				0BD906E91EC0C00300C8C18E /* Info.plist */,
110
 				0BD906E91EC0C00300C8C18E /* Info.plist */,
111
+				0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
109
 				0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
112
 				0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
110
 				0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
113
 				0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
111
 				0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
114
 				0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
305
 				0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
308
 				0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
306
 				0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
309
 				0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
307
 				0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
310
 				0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
311
+				0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
308
 				0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
312
 				0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
309
 				0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
313
 				0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
310
 				0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
314
 				0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,

+ 83
- 0
ios/sdk/src/LaunchOptions.m 查看文件

1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <Intents/Intents.h>
18
+
19
+#import <React/RCTBridge.h>
20
+#import <React/RCTBridgeModule.h>
21
+
22
+@interface LaunchOptions : NSObject<RCTBridgeModule>
23
+
24
+@property (nonatomic, weak) RCTBridge *bridge;
25
+
26
+@end
27
+
28
+@implementation LaunchOptions
29
+
30
+RCT_EXPORT_MODULE();
31
+
32
+- (dispatch_queue_t)methodQueue {
33
+    return dispatch_get_main_queue();
34
+}
35
+
36
+RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
37
+                  reject:(__unused RCTPromiseRejectBlock)reject) {
38
+    id initialURL = nil;
39
+    if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
40
+        NSURL *url = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
41
+        initialURL = url.absoluteString;
42
+    } else {
43
+        NSDictionary *userActivityDictionary
44
+            = self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
45
+        NSUserActivity *userActivity
46
+            = [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
47
+        if (userActivity != nil) {
48
+            NSString *activityType = userActivity.activityType;
49
+
50
+            if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
51
+                // App was started by opening a URL in the browser
52
+                initialURL = userActivity.webpageURL.absoluteString;
53
+            } else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
54
+                       || [activityType isEqualToString:@"INStartVideoCallIntent"]) {
55
+                // App was started by a CallKit Intent
56
+                INIntent *intent = userActivity.interaction.intent;
57
+                NSArray<INPerson *> *contacts;
58
+                NSString *url;
59
+                BOOL startAudioOnly = NO;
60
+
61
+                if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
62
+                    contacts = ((INStartAudioCallIntent *) intent).contacts;
63
+                    startAudioOnly = YES;
64
+                } else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
65
+                    contacts = ((INStartVideoCallIntent *) intent).contacts;
66
+                }
67
+
68
+                if (contacts && (url = contacts.firstObject.personHandle.value)) {
69
+                    initialURL
70
+                        = @{
71
+                            @"config": @{@"startAudioOnly":@(startAudioOnly)},
72
+                            @"url": url
73
+                            };
74
+                }
75
+            }
76
+        }
77
+    }
78
+
79
+    resolve(initialURL != nil ? initialURL : (id)kCFNull);
80
+}
81
+
82
+@end
83
+

+ 21
- 2
react/index.native.js 查看文件

14
 
14
 
15
 import PropTypes from 'prop-types';
15
 import PropTypes from 'prop-types';
16
 import React, { Component } from 'react';
16
 import React, { Component } from 'react';
17
-import { AppRegistry, Linking } from 'react-native';
17
+import { AppRegistry, Linking, NativeModules } from 'react-native';
18
 
18
 
19
 import { App } from './features/app';
19
 import { App } from './features/app';
20
 import { equals } from './features/base/redux';
20
 import { equals } from './features/base/redux';
77
         // Handle the URL, if any, with which the app was launched. But props
77
         // Handle the URL, if any, with which the app was launched. But props
78
         // have precedence.
78
         // have precedence.
79
         if (typeof this.props.url === 'undefined') {
79
         if (typeof this.props.url === 'undefined') {
80
-            Linking.getInitialURL()
80
+            this._getInitialURL()
81
                 .then(url => {
81
                 .then(url => {
82
                     if (typeof this.state.url === 'undefined') {
82
                     if (typeof this.state.url === 'undefined') {
83
                         this.setState({ url });
83
                         this.setState({ url });
95
         }
95
         }
96
     }
96
     }
97
 
97
 
98
+    /**
99
+     * Gets the initial URL the app was launched with. This can be a universal
100
+     * (or deep) link, or a CallKit intent in iOS. Since the native
101
+     * {@code Linking} module doesn't provide a way to access intents in iOS,
102
+     * those are handled with the {@code LaunchOptions} module, which
103
+     * essentially provides a replacement which takes that into consideration.
104
+     *
105
+     * @private
106
+     * @returns {Promise} - A promise which will be fulfilled with the URL that
107
+     * the app was launched with.
108
+     */
109
+    _getInitialURL() {
110
+        if (NativeModules.LaunchOptions) {
111
+            return NativeModules.LaunchOptions.getInitialURL();
112
+        }
113
+
114
+        return Linking.getInitialURL();
115
+    }
116
+
98
     /**
117
     /**
99
      * Implements React's {@link Component#componentWillReceiveProps()}.
118
      * Implements React's {@link Component#componentWillReceiveProps()}.
100
      *
119
      *

Loading…
取消
儲存