diff --git a/socket.js b/socket.js index 47513be..43a33d5 100644 --- a/socket.js +++ b/socket.js @@ -104,17 +104,30 @@ Socket._copyToArray = function(array) { return outputArray; }; +Socket.prototype.shutdownWrite = function () { + exec( + function() { + console.debug("SocketsForCordova: Shutdown write successfully called."); + }, + function(errorMessage) { + console.error("SocketsForCordova: Error when call shutdownWrite on socket. Error: " + errorMessage); + }, + CORDOVA_SERVICE_NAME, + "shutdownWrite", + [ this.socketKey ]); +}; + Socket.prototype.close = function () { exec( - function() { - console.debug("SocketsForCordova: Close successfully closed."); - }, - function(errorMessage) { - console.error("SocketsForCordova: Error when call close on socket. Error: " + errorMessage); - }, - CORDOVA_SERVICE_NAME, - "close", - [ this.socketKey ]); + function() { + console.debug("SocketsForCordova: Close successfully called."); + }, + function(errorMessage) { + console.error("SocketsForCordova: Error when call close on socket. Error: " + errorMessage); + }, + CORDOVA_SERVICE_NAME, + "close", + [ this.socketKey ]); }; Socket.dispatchEvent = function(event) { diff --git a/src/ios/SocketsForCordova/Classes/SocketAdapter.h b/src/ios/SocketsForCordova/Classes/SocketAdapter.h index 4b67810..df026d6 100644 --- a/src/ios/SocketsForCordova/Classes/SocketAdapter.h +++ b/src/ios/SocketsForCordova/Classes/SocketAdapter.h @@ -6,11 +6,14 @@ - (void)connect:(NSString *)host port:(NSNumber*)port; - (void)write:(NSArray *)dataArray; +- (void)shutdownWrite; - (void)close; - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event; +@property (copy) void (^openEventHandler)(); +@property (copy) void (^openErrorEventHandler)(NSString*); @property (copy) void (^dataConsumer)(NSArray*); @property (copy) void (^closeEventHandler)(BOOL); -@property (copy) void (^errorHandler)(NSString*); +@property (copy) void (^errorEventHandler)(NSString*); @end \ No newline at end of file diff --git a/src/ios/SocketsForCordova/Classes/SocketAdapter.m b/src/ios/SocketsForCordova/Classes/SocketAdapter.m index b725d31..dbe9f0e 100644 --- a/src/ios/SocketsForCordova/Classes/SocketAdapter.m +++ b/src/ios/SocketsForCordova/Classes/SocketAdapter.m @@ -1,4 +1,6 @@ #include +#include +#include #import "SocketAdapter.h" CFReadStreamRef readStream; @@ -7,35 +9,79 @@ CFWriteStreamRef writeStream; NSInputStream *inputStream; NSOutputStream *outputStream; +BOOL wasOpenned = FALSE; + @implementation SocketAdapter - (void)connect:(NSString *)host port:(NSNumber*)port { NSLog(@"Setting up connection to %@ : %@", host, [port stringValue]); + if (![self isIp:host]) { + host = [self resolveIp:host]; + } + CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)host, [port intValue], &readStream, &writeStream); CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + if(!CFWriteStreamOpen(writeStream) || !CFReadStreamOpen(readStream)) { + NSLog(@"Error, streams not open"); + + @throw [NSException exceptionWithName:@"SocketException" reason:@"Cannot open streams." userInfo:nil]; + } + inputStream = (__bridge NSInputStream *)readStream; [inputStream setDelegate:self]; - //[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; outputStream = (__bridge NSOutputStream *)writeStream; + //[outputStream setDelegate:self]; [outputStream open]; - + [self performSelectorOnMainThread:@selector(runReadLoop) withObject:nil waitUntilDone:NO]; } +-(BOOL)isIp:(NSString*) host { + const char *utf8 = [host UTF8String]; + + // Check valid IPv4. + struct in_addr dst; + int success = inet_pton(AF_INET, utf8, &(dst.s_addr)); + if (success != 1) { + // Check valid IPv6. + struct in6_addr dst6; + success = inet_pton(AF_INET6, utf8, &dst6); + } + return (success == 1); +} + +-(NSString*)resolveIp:(NSString*) host { + + NSLog(@"Resolving host: %@", host); + + const char *buff = [host cStringUsingEncoding:NSUTF8StringEncoding]; + struct hostent *host_entry = gethostbyname(buff); + + if(host_entry == NULL) { + @throw [NSException exceptionWithName:@"NSException" reason:@"Cannot resolve hostname." userInfo:nil]; + } + + char *hostCstring = inet_ntoa(*((struct in_addr *)host_entry->h_addr_list[0])); + host = [NSString stringWithUTF8String:hostCstring]; + + NSLog(@"Resolved ip: %@", host); + + return host; +} - (void)runReadLoop { [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } -- (void)close { - NSLog(@"Closing socket."); +- (void)shutdownWrite { + NSLog(@"Shuting down write on socket."); [self closeOutputStream]; @@ -71,6 +117,11 @@ NSOutputStream *outputStream; - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event { switch(event) { + case NSStreamEventOpenCompleted: { + self.openEventHandler(); + wasOpenned = TRUE; + break; + } case NSStreamEventHasBytesAvailable: { if(stream == inputStream) { uint8_t buf[65535]; @@ -90,18 +141,28 @@ NSOutputStream *outputStream; break; } case NSStreamEventEndEncountered: { - [self closeInputStream]; - self.closeEventHandler(FALSE); - break; + if(stream == inputStream) { + [self closeInputStream]; + + self.closeEventHandler(FALSE); + break; + } } case NSStreamEventErrorOccurred: { - self.errorHandler([[stream streamError] localizedDescription]); - - [self abort]; - - self.closeEventHandler(TRUE); + NSLog(@"Stream event error: %@", [[stream streamError] localizedDescription]); + + if (wasOpenned) { + self.errorEventHandler([[stream streamError] localizedDescription]); + self.closeEventHandler(TRUE); + } + else { + self.openErrorEventHandler([[stream streamError] localizedDescription]); + self.errorEventHandler([[stream streamError] localizedDescription]); + } + + [self close]; break; } default: { @@ -122,7 +183,7 @@ NSOutputStream *outputStream; } } -- (void)abort { +- (void)close { [self closeOutputStream]; [self closeInputStream]; } diff --git a/src/ios/SocketsForCordova/Classes/SocketPlugin.m b/src/ios/SocketsForCordova/Classes/SocketPlugin.m index d4fec0c..20b254c 100644 --- a/src/ios/SocketsForCordova/Classes/SocketPlugin.m +++ b/src/ios/SocketsForCordova/Classes/SocketPlugin.m @@ -6,14 +6,47 @@ @implementation SocketPlugin : CDVPlugin - (void) create : (CDVInvokedUrlCommand*) command { - NSString* socketKey = [command.arguments objectAtIndex : 0]; + //NSString* socketKey = [command.arguments objectAtIndex : 0]; - if (socketAdapters == nil) { + + [self.commandDelegate + sendPluginResult: [CDVPluginResult resultWithStatus : CDVCommandStatus_OK] + callbackId: command.callbackId]; +} + +- (void) connect : (CDVInvokedUrlCommand*) command { + + NSString *socketKey = [command.arguments objectAtIndex:0]; + NSString *host = [command.arguments objectAtIndex:1]; + NSNumber *port = [command.arguments objectAtIndex:2]; + + if (socketAdapters == nil) { self->socketAdapters = [[NSMutableDictionary alloc] init]; } - - SocketAdapter* socketAdapter = [[SocketAdapter alloc] init]; + __block SocketAdapter* socketAdapter = [[SocketAdapter alloc] init]; + socketAdapter.openEventHandler = ^ void () { + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; + + [self->socketAdapters setObject:socketAdapter forKey:socketKey]; + + socketAdapter = nil; + }; + socketAdapter.openErrorEventHandler = ^ void (NSString *error){ + [self.commandDelegate + sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error] + callbackId:command.callbackId]; + + socketAdapter = nil; + }; + socketAdapter.errorEventHandler = ^ void (NSString *error){ + NSMutableDictionary *errorDictionaryData = [[NSMutableDictionary alloc] init]; + [errorDictionaryData setObject:@"Error" forKey:@"type"]; + [errorDictionaryData setObject:error forKey:@"errorMessage"]; + [errorDictionaryData setObject:socketKey forKey:@"socketKey"]; + + [self dispatchEventWithDictionary:errorDictionaryData]; + }; socketAdapter.dataConsumer = ^ void (NSArray* dataArray) { NSMutableDictionary *dataDictionary = [[NSMutableDictionary alloc] init]; [dataDictionary setObject:@"DataReceived" forKey:@"type"]; @@ -29,55 +62,20 @@ [closeDictionaryData setObject:socketKey forKey:@"socketKey"]; [self dispatchEventWithDictionary:closeDictionaryData]; - }; - socketAdapter.errorHandler = ^ void (NSString *error){ - NSMutableDictionary *errorDictionaryData = [[NSMutableDictionary alloc] init]; - [errorDictionaryData setObject:@"Error" forKey:@"type"]; - [errorDictionaryData setObject:error forKey:@"errorMessage"]; - [errorDictionaryData setObject:socketKey forKey:@"socketKey"]; - [self dispatchEventWithDictionary:errorDictionaryData]; + [self removeSocketAdapter:socketKey]; }; - [self->socketAdapters - setObject:socketAdapter - forKey:socketKey]; - - [self.commandDelegate - sendPluginResult: [CDVPluginResult resultWithStatus : CDVCommandStatus_OK] - callbackId: command.callbackId]; -} - -- (SocketAdapter*) getSocketAdapter: (NSString*) socketKey { - SocketAdapter* socketAdapter = [self->socketAdapters objectForKey:socketKey]; - if (socketAdapter == nil) { - NSString *exceptionReason = [NSString stringWithFormat:@"Cannot find socketKey: %@. Connection is probably closed.", socketKey]; - - @throw [NSException exceptionWithName:@"IllegalArgumentException" reason:exceptionReason userInfo:nil]; - } - return socketAdapter; -} - -- (void) connect : (CDVInvokedUrlCommand*) command { - - NSString *socketKey = [command.arguments objectAtIndex:0]; - NSString *host = [command.arguments objectAtIndex:1]; - NSNumber *port = [command.arguments objectAtIndex:2]; - - SocketAdapter *socket = [self getSocketAdapter:socketKey]; - [self.commandDelegate runInBackground:^{ @try { - [socket connect:host port:port]; - - [self.commandDelegate - sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] - callbackId:command.callbackId]; + [socketAdapter connect:host port:port]; } @catch (NSException *e) { [self.commandDelegate - sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:e.reason] - callbackId:command.callbackId]; + sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:e.reason] + callbackId:command.callbackId]; + + socketAdapter = nil; } }]; } @@ -104,6 +102,27 @@ }]; } +- (void) shutdownWrite:(CDVInvokedUrlCommand *) command { + + NSString* socketKey = [command.arguments objectAtIndex:0]; + + SocketAdapter *socket = [self getSocketAdapter:socketKey]; + + [self.commandDelegate runInBackground:^{ + @try { + [socket shutdownWrite]; + [self.commandDelegate + sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] + callbackId:command.callbackId]; + } + @catch (NSException *e) { + [self.commandDelegate + sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:e.reason] + callbackId:command.callbackId]; + } + }]; +} + - (void) close:(CDVInvokedUrlCommand *) command { NSString* socketKey = [command.arguments objectAtIndex:0]; @@ -128,6 +147,26 @@ - (void) setOptions: (CDVInvokedUrlCommand *) command { } +- (SocketAdapter*) getSocketAdapter: (NSString*) socketKey { + SocketAdapter* socketAdapter = [self->socketAdapters objectForKey:socketKey]; + if (socketAdapter == nil) { + NSString *exceptionReason = [NSString stringWithFormat:@"Cannot find socketKey: %@. Connection is probably closed.", socketKey]; + + @throw [NSException exceptionWithName:@"IllegalArgumentException" reason:exceptionReason userInfo:nil]; + } + return socketAdapter; +} + +- (void) removeSocketAdapter: (NSString*) socketKey { + NSLog(@"Removing socket adapter from storage."); + [self->socketAdapters removeObjectForKey:socketKey]; +} + +- (BOOL) socketAdapterExists: (NSString*) socketKey { + SocketAdapter* socketAdapter = [self->socketAdapters objectForKey:socketKey]; + return socketAdapter != nil; +} + - (void) dispatchEventWithDictionary: (NSDictionary*) dictionary { NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:nil]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];