332 lines
9.2 KiB
Objective-C
332 lines
9.2 KiB
Objective-C
#import "RCTInspectorPackagerConnection.h"
|
|
|
|
#if RCT_DEV
|
|
|
|
#import "RCTDefines.h"
|
|
#import "RCTInspector.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTSRWebSocket.h"
|
|
#import "RCTUtils.h"
|
|
|
|
// This is a port of the Android impl, at
|
|
// ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java
|
|
// please keep consistent :)
|
|
|
|
const int RECONNECT_DELAY_MS = 2000;
|
|
|
|
@interface RCTInspectorPackagerConnection () <RCTSRWebSocketDelegate> {
|
|
NSURL *_url;
|
|
NSMutableDictionary<NSString *, RCTInspectorLocalConnection *> *_inspectorConnections;
|
|
RCTSRWebSocket *_webSocket;
|
|
dispatch_queue_t _jsQueue;
|
|
BOOL _closed;
|
|
BOOL _suppressConnectionErrors;
|
|
}
|
|
@end
|
|
|
|
@interface RCTInspectorRemoteConnection () {
|
|
__weak RCTInspectorPackagerConnection *_owningPackagerConnection;
|
|
NSString *_pageId;
|
|
}
|
|
- (instancetype)initWithPackagerConnection:(RCTInspectorPackagerConnection *)owningPackagerConnection
|
|
pageId:(NSString *)pageId;
|
|
@end
|
|
|
|
static NSDictionary<NSString *, id> *makePageIdPayload(NSString *pageId)
|
|
{
|
|
return @{ @"pageId": pageId };
|
|
}
|
|
|
|
@implementation RCTInspectorPackagerConnection
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|
|
|
- (instancetype)initWithURL:(NSURL *)url
|
|
{
|
|
if (self = [super init]) {
|
|
_url = url;
|
|
_inspectorConnections = [NSMutableDictionary new];
|
|
_jsQueue = dispatch_queue_create("com.facebook.react.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)handleProxyMessage:(NSDictionary<NSString *, id> *)message
|
|
{
|
|
NSString *event = message[@"event"];
|
|
NSDictionary *payload = message[@"payload"];
|
|
if ([@"getPages" isEqualToString:event]) {
|
|
[self sendEvent:event payload:[self pages]];
|
|
} else if ([@"wrappedEvent" isEqualToString:event]) {
|
|
[self handleWrappedEvent:payload];
|
|
} else if ([@"connect" isEqualToString:event]) {
|
|
[self handleConnect:payload];
|
|
} else if ([@"disconnect" isEqualToString:event]) {
|
|
[self handleDisconnect:payload];
|
|
} else {
|
|
RCTLogError(@"Unknown event: %@", event);
|
|
}
|
|
}
|
|
|
|
- (void)sendEventToAllConnections:(NSString *)event
|
|
{
|
|
for (NSString *pageId in _inspectorConnections) {
|
|
[_inspectorConnections[pageId] sendMessage:event];
|
|
}
|
|
}
|
|
|
|
- (void)closeAllConnections
|
|
{
|
|
for (NSString *pageId in _inspectorConnections){
|
|
[[_inspectorConnections objectForKey:pageId] disconnect];
|
|
}
|
|
[_inspectorConnections removeAllObjects];
|
|
}
|
|
|
|
- (void)handleConnect:(NSDictionary *)payload
|
|
{
|
|
NSString *pageId = payload[@"pageId"];
|
|
if (_inspectorConnections[pageId]) {
|
|
[_inspectorConnections removeObjectForKey:pageId];
|
|
RCTLogError(@"Already connected: %@", pageId);
|
|
return;
|
|
}
|
|
|
|
RCTInspectorRemoteConnection *remoteConnection =
|
|
[[RCTInspectorRemoteConnection alloc] initWithPackagerConnection:self
|
|
pageId:pageId];
|
|
|
|
RCTInspectorLocalConnection *inspectorConnection = [RCTInspector connectPage:[pageId integerValue]
|
|
forRemoteConnection:remoteConnection];
|
|
_inspectorConnections[pageId] = inspectorConnection;
|
|
}
|
|
|
|
- (void)handleDisconnect:(NSDictionary *)payload
|
|
{
|
|
NSString *pageId = payload[@"pageId"];
|
|
RCTInspectorLocalConnection *inspectorConnection = _inspectorConnections[pageId];
|
|
if (inspectorConnection) {
|
|
[self removeConnectionForPage:pageId];
|
|
[inspectorConnection disconnect];
|
|
}
|
|
}
|
|
|
|
- (void)removeConnectionForPage:(NSString *)pageId
|
|
{
|
|
[_inspectorConnections removeObjectForKey:pageId];
|
|
}
|
|
|
|
- (void)handleWrappedEvent:(NSDictionary *)payload
|
|
{
|
|
NSString *pageId = payload[@"pageId"];
|
|
NSString *wrappedEvent = payload[@"wrappedEvent"];
|
|
RCTInspectorLocalConnection *inspectorConnection = _inspectorConnections[pageId];
|
|
if (!inspectorConnection) {
|
|
RCTLogWarn(
|
|
@"Not connected to page: %@ , failed trying to handle event: %@",
|
|
pageId,
|
|
wrappedEvent);
|
|
return;
|
|
}
|
|
[inspectorConnection sendMessage:wrappedEvent];
|
|
}
|
|
|
|
- (NSArray *)pages
|
|
{
|
|
NSArray<RCTInspectorPage *> *pages = [RCTInspector pages];
|
|
NSMutableArray *array = [NSMutableArray arrayWithCapacity:pages.count];
|
|
for (RCTInspectorPage *page in pages) {
|
|
NSDictionary *jsonPage = @{
|
|
@"id": [@(page.id) stringValue],
|
|
@"title": page.title,
|
|
@"app": [[NSBundle mainBundle] bundleIdentifier],
|
|
};
|
|
[array addObject:jsonPage];
|
|
}
|
|
return array;
|
|
}
|
|
|
|
- (void)sendWrappedEvent:(NSString *)pageId
|
|
message:(NSString *)message
|
|
{
|
|
NSDictionary *payload = @{
|
|
@"pageId": pageId,
|
|
@"wrappedEvent": message,
|
|
};
|
|
[self sendEvent:@"wrappedEvent" payload:payload];
|
|
}
|
|
|
|
- (void)sendEvent:(NSString *)name payload:(id)payload
|
|
{
|
|
NSDictionary *jsonMessage = @{
|
|
@"event": name,
|
|
@"payload": payload,
|
|
};
|
|
[self sendToPackager:jsonMessage];
|
|
}
|
|
|
|
// analogous to InspectorPackagerConnection.Connection.onFailure(...)
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
|
{
|
|
if (_webSocket) {
|
|
[self abort:@"Websocket exception"
|
|
withCause:error];
|
|
}
|
|
if (!_closed) {
|
|
[self reconnect];
|
|
}
|
|
}
|
|
|
|
// analogous to InspectorPackagerConnection.Connection.onMessage(...)
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)opaqueMessage
|
|
{
|
|
// warn but don't die on unrecognized messages
|
|
if (![opaqueMessage isKindOfClass:[NSString class]]) {
|
|
RCTLogWarn(@"Unrecognized inspector message, object is of type: %@", [opaqueMessage class]);
|
|
return;
|
|
}
|
|
|
|
NSString *messageText = opaqueMessage;
|
|
NSError *error = nil;
|
|
id parsedJSON = RCTJSONParse(messageText, &error);
|
|
if (error) {
|
|
RCTLogWarn(@"Unrecognized inspector message, string was not valid JSON: %@",
|
|
messageText);
|
|
return;
|
|
}
|
|
|
|
[self handleProxyMessage:parsedJSON];
|
|
}
|
|
|
|
// analogous to InspectorPackagerConnection.Connection.onClosed(...)
|
|
- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code
|
|
reason:(NSString *)reason
|
|
wasClean:(BOOL)wasClean
|
|
{
|
|
_webSocket = nil;
|
|
[self closeAllConnections];
|
|
if (!_closed) {
|
|
[self reconnect];
|
|
}
|
|
}
|
|
|
|
- (void)connect
|
|
{
|
|
if (_closed) {
|
|
RCTLogError(@"Illegal state: Can't connect after having previously been closed.");
|
|
return;
|
|
}
|
|
|
|
// The corresopnding android code has a lot of custom config options for
|
|
// timeouts, but it appears the iOS RCTSRWebSocket API doesn't have the same
|
|
// implemented options.
|
|
_webSocket = [[RCTSRWebSocket alloc] initWithURL:_url];
|
|
[_webSocket setDelegateDispatchQueue:_jsQueue];
|
|
_webSocket.delegate = self;
|
|
[_webSocket open];
|
|
}
|
|
|
|
- (void)reconnect
|
|
{
|
|
if (_closed) {
|
|
RCTLogError(@"Illegal state: Can't reconnect after having previously been closed.");
|
|
return;
|
|
}
|
|
|
|
if (_suppressConnectionErrors) {
|
|
RCTLogWarn(@"Couldn't connect to packager, will silently retry");
|
|
_suppressConnectionErrors = true;
|
|
}
|
|
|
|
__weak RCTInspectorPackagerConnection *weakSelf = self;
|
|
dispatch_after(
|
|
dispatch_time(DISPATCH_TIME_NOW, RECONNECT_DELAY_MS *NSEC_PER_MSEC),
|
|
dispatch_get_main_queue(), ^{
|
|
RCTInspectorPackagerConnection *strongSelf = weakSelf;
|
|
if (strongSelf && !strongSelf->_closed) {
|
|
[strongSelf connect];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)closeQuietly
|
|
{
|
|
_closed = true;
|
|
[self disposeWebSocket];
|
|
}
|
|
|
|
- (void)sendToPackager:(NSDictionary *)messageObject
|
|
{
|
|
__weak RCTInspectorPackagerConnection *weakSelf = self;
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
RCTInspectorPackagerConnection *strongSelf = weakSelf;
|
|
if (strongSelf && !strongSelf->_closed) {
|
|
NSError *error;
|
|
NSString *messageText = RCTJSONStringify(messageObject, &error);
|
|
if (error) {
|
|
RCTLogWarn(@"Couldn't send event to packager: %@", error);
|
|
} else {
|
|
[strongSelf->_webSocket send:messageText];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)abort:(NSString *)message
|
|
withCause:(NSError *)cause
|
|
{
|
|
// Don't log ECONNREFUSED at all; it's expected in cases where the server isn't listening.
|
|
if (![cause.domain isEqual:NSPOSIXErrorDomain] || cause.code != ECONNREFUSED) {
|
|
RCTLogInfo(@"Error occurred, shutting down websocket connection: %@ %@", message, cause);
|
|
}
|
|
|
|
[self closeAllConnections];
|
|
[self disposeWebSocket];
|
|
}
|
|
|
|
- (void)disposeWebSocket
|
|
{
|
|
if (_webSocket) {
|
|
[_webSocket closeWithCode:1000
|
|
reason:@"End of session"];
|
|
_webSocket.delegate = nil;
|
|
_webSocket = nil;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTInspectorRemoteConnection
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|
|
|
- (instancetype)initWithPackagerConnection:(RCTInspectorPackagerConnection *)owningPackagerConnection
|
|
pageId:(NSString *)pageId
|
|
{
|
|
if (self = [super init]) {
|
|
_owningPackagerConnection = owningPackagerConnection;
|
|
_pageId = pageId;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)onMessage:(NSString *)message
|
|
{
|
|
[_owningPackagerConnection sendWrappedEvent:_pageId
|
|
message:message];
|
|
}
|
|
|
|
- (void)onDisconnect
|
|
{
|
|
RCTInspectorPackagerConnection *owningPackagerConnectionStrong = _owningPackagerConnection;
|
|
if (owningPackagerConnectionStrong) {
|
|
[owningPackagerConnectionStrong removeConnectionForPage:_pageId];
|
|
[owningPackagerConnectionStrong sendEvent:@"disconnect"
|
|
payload:makePageIdPayload(_pageId)];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|