RLMApp.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2020 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMApp_Private.hpp"
  19. #import "RLMBSON_Private.hpp"
  20. #import "RLMCredentials_Private.hpp"
  21. #import "RLMEmailPasswordAuth.h"
  22. #import "RLMLogger.h"
  23. #import "RLMPushClient_Private.hpp"
  24. #import "RLMSyncManager_Private.hpp"
  25. #import "RLMUser_Private.hpp"
  26. #import "RLMUtil.hpp"
  27. #import <realm/object-store/sync/sync_manager.hpp>
  28. #import <realm/sync/config.hpp>
  29. #if !defined(REALM_COCOA_VERSION)
  30. #import "RLMVersion.h"
  31. #endif
  32. using namespace realm;
  33. #pragma mark CocoaNetworkTransport
  34. namespace {
  35. /// Internal transport struct to bridge RLMNetworkingTransporting to the GenericNetworkTransport.
  36. class CocoaNetworkTransport : public realm::app::GenericNetworkTransport {
  37. public:
  38. CocoaNetworkTransport(id<RLMNetworkTransport> transport) : m_transport(transport) {}
  39. void send_request_to_server(const app::Request& request,
  40. util::UniqueFunction<void(const app::Response&)>&& completion) override {
  41. // Convert the app::Request to an RLMRequest
  42. auto rlmRequest = [RLMRequest new];
  43. rlmRequest.url = @(request.url.data());
  44. rlmRequest.body = @(request.body.data());
  45. NSMutableDictionary *headers = [NSMutableDictionary new];
  46. for (auto&& header : request.headers) {
  47. headers[@(header.first.data())] = @(header.second.data());
  48. }
  49. rlmRequest.headers = headers;
  50. rlmRequest.method = static_cast<RLMHTTPMethod>(request.method);
  51. rlmRequest.timeout = request.timeout_ms / 1000.0;
  52. // Send the request through to the Cocoa level transport
  53. auto completion_ptr = completion.release();
  54. [m_transport sendRequestToServer:rlmRequest completion:^(RLMResponse *response) {
  55. util::UniqueFunction<void(const app::Response&)> completion(completion_ptr);
  56. std::map<std::string, std::string> bridgingHeaders;
  57. [response.headers enumerateKeysAndObjectsUsingBlock:[&](NSString *key, NSString *value, BOOL *) {
  58. bridgingHeaders[key.UTF8String] = value.UTF8String;
  59. }];
  60. // Convert the RLMResponse to an app:Response and pass downstream to
  61. // the object store
  62. completion(app::Response{
  63. .http_status_code = static_cast<int>(response.httpStatusCode),
  64. .custom_status_code = static_cast<int>(response.customStatusCode),
  65. .headers = bridgingHeaders,
  66. .body = response.body ? response.body.UTF8String : ""
  67. });
  68. }];
  69. }
  70. id<RLMNetworkTransport> transport() const {
  71. return m_transport;
  72. }
  73. private:
  74. id<RLMNetworkTransport> m_transport;
  75. };
  76. }
  77. #pragma mark RLMAppConfiguration
  78. @implementation RLMAppConfiguration {
  79. realm::app::App::Config _config;
  80. }
  81. - (instancetype)initWithConfig:(const realm::app::App::Config &)config {
  82. if (self = [super init]) {
  83. _config = config;
  84. return self;
  85. }
  86. return nil;
  87. }
  88. - (instancetype)init {
  89. return [self initWithBaseURL:nil
  90. transport:nil
  91. localAppName:nil
  92. localAppVersion:nil];
  93. }
  94. - (instancetype)initWithBaseURL:(nullable NSString *)baseURL
  95. transport:(nullable id<RLMNetworkTransport>)transport
  96. localAppName:(nullable NSString *)localAppName
  97. localAppVersion:(nullable NSString *)localAppVersion {
  98. return [self initWithBaseURL:baseURL
  99. transport:transport
  100. localAppName:localAppName
  101. localAppVersion:localAppVersion
  102. defaultRequestTimeoutMS:60000];
  103. }
  104. - (instancetype)initWithBaseURL:(nullable NSString *)baseURL
  105. transport:(nullable id<RLMNetworkTransport>)transport
  106. localAppName:(nullable NSString *)localAppName
  107. localAppVersion:(nullable NSString *)localAppVersion
  108. defaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS {
  109. if (self = [super init]) {
  110. self.baseURL = baseURL;
  111. self.transport = transport;
  112. self.localAppName = localAppName;
  113. self.localAppVersion = localAppVersion;
  114. self.defaultRequestTimeoutMS = defaultRequestTimeoutMS;
  115. _config.device_info.sdk = "Realm Swift";
  116. // Platform info isn't available when running via `swift test`.
  117. // Non-Xcode SPM builds can't build for anything but macOS, so this is
  118. // probably unimportant for now and we can just report "unknown"
  119. auto processInfo = [NSProcessInfo processInfo];
  120. auto platform = [processInfo.environment[@"RUN_DESTINATION_DEVICE_PLATFORM_IDENTIFIER"]
  121. componentsSeparatedByString:@"."].lastObject;
  122. RLMNSStringToStdString(_config.device_info.platform,
  123. platform ?: @"unknown");
  124. RLMNSStringToStdString(_config.device_info.platform_version,
  125. [processInfo operatingSystemVersionString] ?: @"unknown");
  126. RLMNSStringToStdString(_config.device_info.sdk_version, REALM_COCOA_VERSION);
  127. return self;
  128. }
  129. return nil;
  130. }
  131. - (realm::app::App::Config&)config {
  132. return _config;
  133. }
  134. - (void)setAppId:(NSString *)appId {
  135. RLMNSStringToStdString(_config.app_id, appId);
  136. }
  137. - (NSString *)baseURL {
  138. if (_config.base_url) {
  139. return @(_config.base_url->c_str());
  140. }
  141. return nil;
  142. }
  143. static void setOptionalString(std::optional<std::string>& dst, NSString *src) {
  144. std::string tmp;
  145. RLMNSStringToStdString(tmp, src);
  146. dst = tmp.empty() ? util::none : std::optional(std::move(tmp));
  147. }
  148. - (void)setBaseURL:(nullable NSString *)baseURL {
  149. setOptionalString(_config.base_url, baseURL);
  150. }
  151. - (id<RLMNetworkTransport>)transport {
  152. return static_cast<CocoaNetworkTransport&>(*_config.transport).transport();
  153. }
  154. - (void)setTransport:(id<RLMNetworkTransport>)transport {
  155. if (!transport) {
  156. transport = [RLMNetworkTransport new];
  157. }
  158. _config.transport = std::make_shared<CocoaNetworkTransport>(transport);
  159. }
  160. - (NSString *)localAppName {
  161. if (_config.local_app_name) {
  162. return @((_config.base_url)->c_str());
  163. }
  164. return nil;
  165. }
  166. - (void)setLocalAppName:(nullable NSString *)localAppName {
  167. setOptionalString(_config.local_app_name, localAppName);
  168. }
  169. - (NSString *)localAppVersion {
  170. if (_config.local_app_version) {
  171. return @(_config.base_url->c_str());
  172. }
  173. return nil;
  174. }
  175. - (void)setLocalAppVersion:(nullable NSString *)localAppVersion {
  176. setOptionalString(_config.local_app_version, localAppVersion);
  177. }
  178. - (NSUInteger)defaultRequestTimeoutMS {
  179. return _config.default_request_timeout_ms.value_or(60000U);
  180. }
  181. - (void)setDefaultRequestTimeoutMS:(NSUInteger)defaultRequestTimeoutMS {
  182. _config.default_request_timeout_ms = (uint64_t)defaultRequestTimeoutMS;
  183. }
  184. @end
  185. #pragma mark RLMAppSubscriptionToken
  186. @implementation RLMAppSubscriptionToken {
  187. std::shared_ptr<app::App> _app;
  188. std::optional<app::App::Token> _token;
  189. }
  190. - (instancetype)initWithApp:(std::shared_ptr<app::App>)app token:(app::App::Token&&)token {
  191. if (self = [super init]) {
  192. _app = std::move(app);
  193. _token = std::move(token);
  194. }
  195. return self;
  196. }
  197. - (void)unsubscribe {
  198. _token.reset();
  199. _app.reset();
  200. }
  201. @end
  202. #pragma mark RLMApp
  203. @interface RLMApp() <ASAuthorizationControllerDelegate> {
  204. std::shared_ptr<realm::app::App> _app;
  205. __weak id<RLMASLoginDelegate> _authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0));
  206. }
  207. @end
  208. @implementation RLMApp : NSObject
  209. + (void)initialize {
  210. [RLMRealm class];
  211. // Even though there is nothing to log when the App initialises, we want to
  212. // be able to log anything happening after this e.g. login/register.
  213. [RLMLogger class];
  214. }
  215. - (instancetype)initWithApp:(std::shared_ptr<realm::app::App>)app {
  216. if (self = [super init]) {
  217. _configuration = [[RLMAppConfiguration alloc] initWithConfig:app->config()];
  218. _app = app;
  219. _syncManager = [[RLMSyncManager alloc] initWithSyncManager:_app->sync_manager()];
  220. return self;
  221. }
  222. return nil;
  223. }
  224. - (instancetype)initWithId:(NSString *)appId
  225. configuration:(RLMAppConfiguration *)configuration
  226. rootDirectory:(NSURL *)rootDirectory {
  227. if ([appId length] == 0) {
  228. @throw RLMException(@"AppId cannot be an empty string");
  229. }
  230. if (self = [super init]) {
  231. if (!configuration) {
  232. configuration = [[RLMAppConfiguration alloc] initWithBaseURL:nil
  233. transport:nil
  234. localAppName:nil
  235. localAppVersion:nil];
  236. }
  237. _configuration = configuration;
  238. [_configuration setAppId:appId];
  239. _app = RLMTranslateError([&] {
  240. return app::App::get_shared_app(configuration.config,
  241. [RLMSyncManager configurationWithRootDirectory:rootDirectory appId:appId]);
  242. });
  243. _syncManager = [[RLMSyncManager alloc] initWithSyncManager:_app->sync_manager()];
  244. return self;
  245. }
  246. return nil;
  247. }
  248. static NSMutableDictionary *s_apps = [NSMutableDictionary new];
  249. static std::mutex& s_appMutex = *new std::mutex();
  250. + (NSArray *)allApps {
  251. std::lock_guard<std::mutex> lock(s_appMutex);
  252. return s_apps.allValues;
  253. }
  254. + (void)resetAppCache {
  255. std::lock_guard<std::mutex> lock(s_appMutex);
  256. [s_apps removeAllObjects];
  257. app::App::clear_cached_apps();
  258. }
  259. + (instancetype)appWithId:(NSString *)appId
  260. configuration:(RLMAppConfiguration *)configuration
  261. rootDirectory:(NSURL *)rootDirectory {
  262. std::lock_guard<std::mutex> lock(s_appMutex);
  263. if (RLMApp *app = s_apps[appId]) {
  264. return app;
  265. }
  266. RLMApp *app = [[RLMApp alloc] initWithId:appId configuration:configuration rootDirectory:rootDirectory];
  267. s_apps[appId] = app;
  268. return app;
  269. }
  270. + (instancetype)uncachedAppWithId:(NSString *)appId
  271. configuration:(RLMAppConfiguration *)configuration
  272. rootDirectory:(NSURL *)rootDirectory {
  273. REALM_ASSERT(appId.length);
  274. [configuration setAppId:appId];
  275. auto app = RLMTranslateError([&] {
  276. return app::App::get_uncached_app(configuration.config,
  277. [RLMSyncManager configurationWithRootDirectory:rootDirectory appId:appId]);
  278. });
  279. return [[RLMApp alloc] initWithApp:app];
  280. }
  281. + (instancetype)appWithId:(NSString *)appId configuration:(RLMAppConfiguration *)configuration {
  282. return [self appWithId:appId configuration:configuration rootDirectory:nil];
  283. }
  284. + (instancetype)appWithId:(NSString *)appId {
  285. return [self appWithId:appId configuration:nil];
  286. }
  287. - (NSString *)appId {
  288. return @(_app->config().app_id.c_str());
  289. }
  290. - (std::shared_ptr<realm::app::App>)_realmApp {
  291. return _app;
  292. }
  293. - (NSDictionary<NSString *, RLMUser *> *)allUsers {
  294. NSMutableDictionary *buffer = [NSMutableDictionary new];
  295. for (auto&& user : _app->sync_manager()->all_users()) {
  296. NSString *identity = @(user->identity().c_str());
  297. buffer[identity] = [[RLMUser alloc] initWithUser:std::move(user) app:self];
  298. }
  299. return buffer;
  300. }
  301. - (RLMUser *)currentUser {
  302. if (auto user = _app->sync_manager()->get_current_user()) {
  303. return [[RLMUser alloc] initWithUser:user app:self];
  304. }
  305. return nil;
  306. }
  307. - (RLMEmailPasswordAuth *)emailPasswordAuth {
  308. return [[RLMEmailPasswordAuth alloc] initWithApp: self];
  309. }
  310. - (void)loginWithCredential:(RLMCredentials *)credentials
  311. completion:(RLMUserCompletionBlock)completionHandler {
  312. auto completion = ^(std::shared_ptr<SyncUser> user, std::optional<app::AppError> error) {
  313. if (error) {
  314. return completionHandler(nil, makeError(*error));
  315. }
  316. completionHandler([[RLMUser alloc] initWithUser:user app:self], nil);
  317. };
  318. return RLMTranslateError([&] {
  319. return _app->log_in_with_credentials(credentials.appCredentials, completion);
  320. });
  321. }
  322. - (RLMUser *)switchToUser:(RLMUser *)syncUser {
  323. return RLMTranslateError([&] {
  324. return [[RLMUser alloc] initWithUser:_app->switch_user(syncUser._syncUser) app:self];
  325. });
  326. }
  327. - (RLMPushClient *)pushClientWithServiceName:(NSString *)serviceName {
  328. return RLMTranslateError([&] {
  329. return [[RLMPushClient alloc] initWithPushClient:_app->push_notification_client(serviceName.UTF8String)];
  330. });
  331. }
  332. #pragma mark - Sign In With Apple Extension
  333. - (void)setAuthorizationDelegate:(id<RLMASLoginDelegate>)authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) {
  334. _authorizationDelegate = authorizationDelegate;
  335. }
  336. - (id<RLMASLoginDelegate>)authorizationDelegate API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) {
  337. return _authorizationDelegate;
  338. }
  339. - (void)setASAuthorizationControllerDelegateForController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) {
  340. controller.delegate = self;
  341. }
  342. - (void)authorizationController:(__unused ASAuthorizationController *)controller
  343. didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) {
  344. NSString *jwt = [[NSString alloc] initWithData:((ASAuthorizationAppleIDCredential *)authorization.credential).identityToken
  345. encoding:NSUTF8StringEncoding];
  346. [self loginWithCredential:[RLMCredentials credentialsWithAppleToken:jwt]
  347. completion:^(RLMUser *user, NSError *error) {
  348. if (user) {
  349. [self.authorizationDelegate authenticationDidCompleteWithUser:user];
  350. } else {
  351. [self.authorizationDelegate authenticationDidFailWithError:error];
  352. }
  353. }];
  354. }
  355. - (void)authorizationController:(__unused ASAuthorizationController *)controller
  356. didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0)) {
  357. [self.authorizationDelegate authenticationDidFailWithError:error];
  358. }
  359. - (RLMAppSubscriptionToken *)subscribe:(RLMAppNotificationBlock)block {
  360. return [[RLMAppSubscriptionToken alloc] initWithApp:_app token:_app->subscribe([block, self] (auto&) {
  361. block(self);
  362. })];
  363. }
  364. @end