/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #import "RCTPackagerConnection.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #if RCT_DEV @interface RCTPackagerConnection () @end template struct Registration { NSString *method; Handler handler; dispatch_queue_t queue; uint32_t token; }; @implementation RCTPackagerConnection { std::mutex _mutex; // protects all ivars RCTReconnectingWebSocket *_socket; BOOL _socketConnected; NSString *_jsLocationForSocket; id _bundleURLChangeObserver; uint32_t _nextToken; std::vector> _notificationRegistrations; std::vector> _requestRegistrations; std::vector> _connectedRegistrations; } + (instancetype)sharedPackagerConnection { static RCTPackagerConnection *connection; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ connection = [RCTPackagerConnection new]; }); return connection; } - (instancetype)init { if (self = [super init]) { _nextToken = 1; // Prevent randomly erasing a handler if you pass a bogus 0 token _jsLocationForSocket = [RCTBundleURLProvider sharedSettings].jsLocation; _socket = socketForLocation(_jsLocationForSocket); _socket.delegate = self; [_socket start]; RCTPackagerConnection *const __weak weakSelf = self; _bundleURLChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:RCTBundleURLProviderUpdatedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *_Nonnull note) { [weakSelf bundleURLSettingsChanged]; }]; } return self; } static RCTReconnectingWebSocket *socketForLocation(NSString *const jsLocation) { NSURLComponents *const components = [NSURLComponents new]; components.host = jsLocation ?: @"localhost"; components.scheme = @"http"; components.port = @(kRCTBundleURLProviderDefaultPort); components.path = @"/message"; components.queryItems = @[[NSURLQueryItem queryItemWithName:@"role" value:@"ios"]]; static dispatch_queue_t queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("com.facebook.RCTPackagerConnectionQueue", DISPATCH_QUEUE_SERIAL); }); return [[RCTReconnectingWebSocket alloc] initWithURL:components.URL queue:queue]; } - (void)stop { std::lock_guard l(_mutex); if (_socket == nil) { // Already stopped return; } [[NSNotificationCenter defaultCenter] removeObserver:_bundleURLChangeObserver]; _bundleURLChangeObserver = nil; _socketConnected = NO; [_socket stop]; _socket = nil; _notificationRegistrations.clear(); _requestRegistrations.clear(); } - (void)bundleURLSettingsChanged { std::lock_guard l(_mutex); if (_socket == nil) { return; // already stopped } NSString *const jsLocation = [RCTBundleURLProvider sharedSettings].jsLocation; if ([jsLocation isEqual:_jsLocationForSocket]) { return; // unchanged } _socket.delegate = nil; [_socket stop]; _jsLocationForSocket = jsLocation; _socket = socketForLocation(jsLocation); _socket.delegate = self; [_socket start]; } - (RCTHandlerToken)addNotificationHandler:(RCTNotificationHandler)handler queue:(dispatch_queue_t)queue forMethod:(NSString *)method { std::lock_guard l(_mutex); const auto token = _nextToken++; _notificationRegistrations.push_back({method, handler, queue, token}); return token; } - (RCTHandlerToken)addRequestHandler:(RCTRequestHandler)handler queue:(dispatch_queue_t)queue forMethod:(NSString *)method { std::lock_guard l(_mutex); const auto token = _nextToken++; _requestRegistrations.push_back({method, handler, queue, token}); return token; } - (RCTHandlerToken)addConnectedHandler:(RCTConnectedHandler)handler queue:(dispatch_queue_t)queue { std::lock_guard l(_mutex); if (_socketConnected) { dispatch_async(queue, ^{ handler(); }); return 0; // _nextToken starts at 1, so 0 is a no-op token } else { const auto token = _nextToken++; _connectedRegistrations.push_back({nil, handler, queue, token}); return token; } } - (void)removeHandler:(RCTHandlerToken)token { std::lock_guard l(_mutex); eraseRegistrationsWithToken(_notificationRegistrations, token); eraseRegistrationsWithToken(_requestRegistrations, token); eraseRegistrationsWithToken(_connectedRegistrations, token); } template static void eraseRegistrationsWithToken(std::vector> ®istrations, RCTHandlerToken token) { registrations.erase(std::remove_if(registrations.begin(), registrations.end(), [&token](const auto ®) { return reg.token == token; }), registrations.end()); } - (void)addHandler:(id)handler forMethod:(NSString *)method { dispatch_queue_t queue = [handler respondsToSelector:@selector(methodQueue)] ? [handler methodQueue] : dispatch_get_main_queue(); [self addNotificationHandler:^(NSDictionary *notification) { [handler handleNotification:notification]; } queue:queue forMethod:method]; [self addRequestHandler:^(NSDictionary *request, RCTPackagerClientResponder *responder) { [handler handleRequest:request withResponder:responder]; } queue:queue forMethod:method]; } static BOOL isSupportedVersion(NSNumber *version) { NSArray *const kSupportedVersions = @[ @(RCT_PACKAGER_CLIENT_PROTOCOL_VERSION) ]; return [kSupportedVersions containsObject:version]; } #pragma mark - RCTReconnectingWebSocketDelegate - (void)reconnectingWebSocketDidOpen:(RCTReconnectingWebSocket *)webSocket { std::vector> registrations; { std::lock_guard l(_mutex); _socketConnected = YES; registrations = _connectedRegistrations; _connectedRegistrations.clear(); } for (const auto ®istration : registrations) { // Beware: don't capture the reference to handler in a dispatched block! RCTConnectedHandler handler = registration.handler; dispatch_async(registration.queue, ^{ handler(); }); } } - (void)reconnectingWebSocket:(RCTReconnectingWebSocket *)webSocket didReceiveMessage:(id)message { NSError *error = nil; NSDictionary *msg = RCTJSONParse(message, &error); if (error) { RCTLogError(@"%@ failed to parse message with error %@\n\n%@\n", [self class], error, msg); return; } if (!isSupportedVersion(msg[@"version"])) { RCTLogError(@"%@ received message with not supported version %@", [self class], msg[@"version"]); return; } NSString *const method = msg[@"method"]; NSDictionary *const params = msg[@"params"]; id messageId = msg[@"id"]; if (messageId) { // Request const std::vector> registrations(registrationsWithMethod(_mutex, _requestRegistrations, method)); if (registrations.empty()) { RCTLogError(@"No handler found for packager method %@", msg[@"method"]); [[[RCTPackagerClientResponder alloc] initWithId:messageId socket:webSocket] respondWithError: [NSString stringWithFormat:@"No handler found for packager method %@", msg[@"method"]]]; } else { // If there are multiple matching request registrations, only one can win; // otherwise the packager would get multiple responses. Choose the last one. RCTRequestHandler handler = registrations.back().handler; dispatch_async(registrations.back().queue, ^{ handler(params, [[RCTPackagerClientResponder alloc] initWithId:messageId socket:webSocket]); }); } } else { // Notification const std::vector> registrations(registrationsWithMethod(_mutex, _notificationRegistrations, method)); for (const auto ®istration : registrations) { // Beware: don't capture the reference to handler in a dispatched block! RCTNotificationHandler handler = registration.handler; dispatch_async(registration.queue, ^{ handler(params); }); } } } - (void)reconnectingWebSocketDidClose:(RCTReconnectingWebSocket *)webSocket { std::lock_guard l(_mutex); _socketConnected = NO; } template static std::vector> registrationsWithMethod(std::mutex &mutex, const std::vector> ®istrations, NSString *method) { std::lock_guard l(mutex); // Scope lock acquisition to prevent deadlock when calling out std::vector> matches; for (const auto ® : registrations) { if ([reg.method isEqual:method]) { matches.push_back(reg); } } return matches; } @end #endif