From a20821378b39486790e8df876d81204498daa1d8 Mon Sep 17 00:00:00 2001 From: Jochen Becker Date: Mon, 14 Aug 2017 17:05:44 +0200 Subject: [PATCH] - if a request timed out the same error codes are returned for iOS and Android - updated change log --- CHANGELOG.md | 4 + package.json | 2 +- plugin.xml | 2 +- .../cordovahttp/CordovaHttpDelete.java | 3 + .../cordovahttp/CordovaHttpDownload.java | 3 + .../synconset/cordovahttp/CordovaHttpGet.java | 3 + .../cordovahttp/CordovaHttpHead.java | 3 + .../cordovahttp/CordovaHttpPost.java | 3 + .../synconset/cordovahttp/CordovaHttpPut.java | 3 + .../cordovahttp/CordovaHttpUpload.java | 3 + src/ios/CordovaHttpPlugin.m | 131 +++++++++++------- 11 files changed, 107 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fddfe..9b9879c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.5.8 + +- Use the same error codes if a request timed out + ## v1.5.7 - Fixed a bug in cookie handling (cookies containing an "Expires" string) diff --git a/package.json b/package.json index 16d568b..1c4c3ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-advanced-http", - "version": "1.5.7", + "version": "1.5.8", "description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning", "scripts": { "build": "cp node_modules/umd-tough-cookie/lib/umd-tough-cookie.js www/umd-tough-cookie.js", diff --git a/plugin.xml b/plugin.xml index 34131a2..f293a9c 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="1.5.8"> Advanced HTTP plugin diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java b/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java index 20e3d2f..21be65a 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java @@ -3,6 +3,7 @@ */ package com.synconset.cordovahttp; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import javax.net.ssl.SSLHandshakeException; @@ -49,6 +50,8 @@ class CordovaHttpDelete extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java b/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java index f55e825..d8a5128 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpDownload.java @@ -7,6 +7,7 @@ import com.github.kevinsawicki.http.HttpRequest; import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; import java.io.File; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.net.URI; import java.net.URISyntaxException; @@ -59,6 +60,8 @@ class CordovaHttpDownload extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpGet.java b/src/android/com/synconset/cordovahttp/CordovaHttpGet.java index 176ba06..f6d1b09 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpGet.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpGet.java @@ -3,6 +3,7 @@ */ package com.synconset.cordovahttp; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import javax.net.ssl.SSLHandshakeException; @@ -49,6 +50,8 @@ class CordovaHttpGet extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java b/src/android/com/synconset/cordovahttp/CordovaHttpHead.java index 4763b1f..78c1a2d 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpHead.java @@ -3,6 +3,7 @@ */ package com.synconset.cordovahttp; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import javax.net.ssl.SSLHandshakeException; @@ -48,6 +49,8 @@ class CordovaHttpHead extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java b/src/android/com/synconset/cordovahttp/CordovaHttpPost.java index 5e741f6..156a8b2 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPost.java @@ -3,6 +3,7 @@ */ package com.synconset.cordovahttp; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import org.apache.cordova.CallbackContext; @@ -55,6 +56,8 @@ class CordovaHttpPost extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java b/src/android/com/synconset/cordovahttp/CordovaHttpPut.java index fb1cc4f..a3dd15a 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpPut.java @@ -3,6 +3,7 @@ */ package com.synconset.cordovahttp; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import org.apache.cordova.CallbackContext; @@ -55,6 +56,8 @@ class CordovaHttpPut extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java b/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java index 46b75bd..6c4340b 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttpUpload.java @@ -5,6 +5,7 @@ package com.synconset.cordovahttp; import java.io.File; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.net.URI; import java.net.URISyntaxException; @@ -97,6 +98,8 @@ class CordovaHttpUpload extends CordovaHttp implements Runnable { } catch (HttpRequestException e) { if (e.getCause() instanceof UnknownHostException) { this.respondWithError(0, "The host could not be resolved"); + } else if (e.getCause() instanceof SocketTimeoutException) { + this.respondWithError(1, "The request timed out"); } else if (e.getCause() instanceof SSLHandshakeException) { this.respondWithError("SSL handshake failed"); } else { diff --git a/src/ios/CordovaHttpPlugin.m b/src/ios/CordovaHttpPlugin.m index 47fb8a3..c24c229 100644 --- a/src/ios/CordovaHttpPlugin.m +++ b/src/ios/CordovaHttpPlugin.m @@ -6,13 +6,14 @@ @interface CordovaHttpPlugin() - (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager; -- (void)setResults:(NSMutableDictionary*)dictionary withTask:(NSURLSessionTask*)task; +- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data; +- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error; +- (NSNumber*)getStatusCode:(NSError*) error; - (NSMutableDictionary*)copyHeaderFields:(NSDictionary*)headerFields; - (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager; @end - @implementation CordovaHttpPlugin { AFSecurityPolicy *securityPolicy; } @@ -22,11 +23,11 @@ } - (void)setRequestSerializer:(NSString*)serializerName forManager:(AFHTTPSessionManager*)manager { - if ([serializerName isEqualToString:@"json"]) { - manager.requestSerializer = [AFJSONRequestSerializer serializer]; - } else { - manager.requestSerializer = [AFHTTPRequestSerializer serializer]; - } + if ([serializerName isEqualToString:@"json"]) { + manager.requestSerializer = [AFJSONRequestSerializer serializer]; + } else { + manager.requestSerializer = [AFHTTPRequestSerializer serializer]; + } } - (void)setRequestHeaders:(NSDictionary*)headers forManager:(AFHTTPSessionManager*)manager { @@ -35,24 +36,57 @@ }]; } -- (void)setResults:(NSMutableDictionary*)dictionary withTask:(NSURLSessionTask*)task { - if (task.response != nil) { - NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; +- (void)handleSuccess:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response andData:(id)data { + if (response != nil) { [dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"]; [dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"]; } + + if (data != nil) { + [dictionary setObject:data forKey:@"data"]; + } +} + +- (void)handleError:(NSMutableDictionary*)dictionary withResponse:(NSHTTPURLResponse*)response error:(NSError*)error { + if (response != nil) { + [dictionary setObject:[NSNumber numberWithInt:response.statusCode] forKey:@"status"]; + [dictionary setObject:[self copyHeaderFields:response.allHeaderFields] forKey:@"headers"]; + [dictionary setObject:[[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding] forKey:@"error"]; + } else { + [dictionary setObject:[self getStatusCode:error] forKey:@"status"]; + [dictionary setObject:[error localizedDescription] forKey:@"error"]; + } +} + +- (NSNumber*)getStatusCode:(NSError*) error { + switch ([error code]) { + case -1001: + // timeout + return [NSNumber numberWithInt:1]; + case -1002: + // unsupported URL + return [NSNumber numberWithInt:2]; + case -1003: + // server not found + return [NSNumber numberWithInt:0]; + case -1009: + // no connection + return [NSNumber numberWithInt:3]; + default: + return [NSNumber numberWithInt:-1]; + } } - (NSMutableDictionary*)copyHeaderFields:(NSDictionary *)headerFields { - NSMutableDictionary *headerFieldsCopy = [[NSMutableDictionary alloc] initWithCapacity:headerFields.count]; - NSString *headerKeyCopy; + NSMutableDictionary *headerFieldsCopy = [[NSMutableDictionary alloc] initWithCapacity:headerFields.count]; + NSString *headerKeyCopy; - for (NSString *headerKey in headerFields.allKeys) { - headerKeyCopy = [[headerKey mutableCopy] lowercaseString]; - [headerFieldsCopy setValue:[headerFields objectForKey:headerKey] forKey:headerKeyCopy]; - } + for (NSString *headerKey in headerFields.allKeys) { + headerKeyCopy = [[headerKey mutableCopy] lowercaseString]; + [headerFieldsCopy setValue:[headerFields objectForKey:headerKey] forKey:headerKeyCopy]; + } - return headerFieldsCopy; + return headerFieldsCopy; } - (void)setTimeout:(NSTimeInterval)timeout forManager:(AFHTTPSessionManager*)manager { @@ -109,15 +143,14 @@ manager.responseSerializer = [TextResponseSerializer serializer]; [manager POST:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - [dictionary setObject:responseObject forKey:@"data"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -142,15 +175,14 @@ manager.responseSerializer = [TextResponseSerializer serializer]; [manager GET:url parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - [dictionary setObject:responseObject forKey:@"data"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -174,15 +206,14 @@ manager.responseSerializer = [TextResponseSerializer serializer]; [manager PUT:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - [dictionary setObject:responseObject forKey:@"data"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -206,15 +237,14 @@ manager.responseSerializer = [TextResponseSerializer serializer]; [manager DELETE:url parameters:parameters success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - [dictionary setObject:responseObject forKey:@"data"]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:responseObject]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -236,15 +266,15 @@ manager.responseSerializer = [AFHTTPResponseSerializer serializer]; [manager HEAD:url parameters:parameters success:^(NSURLSessionTask *task) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; // no 'body' for HEAD request, omitting 'data' + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -281,14 +311,14 @@ } } progress:nil success:^(NSURLSessionTask *task, id responseObject) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; @@ -305,7 +335,6 @@ NSString *filePath = [command.arguments objectAtIndex: 3]; NSTimeInterval timeoutInSeconds = [[command.arguments objectAtIndex:4] doubleValue]; - [self setRequestHeaders: headers forManager: manager]; [self setTimeout:timeoutInSeconds forManager:manager]; @@ -337,7 +366,7 @@ * * Modified by Andrew Stephan for Sync OnSet * - */ + */ // Download response is okay; begin streaming output to file NSString* parentPath = [filePath stringByDeletingLastPathComponent]; @@ -367,15 +396,15 @@ id filePlugin = [self.commandDelegate getCommandInstance:@"File"]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; + [self handleSuccess:dictionary withResponse:(NSHTTPURLResponse*)task.response andData:nil]; [dictionary setObject:[filePlugin getDirectoryEntry:filePath isDirectory:NO] forKey:@"file"]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } failure:^(NSURLSessionTask *task, NSError *error) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [self setResults: dictionary withTask: task]; - NSString* errResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding]; - [dictionary setObject:errResponse forKey:@"error"]; + [self handleError:dictionary withResponse:(NSHTTPURLResponse*)task.response error:error]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary]; [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }];