diff --git a/ios/app/app.entitlements b/ios/app/app.entitlements
index 939bee422..accd78bb6 100644
--- a/ios/app/app.entitlements
+++ b/ios/app/app.entitlements
@@ -7,5 +7,7 @@
applinks:beta.meet.jit.si
applinks:meet.jit.si
+ com.apple.developer.siri
+
diff --git a/ios/app/app.xcodeproj/project.pbxproj b/ios/app/app.xcodeproj/project.pbxproj
index b73552d21..a2c85bf1d 100644
--- a/ios/app/app.xcodeproj/project.pbxproj
+++ b/ios/app/app.xcodeproj/project.pbxproj
@@ -43,6 +43,7 @@
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 0BBD021F212EB69D00CCB19F /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = ""; };
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
@@ -96,6 +97,7 @@
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
0B412F201EDEE95300B1A0A6 /* Main.storyboard */,
+ 0BBD021F212EB69D00CCB19F /* Types.h */,
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
);
@@ -173,6 +175,9 @@
com.apple.SafariKeychain = {
enabled = 1;
};
+ com.apple.Siri = {
+ enabled = 1;
+ };
};
};
};
diff --git a/ios/app/src/AppDelegate.m b/ios/app/src/AppDelegate.m
index 3768cf44a..3de9b1417 100644
--- a/ios/app/src/AppDelegate.m
+++ b/ios/app/src/AppDelegate.m
@@ -17,6 +17,7 @@
#import "AppDelegate.h"
#import "FIRUtilities.h"
+#import "Types.h"
#import
@@ -37,6 +38,10 @@
[Fabric with:@[[Crashlytics class]]];
}
+ // Set the conference activity type defined in this application.
+ // This cannot be defined by the SDK.
+ JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
+
return [JitsiMeetView application:application
didFinishLaunchingWithOptions:launchOptions];
}
diff --git a/ios/app/src/Info.plist b/ios/app/src/Info.plist
index 335b12c90..b2a7c1960 100644
--- a/ios/app/src/Info.plist
+++ b/ios/app/src/Info.plist
@@ -97,5 +97,9 @@
firebase_crashlytics_collection_enabled
false
+ NSUserActivityTypes
+
+ org.jitsi.JitsiMeet.ios.conference
+
diff --git a/ios/app/src/Types.h b/ios/app/src/Types.h
new file mode 100644
index 000000000..c44cd888e
--- /dev/null
+++ b/ios/app/src/Types.h
@@ -0,0 +1,7 @@
+
+#import
+
+// This must match what's defined in the NSUserActivityTypes array in the
+// Info.plist file.
+static NSString *const JitsiMeetConferenceActivityType
+ = @"org.jitsi.JitsiMeet.ios.conference";
diff --git a/ios/app/src/ViewController.m b/ios/app/src/ViewController.m
index 601ea0be0..205e7e3bf 100644
--- a/ios/app/src/ViewController.m
+++ b/ios/app/src/ViewController.m
@@ -14,8 +14,16 @@
* limitations under the License.
*/
+#import
+#import
+#import
+
+#import "Types.h"
#import "ViewController.h"
+// Needed for NSUserActivity suggestedInvocationPhrase
+@import Intents;
+
/**
* The query to perform through JMAddPeopleController when the InviteButton is
* tapped in order to exercise the public API of the feature invite. If nil, the
@@ -33,11 +41,10 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[super viewDidLoad];
JitsiMeetView *view = (JitsiMeetView *) self.view;
+ view.delegate = self;
#ifdef DEBUG
- view.delegate = self;
-
// inviteController
JMInviteController *inviteController = view.inviteController;
inviteController.delegate = self;
@@ -56,12 +63,13 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[view loadURL:nil];
}
-#if DEBUG
+
// JitsiMeetViewDelegate
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
withData:(NSDictionary *)data {
+#if DEBUG
NSLog(
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
__FILE__, __LINE__, name, data);
@@ -70,6 +78,7 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[NSThread isMainThread],
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
name);
+#endif
}
- (void)conferenceFailed:(NSDictionary *)data {
@@ -78,6 +87,36 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
- (void)conferenceJoined:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
+
+ // Register a NSUserActivity for this conference so it can be invoked as a
+ // Siri shortcut. This is only supported in iOS >= 12.
+#ifdef __IPHONE_12_0
+ if (@available(iOS 12.0, *)) {
+ NSUserActivity *userActivity
+ = [[NSUserActivity alloc] initWithActivityType:JitsiMeetConferenceActivityType];
+
+ NSString *urlStr = data[@"url"];
+ NSURL *url = [NSURL URLWithString:urlStr];
+ NSString *conference = [url.pathComponents lastObject];
+
+ userActivity.title = [NSString stringWithFormat:@"Join %@", conference];
+ userActivity.suggestedInvocationPhrase = @"Join my Jitsi meeting";
+ userActivity.userInfo = @{@"url": urlStr};
+ [userActivity setEligibleForSearch:YES];
+ [userActivity setEligibleForPrediction:YES];
+ [userActivity setPersistentIdentifier:urlStr];
+
+ // Subtitle
+ CSSearchableItemAttributeSet *attributes
+ = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeItem];
+ attributes.contentDescription = urlStr;
+ userActivity.contentAttributeSet = attributes;
+
+ self.userActivity = userActivity;
+ [userActivity becomeCurrent];
+ }
+#endif
+
}
- (void)conferenceLeft:(NSDictionary *)data {
@@ -96,6 +135,8 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
}
+#if DEBUG
+
// JMInviteControllerDelegate
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
diff --git a/ios/sdk/src/JitsiMeetView+Private.h b/ios/sdk/src/JitsiMeetView+Private.h
index cc167c8a0..b62501746 100644
--- a/ios/sdk/src/JitsiMeetView+Private.h
+++ b/ios/sdk/src/JitsiMeetView+Private.h
@@ -18,6 +18,7 @@
@interface JitsiMeetView ()
++ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
@end
diff --git a/ios/sdk/src/JitsiMeetView.h b/ios/sdk/src/JitsiMeetView.h
index 26c409c5f..f0a0d69ad 100644
--- a/ios/sdk/src/JitsiMeetView.h
+++ b/ios/sdk/src/JitsiMeetView.h
@@ -22,6 +22,8 @@
@interface JitsiMeetView : UIView
+@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
+
@property (copy, nonatomic, nullable) NSURL *defaultURL;
@property (nonatomic, nullable, weak) id delegate;
diff --git a/ios/sdk/src/JitsiMeetView.m b/ios/sdk/src/JitsiMeetView.m
index 776fafa41..d13f4e65c 100644
--- a/ios/sdk/src/JitsiMeetView.m
+++ b/ios/sdk/src/JitsiMeetView.m
@@ -88,6 +88,8 @@ void registerFatalErrorHandler() {
@dynamic pictureInPictureEnabled;
+static NSString *_conferenceActivityType;
+
static RCTBridgeWrapper *bridgeWrapper;
/**
@@ -277,6 +279,16 @@ static NSMapTable *views;
[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 {
@@ -358,6 +370,13 @@ static NSMapTable *views;
@"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;