You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JitsiMeetView.m 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * Copyright @ 2017-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. #import <CoreText/CoreText.h>
  17. #import <React/RCTAssert.h>
  18. #import <React/RCTLinkingManager.h>
  19. #import <React/RCTRootView.h>
  20. #import "JitsiMeetView+Private.h"
  21. #import "RCTBridgeWrapper.h"
  22. /**
  23. * A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
  24. * In the Release configuration, React Native will (intentionally) raise an
  25. * unhandled NSException for an unhandled JavaScript error. This will
  26. * effectively kill the application. <tt>_RCTFatal</tt> is suitable to be in
  27. * accord with the Web i.e. not kill the application.
  28. */
  29. RCTFatalHandler _RCTFatal = ^(NSError *error) {
  30. id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
  31. @try {
  32. NSString *name
  33. = [NSString stringWithFormat:@"%@: %@",
  34. RCTFatalExceptionName,
  35. error.localizedDescription];
  36. NSString *message
  37. = RCTFormatError(error.localizedDescription, jsStackTrace, 75);
  38. [NSException raise:name format:@"%@", message];
  39. } @catch (NSException *e) {
  40. if (!jsStackTrace) {
  41. @throw;
  42. }
  43. }
  44. };
  45. /**
  46. * Helper function to dynamically load custom fonts. The UIAppFonts key in the
  47. * plist file doesn't work for frameworks, so fonts have to be manually loaded.
  48. */
  49. void loadCustomFonts(Class clazz) {
  50. NSBundle *bundle = [NSBundle bundleForClass:clazz];
  51. NSArray *fonts = [bundle objectForInfoDictionaryKey:@"JitsiMeetFonts"];
  52. for (NSString *item in fonts) {
  53. NSString *fontName = [item stringByDeletingPathExtension];
  54. NSString *fontExt = [item pathExtension];
  55. NSString *fontPath = [bundle pathForResource:fontName ofType:fontExt];
  56. NSData *inData = [NSData dataWithContentsOfFile:fontPath];
  57. CFErrorRef error;
  58. CGDataProviderRef provider
  59. = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
  60. CGFontRef font = CGFontCreateWithDataProvider(provider);
  61. if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
  62. CFStringRef errorDescription = CFErrorCopyDescription(error);
  63. NSLog(@"Failed to load font: %@", errorDescription);
  64. CFRelease(errorDescription);
  65. }
  66. CFRelease(font);
  67. CFRelease(provider);
  68. }
  69. }
  70. /**
  71. * Helper function to register a fatal error handler for React. Our handler
  72. * won't kill the process, it will swallow JS errors and print stack traces
  73. * instead.
  74. */
  75. void registerFatalErrorHandler() {
  76. #if !DEBUG
  77. // In the Release configuration, React Native will (intentionally) raise
  78. // an unhandled NSException for an unhandled JavaScript error. This will
  79. // effectively kill the application. In accord with the Web, do not kill
  80. // the application.
  81. if (!RCTGetFatalHandler()) {
  82. RCTSetFatalHandler(_RCTFatal);
  83. }
  84. #endif
  85. }
  86. @interface JitsiMeetView() {
  87. /**
  88. * The unique identifier of this {@code JitsiMeetView} within the process
  89. * for the purposes of {@link ExternalAPI}. The name scope was inspired by
  90. * postis which we use on Web for the similar purposes of the iframe-based
  91. * external API.
  92. */
  93. NSString *externalAPIScope;
  94. RCTRootView *rootView;
  95. }
  96. @end
  97. @implementation JitsiMeetView
  98. static RCTBridgeWrapper *bridgeWrapper;
  99. /**
  100. * Copy of the {@code launchOptions} dictionary that the application was started
  101. * with. It is required for the initial URL to be used if a (Universal) link was
  102. * used to launch a new instance of the application.
  103. */
  104. static NSDictionary *_launchOptions;
  105. /**
  106. * The {@code JitsiMeetView}s associated with their {@code ExternalAPI} scopes
  107. * (i.e. unique identifiers within the process).
  108. */
  109. static NSMapTable<NSString *, JitsiMeetView *> *views;
  110. + (BOOL)application:(UIApplication *)application
  111. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  112. // Store launch options, will be used when we create the bridge.
  113. _launchOptions = [launchOptions copy];
  114. return YES;
  115. }
  116. #pragma mark Linking delegate helpers
  117. // https://facebook.github.io/react-native/docs/linking.html
  118. + (BOOL)application:(UIApplication *)application
  119. continueUserActivity:(NSUserActivity *)userActivity
  120. restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
  121. {
  122. return [RCTLinkingManager application:application
  123. continueUserActivity:userActivity
  124. restorationHandler:restorationHandler];
  125. }
  126. + (BOOL)application:(UIApplication *)application
  127. openURL:(NSURL *)url
  128. sourceApplication:(NSString *)sourceApplication
  129. annotation:(id)annotation {
  130. return [RCTLinkingManager application:application
  131. openURL:url
  132. sourceApplication:sourceApplication
  133. annotation:annotation];
  134. }
  135. #pragma mark Initializers
  136. - (instancetype)initWithCoder:(NSCoder *)coder {
  137. self = [super initWithCoder:coder];
  138. if (self) {
  139. [self initWithXXX];
  140. }
  141. return self;
  142. }
  143. - (instancetype)initWithFrame:(CGRect)frame {
  144. self = [super initWithFrame:frame];
  145. if (self) {
  146. [self initWithXXX];
  147. }
  148. return self;
  149. }
  150. #pragma mark API
  151. /**
  152. * Loads a specific {@link NSURL} which may identify a conference to join. If
  153. * the specified {@code NSURL} is {@code nil} and the Welcome page is enabled,
  154. * the Welcome page is displayed instead.
  155. *
  156. * @param url - The {@code NSURL} to load which may identify a conference to
  157. * join.
  158. */
  159. - (void)loadURL:(NSURL *)url {
  160. [self loadURLString:url ? url.absoluteString : nil];
  161. }
  162. /**
  163. * Loads a specific URL which may identify a conference to join. The URL is
  164. * specified in the form of an {@link NSDictionary} of properties which (1)
  165. * internally are sufficient to construct a URL {@code NSString} while (2)
  166. * abstracting the specifics of constructing the URL away from API
  167. * clients/consumers. If the specified URL is {@code nil} and the Welcome page
  168. * is enabled, the Welcome page is displayed instead.
  169. *
  170. * @param urlObject - The URL to load which may identify a conference to join.
  171. */
  172. - (void)loadURLObject:(NSDictionary *)urlObject {
  173. NSDictionary *props = @{
  174. @"externalAPIScope": externalAPIScope,
  175. @"url": urlObject,
  176. @"welcomePageEnabled": @(self.welcomePageEnabled)
  177. };
  178. if (rootView == nil) {
  179. rootView
  180. = [[RCTRootView alloc] initWithBridge:bridgeWrapper.bridge
  181. moduleName:@"App"
  182. initialProperties:props];
  183. rootView.backgroundColor = self.backgroundColor;
  184. // Add React's root view as a subview which completely covers this one.
  185. [rootView setFrame:[self bounds]];
  186. [self addSubview:rootView];
  187. } else {
  188. // Update props with the new URL.
  189. rootView.appProperties = props;
  190. }
  191. }
  192. /**
  193. * Loads a specific URL {@link NSString} which may identify a conference to
  194. * join. If the specified URL {@code NSString} is {@code nil} and the Welcome
  195. * page is enabled, the Welcome page is displayed instead.
  196. *
  197. * @param urlString - The URL {@code NSString} to load which may identify a
  198. * conference to join.
  199. */
  200. - (void)loadURLString:(NSString *)urlString {
  201. [self loadURLObject:urlString ? @{ @"url": urlString } : nil];
  202. }
  203. #pragma mark Private methods
  204. + (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
  205. return [views objectForKey:externalAPIScope];
  206. }
  207. /**
  208. * Internal initialization:
  209. *
  210. * - sets the background color
  211. * - creates the React bridge
  212. * - loads the necessary custom fonts
  213. * - registers a custom fatal error error handler for React
  214. */
  215. - (void)initWithXXX {
  216. static dispatch_once_t dispatchOncePredicate;
  217. dispatch_once(&dispatchOncePredicate, ^{
  218. // Initialize the static state of JitsiMeetView.
  219. bridgeWrapper
  220. = [[RCTBridgeWrapper alloc] initWithLaunchOptions:_launchOptions];
  221. views = [NSMapTable strongToWeakObjectsMapTable];
  222. // Dynamically load custom bundled fonts.
  223. loadCustomFonts(self.class);
  224. // Register a fatal error handler for React.
  225. registerFatalErrorHandler();
  226. });
  227. // Hook this JitsiMeetView into ExternalAPI.
  228. externalAPIScope = [NSUUID UUID].UUIDString;
  229. [views setObject:self forKey:externalAPIScope];
  230. // Set a background color which is in accord with the JavaScript and
  231. // Android parts of the application and causes less perceived visual
  232. // flicker than the default background color.
  233. self.backgroundColor
  234. = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
  235. }
  236. @end