diff options
author | Struan Donald <struan@exo.org.uk> | 2013-07-10 15:17:41 +0100 |
---|---|---|
committer | Struan Donald <struan@exo.org.uk> | 2013-07-10 15:17:41 +0100 |
commit | 8c81bd47bbe3ed65a55a2c5fd376bfd726bd6777 (patch) | |
tree | 4076c08e5e85159da272ee455de412a1ae92679b | |
parent | 7d57c4bfa201c12885b26b186ebac4b3028d570b (diff) |
upgrade iOS to phonegap 2.9
69 files changed, 10394 insertions, 1542 deletions
diff --git a/iPhone/CordovaLib/Classes/CDV.h b/iPhone/CordovaLib/Classes/CDV.h index 5a0ae6a..15d9316 100755 --- a/iPhone/CordovaLib/Classes/CDV.h +++ b/iPhone/CordovaLib/Classes/CDV.h @@ -33,7 +33,6 @@ #import "CDVContact.h" #import "CDVContacts.h" #import "CDVDebug.h" -#import "CDVDebugConsole.h" #import "CDVDevice.h" #import "CDVFile.h" #import "CDVFileTransfer.h" @@ -47,6 +46,7 @@ #import "CDVLocalStorage.h" #import "CDVInAppBrowser.h" #import "CDVScreenOrientationDelegate.h" +#import "CDVTimer.h" #import "NSArray+Comparisons.h" #import "NSData+Base64.h" diff --git a/iPhone/CordovaLib/Classes/CDVAvailability.h b/iPhone/CordovaLib/Classes/CDVAvailability.h index 33c6799..7e8157c 100755 --- a/iPhone/CordovaLib/Classes/CDVAvailability.h +++ b/iPhone/CordovaLib/Classes/CDVAvailability.h @@ -17,6 +17,8 @@ under the License. */ +#define __CORDOVA_IOS__ + #define __CORDOVA_0_9_6 906 #define __CORDOVA_1_0_0 10000 #define __CORDOVA_1_1_0 10100 @@ -37,6 +39,10 @@ #define __CORDOVA_2_3_0 20300 #define __CORDOVA_2_4_0 20400 #define __CORDOVA_2_5_0 20500 +#define __CORDOVA_2_6_0 20600 +#define __CORDOVA_2_7_0 20700 +#define __CORDOVA_2_8_0 20800 +#define __CORDOVA_2_9_0 20900 #define __CORDOVA_NA 99999 /* not available */ /* @@ -47,7 +53,7 @@ #endif */ #ifndef CORDOVA_VERSION_MIN_REQUIRED - #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_5_0 + #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_9_0 #endif /* @@ -65,12 +71,20 @@ /* Return the string version of the decimal version */ #define CDV_VERSION [NSString stringWithFormat:@"%d.%d.%d", \ - (CORDOVA_VERSION_MIN_REQUIRED / 10000), \ - (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \ - (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100] + (CORDOVA_VERSION_MIN_REQUIRED / 10000), \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100, \ + (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100] #ifdef __clang__ #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg))) #else #define CDV_DEPRECATED(version, msg) __attribute__((deprecated())) #endif + +// Enable this to log all exec() calls. +#define CDV_ENABLE_EXEC_LOGGING 0 +#if CDV_ENABLE_EXEC_LOGGING + #define CDV_EXEC_LOG NSLog +#else + #define CDV_EXEC_LOG(...) do {} while (NO) +#endif diff --git a/iPhone/CordovaLib/Classes/CDVCamera.h b/iPhone/CordovaLib/Classes/CDVCamera.h index 204d25f..2932e3b 100755 --- a/iPhone/CordovaLib/Classes/CDVCamera.h +++ b/iPhone/CordovaLib/Classes/CDVCamera.h @@ -18,6 +18,8 @@ */ #import <Foundation/Foundation.h> +#import <CoreLocation/CoreLocation.h> +#import <CoreLocation/CLLocationManager.h> #import "CDVPlugin.h" enum CDVDestinationType { @@ -61,11 +63,15 @@ typedef NSUInteger CDVMediaType; // ======================================================================= // @interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate, - UINavigationControllerDelegate, - UIPopoverControllerDelegate> + UINavigationControllerDelegate, + UIPopoverControllerDelegate, + CLLocationManagerDelegate> {} @property (strong) CDVCameraPicker* pickerController; +@property (strong) NSMutableDictionary *metadata; +@property (strong, nonatomic) CLLocationManager *locationManager; +@property (strong) NSData* data; /* * getPicture @@ -85,8 +91,12 @@ typedef NSUInteger CDVMediaType; - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info; - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker; +- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated; - (UIImage*)imageByScalingAndCroppingForSize:(UIImage*)anImage toSize:(CGSize)targetSize; - (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize; - (UIImage*)imageCorrectedForCaptureOrientation:(UIImage*)anImage; +- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation; +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error; + @end diff --git a/iPhone/CordovaLib/Classes/CDVCamera.m b/iPhone/CordovaLib/Classes/CDVCamera.m index aabe844..d0a36dd 100755 --- a/iPhone/CordovaLib/Classes/CDVCamera.m +++ b/iPhone/CordovaLib/Classes/CDVCamera.m @@ -18,9 +18,15 @@ */ #import "CDVCamera.h" +#import "CDVJpegHeaderWriter.h" #import "NSArray+Comparisons.h" #import "NSData+Base64.h" #import "NSDictionary+Extensions.h" +#import <ImageIO/CGImageProperties.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <ImageIO/CGImageSource.h> +#import <ImageIO/CGImageProperties.h> +#import <ImageIO/CGImageDestination.h> #import <MobileCoreServices/UTCoreTypes.h> #define CDV_PHOTO_PREFIX @"cdv_photo_" @@ -40,7 +46,7 @@ static NSSet* org_apache_cordova_validArrowDirections; org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil]; } -@synthesize hasPendingOperation, pickerController; +@synthesize hasPendingOperation, pickerController, locationManager; - (BOOL)popoverSupported { @@ -60,6 +66,8 @@ static NSSet* org_apache_cordova_validArrowDirections; * 7 allowsEdit * 8 correctOrientation * 9 saveToPhotoAlbum + * 10 popoverOptions + * 11 cameraDirection */ - (void)takePicture:(CDVInvokedUrlCommand*)command { @@ -122,18 +130,22 @@ static NSSet* org_apache_cordova_validArrowDirections; cameraPicker.returnType = ([arguments objectAtIndex:1]) ? [[arguments objectAtIndex:1] intValue] : DestinationTypeFileUri; if (sourceType == UIImagePickerControllerSourceTypeCamera) { - // we only allow taking pictures (no video) in this api + // We only allow taking pictures (no video) in this API. cameraPicker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil]; + + // We can only set the camera device if we're actually using the camera. + NSNumber* cameraDirection = [command argumentAtIndex:11 withDefault:[NSNumber numberWithInteger:UIImagePickerControllerCameraDeviceRear]]; + cameraPicker.cameraDevice = (UIImagePickerControllerCameraDevice)[cameraDirection intValue]; } else if (mediaType == MediaTypeAll) { cameraPicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType]; } else { - NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie:kUTTypeImage), nil]; + NSArray* mediaArray = [NSArray arrayWithObjects:(NSString*)(mediaType == MediaTypeVideo ? kUTTypeMovie : kUTTypeImage), nil]; cameraPicker.mediaTypes = mediaArray; } if ([self popoverSupported] && (sourceType != UIImagePickerControllerSourceTypeCamera)) { if (cameraPicker.popoverController == nil) { - cameraPicker.popoverController = [[NSClassFromString (@"UIPopoverController")alloc] initWithContentViewController:cameraPicker]; + cameraPicker.popoverController = [[NSClassFromString(@"UIPopoverController")alloc] initWithContentViewController:cameraPicker]; } NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil]; [self displayPopover:options]; @@ -180,6 +192,17 @@ static NSSet* org_apache_cordova_validArrowDirections; animated:YES]; } +- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated +{ + if([navigationController isKindOfClass:[UIImagePickerController class]]){ + UIImagePickerController * cameraPicker = (UIImagePickerController*)navigationController; + + if(![cameraPicker.mediaTypes containsObject:(NSString*) kUTTypeImage]){ + [viewController.navigationItem setTitle:NSLocalizedString(@"Videos title", nil)]; + } + } +} + - (void)cleanup:(CDVInvokedUrlCommand*)command { // empty the tmp directory @@ -264,10 +287,6 @@ static NSSet* org_apache_cordova_validArrowDirections; image = [info objectForKey:UIImagePickerControllerOriginalImage]; } - if (cameraPicker.saveToPhotoAlbum) { - UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); - } - if (cameraPicker.correctOrientation) { image = [self imageCorrectedForCaptureOrientation:image]; } @@ -289,12 +308,28 @@ static NSSet* org_apache_cordova_validArrowDirections; data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage); } else { data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f); + + NSDictionary *controllerMetadata = [info objectForKey:@"UIImagePickerControllerMediaMetadata"]; + if (controllerMetadata) { + self.data = data; + self.metadata = [[NSMutableDictionary alloc] init]; + + NSMutableDictionary *EXIFDictionary = [[controllerMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]; + if (EXIFDictionary) [self.metadata setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary]; + + [[self locationManager] startUpdatingLocation]; + return; + } + } + + if (cameraPicker.saveToPhotoAlbum) { + UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:data], nil, nil, nil); } if (cameraPicker.returnType == DestinationTypeFileUri) { // write to temp directory and return URI // get the temp directory path - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe // generate unique file name @@ -419,7 +454,7 @@ static NSSet* org_apache_cordova_validArrowDirections; rotation_radians = 0.0; break; - case UIImageOrientationDown : + case UIImageOrientationDown: rotation_radians = M_PI; // don't be scared of radians, if you're reading this, you're good at math break; @@ -518,7 +553,7 @@ static NSSet* org_apache_cordova_validArrowDirections; // first parameter an image [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"upload\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]]; - [postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding : NSUTF8StringEncoding]]; [postBody appendData:imageData]; // // second parameter information @@ -539,6 +574,143 @@ static NSSet* org_apache_cordova_validArrowDirections; self.hasPendingOperation = NO; } + +- (CLLocationManager *)locationManager { + + if (locationManager != nil) { + return locationManager; + } + + locationManager = [[CLLocationManager alloc] init]; + [locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters]; + [locationManager setDelegate:self]; + + return locationManager; +} + +- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation +{ + if (locationManager != nil) { + [self.locationManager stopUpdatingLocation]; + self.locationManager = nil; + + NSMutableDictionary *GPSDictionary = [[NSMutableDictionary dictionary] init]; + + CLLocationDegrees latitude = newLocation.coordinate.latitude; + CLLocationDegrees longitude = newLocation.coordinate.longitude; + + // latitude + if (latitude < 0.0) { + latitude = latitude * -1.0f; + [GPSDictionary setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef]; + } else { + [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef]; + } + [GPSDictionary setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString*)kCGImagePropertyGPSLatitude]; + + // longitude + if (longitude < 0.0) { + longitude = longitude * -1.0f; + [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef]; + } + else { + [GPSDictionary setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef]; + } + [GPSDictionary setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString*)kCGImagePropertyGPSLongitude]; + + // altitude + CGFloat altitude = newLocation.altitude; + if (!isnan(altitude)){ + if (altitude < 0) { + altitude = -altitude; + [GPSDictionary setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef]; + } else { + [GPSDictionary setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef]; + } + [GPSDictionary setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude]; + } + + // Time and date + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"HH:mm:ss.SSSSSS"]; + [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; + [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp]; + [formatter setDateFormat:@"yyyy:MM:dd"]; + [GPSDictionary setObject:[formatter stringFromDate:newLocation.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp]; + + [self.metadata setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary]; + [self imagePickerControllerReturnImageResult]; + } +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if (locationManager != nil) { + [self.locationManager stopUpdatingLocation]; + self.locationManager = nil; + + [self imagePickerControllerReturnImageResult]; + } +} + +- (void)imagePickerControllerReturnImageResult +{ + CDVPluginResult* result = nil; + + if (self.metadata) { + CGImageSourceRef sourceImage = CGImageSourceCreateWithData((__bridge_retained CFDataRef)self.data, NULL); + CFStringRef sourceType = CGImageSourceGetType(sourceImage); + + CGImageDestinationRef destinationImage = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)self.data, sourceType, 1, NULL); + CGImageDestinationAddImageFromSource(destinationImage, sourceImage, 0, (__bridge CFDictionaryRef)self.metadata); + CGImageDestinationFinalize(destinationImage); + + CFRelease(sourceImage); + CFRelease(destinationImage); + } + + if (self.pickerController.saveToPhotoAlbum) { + UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[self data]], nil, nil, nil); + } + + if (self.pickerController.returnType == DestinationTypeFileUri) { + // write to temp directory and return URI + // get the temp directory path + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; + NSError* err = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe + // generate unique file name + NSString* filePath; + + int i = 1; + do { + filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, self.pickerController.encodingType == EncodingTypePNG ? @"png":@"jpg"]; + } while ([fileMgr fileExistsAtPath:filePath]); + + // save file + if (![self.data writeToFile:filePath options:NSAtomicWrite error:&err]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; + } + else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]]; + } + } + else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[self.data base64EncodedString]]; + } + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId]; + } + + if (result) { + [self.commandDelegate sendPluginResult:result callbackId:self.pickerController.callbackId]; + } + + self.hasPendingOperation = NO; + self.pickerController = nil; + self.data = nil; + self.metadata = nil; +} + @end @implementation CDVCameraPicker diff --git a/iPhone/CordovaLib/Classes/CDVCapture.m b/iPhone/CordovaLib/Classes/CDVCapture.m index ed9f664..884702d 100755 --- a/iPhone/CordovaLib/Classes/CDVCapture.m +++ b/iPhone/CordovaLib/Classes/CDVCapture.m @@ -108,7 +108,6 @@ if ([options isKindOfClass:[NSNull class]]) { options = [NSDictionary dictionary]; } - NSString* mode = [options objectForKey:@"mode"]; // options could contain limit and mode neither of which are supported at this time // taking more than one picture (limit) is only supported if provide own controls via cameraOverlayView property @@ -139,7 +138,6 @@ }*/ // CDVImagePicker specific property pickerController.callbackId = callbackId; - pickerController.mimeType = mode; if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { [self.viewController presentViewController:pickerController animated:YES completion:nil]; @@ -169,7 +167,7 @@ } // write to temp directory and return URI - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; // use file system temporary directory NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -206,9 +204,9 @@ options = [NSDictionary dictionary]; } - // options could contain limit, duration and mode, only duration is supported (but is not due to apple bug) + // options could contain limit, duration and mode // taking more than one video (limit) is only supported if provide own controls via cameraOverlayView property - // NSNumber* duration = [options objectForKey:@"duration"]; + NSNumber* duration = [options objectForKey:@"duration"]; NSString* mediaType = nil; if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { @@ -240,12 +238,12 @@ // iOS 3.0 pickerController.mediaTypes = [NSArray arrayWithObjects:mediaType, nil]; - /*if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]){ + if ([mediaType isEqualToString:(NSString*)kUTTypeMovie]){ if (duration) { pickerController.videoMaximumDuration = [duration doubleValue]; } //NSLog(@"pickerController.videoMaximumDuration = %f", pickerController.videoMaximumDuration); - }*/ + } // iOS 4.0 if ([pickerController respondsToSelector:@selector(cameraCaptureMode)]) { @@ -439,7 +437,7 @@ if ([command isKindOfClass:[CDVFile class]]) { CDVFile* cdvFile = (CDVFile*)command; NSString* mimeType = [cdvFile getMimeTypeFromPath:fullPath]; - [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType:[NSNull null]) forKey:@"type"]; + [fileDict setObject:(mimeType != nil ? (NSObject*)mimeType : [NSNull null]) forKey:@"type"]; } } NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:nil]; @@ -533,7 +531,6 @@ // delegate to CVDAudioRecorderViewController return [self.topViewController supportedInterfaceOrientations]; } - #endif @end @@ -663,7 +660,7 @@ // create file to record to in temporary dir - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // use file system temporary directory + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; // use file system temporary directory NSError* err = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -701,7 +698,6 @@ orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown); return orientation; } - #endif - (void)viewDidUnload @@ -766,7 +762,7 @@ BOOL isUIAccessibilityAnnouncementNotification = (&UIAccessibilityAnnouncementNotification != NULL); if (isUIAccessibilityAnnouncementNotification) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500ull * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ - UIAccessibilityPostNotification (UIAccessibilityAnnouncementNotification, NSLocalizedString (@"timed recording complete", nil)); + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, NSLocalizedString(@"timed recording complete", nil)); }); } } else { diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h index e177c63..0401136 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h @@ -30,7 +30,6 @@ - (NSString*)pathForResource:(NSString*)resourcepath; - (id)getCommandInstance:(NSString*)pluginName; -- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className CDV_DEPRECATED(2.2, "Use CDVViewController to register plugins, or use config.xml."); // Plugins should not be using this interface to call other plugins since it // will result in bogus callbacks being made. diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m index e399289..fa0e5e0 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m @@ -55,7 +55,11 @@ - (void)evalJsHelper2:(NSString*)js { + CDV_EXEC_LOG(@"Exec: evalling: %@", [js substringToIndex:MIN([js length], 160)]); NSString* commandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString:js]; + if ([commandsJSON length] > 0) { + CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by chaining."); + } [_commandQueue enqueCommandBatch:commandsJSON]; } @@ -78,21 +82,16 @@ - (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId { + CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status); // This occurs when there is are no win/fail callbacks for the call. - if ([@"INVALID" isEqualToString:callbackId]) { + if ([@"INVALID" isEqualToString : callbackId]) { return; } int status = [result.status intValue]; BOOL keepCallback = [result.keepCallback boolValue]; - id message = result.message == nil ? [NSNull null] : result.message; + NSString* argumentsAsJSON = [result argumentsAsJSON]; - // Use an array to encode the message as JSON. - message = [NSArray arrayWithObject:message]; - NSString* encodedMessage = [message JSONString]; - // And then strip off the outer []s. - encodedMessage = [encodedMessage substringWithRange:NSMakeRange(1, [encodedMessage length] - 2)]; - NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", - callbackId, status, encodedMessage, keepCallback]; + NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback]; [self evalJsHelper:js]; } @@ -122,11 +121,6 @@ return [_viewController getCommandInstance:pluginName]; } -- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className -{ - [_viewController registerPlugin:plugin withClassName:className]; -} - - (void)runInBackground:(void (^)())block { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); diff --git a/iPhone/CordovaLib/Classes/CDVCommandQueue.h b/iPhone/CordovaLib/Classes/CDVCommandQueue.h index ebdf844..27c47b5 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandQueue.h +++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.h @@ -22,13 +22,7 @@ @class CDVInvokedUrlCommand; @class CDVViewController; -@interface CDVCommandQueue : NSObject { - @private - NSInteger _lastCommandQueueFlushRequestId; - __weak CDVViewController* _viewController; - NSMutableArray* _queue; - BOOL _currentlyExecuting; -} +@interface CDVCommandQueue : NSObject @property (nonatomic, readonly) BOOL currentlyExecuting; diff --git a/iPhone/CordovaLib/Classes/CDVCommandQueue.m b/iPhone/CordovaLib/Classes/CDVCommandQueue.m index a8a58b7..1a0dfa0 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandQueue.m +++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.m @@ -23,6 +23,14 @@ #import "CDVViewController.h" #import "CDVCommandDelegateImpl.h" +@interface CDVCommandQueue () { + NSInteger _lastCommandQueueFlushRequestId; + __weak CDVViewController* _viewController; + NSMutableArray* _queue; + BOOL _currentlyExecuting; +} +@end + @implementation CDVCommandQueue @synthesize currentlyExecuting = _currentlyExecuting; @@ -74,6 +82,9 @@ @"cordova.require('cordova/exec').nativeFetchMessages()"]; [self enqueCommandBatch:queuedCommandsJSON]; + if ([queuedCommandsJSON length] > 0) { + CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by request."); + } } - (void)executePending @@ -92,13 +103,15 @@ // Iterate over and execute all of the commands. for (NSArray* jsonEntry in commandBatch) { CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; + CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName); + if (![self execute:command]) { #ifdef DEBUG NSString* commandJson = [jsonEntry JSONString]; static NSUInteger maxLogLength = 1024; NSString* commandString = ([commandJson length] > maxLogLength) ? - [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : - commandJson; + [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : + commandJson; DLog(@"FAILED pluginJSON = %@", commandString); #endif diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.h b/iPhone/CordovaLib/Classes/CDVConfigParser.h index 7392580..2e06c88 100755 --- a/iPhone/CordovaLib/Classes/CDVConfigParser.h +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.h @@ -17,7 +17,10 @@ under the License. */ -@interface CDVConfigParser : NSObject <NSXMLParserDelegate>{} +@interface CDVConfigParser : NSObject <NSXMLParserDelegate> +{ + NSString* featureName; +} @property (nonatomic, readonly, strong) NSMutableDictionary* pluginsDict; @property (nonatomic, readonly, strong) NSMutableDictionary* settings; diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.m b/iPhone/CordovaLib/Classes/CDVConfigParser.m index 6fd5913..55b25e6 100755 --- a/iPhone/CordovaLib/Classes/CDVConfigParser.m +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.m @@ -41,6 +41,7 @@ self.settings = [[NSMutableDictionary alloc] initWithCapacity:30]; self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:30]; self.startupPluginNames = [[NSMutableArray alloc] initWithCapacity:8]; + featureName = nil; } return self; } @@ -52,9 +53,27 @@ } else if ([elementName isEqualToString:@"plugin"]) { NSString* name = [attributeDict[@"name"] lowercaseString]; pluginsDict[name] = attributeDict[@"value"]; - if ([@"true" isEqualToString:attributeDict[@"onload"]]) { + if ([@"true" isEqualToString : attributeDict[@"onload"]]) { [self.startupPluginNames addObject:name]; } + NSLog(@"\nUse of the <plugin> tag has been deprecated. Use a <feature> tag instead. Change:\n" + @" <plugin name=\"%@\" value=\"%@\" />\n" + @"To:\n" + @" <feature name=\"%@\">\n" + @" <param name=\"ios-package\" value=\"%@\" />\n" + @" </feature>\n" + , attributeDict[@"name"], attributeDict[@"value"], attributeDict[@"name"], attributeDict[@"value"]); + } else if ([elementName isEqualToString:@"feature"]) { // store feature name to use with correct parameter set + featureName = [attributeDict[@"name"] lowercaseString]; + } else if ((featureName != nil) && [elementName isEqualToString:@"param"]) { + NSString* paramName = [attributeDict[@"name"] lowercaseString]; + id value = attributeDict[@"value"]; + if ([paramName isEqualToString:@"ios-package"]) { + pluginsDict[featureName] = value; + } + if ([paramName isEqualToString:@"onload"] && [@"true" isEqualToString : value]) { + [self.startupPluginNames addObject:featureName]; + } } else if ([elementName isEqualToString:@"access"]) { [whitelistHosts addObject:attributeDict[@"origin"]]; } else if ([elementName isEqualToString:@"content"]) { @@ -62,6 +81,13 @@ } } +- (void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName +{ + if ([elementName isEqualToString:@"feature"]) { // no longer handling a feature so release + featureName = nil; + } +} + - (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError { NSAssert(NO, @"config.xml parse error line %d col %d", [parser lineNumber], [parser columnNumber]); diff --git a/iPhone/CordovaLib/Classes/CDVConnection.m b/iPhone/CordovaLib/Classes/CDVConnection.m index 3030711..b3f5cab 100755 --- a/iPhone/CordovaLib/Classes/CDVConnection.m +++ b/iPhone/CordovaLib/Classes/CDVConnection.m @@ -52,7 +52,8 @@ return @"none"; case ReachableViaWWAN: - return @"2g"; // no generic default, so we use the lowest common denominator + // Return value of '2g' is deprecated as of 2.6.0 and will be replaced with 'cellular' in 3.0.0 + return @"2g"; case ReachableViaWiFi: return @"wifi"; @@ -66,7 +67,8 @@ { return [theConnectionType isEqualToString:@"2g"] || [theConnectionType isEqualToString:@"3g"] || - [theConnectionType isEqualToString:@"4g"]; + [theConnectionType isEqualToString:@"4g"] || + [theConnectionType isEqualToString:@"cellular"]; } - (void)updateReachability:(CDVReachability*)reachability @@ -111,6 +113,7 @@ self.internetReach = [CDVReachability reachabilityForInternetConnection]; self.connectionType = [self w3cConnectionTypeFor:self.internetReach]; [self.internetReach startNotifier]; + [self printDeprecationNotice]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateConnectionType:) name:kReachabilityChangedNotification object:nil]; if (&UIApplicationDidEnterBackgroundNotification && &UIApplicationWillEnterForegroundNotification) { @@ -121,4 +124,9 @@ return self; } +- (void)printDeprecationNotice +{ + NSLog(@"DEPRECATION NOTICE: The Connection ReachableViaWWAN return value of '2g' is deprecated as of Cordova version 2.6.0 and will be changed to 'cellular' in a future release. "); +} + @end diff --git a/iPhone/CordovaLib/Classes/CDVContact.m b/iPhone/CordovaLib/Classes/CDVContact.m index 9efaf10..3844525 100755 --- a/iPhone/CordovaLib/Classes/CDVContact.m +++ b/iPhone/CordovaLib/Classes/CDVContact.m @@ -38,7 +38,9 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; if ((self = [super init]) != nil) { ABRecordRef rec = ABPersonCreate(); self.record = rec; - CFRelease(rec); + if (rec) { + CFRelease(rec); + } } return self; } @@ -167,9 +169,9 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; if (org_apache_cordova_contacts_objectAndProperties == nil) { org_apache_cordova_contacts_objectAndProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSArray arrayWithObjects:kW3ContactGivenName, kW3ContactFamilyName, - kW3ContactMiddleName, kW3ContactHonorificPrefix, kW3ContactHonorificSuffix, kW3ContactFormattedName, nil], kW3ContactName, + kW3ContactMiddleName, kW3ContactHonorificPrefix, kW3ContactHonorificSuffix, kW3ContactFormattedName, nil], kW3ContactName, [NSArray arrayWithObjects:kW3ContactStreetAddress, kW3ContactLocality, kW3ContactRegion, - kW3ContactPostalCode, kW3ContactCountry, /*kW3ContactAddressFormatted,*/ nil], kW3ContactAddresses, + kW3ContactPostalCode, kW3ContactCountry, /*kW3ContactAddressFormatted,*/ nil], kW3ContactAddresses, [NSArray arrayWithObjects:kW3ContactOrganizationName, kW3ContactTitle, kW3ContactDepartment, nil], kW3ContactOrganizations, [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactPhoneNumbers, [NSArray arrayWithObjects:kW3ContactFieldType, kW3ContactFieldValue, kW3ContactFieldPrimary, nil], kW3ContactEmails, @@ -228,7 +230,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; NSArray* propArray = [[CDVContact defaultObjectAndProperties] objectForKey:kW3ContactName]; for (id i in propArray) { - if (![(NSString*) i isEqualToString:kW3ContactFormattedName]) { // kW3ContactFormattedName is generated from ABRecordCopyCompositeName() and can't be set + if (![(NSString*)i isEqualToString : kW3ContactFormattedName]) { // kW3ContactFormattedName is generated from ABRecordCopyCompositeName() and can't be set [self setValue:[dict valueForKey:i] forProperty:(ABPropertyID)[(NSNumber*)[[CDVContact defaultW3CtoAB] objectForKey:i] intValue] inRecord:person asUpdate:bUpdate]; } @@ -298,9 +300,9 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; bRemove = YES; } if ([dict isKindOfClass:[NSDictionary class]] || (bRemove == YES)) { - [self setValue:(bRemove ? @"":[dict valueForKey:@"name"]) forProperty:kABPersonOrganizationProperty inRecord:person asUpdate:bUpdate]; - [self setValue:(bRemove ? @"":[dict valueForKey:kW3ContactTitle]) forProperty:kABPersonJobTitleProperty inRecord:person asUpdate:bUpdate]; - [self setValue:(bRemove ? @"":[dict valueForKey:kW3ContactDepartment]) forProperty:kABPersonDepartmentProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:@"name"]) forProperty:kABPersonOrganizationProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:kW3ContactTitle]) forProperty:kABPersonJobTitleProperty inRecord:person asUpdate:bUpdate]; + [self setValue:(bRemove ? @"" : [dict valueForKey:kW3ContactDepartment]) forProperty:kABPersonDepartmentProperty inRecord:person asUpdate:bUpdate]; } } // add dates @@ -658,7 +660,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; [dict setObject:setValue forKey:(NSString*)[[CDVContact defaultW3CtoAB] valueForKey:(NSString*)k]]; } else if ((value == nil) || ([value isKindOfClass:[NSString class]] && ([value length] != 0))) { // value not provided in contact dictionary - if prop exists in AB dictionary, preserve it - valueAB = [(__bridge NSDictionary*) existingDictionary valueForKey:[[CDVContact defaultW3CtoAB] valueForKey:k]]; + valueAB = [(__bridge NSDictionary*)existingDictionary valueForKey : [[CDVContact defaultW3CtoAB] valueForKey:k]]; if (valueAB != nil) { [dict setValue:valueAB forKey:[[CDVContact defaultW3CtoAB] valueForKey:k]]; } @@ -893,7 +895,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; if (data != nil) { [nc setObject:data forKey:kW3ContactName]; } - if ([self.returnFields objectForKey:kW3ContactDisplayName] && ((data == nil) || ([(NSDictionary*) data objectForKey:kW3ContactFormattedName] == [NSNull null]))) { + if ([self.returnFields objectForKey:kW3ContactDisplayName] && ((data == nil) || ([(NSDictionary*)data objectForKey : kW3ContactFormattedName] == [NSNull null]))) { // user asked for displayName which iOS doesn't support but there is no other name data being returned // try and use Composite Name so some name is returned id tryName = (__bridge_transfer NSString*)ABRecordCopyCompositeName(self.record); @@ -1114,7 +1116,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; // always set id value = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; [newDict setObject:(value != nil) ? value:[NSNull null] forKey:kW3ContactFieldId]; - [(NSMutableArray*) valuesArray addObject:newDict]; + [(NSMutableArray*)valuesArray addObject : newDict]; } } else { valuesArray = [NSNull null]; @@ -1188,7 +1190,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; } if ([newAddress count] > 0) { // ?? this will always be true since we set id,label,primary field?? - [(NSMutableArray*) addresses addObject:newAddress]; + [(NSMutableArray*)addresses addObject : newAddress]; } CFRelease(dict); } // end of loop through addresses @@ -1244,7 +1246,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageServiceKey, (void*)&value); if (bFound && (value != NULL)) { CFRetain(value); - [newDict setObject:(id)[[CDVContact class] convertPropertyLabelToContactType:(__bridge NSString*)value] forKey:kW3ContactFieldType]; + [newDict setObject:(id)[[CDVContact class] convertPropertyLabelToContactType : (__bridge NSString*)value] forKey:kW3ContactFieldType]; CFRelease(value); } else { [newDict setObject:[NSNull null] forKey:kW3ContactFieldType]; @@ -1254,7 +1256,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; id identifier = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)]; [newDict setObject:(identifier != nil) ? identifier:[NSNull null] forKey:kW3ContactFieldId]; - [(NSMutableArray*) imArray addObject:newDict]; + [(NSMutableArray*)imArray addObject : newDict]; CFRelease(dict); } } else { @@ -1308,7 +1310,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; [newDict setObject:@"false" forKey:kW3ContactFieldPrimary]; [newDict setObject:[NSNull null] forKey:kW3ContactFieldType]; array = [NSMutableArray arrayWithCapacity:1]; - [(NSMutableArray*) array addObject:newDict]; + [(NSMutableArray*)array addObject : newDict]; } else { array = [NSNull null]; } @@ -1327,7 +1329,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; NSData* data = (__bridge NSData*)photoData; // write to temp directory and store URI in photos array // get the temp directory path - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath]; NSError* err = nil; NSString* filePath = [NSString stringWithFormat:@"%@/photo_XXXXX", docsPath]; char template[filePath.length + 1]; diff --git a/iPhone/CordovaLib/Classes/CDVContacts.h b/iPhone/CordovaLib/Classes/CDVContacts.h index 17470c0..0342f5b 100755 --- a/iPhone/CordovaLib/Classes/CDVContacts.h +++ b/iPhone/CordovaLib/Classes/CDVContacts.h @@ -24,9 +24,9 @@ #import "CDVContact.h" @interface CDVContacts : CDVPlugin <ABNewPersonViewControllerDelegate, - ABPersonViewControllerDelegate, - ABPeoplePickerNavigationControllerDelegate - > + ABPersonViewControllerDelegate, + ABPeoplePickerNavigationControllerDelegate + > { ABAddressBookRef addressBook; } @@ -63,7 +63,7 @@ - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person; - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person - property :(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue; + property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue; /* * search - searches for contacts. Only person records are currently supported. @@ -140,9 +140,9 @@ - (CDVAddressBookAccessError*)initWithCode:(CDVContactError)code; @end -typedef void (^CDVAddressBookWorkerBlock)( - ABAddressBookRef addressBook, - CDVAddressBookAccessError * error +typedef void (^ CDVAddressBookWorkerBlock)( + ABAddressBookRef addressBook, + CDVAddressBookAccessError* error ); @interface CDVAddressBookHelper : NSObject {} diff --git a/iPhone/CordovaLib/Classes/CDVContacts.m b/iPhone/CordovaLib/Classes/CDVContacts.m index 3faf6ba..6cb9f08 100755 --- a/iPhone/CordovaLib/Classes/CDVContacts.m +++ b/iPhone/CordovaLib/Classes/CDVContacts.m @@ -77,26 +77,26 @@ CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error just return (no error callback) - return; - } - CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; - npController.addressBook = addrBook; // a CF retaining assign - CFRelease (addrBook); + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error just return (no error callback) + return; + } + CDVNewContactsController* npController = [[CDVNewContactsController alloc] init]; + npController.addressBook = addrBook; // a CF retaining assign + CFRelease(addrBook); - npController.newPersonViewDelegate = self; - npController.callbackId = callbackId; + npController.newPersonViewDelegate = self; + npController.callbackId = callbackId; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:npController]; - if ([weakSelf.viewController respondsToSelector:@selector(presentViewController:::)]) { - [weakSelf.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [weakSelf.viewController presentModalViewController:navController animated:YES]; - } - }]; + if ([weakSelf.viewController respondsToSelector:@selector(presentViewController:::)]) { + [weakSelf.viewController presentViewController:navController animated:YES completion:nil]; + } else { + [weakSelf.viewController presentModalViewController:navController animated:YES]; + } + }]; } - (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person @@ -130,48 +130,48 @@ CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error - return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } - ABRecordRef rec = ABAddressBookGetPersonWithRecordID (addrBook, recordID); - - if (rec) { - CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; - personController.displayedPerson = rec; - personController.personViewDelegate = self; - personController.allowsEditing = NO; + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + ABRecordRef rec = ABAddressBookGetPersonWithRecordID(addrBook, recordID); - // create this so DisplayContactViewController will have a "back" button. - UIViewController* parentController = [[UIViewController alloc] init]; - UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; + if (rec) { + CDVDisplayContactViewController* personController = [[CDVDisplayContactViewController alloc] init]; + personController.displayedPerson = rec; + personController.personViewDelegate = self; + personController.allowsEditing = NO; - [navController pushViewController:personController animated:YES]; + // create this so DisplayContactViewController will have a "back" button. + UIViewController* parentController = [[UIViewController alloc] init]; + UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:parentController]; - if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { - [self.viewController presentViewController:navController animated:YES completion:nil]; - } else { - [self.viewController presentModalViewController:navController animated:YES]; - } + [navController pushViewController:personController animated:YES]; - if (bEdit) { - // create the editing controller and push it onto the stack - ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; - editPersonController.displayedPerson = rec; - editPersonController.personViewDelegate = self; - editPersonController.allowsEditing = YES; - [navController pushViewController:editPersonController animated:YES]; - } + if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { + [self.viewController presentViewController:navController animated:YES completion:nil]; } else { - // no record, return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + [self.viewController presentModalViewController:navController animated:YES]; } - CFRelease (addrBook); - }]; + + if (bEdit) { + // create the editing controller and push it onto the stack + ABPersonViewController* editPersonController = [[ABPersonViewController alloc] init]; + editPersonController.displayedPerson = rec; + editPersonController.personViewDelegate = self; + editPersonController.allowsEditing = YES; + [navController pushViewController:editPersonController animated:YES]; + } + } else { + // no record, return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + CFRelease(addrBook); + }]; } - (BOOL)personViewController:(ABPersonViewController*)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person @@ -284,95 +284,95 @@ NSDictionary* findOptions = [command.arguments objectAtIndex:1 withDefault:[NSNull null]]; [self.commandDelegate runInBackground:^{ - // from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread. - // which is why address book is created within the dispatch queue. - // more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/ - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - // it gets uglier, block within block..... - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errCode) { - if (addrBook == NULL) { - // permission was denied or other error - return error - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } + // from Apple: Important You must ensure that an instance of ABAddressBookRef is used by only one thread. + // which is why address book is created within the dispatch queue. + // more details here: http: //blog.byadrian.net/2012/05/05/ios-addressbook-framework-and-gcd/ + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + // it gets uglier, block within block..... + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errCode) { + if (addrBook == NULL) { + // permission was denied or other error - return error + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:errCode ? errCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } - NSArray* foundRecords = nil; - // get the findOptions values - BOOL multiple = NO; // default is false - NSString* filter = nil; - if (![findOptions isKindOfClass:[NSNull class]]) { - id value = nil; - filter = (NSString*)[findOptions objectForKey:@"filter"]; - value = [findOptions objectForKey:@"multiple"]; - if ([value isKindOfClass:[NSNumber class]]) { - // multiple is a boolean that will come through as an NSNumber - multiple = [(NSNumber*) value boolValue]; - // NSLog(@"multiple is: %d", multiple); - } - } + NSArray* foundRecords = nil; + // get the findOptions values + BOOL multiple = NO; // default is false + NSString* filter = nil; + if (![findOptions isKindOfClass:[NSNull class]]) { + id value = nil; + filter = (NSString*)[findOptions objectForKey:@"filter"]; + value = [findOptions objectForKey:@"multiple"]; + if ([value isKindOfClass:[NSNumber class]]) { + // multiple is a boolean that will come through as an NSNumber + multiple = [(NSNumber*)value boolValue]; + // NSLog(@"multiple is: %d", multiple); + } + } - NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; - - NSMutableArray* matches = nil; - if (!filter || [filter isEqualToString:@""]) { - // get all records - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - if (foundRecords && [foundRecords count] > 0) { - // create Contacts and put into matches array - // doesn't make sense to ask for all records when multiple == NO but better check - int xferCount = multiple == YES ? [foundRecords count]:1; - matches = [NSMutableArray arrayWithCapacity:xferCount]; - - for (int k = 0; k < xferCount; k++) { - CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:k]]; - [matches addObject:xferContact]; - xferContact = nil; - } - } - } else { - foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople (addrBook); - matches = [NSMutableArray arrayWithCapacity:1]; - BOOL bFound = NO; - int testCount = [foundRecords count]; - - for (int j = 0; j < testCount; j++) { - CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]]; - if (testContact) { - bFound = [testContact foundValue:filter inFields:returnFields]; - if (bFound) { - [matches addObject:testContact]; - } - testContact = nil; - } - } + NSDictionary* returnFields = [[CDVContact class] calcReturnFields:fields]; + + NSMutableArray* matches = nil; + if (!filter || [filter isEqualToString:@""]) { + // get all records + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook); + if (foundRecords && ([foundRecords count] > 0)) { + // create Contacts and put into matches array + // doesn't make sense to ask for all records when multiple == NO but better check + int xferCount = multiple == YES ? [foundRecords count] : 1; + matches = [NSMutableArray arrayWithCapacity:xferCount]; + + for (int k = 0; k < xferCount; k++) { + CDVContact* xferContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:k]]; + [matches addObject:xferContact]; + xferContact = nil; } - NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; - - if (matches != nil && [matches count] > 0) { - // convert to JS Contacts format and return in callback - // - returnFields determines what properties to return - @autoreleasepool { - int count = multiple == YES ? [matches count]:1; - - for (int i = 0; i < count; i++) { - CDVContact* newContact = [matches objectAtIndex:i]; - NSDictionary* aContact = [newContact toDictionary:returnFields]; - [returnContacts addObject:aContact]; - } + } + } else { + foundRecords = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addrBook); + matches = [NSMutableArray arrayWithCapacity:1]; + BOOL bFound = NO; + int testCount = [foundRecords count]; + + for (int j = 0; j < testCount; j++) { + CDVContact* testContact = [[CDVContact alloc] initFromABRecord:(__bridge ABRecordRef)[foundRecords objectAtIndex:j]]; + if (testContact) { + bFound = [testContact foundValue:filter inFields:returnFields]; + if (bFound) { + [matches addObject:testContact]; } + testContact = nil; } - // return found contacts (array is empty if no contacts found) - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - // NSLog(@"findCallback string: %@", jsString); - - if (addrBook) { - CFRelease (addrBook); + } + } + NSMutableArray* returnContacts = [NSMutableArray arrayWithCapacity:1]; + + if ((matches != nil) && ([matches count] > 0)) { + // convert to JS Contacts format and return in callback + // - returnFields determines what properties to return + @autoreleasepool { + int count = multiple == YES ? [matches count] : 1; + + for (int i = 0; i < count; i++) { + CDVContact* newContact = [matches objectAtIndex:i]; + NSDictionary* aContact = [newContact toDictionary:returnFields]; + [returnContacts addObject:aContact]; } - }]; - }]; // end of workQueue block + } + } + // return found contacts (array is empty if no contacts found) + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:returnContacts]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + // NSLog(@"findCallback string: %@", jsString); + + if (addrBook) { + CFRelease(addrBook); + } + }]; + }]; // end of workQueue block return; } @@ -383,81 +383,10 @@ NSDictionary* contactDict = [command.arguments objectAtIndex:0]; [self.commandDelegate runInBackground:^{ - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { - CDVPluginResult* result = nil; - if (addrBook == NULL) { - // permission was denied or other error - return error - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - return; - } - - bool bIsError = FALSE, bSuccess = FALSE; - BOOL bUpdate = NO; - CDVContactError errCode = UNKNOWN_ERROR; - CFErrorRef error; - NSNumber* cId = [contactDict valueForKey:kW3ContactId]; - CDVContact* aContact = nil; - ABRecordRef rec = nil; - if (cId && ![cId isKindOfClass:[NSNull class]]) { - rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); - if (rec) { - aContact = [[CDVContact alloc] initFromABRecord:rec]; - bUpdate = YES; - } - } - if (!aContact) { - aContact = [[CDVContact alloc] init]; - } - - bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; - if (bSuccess) { - if (!bUpdate) { - bSuccess = ABAddressBookAddRecord (addrBook, [aContact record], &error); - } - if (bSuccess) { - bSuccess = ABAddressBookSave (addrBook, &error); - } - if (!bSuccess) { // need to provide error codes - bIsError = TRUE; - errCode = IO_ERROR; - } else { - // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified - // so would give back all fields (which W3C spec. indicates is not desired) - // for now (while testing) give back saved, full contact - NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; - // NSString* contactStr = [newContact JSONRepresentation]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; - } - } else { - bIsError = TRUE; - errCode = IO_ERROR; - } - CFRelease (addrBook); - - if (bIsError) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; - } - - if (result) { - [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; - } - }]; - }]; // end of queue -} - -- (void)remove:(CDVInvokedUrlCommand*)command -{ - NSString* callbackId = command.callbackId; - NSNumber* cId = [command.arguments objectAtIndex:0]; - - CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; - CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles - [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError * errorCode) { + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errorCode) { CDVPluginResult* result = nil; if (addrBook == NULL) { // permission was denied or other error - return error @@ -467,50 +396,121 @@ } bool bIsError = FALSE, bSuccess = FALSE; + BOOL bUpdate = NO; CDVContactError errCode = UNKNOWN_ERROR; CFErrorRef error; + NSNumber* cId = [contactDict valueForKey:kW3ContactId]; + CDVContact* aContact = nil; ABRecordRef rec = nil; - if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { - rec = ABAddressBookGetPersonWithRecordID (addrBook, [cId intValue]); + if (cId && ![cId isKindOfClass:[NSNull class]]) { + rec = ABAddressBookGetPersonWithRecordID(addrBook, [cId intValue]); if (rec) { - bSuccess = ABAddressBookRemoveRecord (addrBook, rec, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - bSuccess = ABAddressBookSave (addrBook, &error); - if (!bSuccess) { - bIsError = TRUE; - errCode = IO_ERROR; - } else { - // set id to null - // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; - // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - // NSString* contactStr = [contactDict JSONRepresentation]; - } - } - } else { - // no record found return error + aContact = [[CDVContact alloc] initFromABRecord:rec]; + bUpdate = YES; + } + } + if (!aContact) { + aContact = [[CDVContact alloc] init]; + } + + bSuccess = [aContact setFromContactDict:contactDict asUpdate:bUpdate]; + if (bSuccess) { + if (!bUpdate) { + bSuccess = ABAddressBookAddRecord(addrBook, [aContact record], &error); + } + if (bSuccess) { + bSuccess = ABAddressBookSave(addrBook, &error); + } + if (!bSuccess) { // need to provide error codes bIsError = TRUE; - errCode = UNKNOWN_ERROR; + errCode = IO_ERROR; + } else { + // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified + // so would give back all fields (which W3C spec. indicates is not desired) + // for now (while testing) give back saved, full contact + NSDictionary* newContact = [aContact toDictionary:[CDVContact defaultFields]]; + // NSString* contactStr = [newContact JSONRepresentation]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newContact]; } } else { - // invalid contact id provided bIsError = TRUE; - errCode = INVALID_ARGUMENT_ERROR; + errCode = IO_ERROR; } + CFRelease(addrBook); - if (addrBook) { - CFRelease (addrBook); - } if (bIsError) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; } + if (result) { [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; } }]; + }]; // end of queue +} + +- (void)remove:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSNumber* cId = [command.arguments objectAtIndex:0]; + + CDVAddressBookHelper* abHelper = [[CDVAddressBookHelper alloc] init]; + CDVContacts* __weak weakSelf = self; // play it safe to avoid retain cycles + + [abHelper createAddressBook: ^(ABAddressBookRef addrBook, CDVAddressBookAccessError* errorCode) { + CDVPluginResult* result = nil; + if (addrBook == NULL) { + // permission was denied or other error - return error + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode ? errorCode.errorCode:UNKNOWN_ERROR]; + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + return; + } + + bool bIsError = FALSE, bSuccess = FALSE; + CDVContactError errCode = UNKNOWN_ERROR; + CFErrorRef error; + ABRecordRef rec = nil; + if (cId && ![cId isKindOfClass:[NSNull class]] && ([cId intValue] != kABRecordInvalidID)) { + rec = ABAddressBookGetPersonWithRecordID(addrBook, [cId intValue]); + if (rec) { + bSuccess = ABAddressBookRemoveRecord(addrBook, rec, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + bSuccess = ABAddressBookSave(addrBook, &error); + if (!bSuccess) { + bIsError = TRUE; + errCode = IO_ERROR; + } else { + // set id to null + // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; + // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: contactDict]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + // NSString* contactStr = [contactDict JSONRepresentation]; + } + } + } else { + // no record found return error + bIsError = TRUE; + errCode = UNKNOWN_ERROR; + } + } else { + // invalid contact id provided + bIsError = TRUE; + errCode = INVALID_ARGUMENT_ERROR; + } + + if (addrBook) { + CFRelease(addrBook); + } + if (bIsError) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; + } + if (result) { + [weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + }]; return; } @@ -569,24 +569,24 @@ addressBook = ABAddressBookCreateWithOptions(NULL, &error); // NSLog(@"addressBook access: %lu", status); ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { - // callback can occur in background, address book must be accessed on thread it was created on - dispatch_sync (dispatch_get_main_queue (), ^{ + // callback can occur in background, address book must be accessed on thread it was created on + dispatch_sync(dispatch_get_main_queue(), ^{ if (error) { - workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + workerBlock(NULL, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); } else if (!granted) { - workerBlock (NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); + workerBlock(NULL, [[CDVAddressBookAccessError alloc] initWithCode:PERMISSION_DENIED_ERROR]); } else { // access granted - workerBlock (addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); + workerBlock(addressBook, [[CDVAddressBookAccessError alloc] initWithCode:UNKNOWN_ERROR]); } }); - }); + }); } else #endif { // iOS 4 or 5 no checks needed - addressBook = ABAddressBookCreate (); - workerBlock (addressBook, NULL); + addressBook = ABAddressBookCreate(); + workerBlock(addressBook, NULL); } } diff --git a/iPhone/CordovaLib/Classes/CDVDebugConsole.m b/iPhone/CordovaLib/Classes/CDVDebugConsole.m deleted file mode 100755 index 29cbb91..0000000 --- a/iPhone/CordovaLib/Classes/CDVDebugConsole.m +++ /dev/null @@ -1,37 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -#import "CDVDebugConsole.h" - -@implementation CDVDebugConsole - -- (void)log:(CDVInvokedUrlCommand*)command -{ - NSString* message = [command.arguments objectAtIndex:0]; - NSDictionary* options = [command.arguments objectAtIndex:1]; - NSString* log_level = @"INFO"; - - if ([options objectForKey:@"logLevel"]) { - log_level = [options objectForKey:@"logLevel"]; - } - - NSLog(@"[%@] %@", log_level, message); -} - -@end diff --git a/iPhone/CordovaLib/Classes/CDVDevice.m b/iPhone/CordovaLib/Classes/CDVDevice.m index cc7ad89..a331b81 100755 --- a/iPhone/CordovaLib/Classes/CDVDevice.m +++ b/iPhone/CordovaLib/Classes/CDVDevice.m @@ -75,7 +75,6 @@ [devProps setObject:@"iOS" forKey:@"platform"]; [devProps setObject:[device systemVersion] forKey:@"version"]; [devProps setObject:[device uniqueAppInstanceIdentifier] forKey:@"uuid"]; - [devProps setObject:[device model] forKey:@"name"]; [devProps setObject:[[self class] cordovaVersion] forKey:@"cordova"]; NSDictionary* devReturn = [NSDictionary dictionaryWithDictionary:devProps]; diff --git a/iPhone/CordovaLib/Classes/CDVEcho.m b/iPhone/CordovaLib/Classes/CDVEcho.m index 916e315..c74990d 100755 --- a/iPhone/CordovaLib/Classes/CDVEcho.m +++ b/iPhone/CordovaLib/Classes/CDVEcho.m @@ -51,4 +51,11 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)echoMultiPart:(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsMultipart:command.arguments]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + @end diff --git a/iPhone/CordovaLib/Classes/CDVExif.h b/iPhone/CordovaLib/Classes/CDVExif.h new file mode 100755 index 0000000..3e8adbd --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVExif.h @@ -0,0 +1,43 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#ifndef CordovaLib_ExifData_h +#define CordovaLib_ExifData_h + +// exif data types +typedef enum exifDataTypes { + EDT_UBYTE = 1, // 8 bit unsigned integer + EDT_ASCII_STRING, // 8 bits containing 7 bit ASCII code, null terminated + EDT_USHORT, // 16 bit unsigned integer + EDT_ULONG, // 32 bit unsigned integer + EDT_URATIONAL, // 2 longs, first is numerator and second is denominator + EDT_SBYTE, + EDT_UNDEFINED, // 8 bits + EDT_SSHORT, + EDT_SLONG, // 32bit signed integer (2's complement) + EDT_SRATIONAL, // 2 SLONGS, first long is numerator, second is denominator + EDT_SINGLEFLOAT, + EDT_DOUBLEFLOAT +} ExifDataTypes; + +// maps integer code for exif data types to width in bytes +static const int DataTypeToWidth[] = {1,1,2,4,8,1,1,2,4,8,4,8}; + +static const int RECURSE_HORIZON = 8; +#endif diff --git a/iPhone/CordovaLib/Classes/CDVFile.h b/iPhone/CordovaLib/Classes/CDVFile.h index 4862921..017b066 100755 --- a/iPhone/CordovaLib/Classes/CDVFile.h +++ b/iPhone/CordovaLib/Classes/CDVFile.h @@ -21,6 +21,7 @@ #import "CDVPlugin.h" enum CDVFileError { + NO_ERROR = 0, NOT_FOUND_ERR = 1, SECURITY_ERR = 2, ABORT_ERR = 3, @@ -76,6 +77,7 @@ extern NSString* const kCDVAssetsLibraryPrefix; - (void)readAsText:(CDVInvokedUrlCommand*)command; - (void)readAsDataURL:(CDVInvokedUrlCommand*)command; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command; - (NSString*)getMimeTypeFromPath:(NSString*)fullPath; - (void)write:(CDVInvokedUrlCommand*)command; - (void)testFileExists:(CDVInvokedUrlCommand*)command; @@ -88,7 +90,8 @@ extern NSString* const kCDVAssetsLibraryPrefix; // - (BOOL) fileExists:(NSString*)fileName; // - (BOOL) directoryExists:(NSString*)dirName; -- (void)writeToFile:(NSString*)fileName withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId; +- (void)writeToFile:(NSString*)fileName withData:(NSData*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId; +- (void)writeToFile:(NSString*)fileName withString:(NSString*)data encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId; - (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos; @property (nonatomic, strong) NSString* appDocsPath; diff --git a/iPhone/CordovaLib/Classes/CDVFile.m b/iPhone/CordovaLib/Classes/CDVFile.m index d52405d..9487dd4 100755 --- a/iPhone/CordovaLib/Classes/CDVFile.m +++ b/iPhone/CordovaLib/Classes/CDVFile.m @@ -52,7 +52,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); self.appLibraryPath = [paths objectAtIndex:0]; - self.appTempPath = [NSTemporaryDirectory ()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() + self.appTempPath = [NSTemporaryDirectory()stringByStandardizingPath]; // remove trailing slash from NSTemporaryDirectory() self.persistentPath = [NSString stringWithFormat:@"/%@", [self.appDocsPath lastPathComponent]]; self.temporaryPath = [NSString stringWithFormat:@"/%@", [self.appTempPath lastPathComponent]]; @@ -164,7 +164,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:QUOTA_EXCEEDED_ERR]; } else { NSMutableDictionary* fileSystem = [NSMutableDictionary dictionaryWithCapacity:2]; - [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary:kW3FilePersistent) forKey:@"name"]; + [fileSystem setObject:(type == TEMPORARY ? kW3FileTemporary : kW3FilePersistent) forKey:@"name"]; NSDictionary* dirEntry = [self getDirectoryEntry:fullPath isDirectory:YES]; [fileSystem setObject:dirEntry forKey:@"root"]; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; @@ -227,10 +227,9 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; NSURL* testUri = [NSURL URLWithString:strUri]; CDVPluginResult* result = nil; - if (!testUri || ![testUri isFileURL]) { - // issue ENCODING_ERR + if (!testUri) { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; - } else { + } else if ([testUri isFileURL]) { NSFileManager* fileMgr = [[NSFileManager alloc] init]; NSString* path = [testUri path]; // NSLog(@"url path: %@", path); @@ -262,7 +261,13 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // return NOT_FOUND_ERR result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; } + } else if ([strUri hasPrefix:@"assets-library://"]) { + NSDictionary* fileSystem = [self getDirectoryEntry:strUri isDirectory:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileSystem]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:ENCODING_ERR]; } + if (result != nil) { [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @@ -486,7 +491,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Retrieve the metadata and send it off. NSDate* date = [asset valueForProperty:ALAssetPropertyDate]; @@ -499,7 +504,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; } }; // TODO(maxw): Consider making this a class variable since it's the same every time. - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -525,7 +530,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; } else { // didn't get fileAttribs CDVFileError errorCode = ABORT_ERR; - NSLog (@"error getting metadata: %@", [error localizedDescription]); + NSLog(@"error getting metadata: %@", [error localizedDescription]); if ([error code] == NSFileNoSuchFileError) { errorCode = NOT_FOUND_ERR; } @@ -779,7 +784,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and try to copy it over. if (![fileMgr fileExistsAtPath:destRootPath]) { @@ -796,7 +801,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // We're good to go! Write the file to the new destination. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [data writeToFile:newFullPath atomically:YES]; @@ -808,7 +813,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -953,7 +958,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // In this case, we need to use an asynchronous method to retrieve the file. // Because of this, we can't just assign to `result` and send it at the end of the method. // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Populate the dictionary and send it off. NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; @@ -975,7 +980,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -1053,154 +1058,181 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } +- (void)readFileWithPath:(NSString*)path start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback +{ + if (path == nil) { + callback(nil, nil, SYNTAX_ERR); + } else { + [self.commandDelegate runInBackground:^ { + if ([path hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + + callback(data, MIMEType, NO_ERROR); + } else { + callback(nil, nil, NOT_FOUND_ERR); + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + NSLog(@"Error: %@", error); + callback(nil, nil, SECURITY_ERR); + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:path] resultBlock:resultBlock failureBlock:failureBlock]; + } else { + NSString* mimeType = [self getMimeTypeFromPath:path]; + if (mimeType == nil) { + mimeType = @"*/*"; + } + NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path]; + if (start > 0) { + [file seekToFileOffset:start]; + } + + NSData* readData; + if (end < 0) { + readData = [file readDataToEndOfFile]; + } else { + readData = [file readDataOfLength:(end - start)]; + } + + [file closeFile]; + + callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR); + } + }]; + } +} + /* read and return file data * IN: * NSArray* arguments * 0 - NSString* fullPath - * 1 - NSString* encoding - NOT USED, iOS reads and writes using UTF8! - * 2 - NSString* start - OPTIONAL, only provided when not == 0. - * 3 - NSString* end - OPTIONAL, only provided when not == length. + * 1 - NSString* encoding + * 2 - NSString* start + * 3 - NSString* end */ - (void)readAsText:(CDVInvokedUrlCommand*)command { // arguments - NSString* argPath = [command.arguments objectAtIndex:0]; - NSInteger start = 0; - NSInteger end = -1; - - if ([command.arguments count] >= 3) { - start = [[command.arguments objectAtIndex:2] integerValue]; - } - if ([command.arguments count] >= 4) { - end = [[command.arguments objectAtIndex:3] integerValue]; + NSString* path = [command argumentAtIndex:0]; + NSString* encoding = [command argumentAtIndex:1]; + NSInteger start = [[command argumentAtIndex:2] integerValue]; + NSInteger end = [[command argumentAtIndex:3] integerValue]; + + // TODO: implement + if (![@"UTF-8" isEqualToString : encoding]) { + NSLog(@"Only UTF-8 encodings are currently supported by readAsText"); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ENCODING_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; } - // NSString* encoding = [command.arguments objectAtIndex:2]; // not currently used - CDVPluginResult* result = nil; - - NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - - if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { - // can't read assets-library URLs as text - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; - } else if (!file) { - // invalid path entry - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; - } else { - if (start > 0) { - [file seekToFileOffset:start]; - } - - NSData* readData; - if (end < 0) { - readData = [file readDataToEndOfFile]; - } else { - readData = [file readDataOfLength:(end - start)]; + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* str = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSUTF8StringEncoding freeWhenDone:NO]; + // Check that UTF8 conversion did not fail. + if (str != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:str]; + result.associatedObject = data; + } else { + errorCode = ENCODING_ERR; + } } - - [file closeFile]; - NSString* pNStrBuff = nil; - if (readData) { - pNStrBuff = [[NSString alloc] initWithBytes:[readData bytes] length:[readData length] encoding:NSUTF8StringEncoding]; - } else { - // return empty string if no data - pNStrBuff = @""; + if (result == nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; } - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:pNStrBuff]; - } - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; } /* Read content of text file and return as base64 encoded data url. * IN: * NSArray* arguments * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end * * Determines the mime type from the file extension, returns ENCODING_ERR if mimetype can not be determined. */ - (void)readAsDataURL:(CDVInvokedUrlCommand*)command { - // arguments - NSString* argPath = [command.arguments objectAtIndex:0]; - NSInteger start = 0; - NSInteger end = -1; - - if ([command.arguments count] >= 2) { - start = [[command.arguments objectAtIndex:1] integerValue]; - } - if ([command.arguments count] >= 3) { - end = [[command.arguments objectAtIndex:2] integerValue]; - } + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + // TODO: Would be faster to base64 encode directly to the final string. + NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } - CDVFileError errCode = ABORT_ERR; - __block CDVPluginResult* result = nil; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} - if (!argPath) { - errCode = SYNTAX_ERR; - } else if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { - // In this case, we need to use an asynchronous method to retrieve the file. - // Because of this, we can't just assign to `result` and send it at the end of the method. - // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { - if (asset) { - // We have the asset! Get the data and send it off. - ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); - NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; - NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; - NSString* mimeType = [self getMimeTypeFromPath:[assetRepresentation filename]]; - NSString* dataString = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:dataString]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - } else { - // We couldn't find the asset. Send the appropriate error. - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - } - }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { - // Retrieving the asset failed for some reason. Send the appropriate error. - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; - }; +/* Read content of text file and return as an arraybuffer + * IN: + * NSArray* arguments + * 0 - NSString* fullPath + * 1 - NSString* start + * 2 - NSString* end + */ - ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; - [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; - return; - } else { - NSString* mimeType = [self getMimeTypeFromPath:argPath]; - if (!mimeType) { - // can't return as data URL if can't figure out the mimeType - errCode = ENCODING_ERR; +- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data]; } else { - NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - if (start > 0) { - [file seekToFileOffset:start]; - } + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; + } - NSData* readData; - if (end < 0) { - readData = [file readDataToEndOfFile]; - } else { - readData = [file readDataOfLength:(end - start)]; - } + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} - [file closeFile]; - if (readData) { - NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [readData base64EncodedString]]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:output]; - } else { - errCode = NOT_FOUND_ERR; - } +- (void)readAsBinaryString:(CDVInvokedUrlCommand*)command +{ + NSString* path = [command argumentAtIndex:0]; + NSInteger start = [[command argumentAtIndex:1] integerValue]; + NSInteger end = [[command argumentAtIndex:2] integerValue]; + + [self readFileWithPath:path start:start end:end callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) { + CDVPluginResult* result = nil; + if (data != nil) { + NSString* payload = [[NSString alloc] initWithBytesNoCopy:(void*)[data bytes] length:[data length] encoding:NSASCIIStringEncoding freeWhenDone:NO]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload]; + result.associatedObject = data; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode]; } - } - if (!result) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode]; - } - // NSLog(@"readAsDataURL return: %@", jsString); - [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; } /* helper function to get the mimeType from the file extension @@ -1219,7 +1251,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType); if (!mimeType) { // special case for m4a - if ([(__bridge NSString*) typeId rangeOfString:@"m4a-audio"].location != NSNotFound) { + if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) { mimeType = @"audio/mp4"; } else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) { mimeType = @"audio/wav"; @@ -1271,7 +1303,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; * IN: * NSArray* arguments * 0 - NSString* file path to write to - * 1 - NSString* data to write + * 1 - NSString* or NSData* data to write * 2 - NSNumber* position to begin writing */ - (void)write:(CDVInvokedUrlCommand*)command @@ -1281,7 +1313,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; // arguments NSString* argPath = [arguments objectAtIndex:0]; - NSString* argData = [arguments objectAtIndex:1]; + id argData = [arguments objectAtIndex:1]; unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue]; // text can't be written into assets-library files @@ -1295,15 +1327,22 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; [self truncateFile:fullPath atPosition:pos]; - [self writeToFile:fullPath withData:argData append:YES callback:callbackId]; + if ([argData isKindOfClass:[NSString class]]) { + [self writeToFile:fullPath withString:argData encoding:NSUTF8StringEncoding append:YES callback:callbackId]; + } else if ([argData isKindOfClass:[NSData class]]) { + [self writeToFile:fullPath withData:argData append:YES callback:callbackId]; + } else { + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Invalid parameter type"]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; + } + } -- (void)writeToFile:(NSString*)filePath withData:(NSString*)data append:(BOOL)shouldAppend callback:(NSString*)callbackId +- (void)writeToFile:(NSString*)filePath withData:(NSData*)encData append:(BOOL)shouldAppend callback:(NSString*)callbackId { CDVPluginResult* result = nil; CDVFileError errCode = INVALID_MODIFICATION_ERR; int bytesWritten = 0; - NSData* encData = [data dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; if (filePath) { NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend]; @@ -1333,6 +1372,11 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; [self.commandDelegate sendPluginResult:result callbackId:callbackId]; } +- (void)writeToFile:(NSString*)filePath withString:(NSString*)stringData encoding:(NSStringEncoding)encoding append:(BOOL)shouldAppend callback:(NSString*)callbackId +{ + [self writeToFile:filePath withData:[stringData dataUsingEncoding:encoding allowLossyConversion:YES] append:shouldAppend callback:callbackId]; +} + - (void)testFileExists:(CDVInvokedUrlCommand*)command { // arguments @@ -1343,7 +1387,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; NSString* appFile = argPath; // [ self getFullPath: argPath]; BOOL bExists = [fMgr fileExistsAtPath:appFile]; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1:0)]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(bExists ? 1 : 0)]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @@ -1359,7 +1403,7 @@ NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; BOOL bIsDir = NO; BOOL bExists = [fMgr fileExistsAtPath:appFile isDirectory:&bIsDir]; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1:0)]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:((bExists && bIsDir) ? 1 : 0)]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.h b/iPhone/CordovaLib/Classes/CDVFileTransfer.h index f96bb7d..35e3fdd 100755 --- a/iPhone/CordovaLib/Classes/CDVFileTransfer.h +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.h @@ -48,15 +48,21 @@ extern NSString* const kOptionsKeyCookie; - (NSMutableDictionary*)createFileTransferError:(int)code AndSource:(NSString*)source AndTarget:(NSString*)target; - (NSMutableDictionary*)createFileTransferError:(int)code - AndSource :(NSString*)source - AndTarget :(NSString*)target - AndHttpStatus :(int)httpStatus - AndBody :(NSString*)body; + AndSource:(NSString*)source + AndTarget:(NSString*)target + AndHttpStatus:(int)httpStatus + AndBody:(NSString*)body; @property (readonly) NSMutableDictionary* activeTransfers; +@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskID; @end +@class CDVFileTransferEntityLengthRequest; + @interface CDVFileTransferDelegate : NSObject {} +- (void)updateBytesExpected:(NSInteger)newBytesExpected; +- (void)cancelTransfer:(NSURLConnection*)connection; + @property (strong) NSMutableData* responseData; // atomic @property (nonatomic, strong) CDVFileTransfer* command; @property (nonatomic, assign) CDVFileTransferDirection direction; @@ -70,5 +76,7 @@ extern NSString* const kOptionsKeyCookie; @property (nonatomic, assign) NSInteger bytesTransfered; @property (nonatomic, assign) NSInteger bytesExpected; @property (nonatomic, assign) BOOL trustAllHosts; +@property (strong) NSFileHandle* targetFileHandle; +@property (nonatomic, strong) CDVFileTransferEntityLengthRequest* entityLengthRequest; @end; diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.m b/iPhone/CordovaLib/Classes/CDVFileTransfer.m index 4ccdce6..0f6b174 100755 --- a/iPhone/CordovaLib/Classes/CDVFileTransfer.m +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.m @@ -52,8 +52,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) while (totalBytesWritten < bytesToWrite) { CFIndex result = CFWriteStreamWrite(stream, - bytes + totalBytesWritten, - bytesToWrite - totalBytesWritten); + bytes + totalBytesWritten, + bytesToWrite - totalBytesWritten); if (result < 0) { CFStreamError error = CFWriteStreamGetError(stream); NSLog(@"WriteStreamError domain: %ld error: %ld", error.domain, error.error); @@ -126,17 +126,19 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) // arguments order from js: [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode] // however, params is a JavaScript object and during marshalling is put into the options dict, // thus debug and chunkedMode are the 6th and 7th arguments - NSArray* arguments = command.arguments; - NSString* target = (NSString*)[arguments objectAtIndex:0]; - NSString* server = (NSString*)[arguments objectAtIndex:1]; - NSString* fileKey = [arguments objectAtIndex:2 withDefault:@"file"]; - NSString* fileName = [arguments objectAtIndex:3 withDefault:@"no-filename"]; - NSString* mimeType = [arguments objectAtIndex:4 withDefault:nil]; - NSDictionary* options = [arguments objectAtIndex:5 withDefault:nil]; + NSString* target = [command argumentAtIndex:0]; + NSString* server = [command argumentAtIndex:1]; + NSString* fileKey = [command argumentAtIndex:2 withDefault:@"file"]; + NSString* fileName = [command argumentAtIndex:3 withDefault:@"no-filename"]; + NSString* mimeType = [command argumentAtIndex:4 withDefault:nil]; + NSDictionary* options = [command argumentAtIndex:5 withDefault:nil]; // BOOL trustAllHosts = [[arguments objectAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs - BOOL chunkedMode = [[arguments objectAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; - NSDictionary* headers = [arguments objectAtIndex:8 withDefault:nil]; - + BOOL chunkedMode = [[command argumentAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; + NSDictionary* headers = [command argumentAtIndex:8 withDefault:nil]; + // Allow alternative http method, default to POST. JS side checks + // for allowed methods, currently PUT or POST (forces POST for + // unrecognised values) + NSString* httpMethod = [command argumentAtIndex:10 withDefault:@"POST"]; CDVPluginResult* result = nil; CDVFileTransferError errorCode = 0; @@ -158,7 +160,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) } NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; - [req setHTTPMethod:@"POST"]; + + [req setHTTPMethod:httpMethod]; // Magic value to set a cookie if ([options objectForKey:kOptionsKeyCookie]) { @@ -190,7 +193,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) [postBodyBeforeFile appendData:formBoundaryData]; [postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; [postBodyBeforeFile appendData:[val dataUsingEncoding:NSUTF8StringEncoding]]; - [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBodyBeforeFile appendData:[@"\r\n" dataUsingEncoding : NSUTF8StringEncoding]]; } [postBodyBeforeFile appendData:formBoundaryData]; @@ -212,23 +215,29 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) CFStreamCreateBoundPair(NULL, &readStream, &writeStream, kStreamBufferSize); [req setHTTPBodyStream:CFBridgingRelease(readStream)]; + self.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID]; + self.backgroundTaskID = UIBackgroundTaskInvalid; + NSLog(@"Background task to upload media finished."); + }]; + [self.commandDelegate runInBackground:^{ - if (CFWriteStreamOpen (writeStream)) { - NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile}; - int numChunks = sizeof (chunks) / sizeof (chunks[0]); - - for (int i = 0; i < numChunks; ++i) { - CFIndex result = WriteDataToStream (chunks[i], writeStream); - if (result <= 0) { - break; - } + if (CFWriteStreamOpen(writeStream)) { + NSData* chunks[] = {postBodyBeforeFile, fileData, postBodyAfterFile}; + int numChunks = sizeof(chunks) / sizeof(chunks[0]); + + for (int i = 0; i < numChunks; ++i) { + CFIndex result = WriteDataToStream(chunks[i], writeStream); + if (result <= 0) { + break; } - } else { - NSLog (@"FileTransfer: Failed to open writeStream"); } - CFWriteStreamClose (writeStream); - CFRelease (writeStream); - }]; + } else { + NSLog(@"FileTransfer: Failed to open writeStream"); + } + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + }]; } else { [postBodyBeforeFile appendData:fileData]; [postBodyBeforeFile appendData:postBodyAfterFile]; @@ -265,11 +274,11 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) // return unsupported result for assets-library URLs if ([target hasPrefix:kCDVAssetsLibraryPrefix]) { // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and send it off. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* fileData = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [self uploadData:fileData command:command]; @@ -279,7 +288,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send the appropriate error. CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; @@ -291,12 +300,18 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) } else { // Extract the path part out of a file: URL. NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path]; + if (filePath == nil) { + // We couldn't find the asset. Send the appropriate error. + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } // Memory map the file so that it can be read efficiently even if it is large. NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; if (err != nil) { - NSLog (@"Error opening file %@: %@", target, err); + NSLog(@"Error opening file %@: %@", target, err); } [self uploadData:fileData command:command]; } @@ -333,13 +348,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) CDVFileTransferDelegate* delegate = [activeTransfers objectForKey:objectId]; if (delegate != nil) { - [delegate.connection cancel]; - [activeTransfers removeObjectForKey:objectId]; - - // delete uncomplete file - NSFileManager* fileMgr = [NSFileManager defaultManager]; - [fileMgr removeItemAtPath:delegate.target error:nil]; - + [delegate cancelTransfer:delegate.connection]; CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]]; [self.commandDelegate sendPluginResult:result callbackId:delegate.callbackId]; } @@ -352,6 +361,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSString* filePath = [command.arguments objectAtIndex:1]; BOOL trustAllHosts = [[command.arguments objectAtIndex:2 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs NSString* objectId = [command.arguments objectAtIndex:3]; + NSDictionary* headers = [command.arguments objectAtIndex:4 withDefault:nil]; // return unsupported result for assets-library URLs if ([filePath hasPrefix:kCDVAssetsLibraryPrefix]) { @@ -388,7 +398,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) } NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; - [self applyRequestHeaders:nil toRequest:req]; + [self applyRequestHeaders:headers toRequest:req]; CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init]; delegate.command = self; @@ -413,8 +423,12 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:3]; [result setObject:[NSNumber numberWithInt:code] forKey:@"code"]; - [result setObject:source forKey:@"source"]; - [result setObject:target forKey:@"target"]; + if (source != nil) { + [result setObject:source forKey:@"source"]; + } + if (target != nil) { + [result setObject:target forKey:@"target"]; + } NSLog(@"FileTransferError %@", result); return result; @@ -429,10 +443,16 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:5]; [result setObject:[NSNumber numberWithInt:code] forKey:@"code"]; - [result setObject:source forKey:@"source"]; - [result setObject:target forKey:@"target"]; + if (source != nil) { + [result setObject:source forKey:@"source"]; + } + if (target != nil) { + [result setObject:target forKey:@"target"]; + } [result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"]; - [result setObject:body forKey:@"body"]; + if (body != nil) { + [result setObject:body forKey:@"body"]; + } NSLog(@"FileTransferError %@", result); return result; @@ -449,19 +469,56 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) @end +@interface CDVFileTransferEntityLengthRequest : NSObject { + NSURLConnection* _connection; + CDVFileTransferDelegate* __weak _originalDelegate; +} + +- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate; + +@end; + +@implementation CDVFileTransferEntityLengthRequest; + +- (CDVFileTransferEntityLengthRequest*)initWithOriginalRequest:(NSURLRequest*)originalRequest andDelegate:(CDVFileTransferDelegate*)originalDelegate +{ + if (self) { + DLog(@"Requesting entity length for GZIPped content..."); + + NSMutableURLRequest* req = [originalRequest mutableCopy]; + [req setHTTPMethod:@"HEAD"]; + [req setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"]; + + _originalDelegate = originalDelegate; + _connection = [NSURLConnection connectionWithRequest:req delegate:self]; + } + return self; +} + +- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response +{ + DLog(@"HEAD request returned; content-length is %lld", [response expectedContentLength]); + [_originalDelegate updateBytesExpected:[response expectedContentLength]]; +} + +- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data +{} + +- (void)connectionDidFinishLoading:(NSURLConnection*)connection +{} + +@end + @implementation CDVFileTransferDelegate -@synthesize callbackId, connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId; +@synthesize callbackId, connection = _connection, source, target, responseData, command, bytesTransfered, bytesExpected, direction, responseCode, objectId, targetFileHandle; - (void)connectionDidFinishLoading:(NSURLConnection*)connection { NSString* uploadResponse = nil; NSString* downloadResponse = nil; - BOOL downloadWriteOK = NO; NSMutableDictionary* uploadResult; CDVPluginResult* result = nil; - NSError* __autoreleasing error = nil; - NSString* parentPath; BOOL bDirRequest = NO; CDVFile* file; @@ -484,40 +541,15 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) } } if (self.direction == CDV_TRANSFER_DOWNLOAD) { - DLog(@"Write file %@", target); - // error=[[NSError alloc]init]; + if (self.targetFileHandle) { + [self.targetFileHandle closeFile]; + self.targetFileHandle = nil; + DLog(@"File Transfer Download success"); - if ((self.responseCode >= 200) && (self.responseCode < 300)) { - @try { - parentPath = [self.target stringByDeletingLastPathComponent]; - - // check if the path exists => create directories if needed - if (![[NSFileManager defaultManager] fileExistsAtPath:parentPath]) { - [[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - - downloadWriteOK = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error]; - - if (downloadWriteOK == NO) { - // send our results back - downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; - } else { - DLog(@"File Transfer Download success"); - - file = [[CDVFile alloc] init]; - - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]]; - } - } - @catch(id exception) { - // jump back to main thread - downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; - } + file = [[CDVFile alloc] init]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[file getDirectoryEntry:target isDirectory:bDirRequest]]; } else { downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; } } @@ -526,11 +558,41 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) // remove connection for activeTransfers [command.activeTransfers removeObjectForKey:objectId]; + + // remove background id task in case our upload was done in the background + [[UIApplication sharedApplication] endBackgroundTask:self.command.backgroundTaskID]; + self.command.backgroundTaskID = UIBackgroundTaskInvalid; +} + +- (void)removeTargetFile +{ + NSFileManager* fileMgr = [NSFileManager defaultManager]; + + [fileMgr removeItemAtPath:self.target error:nil]; +} + +- (void)cancelTransfer:(NSURLConnection*)connection +{ + [connection cancel]; + [self.command.activeTransfers removeObjectForKey:self.objectId]; + [self removeTargetFile]; +} + +- (void)cancelTransferWithError:(NSURLConnection*)connection errorMessage:(NSString*)errorMessage +{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[self.command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:self.source AndTarget:self.target AndHttpStatus:self.responseCode AndBody:errorMessage]]; + + NSLog(@"File Transfer Error: %@", errorMessage); + [self cancelTransfer:connection]; + [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; } - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response { + NSError* __autoreleasing error = nil; + self.mimeType = [response MIMEType]; + self.targetFileHandle = nil; // required for iOS 4.3, for some reason; response is // a plain NSURLResponse, not the HTTP subclass @@ -539,6 +601,11 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) self.responseCode = [httpResponse statusCode]; self.bytesExpected = [response expectedContentLength]; + if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode == 200) && (self.bytesExpected == NSURLResponseUnknownLength)) { + // Kick off HEAD request to server to get real length + // bytesExpected will be updated when that response is returned + self.entityLengthRequest = [[CDVFileTransferEntityLengthRequest alloc] initWithOriginalRequest:connection.currentRequest andDelegate:self]; + } } else if ([response.URL isFileURL]) { NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[response.URL path] error:nil]; self.responseCode = 200; @@ -547,6 +614,31 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) self.responseCode = 200; self.bytesExpected = NSURLResponseUnknownLength; } + if ((self.direction == CDV_TRANSFER_DOWNLOAD) && (self.responseCode >= 200) && (self.responseCode < 300)) { + // Download response is okay; begin streaming output to file + NSString* parentPath = [self.target stringByDeletingLastPathComponent]; + + // create parent directories if needed + if ([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:&error] == NO) { + if (error) { + [self cancelTransferWithError:connection errorMessage:[NSString stringWithFormat:@"Could not create path to save downloaded file: %@", [error localizedDescription]]]; + } else { + [self cancelTransferWithError:connection errorMessage:@"Could not create path to save downloaded file"]; + } + return; + } + // create target file + if ([[NSFileManager defaultManager] createFileAtPath:self.target contents:nil attributes:nil] == NO) { + [self cancelTransferWithError:connection errorMessage:@"Could not create target file"]; + return; + } + // open target file for writing + self.targetFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.target]; + if (self.targetFileHandle == nil) { + [self cancelTransferWithError:connection errorMessage:@"Could not open target file for writing"]; + } + DLog(@"Streaming to file %@", target); + } } - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error @@ -556,18 +648,37 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSLog(@"File Transfer Error: %@", [error localizedDescription]); - // remove connection for activeTransfers - [command.activeTransfers removeObjectForKey:objectId]; + [self cancelTransfer:connection]; [self.command.commandDelegate sendPluginResult:result callbackId:callbackId]; } - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { self.bytesTransfered += data.length; - [self.responseData appendData:data]; + if (self.targetFileHandle) { + [self.targetFileHandle writeData:data]; + } else { + [self.responseData appendData:data]; + } + [self updateProgress]; +} +- (void)updateBytesExpected:(NSInteger)newBytesExpected +{ + DLog(@"Updating bytesExpected to %d", newBytesExpected); + self.bytesExpected = newBytesExpected; + [self updateProgress]; +} + +- (void)updateProgress +{ if (self.direction == CDV_TRANSFER_DOWNLOAD) { BOOL lengthComputable = (self.bytesExpected != NSURLResponseUnknownLength); + // If the response is GZipped, and we have an outstanding HEAD request to get + // the length, then hold off on sending progress events. + if (!lengthComputable && (self.entityLengthRequest != nil)) { + return; + } NSMutableDictionary* downloadProgress = [NSMutableDictionary dictionaryWithCapacity:3]; [downloadProgress setObject:[NSNumber numberWithBool:lengthComputable] forKey:@"lengthComputable"]; [downloadProgress setObject:[NSNumber numberWithInt:self.bytesTransfered] forKey:@"loaded"]; @@ -611,6 +722,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { if ((self = [super init])) { self.responseData = [NSMutableData data]; + self.targetFileHandle = nil; } return self; } diff --git a/iPhone/CordovaLib/Classes/CDVGlobalization.m b/iPhone/CordovaLib/Classes/CDVGlobalization.m index 4d960cd..9eb9721 100755 --- a/iPhone/CordovaLib/Classes/CDVGlobalization.m +++ b/iPhone/CordovaLib/Classes/CDVGlobalization.m @@ -138,14 +138,14 @@ // create the formatter using the user's current default locale and formats for dates and times CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // if we have a valid date object then call the formatter if (date) { dateString = (__bridge_transfer NSString*)CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, - formatter, - (__bridge CFDateRef)date); + formatter, + (__bridge CFDateRef)date); } // if the date was converted to a string successfully then return the result @@ -227,18 +227,18 @@ // get the user's default settings for date and time formats CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // set the parsing to be more lenient CFDateFormatterSetProperty(formatter, kCFDateFormatterIsLenient, kCFBooleanTrue); // parse tha date and time string CFDateRef date = CFDateFormatterCreateDateFromString(kCFAllocatorDefault, - formatter, - (__bridge CFStringRef)dateString, - NULL); + formatter, + (__bridge CFStringRef)dateString, + NULL); // if we were able to parse the date then get the date and time components if (date != NULL) { @@ -336,9 +336,9 @@ // get the user's default settings for date and time formats CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - dateStyle, - timeStyle); + currentLocale, + dateStyle, + timeStyle); // get the date pattern to apply when formatting and parsing CFStringRef datePattern = CFDateFormatterGetFormat(formatter); @@ -415,9 +415,9 @@ } CFDateFormatterRef formatter = CFDateFormatterCreate(kCFAllocatorDefault, - currentLocale, - kCFDateFormatterFullStyle, - kCFDateFormatterFullStyle); + currentLocale, + kCFDateFormatterFullStyle, + kCFDateFormatterFullStyle); if ((selector == CDV_SELECTOR_MONTHS) && (style == CDV_FORMAT_LONG)) { dataStyle = kCFDateFormatterMonthSymbols; @@ -545,13 +545,13 @@ } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); // get the localized string based upon the locale and user preferences NSString* numberString = (__bridge_transfer NSString*)CFNumberFormatterCreateStringWithNumber(kCFAllocatorDefault, - formatter, - (__bridge CFNumberRef)number); + formatter, + (__bridge CFNumberRef)number); if (numberString) { NSDictionary* dictionary = [NSDictionary dictionaryWithObject:numberString forKey:@"value"]; @@ -612,8 +612,8 @@ } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); // we need to make this lenient so as to avoid problems with parsing currencies that have non-breaking space characters if (style == kCFNumberFormatterCurrencyStyle) { @@ -622,10 +622,10 @@ // parse againist the largest type to avoid data loss Boolean rc = CFNumberFormatterGetValueFromString(formatter, - (__bridge CFStringRef)numberString, - NULL, - kCFNumberDoubleType, - &doubleValue); + (__bridge CFStringRef)numberString, + NULL, + kCFNumberDoubleType, + &doubleValue); if (rc) { NSDictionary* dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithDouble:doubleValue] forKey:@"value"]; @@ -681,8 +681,8 @@ } CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - style); + currentLocale, + style); NSString* numberPattern = (__bridge NSString*)CFNumberFormatterGetFormat(formatter); @@ -749,8 +749,8 @@ // now set the currency code in the formatter if (rc) { CFNumberFormatterRef formatter = CFNumberFormatterCreate(kCFAllocatorDefault, - currentLocale, - kCFNumberFormatterCurrencyStyle); + currentLocale, + kCFNumberFormatterCurrencyStyle); CFNumberFormatterSetProperty(formatter, kCFNumberFormatterCurrencyCode, (__bridge CFStringRef)currencyCode); CFNumberFormatterSetProperty(formatter, kCFNumberFormatterInternationalCurrencySymbol, (__bridge CFStringRef)currencyCode); diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h index 9ff460a..248274a 100755 --- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h +++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h @@ -20,33 +20,30 @@ #import "CDVPlugin.h" #import "CDVInvokedUrlCommand.h" #import "CDVScreenOrientationDelegate.h" +#import "CDVWebViewDelegate.h" @class CDVInAppBrowserViewController; -@protocol CDVInAppBrowserNavigationDelegate <NSObject> - -- (void)browserLoadStart:(NSURL*)url; -- (void)browserLoadStop:(NSURL*)url; -- (void)browserExit; - -@end - -@interface CDVInAppBrowser : CDVPlugin <CDVInAppBrowserNavigationDelegate> +@interface CDVInAppBrowser : CDVPlugin { + BOOL _injectedIframeBridge; +} @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController; @property (nonatomic, copy) NSString* callbackId; - (void)open:(CDVInvokedUrlCommand*)command; - (void)close:(CDVInvokedUrlCommand*)command; +- (void)injectScriptCode:(CDVInvokedUrlCommand*)command; +- (void)show:(CDVInvokedUrlCommand*)command; @end @interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{ @private - NSURL* _requestedURL; NSString* _userAgent; NSString* _prevUserAgent; NSInteger _userAgentLockToken; + CDVWebViewDelegate* _webViewDelegate; } @property (nonatomic, strong) IBOutlet UIWebView* webView; @@ -58,11 +55,14 @@ @property (nonatomic, strong) IBOutlet UIToolbar* toolbar; @property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate; -@property (nonatomic, weak) id <CDVInAppBrowserNavigationDelegate> navigationDelegate; +@property (nonatomic, weak) CDVInAppBrowser* navigationDelegate; +@property (nonatomic) NSURL* currentURL; - (void)close; - (void)navigateTo:(NSURL*)url; - (void)showLocationBar:(BOOL)show; +- (void)showToolBar:(BOOL)show; +- (void)setCloseButtonTitle:(NSString*)title; - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent; @@ -71,6 +71,9 @@ @interface CDVInAppBrowserOptions : NSObject {} @property (nonatomic, assign) BOOL location; +@property (nonatomic, assign) BOOL toolbar; +@property (nonatomic, copy) NSString* closebuttoncaption; + @property (nonatomic, copy) NSString* presentationstyle; @property (nonatomic, copy) NSString* transitionstyle; @@ -79,6 +82,7 @@ @property (nonatomic, assign) BOOL allowinlinemediaplayback; @property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction; @property (nonatomic, assign) BOOL suppressesincrementalrendering; +@property (nonatomic, assign) BOOL hidden; + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m index 14671a5..b832e23 100755 --- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m +++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m @@ -20,6 +20,7 @@ #import "CDVInAppBrowser.h" #import "CDVPluginResult.h" #import "CDVUserAgentUtil.h" +#import "CDVJSON.h" #define kInAppBrowserTargetSelf @"_self" #define kInAppBrowserTargetSystem @"_system" @@ -100,15 +101,19 @@ } } + CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options]; [self.inAppBrowserViewController showLocationBar:browserOptions.location]; - + [self.inAppBrowserViewController showToolBar:browserOptions.toolbar]; + if (browserOptions.closebuttoncaption != nil) { + [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption]; + } // Set Presentation Style UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default if (browserOptions.presentationstyle != nil) { - if ([browserOptions.presentationstyle isEqualToString:@"pagesheet"]) { + if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) { presentationStyle = UIModalPresentationPageSheet; - } else if ([browserOptions.presentationstyle isEqualToString:@"formsheet"]) { + } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) { presentationStyle = UIModalPresentationFormSheet; } } @@ -117,14 +122,15 @@ // Set Transition Style UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default if (browserOptions.transitionstyle != nil) { - if ([browserOptions.transitionstyle isEqualToString:@"fliphorizontal"]) { + if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) { transitionStyle = UIModalTransitionStyleFlipHorizontal; - } else if ([browserOptions.transitionstyle isEqualToString:@"crossdissolve"]) { + } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) { transitionStyle = UIModalTransitionStyleCrossDissolve; } } self.inAppBrowserViewController.modalTransitionStyle = transitionStyle; + // UIWebView options self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale; self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction; @@ -133,13 +139,20 @@ self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction; self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering; } - - if (self.viewController.modalViewController != self.inAppBrowserViewController) { + + if (! browserOptions.hidden) { + if (self.viewController.modalViewController != self.inAppBrowserViewController) { [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; + } } [self.inAppBrowserViewController navigateTo:url]; } +- (void)show:(CDVInvokedUrlCommand*)command +{ + [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES]; +} + - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options { if ([self.commandDelegate URLIsWhitelisted:url]) { @@ -159,24 +172,162 @@ } } -#pragma mark CDVInAppBrowserNavigationDelegate +// This is a helper method for the inject{Script|Style}{Code|File} API calls, which +// provides a consistent method for injecting JavaScript code into the document. +// +// If a wrapper string is supplied, then the source string will be JSON-encoded (adding +// quotes) and wrapped using string formatting. (The wrapper string should have a single +// '%@' marker). +// +// If no wrapper is supplied, then the source string is executed directly. -- (void)browserLoadStart:(NSURL*)url +- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper { - if (self.callbackId != nil) { + if (!_injectedIframeBridge) { + _injectedIframeBridge = YES; + // Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e = _cdvIframeBridge = d.createElement('iframe');e.style.display='none';d.body.appendChild(e);})(document)"]; + } + + if (jsWrapper != nil) { + NSString* sourceArrayString = [@[source] JSONString]; + if (sourceArrayString) { + NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)]; + NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString]; + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject]; + } + } else { + [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source]; + } +} + +- (void)injectScriptCode:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper = nil; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+window.escape(JSON.stringify([eval(%%@)]));", command.callbackId]; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectScriptFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleCode:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +- (void)injectStyleFile:(CDVInvokedUrlCommand*)command +{ + NSString* jsWrapper; + + if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) { + jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId]; + } else { + jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)"; + } + [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper]; +} + +/** + * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging + * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no + * other code execution is possible. + * + * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form: + * + * gap-iab://<callbackId>/<arguments> + * + * where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789") + * + * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded + * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION + * is returned if the JSON is invalid. + */ +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSURL* url = request.URL; + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + + // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute, + // and the path, if present, should be a JSON-encoded value to pass to the callback. + if ([[url scheme] isEqualToString:@"gap-iab"]) { + NSString* scriptCallbackId = [url host]; + CDVPluginResult* pluginResult = nil; + + if ([scriptCallbackId hasPrefix:@"InAppBrowser"]) { + NSString* scriptResult = [url path]; + NSError* __autoreleasing error = nil; + + // The message should be a JSON-encoded array of the result of the script which executed. + if ((scriptResult != nil) && ([scriptResult length] > 1)) { + scriptResult = [scriptResult substringFromIndex:1]; + NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION]; + } + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId]; + return NO; + } + } else if ((self.callbackId != nil) && isTopLevelNavigation) { + // Send a loadstart event for each top-level navigation (includes redirects). CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"loadstart", @"url":[url absoluteString]}]; + messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; } + + return YES; } -- (void)browserLoadStop:(NSURL*)url +- (void)webViewDidStartLoad:(UIWebView*)theWebView +{ + _injectedIframeBridge = NO; +} + +- (void)webViewDidFinishLoad:(UIWebView*)theWebView { if (self.callbackId != nil) { + // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected). + NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"loadstop", @"url":[url absoluteString]}]; + messageAsDictionary:@{@"type":@"loadstop", @"url":url}]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + } +} + +- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error +{ + if (self.callbackId != nil) { + NSString* url = [self.inAppBrowserViewController.currentURL absoluteString]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR + messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInt:error.code], @"message": error.localizedDescription}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; @@ -187,7 +338,7 @@ { if (self.callbackId != nil) { CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK - messageAsDictionary:@ {@"type":@"exit"}]; + messageAsDictionary:@{@"type":@"exit"}]; [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; @@ -203,12 +354,15 @@ @implementation CDVInAppBrowserViewController +@synthesize currentURL; + - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent { self = [super init]; if (self != nil) { _userAgent = userAgent; _prevUserAgent = prevUserAgent; + _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self]; [self createViews]; } @@ -229,7 +383,7 @@ [self.view addSubview:self.webView]; [self.view sendSubviewToBack:self.webView]; - self.webView.delegate = self; + self.webView.delegate = _webViewDelegate; self.webView.backgroundColor = [UIColor whiteColor]; self.webView.clearsContextBeforeDrawing = YES; @@ -259,9 +413,6 @@ self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)]; self.closeButton.enabled = YES; - self.closeButton.imageInsets = UIEdgeInsetsZero; - self.closeButton.style = UIBarButtonItemStylePlain; - self.closeButton.width = 32.000; UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; @@ -325,32 +476,132 @@ [self.view addSubview:self.spinner]; } +- (void)setCloseButtonTitle:(NSString*)title +{ + // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically + // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one) + self.closeButton = nil; + self.closeButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)]; + self.closeButton.enabled = YES; + self.closeButton.tintColor = [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1]; + + NSMutableArray* items = [self.toolbar.items mutableCopy]; + [items replaceObjectAtIndex:0 withObject:self.closeButton]; + [self.toolbar setItems:items]; +} + - (void)showLocationBar:(BOOL)show { - CGRect addressLabelFrame = self.addressLabel.frame; - BOOL locationBarVisible = (addressLabelFrame.size.height > 0); + CGRect locationbarFrame = self.addressLabel.frame; + + BOOL toolbarVisible = !self.toolbar.hidden; + + // prevent double show/hide + if (show == !(self.addressLabel.hidden)) { + return; + } + + if (show) { + self.addressLabel.hidden = NO; + + if (toolbarVisible) { + // toolBar at the bottom, leave as is + // put locationBar on top of the toolBar + + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= FOOTER_HEIGHT; + self.webView.frame = webViewBounds; + + locationbarFrame.origin.y = webViewBounds.size.height; + self.addressLabel.frame = locationbarFrame; + } else { + // no toolBar, so put locationBar at the bottom + + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= LOCATIONBAR_HEIGHT; + self.webView.frame = webViewBounds; + + locationbarFrame.origin.y = webViewBounds.size.height; + self.addressLabel.frame = locationbarFrame; + } + } else { + self.addressLabel.hidden = YES; + + if (toolbarVisible) { + // locationBar is on top of toolBar, hide locationBar + + // webView take up whole height less toolBar height + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= TOOLBAR_HEIGHT; + self.webView.frame = webViewBounds; + } else { + // no toolBar, expand webView to screen dimensions + + CGRect webViewBounds = self.view.bounds; + self.webView.frame = webViewBounds; + } + } +} + +- (void)showToolBar:(BOOL)show +{ + CGRect toolbarFrame = self.toolbar.frame; + CGRect locationbarFrame = self.addressLabel.frame; + + BOOL locationbarVisible = !self.addressLabel.hidden; // prevent double show/hide - if (locationBarVisible == show) { + if (show == !(self.toolbar.hidden)) { return; } if (show) { - CGRect webViewBounds = self.view.bounds; - webViewBounds.size.height -= FOOTER_HEIGHT; - self.webView.frame = webViewBounds; + self.toolbar.hidden = NO; + + if (locationbarVisible) { + // locationBar at the bottom, move locationBar up + // put toolBar at the bottom + + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= FOOTER_HEIGHT; + self.webView.frame = webViewBounds; + + locationbarFrame.origin.y = webViewBounds.size.height; + self.addressLabel.frame = locationbarFrame; - CGRect addressLabelFrame = self.addressLabel.frame; - addressLabelFrame.size.height = LOCATIONBAR_HEIGHT; - self.addressLabel.frame = addressLabelFrame; + toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT); + self.toolbar.frame = toolbarFrame; + } else { + // no locationBar, so put toolBar at the bottom + + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= TOOLBAR_HEIGHT; + self.webView.frame = webViewBounds; + + toolbarFrame.origin.y = webViewBounds.size.height; + self.toolbar.frame = toolbarFrame; + } } else { - CGRect webViewBounds = self.view.bounds; - webViewBounds.size.height -= TOOLBAR_HEIGHT; - self.webView.frame = webViewBounds; + self.toolbar.hidden = YES; - CGRect addressLabelFrame = self.addressLabel.frame; - addressLabelFrame.size.height = 0; - self.addressLabel.frame = addressLabelFrame; + if (locationbarVisible) { + // locationBar is on top of toolBar, hide toolBar + // put locationBar at the bottom + + // webView take up whole height less locationBar height + CGRect webViewBounds = self.view.bounds; + webViewBounds.size.height -= LOCATIONBAR_HEIGHT; + self.webView.frame = webViewBounds; + + // move locationBar down + locationbarFrame.origin.y = webViewBounds.size.height; + self.addressLabel.frame = locationbarFrame; + } else { + // no locationBar, expand webView to screen dimensions + + CGRect webViewBounds = self.view.bounds; + self.webView.frame = webViewBounds; + } } } @@ -376,6 +627,8 @@ [[self parentViewController] dismissModalViewControllerAnimated:YES]; } + self.currentURL = nil; + if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) { [self.navigationDelegate browserExit]; } @@ -385,16 +638,14 @@ { NSURLRequest* request = [NSURLRequest requestWithURL:url]; - _requestedURL = url; - if (_userAgentLockToken != 0) { [self.webView loadRequest:request]; } else { [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { - _userAgentLockToken = lockToken; - [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken]; - [self.webView loadRequest:request]; - }]; + _userAgentLockToken = lockToken; + [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken]; + [self.webView loadRequest:request]; + }]; } } @@ -420,20 +671,24 @@ [self.spinner startAnimating]; - if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStart:)]) { - NSURL* url = theWebView.request.URL; - if (url == nil) { - url = _requestedURL; - } - [self.navigationDelegate browserLoadStart:url]; + return [self.navigationDelegate webViewDidStartLoad:theWebView]; +} + +- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + + if (isTopLevelNavigation) { + self.currentURL = request.URL; } + return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; } - (void)webViewDidFinishLoad:(UIWebView*)theWebView { // update url, stop spinner, update back/forward - self.addressLabel.text = theWebView.request.URL.absoluteString; + self.addressLabel.text = [self.currentURL absoluteString]; self.backButton.enabled = theWebView.canGoBack; self.forwardButton.enabled = theWebView.canGoForward; @@ -450,15 +705,12 @@ // from it must pass through its white-list. This *does* break PDFs that // contain links to other remote PDF/websites. // More info at https://issues.apache.org/jira/browse/CB-2225 - BOOL isPDF = [@"true" isEqualToString:[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]]; + BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]]; if (isPDF) { [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken]; } - if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStop:)]) { - NSURL* url = theWebView.request.URL; - [self.navigationDelegate browserLoadStop:url]; - } + [self.navigationDelegate webViewDidFinishLoad:theWebView]; } - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error @@ -471,6 +723,8 @@ [self.spinner stopAnimating]; self.addressLabel.text = @"Load Error"; + + [self.navigationDelegate webView:theWebView didFailLoadWithError:error]; } #pragma mark CDVScreenOrientationDelegate @@ -510,12 +764,15 @@ if (self = [super init]) { // default values self.location = YES; + self.toolbar = YES; + self.closebuttoncaption = nil; self.enableviewportscale = NO; self.mediaplaybackrequiresuseraction = NO; self.allowinlinemediaplayback = NO; self.keyboarddisplayrequiresuseraction = YES; self.suppressesincrementalrendering = NO; + self.hidden = NO; } return self; @@ -534,19 +791,20 @@ if ([keyvalue count] == 2) { NSString* key = [[keyvalue objectAtIndex:0] lowercaseString]; - NSString* value = [[keyvalue objectAtIndex:1] lowercaseString]; + NSString* value = [keyvalue objectAtIndex:1]; + NSString* value_lc = [value lowercaseString]; - BOOL isBoolean = [value isEqualToString:@"yes"] || [value isEqualToString:@"no"]; + BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"]; NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setAllowsFloats:YES]; - BOOL isNumber = [numberFormatter numberFromString:value] != nil; + BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil; // set the property according to the key name if ([obj respondsToSelector:NSSelectorFromString(key)]) { if (isNumber) { - [obj setValue:[numberFormatter numberFromString:value] forKey:key]; + [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key]; } else if (isBoolean) { - [obj setValue:[NSNumber numberWithBool:[value isEqualToString:@"yes"]] forKey:key]; + [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key]; } else { [obj setValue:value forKey:key]; } diff --git a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h index 6eb0099..7be8884 100755 --- a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h +++ b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.h @@ -34,9 +34,9 @@ + (CDVInvokedUrlCommand*)commandFromJson:(NSArray*)jsonEntry; - (id)initWithArguments:(NSArray*)arguments - callbackId :(NSString*)callbackId - className :(NSString*)className - methodName :(NSString*)methodName; + callbackId:(NSString*)callbackId + className:(NSString*)className + methodName:(NSString*)methodName; - (id)initFromJson:(NSArray*)jsonEntry; diff --git a/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h new file mode 100755 index 0000000..3b43ef0 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.h @@ -0,0 +1,62 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import <Foundation/Foundation.h> + +@interface CDVJpegHeaderWriter : NSObject { + NSDictionary * SubIFDTagFormatDict; + NSDictionary * IFD0TagFormatDict; +} + +- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata + withExifBlock: (NSString*) exifstr; +- (NSString*) createExifAPP1 : (NSDictionary*) datadict; +- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb + withPlaces: (NSNumber*) width; +- (NSString*) formatNumberWithLeadingZeroes: (NSNumber*) numb + withPlaces: (NSNumber*) places; +- (NSString*) decimalToUnsignedRational: (NSNumber*) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator; +- (void) continuedFraction: (double) val + withFractionList: (NSMutableArray*) fractionlist + withHorizon: (int) horizon; +//- (void) expandContinuedFraction: (NSArray*) fractionlist; +- (void) splitDouble: (double) val + withIntComponent: (int*) rightside + withFloatRemainder: (double*) leftside; +- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator + withDenominator: (NSNumber*) denominator + asSigned: (Boolean) signedFlag; +- (NSString*) hexStringFromData : (NSData*) data; +- (NSNumber*) numericFromHexString : (NSString *) hexstring; + +/* +- (void) readExifMetaData : (NSData*) imgdata; +- (void) spliceImageData : (NSData*) imgdata withExifData: (NSDictionary*) exifdata; +- (void) locateExifMetaData : (NSData*) imgdata; +- (NSString*) createExifAPP1 : (NSDictionary*) datadict; +- (void) createExifDataString : (NSDictionary*) datadict; +- (NSString*) createDataElement : (NSString*) element + withElementData: (NSString*) data + withExternalDataBlock: (NSDictionary*) memblock; +- (NSString*) hexStringFromData : (NSData*) data; +- (NSNumber*) numericFromHexString : (NSString *) hexstring; +*/ +@end diff --git a/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m new file mode 100755 index 0000000..93cafb8 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVJpegHeaderWriter.m @@ -0,0 +1,547 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVJpegHeaderWriter.h" +#include "CDVExif.h" + +/* macros for tag info shorthand: + tagno : tag number + typecode : data type + components : number of components + appendString (TAGINF_W_APPEND only) : string to append to data + Exif date data format include an extra 0x00 to the end of the data + */ +#define TAGINF(tagno, typecode, components) [NSArray arrayWithObjects: tagno, typecode, components, nil] +#define TAGINF_W_APPEND(tagno, typecode, components, appendString) [NSArray arrayWithObjects: tagno, typecode, components, appendString, nil] + +const uint mJpegId = 0xffd8; // JPEG format marker +const uint mExifMarker = 0xffe1; // APP1 jpeg header marker +const uint mExif = 0x45786966; // ASCII 'Exif', first characters of valid exif header after size +const uint mMotorallaByteAlign = 0x4d4d; // 'MM', motorola byte align, msb first or 'sane' +const uint mIntelByteAlgin = 0x4949; // 'II', Intel byte align, lsb first or 'batshit crazy reverso world' +const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a(MM) or 0x2a00(II), tiff version number + + +@implementation CDVJpegHeaderWriter + +- (id) init { + self = [super init]; + // supported tags for exif IFD + IFD0TagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys: + // TAGINF(@"010e", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"ImageDescription", + TAGINF_W_APPEND(@"0132", [NSNumber numberWithInt:EDT_ASCII_STRING], @20, @"00"), @"DateTime", + TAGINF(@"010f", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Make", + TAGINF(@"0110", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Model", + TAGINF(@"0131", [NSNumber numberWithInt:EDT_ASCII_STRING], @0), @"Software", + TAGINF(@"011a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"XResolution", + TAGINF(@"011b", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"YResolution", + // currently supplied outside of Exif data block by UIImagePickerControllerMediaMetadata, this is set manually in CDVCamera.m + /* TAGINF(@"0112", [NSNumber numberWithInt:EDT_USHORT], @1), @"Orientation", + + // rest of the tags are supported by exif spec, but are not specified by UIImagePickerControllerMediaMedadata + // should camera hardware supply these values in future versions, or if they can be derived, ImageHeaderWriter will include them gracefully + TAGINF(@"0128", [NSNumber numberWithInt:EDT_USHORT], @1), @"ResolutionUnit", + TAGINF(@"013e", [NSNumber numberWithInt:EDT_URATIONAL], @2), @"WhitePoint", + TAGINF(@"013f", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"PrimaryChromaticities", + TAGINF(@"0211", [NSNumber numberWithInt:EDT_URATIONAL], @3), @"YCbCrCoefficients", + TAGINF(@"0213", [NSNumber numberWithInt:EDT_USHORT], @1), @"YCbCrPositioning", + TAGINF(@"0214", [NSNumber numberWithInt:EDT_URATIONAL], @6), @"ReferenceBlackWhite", + TAGINF(@"8298", [NSNumber numberWithInt:EDT_URATIONAL], @0), @"Copyright", + + // offset to exif subifd, we determine this dynamically based on the size of the main exif IFD + TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset",*/ + nil]; + + + // supported tages for exif subIFD + SubIFDTagFormatDict = [[NSDictionary alloc] initWithObjectsAndKeys: + //TAGINF(@"9000", [NSNumber numberWithInt:], @), @"ExifVersion", + //TAGINF(@"9202",[NSNumber numberWithInt:EDT_URATIONAL],@1), @"ApertureValue", + //TAGINF(@"9203",[NSNumber numberWithInt:EDT_SRATIONAL],@1), @"BrightnessValue", + TAGINF(@"a001",[NSNumber numberWithInt:EDT_USHORT],@1), @"ColorSpace", + TAGINF_W_APPEND(@"9004",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeDigitized", + TAGINF_W_APPEND(@"9003",[NSNumber numberWithInt:EDT_ASCII_STRING],@20,@"00"), @"DateTimeOriginal", + TAGINF(@"a402", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureMode", + TAGINF(@"8822", [NSNumber numberWithInt:EDT_USHORT], @1), @"ExposureProgram", + //TAGINF(@"829a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"ExposureTime", + //TAGINF(@"829d", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FNumber", + TAGINF(@"9209", [NSNumber numberWithInt:EDT_USHORT], @1), @"Flash", + // FocalLengthIn35mmFilm + TAGINF(@"a405", [NSNumber numberWithInt:EDT_USHORT], @1), @"FocalLenIn35mmFilm", + //TAGINF(@"920a", [NSNumber numberWithInt:EDT_URATIONAL], @1), @"FocalLength", + //TAGINF(@"8827", [NSNumber numberWithInt:EDT_USHORT], @2), @"ISOSpeedRatings", + TAGINF(@"9207", [NSNumber numberWithInt:EDT_USHORT],@1), @"MeteringMode", + // specific to compressed data + TAGINF(@"a002", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelXDimension", + TAGINF(@"a003", [NSNumber numberWithInt:EDT_ULONG],@1), @"PixelYDimension", + // data type undefined, but this is a DSC camera, so value is always 1, treat as ushort + TAGINF(@"a301", [NSNumber numberWithInt:EDT_USHORT],@1), @"SceneType", + TAGINF(@"a217",[NSNumber numberWithInt:EDT_USHORT],@1), @"SensingMethod", + //TAGINF(@"9201", [NSNumber numberWithInt:EDT_SRATIONAL], @1), @"ShutterSpeedValue", + // specifies location of main subject in scene (x,y,wdith,height) expressed before rotation processing + //TAGINF(@"9214", [NSNumber numberWithInt:EDT_USHORT], @4), @"SubjectArea", + TAGINF(@"a403", [NSNumber numberWithInt:EDT_USHORT], @1), @"WhiteBalance", + nil]; + return self; +} + +- (NSData*) spliceExifBlockIntoJpeg: (NSData*) jpegdata withExifBlock: (NSString*) exifstr { + + CDVJpegHeaderWriter * exifWriter = [[CDVJpegHeaderWriter alloc] init]; + + NSMutableData * exifdata = [NSMutableData dataWithCapacity: [exifstr length]/2]; + int idx; + for (idx = 0; idx+1 < [exifstr length]; idx+=2) { + NSRange range = NSMakeRange(idx, 2); + NSString* hexStr = [exifstr substringWithRange:range]; + NSScanner* scanner = [NSScanner scannerWithString:hexStr]; + unsigned int intValue; + [scanner scanHexInt:&intValue]; + [exifdata appendBytes:&intValue length:1]; + } + + NSMutableData * ddata = [NSMutableData dataWithCapacity: [jpegdata length]]; + NSMakeRange(0,4); + int loc = 0; + bool done = false; + // read the jpeg data until we encounter the app1==0xFFE1 marker + while (loc+1 < [jpegdata length]) { + NSData * blag = [jpegdata subdataWithRange: NSMakeRange(loc,2)]; + if( [[blag description] isEqualToString : @"<ffe1>"]) { + // read the APP1 block size bits + NSString * the = [exifWriter hexStringFromData:[jpegdata subdataWithRange: NSMakeRange(loc+2,2)]]; + NSNumber * app1width = [exifWriter numericFromHexString:the]; + //consume the original app1 block + [ddata appendData:exifdata]; + // advance our loc marker past app1 + loc += [app1width intValue] + 2; + done = true; + } else { + if(!done) { + [ddata appendData:blag]; + loc += 2; + } else { + break; + } + } + } + // copy the remaining data + [ddata appendData:[jpegdata subdataWithRange: NSMakeRange(loc,[jpegdata length]-loc)]]; + return ddata; +} + + + +/** + * Create the Exif data block as a hex string + * jpeg uses Application Markers (APP's) as markers for application data + * APP1 is the application marker reserved for exif data + * + * (NSDictionary*) datadict - with subdictionaries marked '{TIFF}' and '{EXIF}' as returned by imagePickerController with a valid + * didFinishPickingMediaWithInfo data dict, under key @"UIImagePickerControllerMediaMetadata" + * + * the following constructs a hex string to Exif specifications, and is therefore brittle + * altering the order of arguments to the string constructors, modifying field sizes or formats, + * and any other minor change will likely prevent the exif data from being read + */ +- (NSString*) createExifAPP1 : (NSDictionary*) datadict { + NSMutableString * app1; // holds finalized product + NSString * exifIFD; // exif information file directory + NSString * subExifIFD; // subexif information file directory + + // FFE1 is the hex APP1 marker code, and will allow client apps to read the data + NSString * app1marker = @"ffe1"; + // SSSS size, to be determined + // EXIF ascii characters followed by 2bytes of zeros + NSString * exifmarker = @"457869660000"; + // Tiff header: 4d4d is motorolla byte align (big endian), 002a is hex for 42 + NSString * tiffheader = @"4d4d002a"; + //first IFD offset from the Tiff header to IFD0. Since we are writing it, we know it's address 0x08 + NSString * ifd0offset = @"00000008"; + // current offset to next data area + int currentDataOffset = 0; + + //data labeled as TIFF in UIImagePickerControllerMediaMetaData is part of the EXIF IFD0 portion of APP1 + exifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{TIFF}"] withFormatDict: IFD0TagFormatDict isIFD0:YES currentDataOffset:¤tDataOffset]; + + //data labeled as EXIF in UIImagePickerControllerMediaMetaData is part of the EXIF Sub IFD portion of APP1 + subExifIFD = [self createExifIFDFromDict: [datadict objectForKey:@"{Exif}"] withFormatDict: SubIFDTagFormatDict isIFD0:NO currentDataOffset:¤tDataOffset]; + /* + NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",exifIFD,[exifIFD length]); + + NSLog(@"SUB EXIF IFD %@ WITH SIZE: %d",subExifIFD,[subExifIFD length]); + */ + // construct the complete app1 data block + app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@", + app1marker, + 16 + ([exifIFD length]/2) + ([subExifIFD length]/2) /*16+[exifIFD length]/2*/, + exifmarker, + tiffheader, + ifd0offset, + exifIFD, + subExifIFD]; + + return app1; +} + +// returns hex string representing a valid exif information file directory constructed from the datadict and formatdict +- (NSString*) createExifIFDFromDict : (NSDictionary*) datadict + withFormatDict : (NSDictionary*) formatdict + isIFD0 : (BOOL) ifd0flag + currentDataOffset : (int*) dataoffset { + NSArray * datakeys = [datadict allKeys]; // all known data keys + NSArray * knownkeys = [formatdict allKeys]; // only keys in knowkeys are considered for entry in this IFD + NSMutableArray * ifdblock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // all ifd entries + NSMutableArray * ifddatablock = [[NSMutableArray alloc] initWithCapacity: [datadict count]]; // data block entries + // ifd0flag = NO; // ifd0 requires a special flag and has offset to next ifd appended to end + + // iterate through known provided data keys + for (int i = 0; i < [datakeys count]; i++) { + NSString * key = [datakeys objectAtIndex:i]; + // don't muck about with unknown keys + if ([knownkeys indexOfObject: key] != NSNotFound) { + // create new IFD entry + NSString * entry = [self createIFDElement: key + withFormat: [formatdict objectForKey:key] + withElementData: [datadict objectForKey:key]]; + // create the IFD entry's data block + NSString * data = [self createIFDElementDataWithFormat: [formatdict objectForKey:key] + withData: [datadict objectForKey:key]]; + if (entry) { + [ifdblock addObject:entry]; + if(!data) { + [ifdblock addObject:@""]; + } else { + [ifddatablock addObject:data]; + } + } + } + } + + NSMutableString * exifstr = [[NSMutableString alloc] initWithCapacity: [ifdblock count] * 24]; + NSMutableString * dbstr = [[NSMutableString alloc] initWithCapacity: 100]; + + int addr=*dataoffset; // current offset/address in datablock + if (ifd0flag) { + // calculate offset to datablock based on ifd file entry count + addr += 14+(12*([ifddatablock count]+1)); // +1 for tag 0x8769, exifsubifd offset + } else { + // current offset + numSubIFDs (2-bytes) + 12*numSubIFDs + endMarker (4-bytes) + addr += 2+(12*[ifddatablock count])+4; + } + + for (int i = 0; i < [ifdblock count]; i++) { + NSString * entry = [ifdblock objectAtIndex:i]; + NSString * data = [ifddatablock objectAtIndex:i]; + + // check if the data fits into 4 bytes + if( [data length] <= 8) { + // concatenate the entry and the (4byte) data entry into the final IFD entry and append to exif ifd string + [exifstr appendFormat : @"%@%@", entry, data]; + } else { + [exifstr appendFormat : @"%@%08x", entry, addr]; + [dbstr appendFormat: @"%@", data]; + addr+= [data length] / 2; + /* + NSLog(@"=====data-length[%i]=======",[data length]); + NSLog(@"addr-offset[%i]",addr); + NSLog(@"entry[%@]",entry); + NSLog(@"data[%@]",data); + */ + } + } + + // calculate IFD0 terminal offset tags, currently ExifSubIFD + int entrycount = [ifdblock count]; + if (ifd0flag) { + // 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header + NSNumber * offset = [NSNumber numberWithInt:[exifstr length] / 2 + [dbstr length] / 2 + 18+8]; + + [self appendExifOffsetTagTo: exifstr + withOffset : offset]; + entrycount++; + } + *dataoffset = addr; + return [[NSString alloc] initWithFormat: @"%04x%@%@%@", + entrycount, + exifstr, + @"00000000", // offset to next IFD, 0 since there is none + dbstr]; // lastly, the datablock +} + +// Creates an exif formatted exif information file directory entry +- (NSString*) createIFDElement: (NSString*) elementName withFormat: (NSArray*) formtemplate withElementData: (NSString*) data { + //NSArray * fielddata = [formatdict objectForKey: elementName];// format data of desired field + if (formtemplate) { + // format string @"%@%@%@%@", tag number, data format, components, value + NSNumber * dataformat = [formtemplate objectAtIndex:1]; + NSNumber * components = [formtemplate objectAtIndex:2]; + if([components intValue] == 0) { + components = [NSNumber numberWithInt: [data length] * DataTypeToWidth[[dataformat intValue]-1]]; + } + + return [[NSString alloc] initWithFormat: @"%@%@%08x", + [formtemplate objectAtIndex:0], // the field code + [self formatNumberWithLeadingZeroes: dataformat withPlaces: @4], // the data type code + [components intValue]]; // number of components + } + return NULL; +} + +/** + * appends exif IFD0 tag 8769 "ExifOffset" to the string provided + * (NSMutableString*) str - string you wish to append the 8769 tag to: APP1 or IFD0 hex data string + * // TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1), @"ExifOffset", + */ +- (void) appendExifOffsetTagTo: (NSMutableString*) str withOffset : (NSNumber*) offset { + NSArray * format = TAGINF(@"8769", [NSNumber numberWithInt:EDT_ULONG], @1); + + NSString * entry = [self createIFDElement: @"ExifOffset" + withFormat: format + withElementData: [offset stringValue]]; + + NSString * data = [self createIFDElementDataWithFormat: format + withData: [offset stringValue]]; + [str appendFormat:@"%@%@", entry, data]; +} + +// formats the Information File Directory Data to exif format +- (NSString*) createIFDElementDataWithFormat: (NSArray*) dataformat withData: (NSString*) data { + NSMutableString * datastr = nil; + NSNumber * tmp = nil; + NSNumber * formatcode = [dataformat objectAtIndex:1]; + NSUInteger formatItemsCount = [dataformat count]; + NSNumber * num = @0; + NSNumber * denom = @0; + + switch ([formatcode intValue]) { + case EDT_UBYTE: + break; + case EDT_ASCII_STRING: + datastr = [[NSMutableString alloc] init]; + for (int i = 0; i < [data length]; i++) { + [datastr appendFormat:@"%02x",[data characterAtIndex:i]]; + } + if (formatItemsCount > 3) { + // We have additional data to append. + // currently used by Date format to append final 0x00 but can be used by other data types as well in the future + [datastr appendString:[dataformat objectAtIndex:3]]; + } + if ([datastr length] < 8) { + NSString * format = [NSString stringWithFormat:@"%%0%dd", 8 - [datastr length]]; + [datastr appendFormat:format,0]; + } + return datastr; + case EDT_USHORT: + return [[NSString alloc] initWithFormat : @"%@%@", + [self formattedHexStringFromDecimalNumber: [NSNumber numberWithInt: [data intValue]] withPlaces: @4], + @"0000"]; + case EDT_ULONG: + tmp = [NSNumber numberWithUnsignedLong:[data intValue]]; + return [NSString stringWithFormat : @"%@", + [self formattedHexStringFromDecimalNumber: tmp withPlaces: @8]]; + case EDT_URATIONAL: + return [self decimalToUnsignedRational: [NSNumber numberWithDouble:[data doubleValue]] + withResultNumerator: &num + withResultDenominator: &denom]; + case EDT_SBYTE: + + break; + case EDT_UNDEFINED: + break; // 8 bits + case EDT_SSHORT: + break; + case EDT_SLONG: + break; // 32bit signed integer (2's complement) + case EDT_SRATIONAL: + break; // 2 SLONGS, first long is numerator, second is denominator + case EDT_SINGLEFLOAT: + break; + case EDT_DOUBLEFLOAT: + break; + } + return datastr; +} + +//====================================================================================================================== +// Utility Methods +//====================================================================================================================== + +// creates a formatted little endian hex string from a number and width specifier +- (NSString*) formattedHexStringFromDecimalNumber: (NSNumber*) numb withPlaces: (NSNumber*) width { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:[width intValue]]; + NSString * formatstr = [[NSString alloc] initWithFormat: @"%%%@%dx", @"0", [width intValue]]; + [str appendFormat:formatstr, [numb intValue]]; + return str; +} + +// format number as string with leading 0's +- (NSString*) formatNumberWithLeadingZeroes: (NSNumber *) numb withPlaces: (NSNumber *) places { + NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init]; + NSString *formatstr = [@"" stringByPaddingToLength:[places unsignedIntegerValue] withString:@"0" startingAtIndex:0]; + [formatter setPositiveFormat:formatstr]; + return [formatter stringFromNumber:numb]; +} + +// approximate a decimal with a rational by method of continued fraction +// can be collasped into decimalToUnsignedRational after testing +- (void) decimalToRational: (NSNumber *) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8]; + + [self continuedFraction: [numb doubleValue] + withFractionList: fractionlist + withHorizon: 8]; + + // simplify complex fraction represented by partial fraction list + [self expandContinuedFraction: fractionlist + withResultNumerator: numerator + withResultDenominator: denominator]; + +} + +// approximate a decimal with an unsigned rational by method of continued fraction +- (NSString*) decimalToUnsignedRational: (NSNumber *) numb + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + NSMutableArray * fractionlist = [[NSMutableArray alloc] initWithCapacity:8]; + + // generate partial fraction list + [self continuedFraction: [numb doubleValue] + withFractionList: fractionlist + withHorizon: 8]; + + // simplify complex fraction represented by partial fraction list + [self expandContinuedFraction: fractionlist + withResultNumerator: numerator + withResultDenominator: denominator]; + + return [self formatFractionList: fractionlist]; +} + +// recursive implementation of decimal approximation by continued fraction +- (void) continuedFraction: (double) val + withFractionList: (NSMutableArray*) fractionlist + withHorizon: (int) horizon { + int whole; + double remainder; + // 1. split term + [self splitDouble: val withIntComponent: &whole withFloatRemainder: &remainder]; + [fractionlist addObject: [NSNumber numberWithInt:whole]]; + + // 2. calculate reciprocal of remainder + if (!remainder) return; // early exit, exact fraction found, avoids recip/0 + double recip = 1 / remainder; + + // 3. exit condition + if ([fractionlist count] > horizon) { + return; + } + + // 4. recurse + [self continuedFraction:recip withFractionList: fractionlist withHorizon: horizon]; + +} + +// expand continued fraction list, creating a single level rational approximation +-(void) expandContinuedFraction: (NSArray*) fractionlist + withResultNumerator: (NSNumber**) numerator + withResultDenominator: (NSNumber**) denominator { + int i = 0; + int den = 0; + int num = 0; + if ([fractionlist count] == 1) { + *numerator = [NSNumber numberWithInt:[[fractionlist objectAtIndex:0] intValue]]; + *denominator = @1; + return; + } + + //begin at the end of the list + i = [fractionlist count] - 1; + num = 1; + den = [[fractionlist objectAtIndex:i] intValue]; + + while (i > 0) { + int t = [[fractionlist objectAtIndex: i-1] intValue]; + num = t * den + num; + if (i==1) { + break; + } else { + t = num; + num = den; + den = t; + } + i--; + } + // set result parameters values + *numerator = [NSNumber numberWithInt: num]; + *denominator = [NSNumber numberWithInt: den]; +} + +// formats expanded fraction list to string matching exif specification +- (NSString*) formatFractionList: (NSArray *) fractionlist { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16]; + + if ([fractionlist count] == 1){ + [str appendFormat: @"%08x00000001", [[fractionlist objectAtIndex:0] intValue]]; + } + return str; +} + +// format rational as +- (NSString*) formatRationalWithNumerator: (NSNumber*) numerator withDenominator: (NSNumber*) denominator asSigned: (Boolean) signedFlag { + NSMutableString * str = [[NSMutableString alloc] initWithCapacity:16]; + if (signedFlag) { + long num = [numerator longValue]; + long den = [denominator longValue]; + [str appendFormat: @"%08lx%08lx", num >= 0 ? num : ~ABS(num) + 1, num >= 0 ? den : ~ABS(den) + 1]; + } else { + [str appendFormat: @"%08lx%08lx", [numerator unsignedLongValue], [denominator unsignedLongValue]]; + } + return str; +} + +// split a floating point number into two integer values representing the left and right side of the decimal +- (void) splitDouble: (double) val withIntComponent: (int*) rightside withFloatRemainder: (double*) leftside { + *rightside = val; // convert numb to int representation, which truncates the decimal portion + *leftside = val - *rightside; +} + + +// +- (NSString*) hexStringFromData : (NSData*) data { + //overflow detection + const unsigned char *dataBuffer = [data bytes]; + return [[NSString alloc] initWithFormat: @"%02x%02x", + (unsigned char)dataBuffer[0], + (unsigned char)dataBuffer[1]]; +} + +// convert a hex string to a number +- (NSNumber*) numericFromHexString : (NSString *) hexstring { + NSScanner * scan = NULL; + unsigned int numbuf= 0; + + scan = [NSScanner scannerWithString:hexstring]; + [scan scanHexInt:&numbuf]; + return [NSNumber numberWithInt:numbuf]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.h b/iPhone/CordovaLib/Classes/CDVLocalStorage.h index cc6613f..dec6ab3 100755 --- a/iPhone/CordovaLib/Classes/CDVLocalStorage.h +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.h @@ -34,8 +34,8 @@ + (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType; // Visible for testing. + (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict - bundlePath :(NSString*)bundlePath - fileManager :(NSFileManager*)fileManager; + bundlePath:(NSString*)bundlePath + fileManager:(NSFileManager*)fileManager; @end @interface CDVBackupInfo : NSObject diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.m b/iPhone/CordovaLib/Classes/CDVLocalStorage.m index 68175f1..238d680 100755 --- a/iPhone/CordovaLib/Classes/CDVLocalStorage.m +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.m @@ -35,7 +35,7 @@ { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) name:UIApplicationWillResignActiveNotification object:nil]; - BOOL cloudBackup = [@"cloud" isEqualToString:self.commandDelegate.settings[@"BackupWebStorage"]]; + BOOL cloudBackup = [@"cloud" isEqualToString : self.commandDelegate.settings[@"BackupWebStorage"]]; self.backupInfo = [[self class] createBackupInfoWithCloudBackup:cloudBackup]; } @@ -64,8 +64,8 @@ // ////////// LOCALSTORAGE original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0.localstorage":@"file__0.localstorage"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db":@"file__0.localstorage")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db" : @"file__0.localstorage")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -77,8 +77,8 @@ // ////////// WEBSQL MAIN DB original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/Databases.db":@"Databases.db"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db":@"Databases.db")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db" : @"Databases.db")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -90,8 +90,8 @@ // ////////// WEBSQL DATABASES original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0":@"file__0"]; - backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage":@"")]; - backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db":@"file__0")]; + backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")]; + backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db" : @"file__0")]; backupItem = [[CDVBackupInfo alloc] init]; backupItem.backup = backup; @@ -106,8 +106,8 @@ + (NSMutableArray*)createBackupInfoWithCloudBackup:(BOOL)cloudBackup { // create backup info from backup folder to caches folder - NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"]; NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"]; @@ -131,7 +131,7 @@ return success; } -+ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError * __autoreleasing*)error ++ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError* __autoreleasing*)error { NSFileManager* fileManager = [NSFileManager defaultManager]; @@ -149,7 +149,7 @@ // generate unique filepath in temp directory CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); - NSString* tempBackup = [[NSTemporaryDirectory () stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"]; + NSString* tempBackup = [[NSTemporaryDirectory() stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"]; CFRelease(uuidString); CFRelease(uuidRef); @@ -334,8 +334,8 @@ return; } - NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains (NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:0]; @@ -386,15 +386,15 @@ backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; backgroundTaskID = UIBackgroundTaskInvalid; - NSLog (@"Background task to backup WebSQL/LocalStorage expired."); + NSLog(@"Background task to backup WebSQL/LocalStorage expired."); }]; CDVLocalStorage __weak* weakSelf = self; [self.commandDelegate runInBackground:^{ - [weakSelf backup:nil]; + [weakSelf backup:nil]; - [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; - backgroundTaskID = UIBackgroundTaskInvalid; - }]; + [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID]; + backgroundTaskID = UIBackgroundTaskInvalid; + }]; } } diff --git a/iPhone/CordovaLib/Classes/CDVLocation.h b/iPhone/CordovaLib/Classes/CDVLocation.h index 7087d43..caf0798 100755 --- a/iPhone/CordovaLib/Classes/CDVLocation.h +++ b/iPhone/CordovaLib/Classes/CDVLocation.h @@ -83,11 +83,11 @@ typedef NSUInteger CDVLocationStatus; - (void)startLocation:(BOOL)enableHighAccuracy; - (void)locationManager:(CLLocationManager*)manager - didUpdateToLocation :(CLLocation*)newLocation - fromLocation :(CLLocation*)oldLocation; + didUpdateToLocation:(CLLocation*)newLocation + fromLocation:(CLLocation*)oldLocation; - (void)locationManager:(CLLocationManager*)manager - didFailWithError :(NSError*)error; + didFailWithError:(NSError*)error; - (BOOL)isLocationServicesEnabled; @@ -97,7 +97,7 @@ typedef NSUInteger CDVLocationStatus; - (void)stopHeading:(CDVInvokedUrlCommand*)command; - (void)startHeadingWithFilter:(CLLocationDegrees)filter; - (void)locationManager:(CLLocationManager*)manager - didUpdateHeading :(CLHeading*)heading; + didUpdateHeading:(CLHeading*)heading; - (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager*)manager; diff --git a/iPhone/CordovaLib/Classes/CDVLocation.m b/iPhone/CordovaLib/Classes/CDVLocation.m index 07af30e..ed9ec26 100755 --- a/iPhone/CordovaLib/Classes/CDVLocation.m +++ b/iPhone/CordovaLib/Classes/CDVLocation.m @@ -591,17 +591,17 @@ - (NSString*)JSONRepresentation { return [NSString stringWithFormat: - @"{ timestamp: %.00f, \ + @"{ timestamp: %.00f, \ coords: { latitude: %f, longitude: %f, altitude: %.02f, heading: %.02f, speed: %.02f, accuracy: %.02f, altitudeAccuracy: %.02f } \ }", - [self.timestamp timeIntervalSince1970] * 1000.0, - self.coordinate.latitude, - self.coordinate.longitude, - self.altitude, - self.course, - self.speed, - self.horizontalAccuracy, - self.verticalAccuracy + [self.timestamp timeIntervalSince1970] * 1000.0, + self.coordinate.latitude, + self.coordinate.longitude, + self.altitude, + self.course, + self.speed, + self.horizontalAccuracy, + self.verticalAccuracy ]; } @@ -614,9 +614,9 @@ - (NSString*)JSONRepresentation { return [NSString stringWithFormat: - @"{ code: %d, message: '%@'}", - self.code, - [self localizedDescription] + @"{ code: %d, message: '%@'}", + self.code, + [self localizedDescription] ]; } diff --git a/iPhone/CordovaLib/Classes/CDVNotification.h b/iPhone/CordovaLib/Classes/CDVNotification.h index 1eedb54..5b5b89f 100755 --- a/iPhone/CordovaLib/Classes/CDVNotification.h +++ b/iPhone/CordovaLib/Classes/CDVNotification.h @@ -26,6 +26,7 @@ - (void)alert:(CDVInvokedUrlCommand*)command; - (void)confirm:(CDVInvokedUrlCommand*)command; +- (void)prompt:(CDVInvokedUrlCommand*)command; - (void)vibrate:(CDVInvokedUrlCommand*)command; @end diff --git a/iPhone/CordovaLib/Classes/CDVNotification.m b/iPhone/CordovaLib/Classes/CDVNotification.m index 992239e..464eb1f 100755 --- a/iPhone/CordovaLib/Classes/CDVNotification.m +++ b/iPhone/CordovaLib/Classes/CDVNotification.m @@ -20,12 +20,25 @@ #import "CDVNotification.h" #import "NSDictionary+Extensions.h" +#define DIALOG_TYPE_ALERT @"alert" +#define DIALOG_TYPE_PROMPT @"prompt" + @implementation CDVNotification -- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSString*)buttons callbackId:(NSString*)callbackId +/* + * showDialogWithMessage - Common method to instantiate the alert view for alert, confirm, and prompt notifications. + * Parameters: + * message The alert view message. + * title The alert view title. + * buttons The array of customized strings for the buttons. + * defaultText The input text for the textbox (if textbox exists). + * callbackId The commmand callback id. + * dialogType The type of alert view [alert | prompt]. + */ +- (void)showDialogWithMessage:(NSString*)message title:(NSString*)title buttons:(NSArray*)buttons defaultText:(NSString*)defaultText callbackId:(NSString*)callbackId dialogType:(NSString*)dialogType { CDVAlertView* alertView = [[CDVAlertView alloc] - initWithTitle:title + initWithTitle:title message:message delegate:self cancelButtonTitle:nil @@ -33,11 +46,16 @@ alertView.callbackId = callbackId; - NSArray* labels = [buttons componentsSeparatedByString:@","]; - int count = [labels count]; + int count = [buttons count]; for (int n = 0; n < count; n++) { - [alertView addButtonWithTitle:[labels objectAtIndex:n]]; + [alertView addButtonWithTitle:[buttons objectAtIndex:n]]; + } + + if ([dialogType isEqualToString:DIALOG_TYPE_PROMPT]) { + alertView.alertViewStyle = UIAlertViewStylePlainTextInput; + UITextField* textField = [alertView textFieldAtIndex:0]; + textField.text = defaultText; } [alertView show]; @@ -46,52 +64,55 @@ - (void)alert:(CDVInvokedUrlCommand*)command { NSString* callbackId = command.callbackId; - NSArray* arguments = command.arguments; - int argc = [arguments count]; - - NSString* message = argc > 0 ? [arguments objectAtIndex:0] : nil; - NSString* title = argc > 1 ? [arguments objectAtIndex:1] : nil; - NSString* buttons = argc > 2 ? [arguments objectAtIndex:2] : nil; - - if (!title) { - title = NSLocalizedString(@"Alert", @"Alert"); - } - if (!buttons) { - buttons = NSLocalizedString(@"OK", @"OK"); - } + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSString* buttons = [command argumentAtIndex:2]; - [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; + [self showDialogWithMessage:message title:title buttons:@[buttons] defaultText:nil callbackId:callbackId dialogType:DIALOG_TYPE_ALERT]; } - (void)confirm:(CDVInvokedUrlCommand*)command { NSString* callbackId = command.callbackId; - NSArray* arguments = command.arguments; - int argc = [arguments count]; + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSArray* buttons = [command argumentAtIndex:2]; - NSString* message = argc > 0 ? [arguments objectAtIndex:0] : nil; - NSString* title = argc > 1 ? [arguments objectAtIndex:1] : nil; - NSString* buttons = argc > 2 ? [arguments objectAtIndex:2] : nil; + [self showDialogWithMessage:message title:title buttons:buttons defaultText:nil callbackId:callbackId dialogType:DIALOG_TYPE_ALERT]; +} - if (!title) { - title = NSLocalizedString(@"Confirm", @"Confirm"); - } - if (!buttons) { - buttons = NSLocalizedString(@"OK,Cancel", @"OK,Cancel"); - } +- (void)prompt:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* message = [command argumentAtIndex:0]; + NSString* title = [command argumentAtIndex:1]; + NSArray* buttons = [command argumentAtIndex:2]; + NSString* defaultText = [command argumentAtIndex:3]; - [self showDialogWithMessage:message title:title buttons:buttons callbackId:callbackId]; + [self showDialogWithMessage:message title:title buttons:buttons defaultText:defaultText callbackId:callbackId dialogType:DIALOG_TYPE_PROMPT]; } /** - Callback invoked when an alert dialog's buttons are clicked. - Passes the index + label back to JS - */ + * Callback invoked when an alert dialog's buttons are clicked. + */ - (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { CDVAlertView* cdvAlertView = (CDVAlertView*)alertView; - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:++buttonIndex]; - + CDVPluginResult* result; + + // Determine what gets returned to JS based on the alert view type. + if (alertView.alertViewStyle == UIAlertViewStyleDefault) { + // For alert and confirm, return button index as int back to JS. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:buttonIndex + 1]; + } else { + // For prompt, return button index and input text back to JS. + NSString* value0 = [[alertView textFieldAtIndex:0] text]; + NSDictionary* info = @{ + @"buttonIndex":@(buttonIndex + 1), + @"input1":(value0 ? value0 : [NSNull null]) + }; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:info]; + } [self.commandDelegate sendPluginResult:result callbackId:cdvAlertView.callbackId]; } diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.h b/iPhone/CordovaLib/Classes/CDVPlugin.h index f5b50eb..33ba1c4 100755 --- a/iPhone/CordovaLib/Classes/CDVPlugin.h +++ b/iPhone/CordovaLib/Classes/CDVPlugin.h @@ -23,10 +23,10 @@ #import "NSMutableArray+QueueAdditions.h" #import "CDVCommandDelegate.h" -#define CDVPageDidLoadNotification @"CDVPageDidLoadNotification" -#define CDVPluginHandleOpenURLNotification @"CDVPluginHandleOpenURLNotification" -#define CDVPluginResetNotification @"CDVPluginResetNotification" -#define CDVLocalNotification @"CDVLocalNotification" +extern NSString* const CDVPageDidLoadNotification; +extern NSString* const CDVPluginHandleOpenURLNotification; +extern NSString* const CDVPluginResetNotification; +extern NSString* const CDVLocalNotification; @interface CDVPlugin : NSObject {} diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.m b/iPhone/CordovaLib/Classes/CDVPlugin.m index a42d241..8c932a0 100755 --- a/iPhone/CordovaLib/Classes/CDVPlugin.m +++ b/iPhone/CordovaLib/Classes/CDVPlugin.m @@ -19,6 +19,11 @@ #import "CDVPlugin.h" +NSString* const CDVPageDidLoadNotification = @"CDVPageDidLoadNotification"; +NSString* const CDVPluginHandleOpenURLNotification = @"CDVPluginHandleOpenURLNotification"; +NSString* const CDVPluginResetNotification = @"CDVPluginResetNotification"; +NSString* const CDVLocalNotification = @"CDVLocalNotification"; + @interface CDVPlugin () @property (readwrite, assign) BOOL hasPendingOperation; @@ -41,7 +46,7 @@ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURL:) name:CDVPluginHandleOpenURLNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:theWebView]; self.webView = theWebView; } @@ -64,7 +69,7 @@ // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveLocalNotification:) name:CDVLocalNotification object:nil]; // Added in 2.5.0 - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:self.webView]; } - (void)dispose diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.h b/iPhone/CordovaLib/Classes/CDVPluginResult.h index 8683205..11b5377 100755 --- a/iPhone/CordovaLib/Classes/CDVPluginResult.h +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.h @@ -37,6 +37,9 @@ typedef enum { @property (nonatomic, strong, readonly) NSNumber* status; @property (nonatomic, strong, readonly) id message; @property (nonatomic, strong) NSNumber* keepCallback; +// This property can be used to scope the lifetime of another object. For example, +// Use it to store the associated NSData when `message` is created using initWithBytesNoCopy. +@property (nonatomic, strong) id associatedObject; - (CDVPluginResult*)init; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal; @@ -47,6 +50,7 @@ typedef enum { + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode; + (void)setVerbose:(BOOL)verbose; @@ -54,6 +58,9 @@ typedef enum { - (void)setKeepCallbackAsBool:(BOOL)bKeepCallback; +- (NSString*)argumentsAsJSON; + +// These methods are used by the legacy plugin return result method - (NSString*)toJSONString; - (NSString*)toSuccessCallbackString:(NSString*)callbackId; - (NSString*)toErrorCallbackString:(NSString*)callbackId; diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.m b/iPhone/CordovaLib/Classes/CDVPluginResult.m index d9ba08f..af7c528 100755 --- a/iPhone/CordovaLib/Classes/CDVPluginResult.m +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.m @@ -29,10 +29,40 @@ @end @implementation CDVPluginResult -@synthesize status, message, keepCallback; +@synthesize status, message, keepCallback, associatedObject; static NSArray* org_apache_cordova_CommandStatusMsgs; +id messageFromArrayBuffer(NSData* data) +{ + return @{ + @"CDVType" : @"ArrayBuffer", + @"data" :[data base64EncodedString] + }; +} + +id massageMessage(id message) +{ + if ([message isKindOfClass:[NSData class]]) { + return messageFromArrayBuffer(message); + } + return message; +} + +id messageFromMultipart(NSArray* theMessages) +{ + NSMutableArray* messages = [NSMutableArray arrayWithArray:theMessages]; + + for (NSUInteger i = 0; i < messages.count; ++i) { + [messages replaceObjectAtIndex:i withObject:massageMessage([messages objectAtIndex:i])]; + } + + return @{ + @"CDVType" : @"MultiPart", + @"messages" : messages + }; +} + + (void)initialize { org_apache_cordova_CommandStatusMsgs = [[NSArray alloc] initWithObjects:@"No result", @@ -101,17 +131,17 @@ static NSArray* org_apache_cordova_CommandStatusMsgs; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage { - NSDictionary* arrDict = [NSDictionary dictionaryWithObjectsAndKeys: - @"ArrayBuffer", @"CDVType", - [theMessage base64EncodedString], @"data", - nil]; + return [[self alloc] initWithStatus:statusOrdinal message:messageFromArrayBuffer(theMessage)]; +} - return [[self alloc] initWithStatus:statusOrdinal message:arrDict]; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsMultipart:(NSArray*)theMessages +{ + return [[self alloc] initWithStatus:statusOrdinal message:messageFromMultipart(theMessages)]; } + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode { - NSDictionary* errDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"code"]; + NSDictionary* errDict = @{@"code" :[NSNumber numberWithInt:errorCode]}; return [[self alloc] initWithStatus:statusOrdinal message:errDict]; } @@ -121,6 +151,19 @@ static NSArray* org_apache_cordova_CommandStatusMsgs; [self setKeepCallback:[NSNumber numberWithBool:bKeepCallback]]; } +- (NSString*)argumentsAsJSON +{ + id arguments = (self.message == nil ? [NSNull null] : self.message); + NSArray* argumentsWrappedInArray = [NSArray arrayWithObject:arguments]; + + NSString* argumentsJSON = [argumentsWrappedInArray JSONString]; + + argumentsJSON = [argumentsJSON substringWithRange:NSMakeRange(1, [argumentsJSON length] - 2)]; + + return argumentsJSON; +} + +// These methods are used by the legacy plugin return result method - (NSString*)toJSONString { NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: diff --git a/iPhone/CordovaLib/Classes/CDVReachability.m b/iPhone/CordovaLib/Classes/CDVReachability.m index 3c5a48b..89f4ec9 100755 --- a/iPhone/CordovaLib/Classes/CDVReachability.m +++ b/iPhone/CordovaLib/Classes/CDVReachability.m @@ -61,18 +61,18 @@ static void CDVPrintReachabilityFlags(SCNetworkReachabilityFlags flags, const ch { #if kShouldPrintReachabilityFlags NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", - (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', - (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', - - (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', - (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', - (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', - comment - ); + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); #endif } @@ -90,7 +90,7 @@ static void CDVReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRe return; } - if (![(__bridge NSObject*) info isKindOfClass:[CDVReachability class]]) { + if (![(__bridge NSObject*)info isKindOfClass :[CDVReachability class]]) { NSLog(@"info was wrong class in ReachabilityCallback"); return; } @@ -214,7 +214,7 @@ static void CDVReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRe } if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || - ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))) { + ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))) { // ... and the connection is on-demand (or on-traffic) if the // calling application is using the CFSocketStream or higher APIs diff --git a/iPhone/CordovaLib/Classes/CDVSound.m b/iPhone/CordovaLib/Classes/CDVSound.m index 99515d7..71eab59 100755 --- a/iPhone/CordovaLib/Classes/CDVSound.m +++ b/iPhone/CordovaLib/Classes/CDVSound.m @@ -87,7 +87,7 @@ NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); } else { // if resourcePath is not from FileSystem put in tmp dir, else attempt to use provided resource path - NSString* tmpPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSString* tmpPath = [NSTemporaryDirectory()stringByStandardizingPath]; BOOL isTmp = [resourcePath rangeOfString:tmpPath].location != NSNotFound; BOOL isDoc = [resourcePath rangeOfString:docsPath].location != NSNotFound; if (!isTmp && !isDoc) { @@ -127,7 +127,7 @@ filePath = [self.commandDelegate pathForResource:resourcePath]; if (filePath == nil) { // see if this exists in the documents/temp directory from a previous recording - NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory ()stringByStandardizingPath], resourcePath]; + NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], resourcePath]; if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) { // inefficient as existence will be checked again below but only way to determine if file exists from previous recording filePath = testPath; @@ -273,6 +273,9 @@ } else { audioFile = [[self soundCache] objectForKey:mediaId]; audioFile.volume = volume; + if (audioFile.player) { + audioFile.player.volume = [volume floatValue]; + } [[self soundCache] setObject:audioFile forKey:mediaId]; } @@ -385,7 +388,7 @@ // bug in AVAudioPlayer when playing downloaded data in NSData - we have to download the file and play from disk CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); - NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory ()stringByStandardizingPath], uuidString]; + NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], uuidString]; CFRelease(uuidString); CFRelease(uuidRef); @@ -458,9 +461,19 @@ double position = [[command.arguments objectAtIndex:1] doubleValue]; if ((audioFile != nil) && (audioFile.player != nil)) { + NSString* jsString; double posInSeconds = position / 1000; - audioFile.player.currentTime = posInSeconds; - NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + if (posInSeconds >= audioFile.player.duration) { + // The seek is past the end of file. Stop media and reset to beginning instead of seeking past the end. + [audioFile.player stop]; + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, 0.0, @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + // NSLog(@"seekToEndJsString=%@",jsString); + } else { + audioFile.player.currentTime = posInSeconds; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + // NSLog(@"seekJsString=%@",jsString); + } [self.commandDelegate evalJs:jsString]; } @@ -625,6 +638,7 @@ NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath); } if (flag) { + audioFile.player.currentTime = 0; jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; } else { // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE]; diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.h b/iPhone/CordovaLib/Classes/CDVSplashScreen.h index a0868a0..704ab43 100755 --- a/iPhone/CordovaLib/Classes/CDVSplashScreen.h +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.h @@ -23,7 +23,8 @@ @interface CDVSplashScreen : CDVPlugin { UIActivityIndicatorView* _activityView; UIImageView* _imageView; - UIView* _parentView; + NSString* _curImageName; + BOOL _visible; } - (void)show:(CDVInvokedUrlCommand*)command; diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.m b/iPhone/CordovaLib/Classes/CDVSplashScreen.m index cba1b53..fdb79fa 100755 --- a/iPhone/CordovaLib/Classes/CDVSplashScreen.m +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.m @@ -19,29 +19,25 @@ #import "CDVSplashScreen.h" -#define kSplashScreenStateShow 0 -#define kSplashScreenStateHide 1 - #define kSplashScreenDurationDefault 0.25f @implementation CDVSplashScreen - (void)pluginInitialize { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:self.webView]; - [self show:nil]; + [self setVisible:YES]; } - (void)show:(CDVInvokedUrlCommand*)command { - [self updateSplashScreenWithState:kSplashScreenStateShow]; + [self setVisible:YES]; } - (void)hide:(CDVInvokedUrlCommand*)command { - [self updateSplashScreenWithState:kSplashScreenStateHide]; + [self setVisible:NO]; } - (void)pageDidLoad @@ -50,16 +46,13 @@ // if value is missing, default to yes if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { - [self hide:nil]; + [self setVisible:NO]; } } -- (void)onOrientationWillChange:(NSNotification*)notification +- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { - if (_imageView != nil) { - UIInterfaceOrientation orientation = [notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] intValue]; - [self updateSplashImageForOrientation:orientation]; - } + [self updateImage]; } - (void)createViews @@ -83,43 +76,125 @@ topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; } + UIView* parentView = self.viewController.view; + parentView.userInteractionEnabled = NO; // disable user interaction while splashscreen is shown _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; - _activityView.tag = 2; - _activityView.center = self.viewController.view.center; + _activityView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2); + _activityView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin + | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; [_activityView startAnimating]; + // Set the frame & image later. _imageView = [[UIImageView alloc] init]; - [self.viewController.view addSubview:_imageView]; - [self.viewController.view.superview addSubview:_activityView]; - [self.viewController.view.superview layoutSubviews]; + [parentView addSubview:_imageView]; + + id showSplashScreenSpinnerValue = [self.commandDelegate.settings objectForKey:@"ShowSplashScreenSpinner"]; + // backwards compatibility - if key is missing, default to true + if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { + [parentView addSubview:_activityView]; + } + + // Frame is required when launching in portrait mode. + // Bounds for landscape since it captures the rotation. + [parentView addObserver:self forKeyPath:@"frame" options:0 context:nil]; + [parentView addObserver:self forKeyPath:@"bounds" options:0 context:nil]; + + [self updateImage]; } -- (void)updateSplashImageForOrientation:(UIInterfaceOrientation)orientation +- (void)destroyViews { - // IPHONE (default) - NSString* imageName = @"Default"; + [_imageView removeFromSuperview]; + [_activityView removeFromSuperview]; + _imageView = nil; + _activityView = nil; + _curImageName = nil; + + self.viewController.view.userInteractionEnabled = YES; // re-enable user interaction upon completion + [self.viewController.view removeObserver:self forKeyPath:@"frame"]; + [self.viewController.view removeObserver:self forKeyPath:@"bounds"]; +} + +// Sets the view's frame and image. +- (void)updateImage +{ + UIInterfaceOrientation orientation = self.viewController.interfaceOrientation; + + // Use UILaunchImageFile if specified in plist. Otherwise, use Default. + NSString* imageName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; + + if (imageName) { + imageName = [imageName stringByDeletingPathExtension]; + } else { + imageName = @"Default"; + } if (CDV_IsIPhone5()) { imageName = [imageName stringByAppendingString:@"-568h"]; } else if (CDV_IsIPad()) { - // set default to portrait upside down - imageName = @"Default-Portrait"; // @"Default-PortraitUpsideDown.png"; + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + case UIInterfaceOrientationLandscapeRight: + imageName = [imageName stringByAppendingString:@"-Landscape"]; + break; + + case UIInterfaceOrientationPortrait: + case UIInterfaceOrientationPortraitUpsideDown: + default: + imageName = [imageName stringByAppendingString:@"-Portrait"]; + break; + } + } - if (orientation == UIInterfaceOrientationLandscapeLeft) { - imageName = @"Default-Landscape.png"; // @"Default-LandscapeLeft.png"; - } else if (orientation == UIInterfaceOrientationLandscapeRight) { - imageName = @"Default-Landscape.png"; // @"Default-LandscapeRight.png"; + if (![imageName isEqualToString:_curImageName]) { + UIImage* img = [UIImage imageNamed:imageName]; + _imageView.image = img; + _curImageName = imageName; + } + + // Check that splash screen's image exists before updating bounds + if (_imageView.image) { + [self updateBounds]; + } else { + NSLog(@"WARNING: The splashscreen image named %@ was not found", imageName); + } +} + +- (void)updateBounds +{ + UIImage* img = _imageView.image; + CGRect imgBounds = CGRectMake(0, 0, img.size.width, img.size.height); + + CGSize screenSize = [self.viewController.view convertRect:[UIScreen mainScreen].bounds fromView:nil].size; + + // There's a special case when the image is the size of the screen. + if (CGSizeEqualToSize(screenSize, imgBounds.size)) { + CGRect statusFrame = [self.viewController.view convertRect:[UIApplication sharedApplication].statusBarFrame fromView:nil]; + imgBounds.origin.y -= statusFrame.size.height; + } else { + CGRect viewBounds = self.viewController.view.bounds; + CGFloat imgAspect = imgBounds.size.width / imgBounds.size.height; + CGFloat viewAspect = viewBounds.size.width / viewBounds.size.height; + // This matches the behaviour of the native splash screen. + CGFloat ratio; + if (viewAspect > imgAspect) { + ratio = viewBounds.size.width / imgBounds.size.width; + } else { + ratio = viewBounds.size.height / imgBounds.size.height; } + imgBounds.size.height *= ratio; + imgBounds.size.width *= ratio; } - _imageView.image = [UIImage imageNamed:imageName]; - _imageView.frame = CGRectMake(0, 0, _imageView.image.size.width, _imageView.image.size.height); + _imageView.frame = imgBounds; } -- (void)updateSplashScreenWithState:(int)state +- (void)setVisible:(BOOL)visible { - float toAlpha = state == kSplashScreenStateShow ? 1.0f : 0.0f; - BOOL hidden = state == kSplashScreenStateShow ? NO : YES; + if (visible == _visible) { + return; + } + _visible = visible; id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:@"FadeSplashScreen"]; id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:@"FadeSplashScreenDuration"]; @@ -129,45 +204,26 @@ if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { fadeDuration = 0; } - if (hidden && (_imageView == nil)) { - return; - } else if (_imageView == nil) { - [self createViews]; - fadeDuration = 0; - } - if (!hidden) { - [self updateSplashImageForOrientation:self.viewController.interfaceOrientation]; - } - - if (fadeDuration == 0) { - [_imageView setHidden:hidden]; - [_activityView setHidden:hidden]; - } else { - if (state == kSplashScreenStateShow) { - // reset states - [_imageView setHidden:NO]; - [_activityView setHidden:NO]; - [_imageView setAlpha:0.0f]; - [_activityView setAlpha:0.0f]; + // Never animate the showing of the splash screen. + if (visible) { + if (_imageView == nil) { + [self createViews]; } - + } else if (fadeDuration == 0) { + [self destroyViews]; + } else { [UIView transitionWithView:self.viewController.view duration:fadeDuration options:UIViewAnimationOptionTransitionNone animations:^(void) { - [_imageView setAlpha:toAlpha]; - [_activityView setAlpha:toAlpha]; - } + [_imageView setAlpha:0]; + [_activityView setAlpha:0]; + } + completion:^(BOOL finished) { - if (state == kSplashScreenStateHide) { - // Clean-up resources. - [_imageView removeFromSuperview]; - [_activityView removeFromSuperview]; - _imageView = nil; - _activityView = nil; - } - }]; + [self destroyViews]; + }]; } } diff --git a/iPhone/CordovaLib/Classes/CDVDebugConsole.h b/iPhone/CordovaLib/Classes/CDVTimer.h index 6a0a185..6d31593 100755 --- a/iPhone/CordovaLib/Classes/CDVDebugConsole.h +++ b/iPhone/CordovaLib/Classes/CDVTimer.h @@ -18,11 +18,10 @@ */ #import <Foundation/Foundation.h> -#import <UIKit/UIKit.h> -#import "CDVPlugin.h" -@interface CDVDebugConsole : CDVPlugin {} +@interface CDVTimer : NSObject -- (void)log:(CDVInvokedUrlCommand*)command; ++ (void)start:(NSString*)name; ++ (void)stop:(NSString*)name; @end diff --git a/iPhone/CordovaLib/Classes/CDVTimer.m b/iPhone/CordovaLib/Classes/CDVTimer.m new file mode 100755 index 0000000..784e94d --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVTimer.m @@ -0,0 +1,123 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVTimer.h" + +#pragma mark CDVTimerItem + +@interface CDVTimerItem : NSObject + +@property (nonatomic, strong) NSString* name; +@property (nonatomic, strong) NSDate* started; +@property (nonatomic, strong) NSDate* ended; + +- (void)log; + +@end + +@implementation CDVTimerItem + +- (void)log +{ + NSLog(@"[CDVTimer][%@] %fms", self.name, [self.ended timeIntervalSinceDate:self.started] * 1000.0); +} + +@end + +#pragma mark CDVTimer + +@interface CDVTimer () + +@property (nonatomic, strong) NSMutableDictionary* items; + +@end + +@implementation CDVTimer + +#pragma mark object methods + +- (id)init +{ + if (self = [super init]) { + self.items = [NSMutableDictionary dictionaryWithCapacity:6]; + } + + return self; +} + +- (void)add:(NSString*)name +{ + if ([self.items objectForKey:[name lowercaseString]] == nil) { + CDVTimerItem* item = [CDVTimerItem new]; + item.name = name; + item.started = [NSDate new]; + [self.items setObject:item forKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' already exists.", name); + } +} + +- (void)remove:(NSString*)name +{ + CDVTimerItem* item = [self.items objectForKey:[name lowercaseString]]; + + if (item != nil) { + item.ended = [NSDate new]; + [item log]; + [self.items removeObjectForKey:[name lowercaseString]]; + } else { + NSLog(@"Timer called '%@' does not exist.", name); + } +} + +- (void)removeAll +{ + [self.items removeAllObjects]; +} + +#pragma mark class methods + ++ (void)start:(NSString*)name +{ + [[CDVTimer sharedInstance] add:name]; +} + ++ (void)stop:(NSString*)name +{ + [[CDVTimer sharedInstance] remove:name]; +} + ++ (void)clearAll +{ + [[CDVTimer sharedInstance] removeAll]; +} + ++ (CDVTimer*)sharedInstance +{ + static dispatch_once_t pred = 0; + __strong static CDVTimer* _sharedObject = nil; + + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; + }); + + return _sharedObject; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVURLProtocol.m b/iPhone/CordovaLib/Classes/CDVURLProtocol.m index 1959c77..afc10de 100755 --- a/iPhone/CordovaLib/Classes/CDVURLProtocol.m +++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.m @@ -31,7 +31,7 @@ @property (nonatomic) NSInteger statusCode; @end -static CDVWhitelist * gWhitelist = nil; +static CDVWhitelist* gWhitelist = nil; // Contains a set of NSNumbers of addresses of controllers. It doesn't store // the actual pointer to avoid retaining. static NSMutableSet* gRegisteredControllers = nil; @@ -159,12 +159,12 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) [self sendResponseWithResponseCode:200 data:nil mimeType:nil]; return; } else if ([[url absoluteString] hasPrefix:kCDVAssetsLibraryPrefix]) { - ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) { if (asset) { // We have the asset! Get the data and send it along. ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; - NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); - Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + Byte* buffer = (Byte*)malloc([assetRepresentation size]); NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; [self sendResponseWithResponseCode:200 data:data mimeType:MIMEType]; @@ -173,7 +173,7 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) [self sendResponseWithResponseCode:404 data:nil mimeType:nil]; } }; - ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) { // Retrieving the asset failed for some reason. Send an error. [self sendResponseWithResponseCode:401 data:nil mimeType:nil]; }; @@ -202,7 +202,7 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) if (mimeType == nil) { mimeType = @"text/plain"; } - NSString* encodingName = [@"text/plain" isEqualToString:mimeType] ? @"UTF-8" : nil; + NSString* encodingName = [@"text/plain" isEqualToString : mimeType] ? @"UTF-8" : nil; CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithURL:[[self request] URL] MIMEType:mimeType diff --git a/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m index 5c43c51..9923d47 100755 --- a/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m +++ b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m @@ -96,7 +96,7 @@ static NSMutableArray* gPendingSetUserAgentBlocks = nil; void (^block)() = [gPendingSetUserAgentBlocks objectAtIndex:0]; [gPendingSetUserAgentBlocks removeObjectAtIndex:0]; gCurrentLockToken = ++gNextLockToken; - NSLog (@"Gave lock %d", gCurrentLockToken); + NSLog(@"Gave lock %d", gCurrentLockToken); block(gCurrentLockToken); } else { gCurrentLockToken = 0; diff --git a/iPhone/CordovaLib/Classes/CDVViewController.h b/iPhone/CordovaLib/Classes/CDVViewController.h index 82e22f6..2338baf 100755 --- a/iPhone/CordovaLib/Classes/CDVViewController.h +++ b/iPhone/CordovaLib/Classes/CDVViewController.h @@ -22,15 +22,14 @@ #import "CDVAvailability.h" #import "CDVInvokedUrlCommand.h" #import "CDVCommandDelegate.h" +#import "CDVCommandQueue.h" #import "CDVWhitelist.h" #import "CDVScreenOrientationDelegate.h" - -@class CDVCommandQueue; -@class CDVCommandDelegateImpl; +#import "CDVPlugin.h" @interface CDVViewController : UIViewController <UIWebViewDelegate, CDVScreenOrientationDelegate>{ @protected - CDVCommandDelegateImpl* _commandDelegate; + id <CDVCommandDelegate> _commandDelegate; @protected CDVCommandQueue* _commandQueue; NSString* _userAgent; @@ -49,7 +48,7 @@ @property (nonatomic, readwrite, copy) NSString* wwwFolderName; @property (nonatomic, readwrite, copy) NSString* startPage; @property (nonatomic, readonly, strong) CDVCommandQueue* commandQueue; -@property (nonatomic, readonly, strong) CDVCommandDelegateImpl* commandDelegate; +@property (nonatomic, readonly, strong) id <CDVCommandDelegate> commandDelegate; @property (nonatomic, readonly) NSString* userAgent; + (NSDictionary*)getBundlePlist:(NSString*)plistName; @@ -67,6 +66,7 @@ - (id)getCommandInstance:(NSString*)pluginName; - (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className; +- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName; - (BOOL)URLisAllowed:(NSURL*)url; diff --git a/iPhone/CordovaLib/Classes/CDVViewController.m b/iPhone/CordovaLib/Classes/CDVViewController.m index bec716d..94f4552 100755 --- a/iPhone/CordovaLib/Classes/CDVViewController.m +++ b/iPhone/CordovaLib/Classes/CDVViewController.m @@ -19,7 +19,6 @@ #import <objc/message.h> #import "CDV.h" -#import "CDVCommandQueue.h" #import "CDVCommandDelegateImpl.h" #import "CDVConfigParser.h" #import "CDVUserAgentUtil.h" @@ -103,6 +102,48 @@ return self; } +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(keyboardWillShowOrHide:) + name:UIKeyboardWillShowNotification + object:nil]; + [nc addObserver:self + selector:@selector(keyboardWillShowOrHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + [nc removeObserver:self name:UIKeyboardWillHideNotification object:nil]; +} + +- (void)keyboardWillShowOrHide:(NSNotification*)notif +{ + if (![@"true" isEqualToString : self.settings[@"KeyboardShrinksView"]]) { + return; + } + BOOL showEvent = [notif.name isEqualToString:UIKeyboardWillShowNotification]; + + CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil]; + + CGRect newFrame = self.view.bounds; + if (showEvent) { + newFrame.size.height -= keyboardFrame.size.height; + } + self.webView.frame = newFrame; + self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, -keyboardFrame.size.height, 0); +} + - (void)printDeprecationNotice { if (!IsAtLeastiOSVersion(@"5.0")) { @@ -208,9 +249,6 @@ id backupWebStorage = self.settings[@"BackupWebStorage"]; if ([backupWebStorage isKindOfClass:[NSString class]]) { backupWebStorageType = backupWebStorage; - } else if ([backupWebStorage isKindOfClass:[NSNumber class]]) { - NSLog(@"Deprecated: BackupWebStorage boolean property is a string property now (none, local, cloud). A boolean value of 'true' will be mapped to 'cloud'. Consult the docs: http://docs.cordova.io/en/edge/guide_project-settings_ios_index.md.html#Project%%20Settings%%20for%%20iOS"); - backupWebStorageType = [(NSNumber*) backupWebStorage boolValue] ? @"cloud" : @"none"; } self.settings[@"BackupWebStorage"] = backupWebStorageType; @@ -231,6 +269,10 @@ if ([self.settings objectForKey:@"MediaPlaybackRequiresUserAction"]) { mediaPlaybackRequiresUserAction = [(NSNumber*)[settings objectForKey:@"MediaPlaybackRequiresUserAction"] boolValue]; } + BOOL hideKeyboardFormAccessoryBar = NO; // default value + if ([self.settings objectForKey:@"HideKeyboardFormAccessoryBar"]) { + hideKeyboardFormAccessoryBar = [(NSNumber*)[settings objectForKey:@"HideKeyboardFormAccessoryBar"] boolValue]; + } self.webView.scalesPageToFit = [enableViewportScale boolValue]; @@ -239,14 +281,26 @@ */ if ([enableLocation boolValue]) { + NSLog(@"Deprecated: The 'EnableLocation' boolean property is deprecated in 2.5.0, and will be removed in 3.0.0. Use the 'onload' boolean attribute (of the CDVLocation plugin."); [[self.commandDelegate getCommandInstance:@"Geolocation"] getLocation:[CDVInvokedUrlCommand new]]; } + if (hideKeyboardFormAccessoryBar) { + __weak CDVViewController* weakSelf = self; + [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* notification) { + // we can't hide it here because the accessory bar hasn't been created yet, so we delay on the queue + [weakSelf performSelector:@selector(hideKeyboardFormAccessoryBar) withObject:nil afterDelay:0]; + }]; + } + /* * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup. */ if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorageType isEqualToString:@"local"]) || - ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { + ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])]; } @@ -260,12 +314,19 @@ self.webView.mediaPlaybackRequiresUserAction = NO; } - // UIWebViewBounce property - defaults to true - NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"]; - BOOL bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]); + // By default, overscroll bouncing is allowed. + // UIWebViewBounce has been renamed to DisallowOverscroll, but both are checked. + BOOL bounceAllowed = YES; + NSNumber* disallowOverscroll = [self.settings objectForKey:@"DisallowOverscroll"]; + if (disallowOverscroll == nil) { + NSNumber* bouncePreference = [self.settings objectForKey:@"UIWebViewBounce"]; + bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]); + } else { + bounceAllowed = ![disallowOverscroll boolValue]; + } // prevent webView from bouncing - // based on UIWebViewBounce key in config.xml + // based on the DisallowOverscroll/UIWebViewBounce key in config.xml if (!bounceAllowed) { if ([self.webView respondsToSelector:@selector(scrollView)]) { ((UIScrollView*)[self.webView scrollView]).bounces = NO; @@ -307,8 +368,16 @@ } } - for (NSString* pluginName in self.startupPluginNames) { - [self getCommandInstance:pluginName]; + if ([self.startupPluginNames count] > 0) { + [CDVTimer start:@"TotalPluginStartup"]; + + for (NSString* pluginName in self.startupPluginNames) { + [CDVTimer start:pluginName]; + [self getCommandInstance:pluginName]; + [CDVTimer stop:pluginName]; + } + + [CDVTimer stop:@"TotalPluginStartup"]; } // TODO: Remove this explicit instantiation once we move to cordova-CLI. @@ -318,16 +387,44 @@ // ///////////////// [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) { - _userAgentLockToken = lockToken; - [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; - if (!loadErr) { - NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; - [self.webView loadRequest:appReq]; - } else { - NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr]; - [self.webView loadHTMLString:html baseURL:nil]; + _userAgentLockToken = lockToken; + [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken]; + if (!loadErr) { + NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0]; + [self.webView loadRequest:appReq]; + } else { + NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr]; + [self.webView loadHTMLString:html baseURL:nil]; + } + }]; +} + +- (void)hideKeyboardFormAccessoryBar +{ + NSArray* windows = [[UIApplication sharedApplication] windows]; + + for (UIWindow* window in windows) { + for (UIView* view in window.subviews) { + if ([[view description] hasPrefix:@"<UIPeripheralHostView"]) { + for (UIView* peripheralView in view.subviews) { + // hides the accessory bar + if ([[peripheralView description] hasPrefix:@"<UIWebFormAccessory"]) { + // remove the extra scroll space for the form accessory bar + CGRect newFrame = self.webView.scrollView.frame; + newFrame.size.height += peripheralView.frame.size.height; + self.webView.scrollView.frame = newFrame; + + // remove the form accessory bar + [peripheralView removeFromSuperview]; + } + // hides the thin grey line used to adorn the bar (iOS 6) + if ([[peripheralView description] hasPrefix:@"<UIImageView"]) { + [[peripheralView layer] setOpacity:0.0]; + } + } } - }]; + } + } } - (NSArray*)parseInterfaceOrientations:(NSArray*)orientations @@ -518,12 +615,6 @@ // It's safe to release the lock even if this is just a sub-frame that's finished loading. [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; - // The .onNativeReady().fire() will work when cordova.js is already loaded. - // The _nativeReady = true; is used when this is run before cordova.js is loaded. - NSString* nativeReady = @"try{cordova.require('cordova/channel').onNativeReady.fire();}catch(e){window._nativeReady = true;}"; - // Don't use [commandDelegate evalJs] here since it relies on cordova.js being loaded already. - [self.webView stringByEvaluatingJavaScriptFromString:nativeReady]; - /* * Hide the Top Activity THROBBER in the Battery Bar */ @@ -531,7 +622,7 @@ [self processOpenUrl]; - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:nil]]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:self.webView]]; } - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error @@ -660,6 +751,22 @@ [plugin pluginInitialize]; } +- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName +{ + if ([plugin respondsToSelector:@selector(setViewController:)]) { + [plugin setViewController:self]; + } + + if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) { + [plugin setCommandDelegate:_commandDelegate]; + } + + NSString* className = NSStringFromClass([plugin class]); + [self.pluginObjects setObject:plugin forKey:className]; + [self.pluginsMap setValue:className forKey:[pluginName lowercaseString]]; + [plugin pluginInitialize]; +} + /** Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned. */ @@ -678,7 +785,7 @@ id obj = [self.pluginObjects objectForKey:className]; if (!obj) { - obj = [[NSClassFromString (className)alloc] initWithWebView:webView]; + obj = [[NSClassFromString(className)alloc] initWithWebView:webView]; if (obj != nil) { [self registerPlugin:obj withClassName:className]; @@ -814,13 +921,8 @@ - (void)dealloc { [CDVURLProtocol unregisterViewController:self]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:CDVPluginHandleOpenURLNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + self.webView.delegate = nil; self.webView = nil; [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h index 8a89a22..dd71807 100755 --- a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h +++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h @@ -30,8 +30,10 @@ NSInteger _loadCount; NSInteger _state; NSInteger _curLoadToken; + NSInteger _loadStartPollCount; } - (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate; +- (BOOL)request:(NSURLRequest*)newRequest isFragmentIdentifierToRequest:(NSURLRequest*)originalRequest; @end diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m index 9ee8186..7254c2e 100755 --- a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m +++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m @@ -17,14 +17,77 @@ under the License. */ +// +// Testing shows: +// +// In all cases, webView.request.URL is the previous page's URL (or empty) during the didStartLoad callback. +// When loading a page with a redirect: +// 1. shouldStartLoading (requestURL is target page) +// 2. didStartLoading +// 3. shouldStartLoading (requestURL is redirect target) +// 4. didFinishLoad (request.URL is redirect target) +// +// Note the lack of a second didStartLoading ** +// +// When loading a page with iframes: +// 1. shouldStartLoading (requestURL is main page) +// 2. didStartLoading +// 3. shouldStartLoading (requestURL is one of the iframes) +// 4. didStartLoading +// 5. didFinishLoad +// 6. didFinishLoad +// +// Note there is no way to distinguish which didFinishLoad maps to which didStartLoad ** +// +// Loading a page by calling window.history.go(-1): +// 1. didStartLoading +// 2. didFinishLoad +// +// Note the lack of a shouldStartLoading call ** +// Actually - this is fixed on iOS6. iOS6 has a shouldStart. ** +// +// Loading a page by calling location.reload() +// 1. shouldStartLoading +// 2. didStartLoading +// 3. didFinishLoad +// +// Loading a page with an iframe that fails to load: +// 1. shouldStart (main page) +// 2. didStart +// 3. shouldStart (iframe) +// 4. didStart +// 5. didFailWithError +// 6. didFinish +// +// Loading a page with an iframe that fails to load due to an invalid URL: +// 1. shouldStart (main page) +// 2. didStart +// 3. shouldStart (iframe) +// 5. didFailWithError +// 6. didFinish +// +// This case breaks our logic since there is a missing didStart. To prevent this, +// we check URLs in shouldStart and return NO if they are invalid. +// +// Loading a page with an invalid URL +// 1. shouldStart (main page) +// 2. didFailWithError +// +// TODO: Record order when page is re-navigated before the first navigation finishes. +// + #import "CDVWebViewDelegate.h" #import "CDVAvailability.h" +// #define VerboseLog NSLog +#define VerboseLog(...) do {} while (0) + typedef enum { - STATE_NORMAL, - STATE_SHOULD_LOAD_MISSING, - STATE_WAITING_FOR_START, - STATE_WAITING_FOR_FINISH + STATE_IDLE, + STATE_WAITING_FOR_LOAD_START, + STATE_WAITING_FOR_LOAD_FINISH, + STATE_IOS5_POLLING_FOR_LOAD_START, + STATE_IOS5_POLLING_FOR_LOAD_FINISH } State; @implementation CDVWebViewDelegate @@ -35,11 +98,50 @@ typedef enum { if (self != nil) { _delegate = delegate; _loadCount = -1; - _state = STATE_NORMAL; + _state = STATE_IDLE; } return self; } +- (BOOL)request:(NSURLRequest*)newRequest isFragmentIdentifierToRequest:(NSURLRequest*)originalRequest +{ + if (originalRequest.URL && newRequest.URL) { + NSString* originalRequestUrl = [originalRequest.URL absoluteString]; + NSString* newRequestUrl = [newRequest.URL absoluteString]; + + // no fragment, easy + if (newRequest.URL.fragment == nil) { + return NO; + } + + // if the urls have fragments and they are equal + if ((originalRequest.URL.fragment && newRequest.URL.fragment) && [originalRequestUrl isEqualToString:newRequestUrl]) { + return YES; + } + + NSString* urlFormat = @"%@://%@:%d/%@#%@"; + // reconstruct the URLs (ignoring basic auth credentials, query string) + NSString* baseOriginalRequestUrl = [NSString stringWithFormat:urlFormat, + [originalRequest.URL scheme], + [originalRequest.URL host], + [[originalRequest.URL port] intValue], + [originalRequest.URL path], + [newRequest.URL fragment] // add the new request's fragment + ]; + NSString* baseNewRequestUrl = [NSString stringWithFormat:urlFormat, + [newRequest.URL scheme], + [newRequest.URL host], + [[newRequest.URL port] intValue], + [newRequest.URL path], + [newRequest.URL fragment] + ]; + + return [baseOriginalRequestUrl isEqualToString:baseNewRequestUrl]; + } + + return NO; +} + - (BOOL)isPageLoaded:(UIWebView*)webView { NSString* readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"]; @@ -62,95 +164,216 @@ typedef enum { - (void)pollForPageLoadStart:(UIWebView*)webView { - if ((_state != STATE_WAITING_FOR_START) && (_state != STATE_SHOULD_LOAD_MISSING)) { + if (_state != STATE_IOS5_POLLING_FOR_LOAD_START) { return; } if (![self isJsLoadTokenSet:webView]) { - _state = STATE_WAITING_FOR_FINISH; + VerboseLog(@"Polled for page load start. result = YES!"); + _state = STATE_IOS5_POLLING_FOR_LOAD_FINISH; [self setLoadToken:webView]; - [_delegate webViewDidStartLoad:webView]; + if ([_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [_delegate webViewDidStartLoad:webView]; + } [self pollForPageLoadFinish:webView]; + } else { + VerboseLog(@"Polled for page load start. result = NO"); + // Poll only for 1 second, and then fall back on checking only when delegate methods are called. + ++_loadStartPollCount; + if (_loadStartPollCount < (1000 * .05)) { + [self performSelector:@selector(pollForPageLoadStart:) withObject:webView afterDelay:.05]; + } } } - (void)pollForPageLoadFinish:(UIWebView*)webView { - if (_state != STATE_WAITING_FOR_FINISH) { + if (_state != STATE_IOS5_POLLING_FOR_LOAD_FINISH) { return; } if ([self isPageLoaded:webView]) { - _state = STATE_SHOULD_LOAD_MISSING; - [_delegate webViewDidFinishLoad:webView]; + VerboseLog(@"Polled for page load finish. result = YES!"); + _state = STATE_IDLE; + if ([_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [_delegate webViewDidFinishLoad:webView]; + } } else { - [self performSelector:@selector(pollForPageLoaded) withObject:webView afterDelay:50]; + VerboseLog(@"Polled for page load finish. result = NO"); + [self performSelector:@selector(pollForPageLoadFinish:) withObject:webView afterDelay:.05]; } } - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType { - BOOL shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + BOOL shouldLoad = YES; + + if ([_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { + shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + } + + VerboseLog(@"webView shouldLoad=%d (before) state=%d loadCount=%d URL=%@", shouldLoad, _state, _loadCount, request.URL); if (shouldLoad) { BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; if (isTopLevelNavigation) { - _loadCount = 0; - _state = STATE_NORMAL; + switch (_state) { + case STATE_WAITING_FOR_LOAD_FINISH: + // Redirect case. + // We expect loadCount == 1. + if (_loadCount != 1) { + NSLog(@"CDVWebViewDelegate: Detected redirect when loadCount=%d", _loadCount); + } + break; + + case STATE_IDLE: + case STATE_IOS5_POLLING_FOR_LOAD_START: + // Page navigation start. + _loadCount = 0; + _state = STATE_WAITING_FOR_LOAD_START; + break; + + default: + { + NSString* description = [NSString stringWithFormat:@"CDVWebViewDelegate: Navigation started when state=%d", _state]; + NSLog(@"%@", description); + _loadCount = 0; + _state = STATE_WAITING_FOR_LOAD_START; + if (![self request:request isFragmentIdentifierToRequest:webView.request]) { + if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + NSDictionary* errorDictionary = @{NSLocalizedDescriptionKey : description}; + NSError* error = [[NSError alloc] initWithDomain:@"CDVWebViewDelegate" code:1 userInfo:errorDictionary]; + [_delegate webView:webView didFailLoadWithError:error]; + } + } + } + } + } else { + // Deny invalid URLs so that we don't get the case where we go straight from + // webViewShouldLoad -> webViewDidFailLoad (messes up _loadCount). + shouldLoad = shouldLoad && [NSURLConnection canHandleRequest:request]; } + VerboseLog(@"webView shouldLoad=%d (after) isTopLevelNavigation=%d state=%d loadCount=%d", shouldLoad, isTopLevelNavigation, _state, _loadCount); } return shouldLoad; } - (void)webViewDidStartLoad:(UIWebView*)webView { - if (_state == STATE_NORMAL) { - if (_loadCount == 0) { - [_delegate webViewDidStartLoad:webView]; - _loadCount += 1; - } else if (_loadCount > 0) { - _loadCount += 1; - } else if (!IsAtLeastiOSVersion(@"6.0")) { + VerboseLog(@"webView didStartLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + switch (_state) { + case STATE_IDLE: + if (IsAtLeastiOSVersion(@"6.0")) { + break; + } // If history.go(-1) is used pre-iOS6, the shouldStartLoadWithRequest function is not called. // Without shouldLoad, we can't distinguish an iframe from a top-level navigation. // We could try to distinguish using [UIWebView canGoForward], but that's too much complexity, // and would work only on the first time it was used. // Our work-around is to set a JS variable and poll until it disappears (from a naviagtion). - _state = STATE_WAITING_FOR_START; + _state = STATE_IOS5_POLLING_FOR_LOAD_START; + _loadStartPollCount = 0; [self setLoadToken:webView]; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + [self pollForPageLoadStart:webView]; + break; + + case STATE_WAITING_FOR_LOAD_START: + if (_loadCount != 0) { + NSLog(@"CDVWebViewDelegate: Unexpected loadCount in didStart. count=%d", _loadCount); + } + fireCallback = YES; + _state = STATE_WAITING_FOR_LOAD_FINISH; + _loadCount = 1; + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + _loadCount += 1; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + + default: + NSLog(@"CDVWebViewDelegate: Unexpected didStart with state=%d loadCount=%d", _state, _loadCount); + } + VerboseLog(@"webView didStartLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [_delegate webViewDidStartLoad:webView]; } } - (void)webViewDidFinishLoad:(UIWebView*)webView { - if (_state == STATE_NORMAL) { - if (_loadCount == 1) { - [_delegate webViewDidFinishLoad:webView]; - _loadCount -= 1; - } else if (_loadCount > 1) { + VerboseLog(@"webView didFinishLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + switch (_state) { + case STATE_IDLE: + break; + + case STATE_WAITING_FOR_LOAD_START: + NSLog(@"CDVWebViewDelegate: Unexpected didFinish while waiting for load start."); + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + if (_loadCount == 1) { + fireCallback = YES; + _state = STATE_IDLE; + } _loadCount -= 1; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + } + VerboseLog(@"webView didFinishLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { + [_delegate webViewDidFinishLoad:webView]; } } - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error { - if (_state == STATE_NORMAL) { - if (_loadCount == 1) { - [_delegate webView:webView didFailLoadWithError:error]; - _loadCount -= 1; - } else if (_loadCount > 1) { - _loadCount -= 1; - } - } else { - [self pollForPageLoadStart:webView]; - [self pollForPageLoadFinish:webView]; + VerboseLog(@"webView didFailLoad (before). state=%d loadCount=%d", _state, _loadCount); + BOOL fireCallback = NO; + + switch (_state) { + case STATE_IDLE: + break; + + case STATE_WAITING_FOR_LOAD_START: + _state = STATE_IDLE; + fireCallback = YES; + break; + + case STATE_WAITING_FOR_LOAD_FINISH: + if (_loadCount == 1) { + _state = STATE_IDLE; + fireCallback = YES; + } + _loadCount = -1; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_START: + [self pollForPageLoadStart:webView]; + break; + + case STATE_IOS5_POLLING_FOR_LOAD_FINISH: + [self pollForPageLoadFinish:webView]; + break; + } + VerboseLog(@"webView didFailLoad (after). state=%d loadCount=%d, fireCallback=%d", _state, _loadCount, fireCallback); + if (fireCallback && [_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { + [_delegate webView:webView didFailLoadWithError:error]; } } diff --git a/iPhone/CordovaLib/Classes/CDVWhitelist.h b/iPhone/CordovaLib/Classes/CDVWhitelist.h index 3741e94..9165097 100755 --- a/iPhone/CordovaLib/Classes/CDVWhitelist.h +++ b/iPhone/CordovaLib/Classes/CDVWhitelist.h @@ -23,14 +23,12 @@ extern NSString* const kCDVDefaultWhitelistRejectionString; @interface CDVWhitelist : NSObject -@property (nonatomic, readonly, strong) NSArray* whitelist; -@property (nonatomic, readonly, strong) NSArray* expandedWhitelist; -@property (nonatomic, readonly, assign) BOOL allowAll; @property (nonatomic, copy) NSString* whitelistRejectionFormatString; - (id)initWithArray:(NSArray*)array; -- (BOOL)URLIsAllowed:(NSURL*)url; - (BOOL)schemeIsAllowed:(NSString*)scheme; +- (BOOL)URLIsAllowed:(NSURL*)url; +- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure; - (NSString*)errorStringForURL:(NSURL*)url; @end diff --git a/iPhone/CordovaLib/Classes/CDVWhitelist.m b/iPhone/CordovaLib/Classes/CDVWhitelist.m index 77e20ac..e6807bd 100755 --- a/iPhone/CordovaLib/Classes/CDVWhitelist.m +++ b/iPhone/CordovaLib/Classes/CDVWhitelist.m @@ -20,12 +20,12 @@ #import "CDVWhitelist.h" NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejection: url='%@'"; +NSString* const kCDVDefaultSchemeName = @"cdv-default-scheme"; @interface CDVWhitelist () @property (nonatomic, readwrite, strong) NSArray* whitelist; -@property (nonatomic, readwrite, strong) NSArray* expandedWhitelist; -@property (nonatomic, readwrite, assign) BOOL allowAll; +@property (nonatomic, readwrite, strong) NSDictionary* expandedWhitelists; - (void)processWhitelist; @@ -33,15 +33,14 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio @implementation CDVWhitelist -@synthesize whitelist, expandedWhitelist, allowAll, whitelistRejectionFormatString; +@synthesize whitelist, expandedWhitelists, whitelistRejectionFormatString; - (id)initWithArray:(NSArray*)array { self = [super init]; if (self) { self.whitelist = array; - self.expandedWhitelist = nil; - self.allowAll = NO; + self.expandedWhitelists = nil; self.whitelistRejectionFormatString = kCDVDefaultWhitelistRejectionString; [self processWhitelist]; } @@ -94,6 +93,17 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio } } +- (NSString*)extractSchemeFromUrlString:(NSString*)url +{ + NSURL* aUrl = [NSURL URLWithString:url]; + + if ((aUrl != nil) && ([aUrl scheme] != nil)) { // found scheme + return [aUrl scheme]; + } else { + return kCDVDefaultSchemeName; + } +} + - (void)processWhitelist { if (self.whitelist == nil) { @@ -101,70 +111,99 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio return; } - NSMutableArray* expanded = [NSMutableArray arrayWithCapacity:[self.whitelist count]]; - - // iterate through settings ExternalHosts, check for equality - NSEnumerator* enumerator = [self.whitelist objectEnumerator]; - id externalHost = nil; + NSMutableDictionary* _expandedWhitelists = [@{kCDVDefaultSchemeName: [NSMutableArray array]} mutableCopy]; // only allow known TLDs (since Aug 23rd 2011), and two character country codes // does not match internationalized domain names with non-ASCII characters NSString* tld_match = @"(aero|asia|arpa|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|[a-z][a-z])"; - while (externalHost = [enumerator nextObject]) { - NSString* regex = [self extractHostFromUrlString:externalHost]; - BOOL is_ip = [self isIPv4Address:regex]; + // iterate through settings ExternalHosts, check for equality + for (NSString* externalHost in self.whitelist) { + NSString* host = [self extractHostFromUrlString:externalHost]; + NSString* scheme = [self extractSchemeFromUrlString:externalHost]; // check for single wildcard '*', if found set allowAll to YES - if ([regex isEqualToString:@"*"]) { - self.allowAll = YES; - self.expandedWhitelist = [NSArray arrayWithObject:regex]; - break; + if ([host isEqualToString:@"*"]) { + [_expandedWhitelists setObject:[NSMutableArray arrayWithObject:host] forKey:scheme]; + continue; + } + + // if this is the first value for this scheme, create a new entry + if ([_expandedWhitelists objectForKey:scheme] == nil) { + [_expandedWhitelists setObject:[NSMutableArray array] forKey:scheme]; } // starts with wildcard match - we make the first '.' optional (so '*.org.apache.cordova' will match 'org.apache.cordova') NSString* prefix = @"*."; - if ([regex hasPrefix:prefix]) { + if ([host hasPrefix:prefix]) { // replace the first two characters '*.' with our regex - regex = [regex stringByReplacingCharactersInRange:NSMakeRange(0, [prefix length]) withString:@"(\\s{0}|*.)"]; // the '*' and '.' will be substituted later + host = [host stringByReplacingCharactersInRange:NSMakeRange(0, [prefix length]) withString:@"(\\s{0}|*.)"]; // the '*' and '.' will be substituted later } // ends with wildcard match for TLD - if (!is_ip && [regex hasSuffix:@".*"]) { + if (![self isIPv4Address:host] && [host hasSuffix:@".*"]) { // replace * with tld_match - regex = [regex stringByReplacingCharactersInRange:NSMakeRange([regex length] - 1, 1) withString:tld_match]; + host = [host stringByReplacingCharactersInRange:NSMakeRange([host length] - 1, 1) withString:tld_match]; } // escape periods - since '.' means any character in regex - regex = [regex stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; + host = [host stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; // wildcard is match 1 or more characters (to make it simple, since we are not doing verification whether the hostname is valid) - regex = [regex stringByReplacingOccurrencesOfString:@"*" withString:@".*"]; + host = [host stringByReplacingOccurrencesOfString:@"*" withString:@".*"]; - [expanded addObject:regex]; + [[_expandedWhitelists objectForKey:scheme] addObject:host]; } - self.expandedWhitelist = expanded; + self.expandedWhitelists = _expandedWhitelists; } - (BOOL)schemeIsAllowed:(NSString*)scheme { - return [scheme isEqualToString:@"http"] || - [scheme isEqualToString:@"https"] || - [scheme isEqualToString:@"ftp"] || - [scheme isEqualToString:@"ftps"]; + if ([scheme isEqualToString:@"http"] || + [scheme isEqualToString:@"https"] || + [scheme isEqualToString:@"ftp"] || + [scheme isEqualToString:@"ftps"]) { + return YES; + } + + return (self.expandedWhitelists != nil) && ([self.expandedWhitelists objectForKey:scheme] != nil); } - (BOOL)URLIsAllowed:(NSURL*)url { - if (self.expandedWhitelist == nil) { + return [self URLIsAllowed:url logFailure:YES]; +} + +- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure +{ + NSString* scheme = [url scheme]; + + // http[s] and ftp[s] should also validate against the common set in the kCDVDefaultSchemeName list + if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"] || [scheme isEqualToString:@"ftps"]) { + NSURL* newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", kCDVDefaultSchemeName, [url host]]]; + // If it is allowed, we are done. If not, continue to check for the actual scheme-specific list + if ([self URLIsAllowed:newUrl logFailure:NO]) { + return YES; + } + } + + // Check that the scheme is supported + if (![self schemeIsAllowed:scheme]) { + if (logFailure) { + NSLog(@"%@", [self errorStringForURL:url]); + } return NO; } - if (self.allowAll) { + NSArray* expandedWhitelist = [self.expandedWhitelists objectForKey:scheme]; + + // Are we allowing everything for this scheme? + // TODO: consider just having a static sentinel value for the "allow all" list, so we can use object equality + if (([expandedWhitelist count] == 1) && [[expandedWhitelist objectAtIndex:0] isEqualToString:@"*"]) { return YES; } // iterate through settings ExternalHosts, check for equality - NSEnumerator* enumerator = [self.expandedWhitelist objectEnumerator]; + NSEnumerator* enumerator = [expandedWhitelist objectEnumerator]; id regex = nil; NSString* urlHost = [url host]; @@ -173,13 +212,20 @@ NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejectio while (regex = [enumerator nextObject]) { NSPredicate* regex_test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex]; + // if wildcard, break out and allow + if ([regex isEqualToString:@"*"]) { + return YES; + } + if ([regex_test evaluateWithObject:urlHost] == YES) { // if it matches at least one rule, return return YES; } } - NSLog(@"%@", [self errorStringForURL:url]); + if (logFailure) { + NSLog(@"%@", [self errorStringForURL:url]); + } // if we got here, the url host is not in the white-list, do nothing return NO; } diff --git a/iPhone/CordovaLib/Classes/NSData+Base64.m b/iPhone/CordovaLib/Classes/NSData+Base64.m index 08c801b..d0f2189 100755 --- a/iPhone/CordovaLib/Classes/NSData+Base64.m +++ b/iPhone/CordovaLib/Classes/NSData+Base64.m @@ -249,13 +249,10 @@ char *CDVNewBase64Encode( // + (NSData*)dataFromBase64String:(NSString*)aString { - NSData* data = [aString dataUsingEncoding:NSASCIIStringEncoding]; - size_t outputLength; - void* outputBuffer = CDVNewBase64Decode([data bytes], [data length], &outputLength); - NSData* result = [NSData dataWithBytes:outputBuffer length:outputLength]; + size_t outputLength = 0; + void* outputBuffer = CDVNewBase64Decode([aString UTF8String], [aString length], &outputLength); - free(outputBuffer); - return result; + return [NSData dataWithBytesNoCopy:outputBuffer length:outputLength freeWhenDone:YES]; } // @@ -273,13 +270,11 @@ char *CDVNewBase64Encode( char* outputBuffer = CDVNewBase64Encode([self bytes], [self length], true, &outputLength); - NSString* result = - [[NSString alloc] - initWithBytes:outputBuffer - length:outputLength - encoding:NSASCIIStringEncoding]; + NSString* result = [[NSString alloc] initWithBytesNoCopy:outputBuffer + length:outputLength + encoding:NSASCIIStringEncoding + freeWhenDone:YES]; - free(outputBuffer); return result; } diff --git a/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m index 80e9ac1..0361ff9 100755 --- a/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m +++ b/iPhone/CordovaLib/Classes/NSDictionary+Extensions.m @@ -28,7 +28,7 @@ bool exists = false; if (val != nil) { - exists = [(NSString*) val compare:expectedValue options:NSCaseInsensitiveSearch] == 0; + exists = [(NSString*)val compare : expectedValue options : NSCaseInsensitiveSearch] == 0; } return exists; diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h deleted file mode 100755 index 9794fa2..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDV.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6. -// - -/* - Returns YES if it is at least version specified as NSString(X) - Usage: - if (IsAtLeastiOSVersion(@"5.1")) { - // do something for iOS 5.1 or greater - } - */ -#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending) diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h deleted file mode 100755 index 7a06e51..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6. -// - -#ifdef PHONEGAP_FRAMEWORK - #import <PhoneGap/PGPlugin.h> -#else - #import "PGPlugin.h" -#endif - -typedef enum { - CDVCommandStatus_NO_RESULT = 0, - CDVCommandStatus_OK, - CDVCommandStatus_CLASS_NOT_FOUND_EXCEPTION, - CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION, - CDVCommandStatus_INSTANTIATION_EXCEPTION, - CDVCommandStatus_MALFORMED_URL_EXCEPTION, - CDVCommandStatus_IO_EXCEPTION, - CDVCommandStatus_INVALID_ACTION, - CDVCommandStatus_JSON_EXCEPTION, - CDVCommandStatus_ERROR -} CDVCommandStatus; - -@interface CDVPlugin : PGPlugin -@end - -@interface CDVPluginResult : PluginResult -@end diff --git a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m b/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m deleted file mode 100755 index 52ccd41..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/0.9.6/CDVPlugin.m +++ /dev/null @@ -1,29 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// Bridge implementation file for using Cordova plugins in PhoneGap 0.9.6. -// - -#import "CDVPlugin.h" - -@implementation CDVPlugin -@end - -@implementation CDVPluginResult -@end diff --git a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h deleted file mode 100755 index 1c76eaa..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDV.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// Bridge implementation file for using Cordova > 1.5 plugins in 1.5.0. -// - -#import <Cordova/CDV.h> - -/* - Returns YES if it is at least version specified as NSString(X) - Usage: - if (IsAtLeastiOSVersion(@"5.1")) { - // do something for iOS 5.1 or greater - } - */ -#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending) diff --git a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h b/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h deleted file mode 100755 index 6e2c4c3..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/1.5.0/CDVPlugin.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// Bridge implementation file for using Cordova > 1.5 plugins in 1.5.0. -// - -#import <Cordova/CDVPlugin.h> diff --git a/iPhone/CordovaLib/Classes/compatibility/README.txt b/iPhone/CordovaLib/Classes/compatibility/README.txt deleted file mode 100755 index 8c2d644..0000000 --- a/iPhone/CordovaLib/Classes/compatibility/README.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -Include these headers if you are using a bleeding edge plugin in an older version of Cordova. - -1.5.0 -- only for 1.5.0 projects -0.9.6 -- for projects between 0.9.6 and 1.4.1
\ No newline at end of file diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index 4868020..a4fcc39 100755 --- a/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -44,6 +44,11 @@ 30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */; }; 3E76876F156A90EE00EB6FA3 /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B7516E16FD18190076A8B4 /* CDVJpegHeaderWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B7516F16FD18190076A8B4 /* CDVJpegHeaderWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */; }; + 68B7517016FD19F80076A8B4 /* CDVExif.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B7516A16FD18190076A8B4 /* CDVExif.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E14B5A81705050A0032169E /* CDVTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E14B5A61705050A0032169E /* CDVTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E14B5A91705050A0032169E /* CDVTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E14B5A71705050A0032169E /* CDVTimer.m */; }; 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43614B65FD800F0E735 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43714B65FD800F0E735 /* CDVViewController.m */; }; 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD261090FBE7009987E8 /* CDVCamera.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -52,8 +57,6 @@ 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */; }; 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2A1090FBE7009987E8 /* CDVContacts.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2B1090FBE7009987E8 /* CDVContacts.m */; }; - 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */; }; 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD301090FBE7009987E8 /* CDVFile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD311090FBE7009987E8 /* CDVFile.m */; }; 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -141,6 +144,12 @@ 686357DC14100B1600DF4CF2 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 68A32D7114102E1C006B237C /* libCordova.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCordova.a; sourceTree = BUILT_PRODUCTS_DIR; }; 68A32D7414103017006B237C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; + 68B7516A16FD18190076A8B4 /* CDVExif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVExif.h; path = Classes/CDVExif.h; sourceTree = "<group>"; }; + 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVJpegHeaderWriter.h; path = Classes/CDVJpegHeaderWriter.h; sourceTree = "<group>"; }; + 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVJpegHeaderWriter.m; path = Classes/CDVJpegHeaderWriter.m; sourceTree = "<group>"; }; + 7E14B5A61705050A0032169E /* CDVTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVTimer.h; path = Classes/CDVTimer.h; sourceTree = "<group>"; }; + 7E14B5A71705050A0032169E /* CDVTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVTimer.m; path = Classes/CDVTimer.m; sourceTree = "<group>"; }; + 8220B5C316D5427E00EC3921 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 8852C43614B65FD800F0E735 /* CDVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVViewController.h; path = Classes/CDVViewController.h; sourceTree = "<group>"; }; 8852C43714B65FD800F0E735 /* CDVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVViewController.m; path = Classes/CDVViewController.m; sourceTree = "<group>"; }; 8887FD261090FBE7009987E8 /* CDVCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCamera.h; path = Classes/CDVCamera.h; sourceTree = "<group>"; }; @@ -149,8 +158,6 @@ 8887FD291090FBE7009987E8 /* NSDictionary+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Extensions.m"; path = "Classes/NSDictionary+Extensions.m"; sourceTree = "<group>"; }; 8887FD2A1090FBE7009987E8 /* CDVContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVContacts.h; path = Classes/CDVContacts.h; sourceTree = "<group>"; }; 8887FD2B1090FBE7009987E8 /* CDVContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVContacts.m; path = Classes/CDVContacts.m; sourceTree = "<group>"; }; - 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDebugConsole.h; path = Classes/CDVDebugConsole.h; sourceTree = "<group>"; }; - 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVDebugConsole.m; path = Classes/CDVDebugConsole.m; sourceTree = "<group>"; }; 8887FD301090FBE7009987E8 /* CDVFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVFile.h; path = Classes/CDVFile.h; sourceTree = "<group>"; }; 8887FD311090FBE7009987E8 /* CDVFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVFile.m; path = Classes/CDVFile.m; sourceTree = "<group>"; }; 8887FD341090FBE7009987E8 /* CDVInvokedUrlCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVInvokedUrlCommand.h; path = Classes/CDVInvokedUrlCommand.h; sourceTree = "<group>"; }; @@ -223,6 +230,7 @@ isa = PBXGroup; children = ( 68A32D7414103017006B237C /* AddressBook.framework */, + 8220B5C316D5427E00EC3921 /* AssetsLibrary.framework */, 686357DC14100B1600DF4CF2 /* CoreMedia.framework */, 686357CE14100ADA00DF4CF2 /* AudioToolbox.framework */, 686357CF14100ADB00DF4CF2 /* AVFoundation.framework */, @@ -292,12 +300,13 @@ 8887FD271090FBE7009987E8 /* CDVCamera.m */, 1F584B991385A28900ED25E8 /* CDVCapture.h */, 1F584B9A1385A28900ED25E8 /* CDVCapture.m */, + 68B7516A16FD18190076A8B4 /* CDVExif.h */, + 68B7516B16FD18190076A8B4 /* CDVJpegHeaderWriter.h */, + 68B7516C16FD18190076A8B4 /* CDVJpegHeaderWriter.m */, 1F3C04CC12BC247D004F9E10 /* CDVContact.h */, 1F3C04CD12BC247D004F9E10 /* CDVContact.m */, 8887FD2A1090FBE7009987E8 /* CDVContacts.h */, 8887FD2B1090FBE7009987E8 /* CDVContacts.m */, - 8887FD2C1090FBE7009987E8 /* CDVDebugConsole.h */, - 8887FD2D1090FBE7009987E8 /* CDVDebugConsole.m */, EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */, EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */, 8887FD301090FBE7009987E8 /* CDVFile.h */, @@ -343,6 +352,8 @@ 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */, 8887FD501090FBE7009987E8 /* NSData+Base64.h */, 8887FD511090FBE7009987E8 /* NSData+Base64.m */, + 7E14B5A61705050A0032169E /* CDVTimer.h */, + 7E14B5A71705050A0032169E /* CDVTimer.m */, ); name = Util; sourceTree = "<group>"; @@ -364,10 +375,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 68B7517016FD19F80076A8B4 /* CDVExif.h in Headers */, + 68B7516E16FD18190076A8B4 /* CDVJpegHeaderWriter.h in Headers */, 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */, 8887FD681090FBE7009987E8 /* NSDictionary+Extensions.h in Headers */, 8887FD6A1090FBE7009987E8 /* CDVContacts.h in Headers */, - 8887FD6C1090FBE7009987E8 /* CDVDebugConsole.h in Headers */, 8887FD701090FBE7009987E8 /* CDVFile.h in Headers */, 8887FD741090FBE7009987E8 /* CDVInvokedUrlCommand.h in Headers */, 8887FD851090FBE7009987E8 /* CDVLocation.h in Headers */, @@ -407,6 +419,7 @@ 30F3930B169F839700B22307 /* CDVJSON.h in Headers */, EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */, EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */, + 7E14B5A81705050A0032169E /* CDVTimer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -467,7 +480,6 @@ 8887FD671090FBE7009987E8 /* CDVCamera.m in Sources */, 8887FD691090FBE7009987E8 /* NSDictionary+Extensions.m in Sources */, 8887FD6B1090FBE7009987E8 /* CDVContacts.m in Sources */, - 8887FD6D1090FBE7009987E8 /* CDVDebugConsole.m in Sources */, 8887FD711090FBE7009987E8 /* CDVFile.m in Sources */, 8887FD751090FBE7009987E8 /* CDVInvokedUrlCommand.m in Sources */, 8887FD861090FBE7009987E8 /* CDVLocation.m in Sources */, @@ -502,6 +514,8 @@ 30F3930C169F839700B22307 /* CDVJSON.m in Sources */, EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */, EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */, + 68B7516F16FD18190076A8B4 /* CDVJpegHeaderWriter.m in Sources */, + 7E14B5A91705050A0032169E /* CDVTimer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -517,6 +531,10 @@ armv7, armv7s, ); + "ARCHS[sdk=iphoneos7.*]" = ( + armv7, + armv7s, + ); "ARCHS[sdk=iphonesimulator*]" = i386; CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; @@ -546,6 +564,10 @@ armv7, armv7s, ); + "ARCHS[sdk=iphoneos7.*]" = ( + armv7, + armv7s, + ); "ARCHS[sdk=iphonesimulator*]" = i386; CLANG_ENABLE_OBJC_ARC = YES; DSTROOT = "/tmp/$(PROJECT_NAME).dst"; @@ -571,6 +593,10 @@ armv7, armv7s, ); + "ARCHS[sdk=iphoneos7.*]" = ( + armv7, + armv7s, + ); "ARCHS[sdk=iphonesimulator*]" = i386; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES; @@ -584,7 +610,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "-DDEBUG"; PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; @@ -604,6 +630,10 @@ armv7, armv7s, ); + "ARCHS[sdk=iphoneos7.*]" = ( + armv7, + armv7s, + ); "ARCHS[sdk=iphonesimulator*]" = i386; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES; @@ -616,7 +646,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = NO; PUBLIC_HEADERS_FOLDER_PATH = include/Cordova; SDKROOT = iphoneos; diff --git a/iPhone/CordovaLib/VERSION b/iPhone/CordovaLib/VERSION index 437459c..c8e38b6 100755 --- a/iPhone/CordovaLib/VERSION +++ b/iPhone/CordovaLib/VERSION @@ -1 +1 @@ -2.5.0 +2.9.0 diff --git a/www/cordova-ios-2.5.0.js b/iPhone/CordovaLib/cordova.js index 3d83df3..9bd85e2 100755 --- a/www/cordova-ios-2.5.0.js +++ b/iPhone/CordovaLib/cordova.js @@ -1,9 +1,5 @@ // Platform: ios - -// commit f50d20a87431c79a54572263729461883f611a53 - -// File generated at :: Tue Feb 26 2013 14:26:19 GMT-0800 (PST) - +// 2.9.0-0-g83dc4bd /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file @@ -22,26 +18,36 @@ specific language governing permissions and limitations under the License. */ - ;(function() { - +var CORDOVA_JS_BUILD_LABEL = '2.9.0-0-g83dc4bd'; // file: lib/scripts/require.js var require, define; (function () { - var modules = {}; + var modules = {}, // Stack of moduleIds currently being built. - var requireStack = []; + requireStack = [], // Map of module ID -> index into requireStack of modules currently being built. - var inProgressModules = {}; + inProgressModules = {}, + SEPERATOR = "."; + + function build(module) { - var factory = module.factory; + var factory = module.factory, + localRequire = function (id) { + var resultantId = id; + //Its a relative path, so lop off the last portion and add the id (minus "./") + if (id.charAt(0) === ".") { + resultantId = module.id.slice(0, module.id.lastIndexOf(SEPERATOR)) + SEPERATOR + id.slice(2); + } + return require(resultantId); + }; module.exports = {}; delete module.factory; - factory(require, module.exports, module); + factory(localRequire, module.exports, module); return module.exports; } @@ -219,6 +225,10 @@ var cordova = { } else { setTimeout(function() { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type == 'deviceready') { + document.dispatchEvent(evt); + } documentEventHandlers[type].fire(evt); }, 0); } @@ -262,7 +272,7 @@ var cordova = { */ callbackSuccess: function(callbackId, args) { try { - cordova.callbackFromNative(callbackId, true, args.status, args.message, args.keepCallback); + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); } catch (e) { console.log("Error in error callback: " + callbackId + " = "+e); } @@ -275,7 +285,7 @@ var cordova = { // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. // Derive success from status. try { - cordova.callbackFromNative(callbackId, false, args.status, args.message, args.keepCallback); + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); } catch (e) { console.log("Error in error callback: " + callbackId + " = "+e); } @@ -284,13 +294,13 @@ var cordova = { /** * Called by native code when returning the result from an action. */ - callbackFromNative: function(callbackId, success, status, message, keepCallback) { + callbackFromNative: function(callbackId, success, status, args, keepCallback) { var callback = cordova.callbacks[callbackId]; if (callback) { if (success && status == cordova.callbackStatus.OK) { - callback.success && callback.success(message); + callback.success && callback.success.apply(null, args); } else if (!success) { - callback.fail && callback.fail(message); + callback.fail && callback.fail.apply(null, args); } // Clear callback if not expecting any more results @@ -724,6 +734,9 @@ channel.createSticky('onCordovaInfoReady'); // Event to indicate that the connection property has been set. channel.createSticky('onCordovaConnectionReady'); +// Event to indicate that all automatically loaded JS plugins are loaded and ready. +channel.createSticky('onPluginsReady'); + // Event to indicate that Cordova is ready channel.createSticky('onDeviceReady'); @@ -739,6 +752,7 @@ channel.createSticky('onDestroy'); // Channels that must fire before "deviceready" is fired. channel.waitForInitialization('onCordovaReady'); channel.waitForInitialization('onCordovaConnectionReady'); +channel.waitForInitialization('onDOMContentLoaded'); module.exports = channel; @@ -826,6 +840,7 @@ function massageArgsJsToNative(args) { if (!args || utils.typeName(args) != 'Array') { return args; } + var ret = []; var encodeArrayBufferAs8bitString = function(ab) { return String.fromCharCode.apply(null, new Uint8Array(ab)); }; @@ -834,17 +849,19 @@ function massageArgsJsToNative(args) { }; args.forEach(function(arg, i) { if (utils.typeName(arg) == 'ArrayBuffer') { - args[i] = { + ret.push({ 'CDVType': 'ArrayBuffer', 'data': encodeArrayBufferAsBase64(arg) - }; + }); + } else { + ret.push(arg); } }); - return args; + return ret; } -function massagePayloadNativeToJs(payload) { - if (payload && payload.hasOwnProperty('CDVType') && payload.CDVType == 'ArrayBuffer') { +function massageMessageNativeToJs(message) { + if (message.CDVType == 'ArrayBuffer') { var stringToArrayBuffer = function(str) { var ret = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { @@ -855,9 +872,23 @@ function massagePayloadNativeToJs(payload) { var base64ToArrayBuffer = function(b64) { return stringToArrayBuffer(atob(b64)); }; - payload = base64ToArrayBuffer(payload.data); + message = base64ToArrayBuffer(message.data); } - return payload; + return message; +} + +function convertMessageToArgsNativeToJs(message) { + var args = []; + if (!message || !message.hasOwnProperty('CDVType')) { + args.push(message); + } else if (message.CDVType == 'MultiPart') { + message.messages.forEach(function(e) { + args.push(massageMessageNativeToJs(e)); + }); + } else { + args.push(massageMessageNativeToJs(message)); + } + return args; } function iOSExec() { @@ -884,11 +915,19 @@ function iOSExec() { // an invalid callbackId and passes it even if no callbacks were given. callbackId = 'INVALID'; } else { - // FORMAT TWO - splitCommand = arguments[0].split("."); - action = splitCommand.pop(); - service = splitCommand.join("."); - actionArgs = Array.prototype.splice.call(arguments, 1); + // FORMAT TWO, REMOVED + try { + splitCommand = arguments[0].split("."); + action = splitCommand.pop(); + service = splitCommand.join("."); + actionArgs = Array.prototype.splice.call(arguments, 1); + + console.log('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + + "cordova.exec(null, null, \"" + service + "\", \"" + action + "\"," + JSON.stringify(actionArgs) + ");" + ); + return; + } catch (e) { + } } // Register the callbacks and add the callbackId to the positional @@ -964,11 +1003,11 @@ iOSExec.nativeFetchMessages = function() { return json; }; -iOSExec.nativeCallback = function(callbackId, status, payload, keepCallback) { +iOSExec.nativeCallback = function(callbackId, status, message, keepCallback) { return iOSExec.nativeEvalAndFetch(function() { var success = status === 0 || status === 1; - payload = massagePayloadNativeToJs(payload); - cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); + var args = convertMessageToArgsNativeToJs(message); + cordova.callbackFromNative(callbackId, success, status, args, keepCallback); }); }; @@ -1161,9 +1200,10 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) { var correctOrientation = !!options.correctOrientation; var saveToPhotoAlbum = !!options.saveToPhotoAlbum; var popoverOptions = getValue(options.popoverOptions, null); + var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, - mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions]; + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection]; exec(successCallback, errorCallback, "Camera", "takePicture", args); return new CameraPopoverHandle(); @@ -1206,6 +1246,10 @@ module.exports = { ARROW_LEFT : 4, ARROW_RIGHT : 8, ARROW_ANY : 15 + }, + Direction:{ + BACK: 0, + FRONT: 1 } }; @@ -1263,8 +1307,6 @@ var CaptureAudioOptions = function(){ this.limit = 1; // Maximum duration of a single sound clip in seconds. this.duration = 0; - // The selected audio mode. Must match with one of the elements in supportedAudioModes array. - this.mode = null; }; module.exports = CaptureAudioOptions; @@ -1305,8 +1347,6 @@ define("cordova/plugin/CaptureImageOptions", function(require, exports, module) var CaptureImageOptions = function(){ // Upper limit of images user can take. Value must be equal or greater than 1. this.limit = 1; - // The selected image mode. Must match with one of the elements in supportedImageModes array. - this.mode = null; }; module.exports = CaptureImageOptions; @@ -1324,8 +1364,6 @@ var CaptureVideoOptions = function(){ this.limit = 1; // Maximum duration of a single video clip in seconds. this.duration = 0; - // The selected video mode. Must match with one of the elements in supportedVideoModes array. - this.mode = null; }; module.exports = CaptureVideoOptions; @@ -2345,11 +2383,7 @@ function initRead(reader, file) { reader._error = null; reader._readyState = FileReader.LOADING; - if (typeof file == 'string') { - // Deprecated in Cordova 2.4. - console.warning('Using a string argument with FileReader.readAs functions is deprecated.'); - reader._fileName = file; - } else if (typeof file.fullPath == 'string') { + if (typeof file.fullPath == 'string') { reader._fileName = file.fullPath; } else { reader._fileName = ''; @@ -2398,14 +2432,7 @@ FileReader.prototype.readAsText = function(file, encoding) { // Default encoding is UTF-8 var enc = encoding ? encoding : "UTF-8"; var me = this; - var execArgs = [this._fileName, enc]; - - // Maybe add slice parameters. - if (file.end < file.size) { - execArgs.push(file.start, file.end); - } else if (file.start > 0) { - execArgs.push(file.start); - } + var execArgs = [this._fileName, enc, file.start, file.end]; // Read file exec( @@ -2474,14 +2501,7 @@ FileReader.prototype.readAsDataURL = function(file) { } var me = this; - var execArgs = [this._fileName]; - - // Maybe add slice parameters. - if (file.end < file.size) { - execArgs.push(file.start, file.end); - } else if (file.start > 0) { - execArgs.push(file.start); - } + var execArgs = [this._fileName, file.start, file.end]; // Read file exec( @@ -2544,9 +2564,59 @@ FileReader.prototype.readAsBinaryString = function(file) { if (initRead(this, file)) { return this._realReader.readAsBinaryString(file); } - // TODO - Can't return binary data to browser. - console.log('method "readAsBinaryString" is not supported at this time.'); - this.abort(); + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsBinaryString", execArgs); }; /** @@ -2558,9 +2628,59 @@ FileReader.prototype.readAsArrayBuffer = function(file) { if (initRead(this, file)) { return this._realReader.readAsArrayBuffer(file); } - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); - this.abort(); + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsArrayBuffer", execArgs); }; module.exports = FileReader; @@ -2606,6 +2726,38 @@ function newProgressEvent(result) { return pe; } +function getBasicAuthHeader(urlString) { + var header = null; + + if (window.btoa) { + // parse the url using the Location object + var url = document.createElement('a'); + url.href = urlString; + + var credentials = null; + var protocol = url.protocol + "//"; + var origin = protocol + url.host; + + // check whether there are the username:password credentials in the url + if (url.href.indexOf(origin) !== 0) { // credentials found + var atIndex = url.href.indexOf("@"); + credentials = url.href.substring(protocol.length, atIndex); + } + + if (credentials) { + var authHeader = "Authorization"; + var authHeaderValue = "Basic " + window.btoa(credentials); + + header = { + name : authHeader, + value : authHeaderValue + }; + } + } + + return header; +} + var idCounter = 0; /** @@ -2636,11 +2788,25 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro var params = null; var chunkedMode = true; var headers = null; + var httpMethod = null; + var basicAuthHeader = getBasicAuthHeader(server); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + if (options) { fileKey = options.fileKey; fileName = options.fileName; mimeType = options.mimeType; headers = options.headers; + httpMethod = options.httpMethod || "POST"; + if (httpMethod.toUpperCase() == "PUT"){ + httpMethod = "PUT"; + } else { + httpMethod = "POST"; + } if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { chunkedMode = options.chunkedMode; } @@ -2653,7 +2819,7 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro } var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); errorCallback(error); }; @@ -2667,7 +2833,7 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro successCallback && successCallback(result); } }; - exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); }; /** @@ -2677,10 +2843,24 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro * @param successCallback (Function} Callback to be invoked when upload has completed * @param errorCallback {Function} Callback to be invoked upon error * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + * @param options {FileDownloadOptions} Optional parameters such as headers */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); var self = this; + + var basicAuthHeader = getBasicAuthHeader(source); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + var headers = null; + if (options) { + headers = options.headers || null; + } + var win = function(result) { if (typeof result.lengthComputable != "undefined") { if (self.onprogress) { @@ -2703,20 +2883,19 @@ FileTransfer.prototype.download = function(source, target, successCallback, erro }; var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); errorCallback(error); }; - exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id]); + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id, headers]); }; /** - * Aborts the ongoing file transfer on this object - * @param successCallback {Function} Callback to be invoked upon success - * @param errorCallback {Function} Callback to be invoked upon error + * Aborts the ongoing file transfer on this object. The original error + * callback for the file transfer will be called if necessary. */ -FileTransfer.prototype.abort = function(successCallback, errorCallback) { - exec(successCallback, errorCallback, 'FileTransfer', 'abort', [this._id]); +FileTransfer.prototype.abort = function() { + exec(null, null, 'FileTransfer', 'abort', [this._id]); }; module.exports = FileTransfer; @@ -2760,12 +2939,13 @@ define("cordova/plugin/FileUploadOptions", function(require, exports, module) { * @param headers {Object} Keys are header names, values are header values. Multiple * headers of the same name are not supported. */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers) { +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { this.fileKey = fileKey || null; this.fileName = fileName || null; this.mimeType = mimeType || null; this.params = params || null; this.headers = headers || null; + this.httpMethod = httpMethod || null; }; module.exports = FileUploadOptions; @@ -2866,9 +3046,31 @@ FileWriter.prototype.abort = function() { /** * Writes data to the file * - * @param text to be written + * @param data text or blob to be written */ -FileWriter.prototype.write = function(text) { +FileWriter.prototype.write = function(data) { + + var isBinary = false; + + // If we don't have Blob or ArrayBuffer support, don't bother. + if (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined') { + + // Check to see if the incoming data is a blob + if (data instanceof Blob) { + var that=this; + var fileReader = new FileReader(); + fileReader.onload = function() { + // Call this method again, with the arraybuffer as argument + FileWriter.prototype.write.call(that, this.result); + }; + fileReader.readAsArrayBuffer(data); + return; + } + + // Mark data type for safer transport over the binary bridge + isBinary = (data instanceof ArrayBuffer); + } + // Throw an exception if we are already writing a file if (this.readyState === FileWriter.WRITING) { throw new FileError(FileError.INVALID_STATE_ERR); @@ -2934,7 +3136,7 @@ FileWriter.prototype.write = function(text) { if (typeof me.onwriteend === "function") { me.onwriteend(new ProgressEvent("writeend", {"target":me})); } - }, "File", "write", [this.fileName, text, this.position]); + }, "File", "write", [this.fileName, data, this.position, isBinary]); }; /** @@ -3100,11 +3302,13 @@ define("cordova/plugin/InAppBrowser", function(require, exports, module) { var exec = require('cordova/exec'); var channel = require('cordova/channel'); +var modulemapper = require('cordova/modulemapper'); function InAppBrowser() { this.channels = { 'loadstart': channel.create('loadstart'), 'loadstop' : channel.create('loadstop'), + 'loaderror' : channel.create('loaderror'), 'exit' : channel.create('exit') }; } @@ -3118,6 +3322,9 @@ InAppBrowser.prototype = { close: function (eventname) { exec(null, null, "InAppBrowser", "close", []); }, + show: function (eventname) { + exec(null, null, "InAppBrowser", "show", []); + }, addEventListener: function (eventname,f) { if (eventname in this.channels) { this.channels[eventname].subscribe(f); @@ -3127,6 +3334,26 @@ InAppBrowser.prototype = { if (eventname in this.channels) { this.channels[eventname].unsubscribe(f); } + }, + + executeScript: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]); + } else { + throw new Error('executeScript requires exactly one of code or file to be specified'); + } + }, + + insertCSS: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]); + } else { + throw new Error('insertCSS requires exactly one of code or file to be specified'); + } } }; @@ -3135,7 +3362,14 @@ module.exports = function(strUrl, strWindowName, strWindowFeatures) { var cb = function(eventname) { iab._eventHandler(eventname); }; - exec(cb, null, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); + + // Don't catch calls that write to existing frames (e.g. named iframes). + if (window.frames && window.frames[strWindowName]) { + var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); + return origOpenFunc.apply(window, arguments); + } + + exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); return iab; }; @@ -4101,7 +4335,7 @@ console.debug = function() { console.assert = function(expression) { if (expression) return; - var message = utils.vformat(arguments[1], [].slice.call(arguments, 2)); + var message = logger.format.apply(logger.format, [].slice.call(arguments, 1)); console.log("ASSERT: " + message); }; @@ -4289,7 +4523,6 @@ function Device() { this.available = false; this.platform = null; this.version = null; - this.name = null; this.uuid = null; this.cordova = null; this.model = null; @@ -4298,12 +4531,15 @@ function Device() { channel.onCordovaReady.subscribe(function() { me.getInfo(function(info) { + var buildLabel = info.cordova; + if (buildLabel != CORDOVA_JS_BUILD_LABEL) { + buildLabel += ' JS=' + CORDOVA_JS_BUILD_LABEL; + } me.available = true; me.platform = info.platform; me.version = info.version; - me.name = info.name; me.uuid = info.uuid; - me.cordova = info.cordova; + me.cordova = buildLabel; me.model = info.model; channel.onCordovaInfoReady.fire(); },function(e) { @@ -4341,7 +4577,8 @@ modulemapper.clobbers('cordova/plugin/device', 'device'); // file: lib/common/plugin/echo.js define("cordova/plugin/echo", function(require, exports, module) { -var exec = require('cordova/exec'); +var exec = require('cordova/exec'), + utils = require('cordova/utils'); /** * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. @@ -4351,11 +4588,25 @@ var exec = require('cordova/exec'); * @param forceAsync Whether to force an async return value (for testing native->js bridge). */ module.exports = function(successCallback, errorCallback, message, forceAsync) { - var action = forceAsync ? 'echoAsync' : 'echo'; - if (!forceAsync && message.constructor == ArrayBuffer) { - action = 'echoArrayBuffer'; + var action = 'echo'; + var messageIsMultipart = (utils.typeName(message) == "Array"); + var args = messageIsMultipart ? message : [message]; + + if (utils.typeName(message) == 'ArrayBuffer') { + if (forceAsync) { + console.warn('Cannot echo ArrayBuffer with forced async, falling back to sync.'); + } + action += 'ArrayBuffer'; + } else if (messageIsMultipart) { + if (forceAsync) { + console.warn('Cannot echo MultiPart Array with forced async, falling back to sync.'); + } + action += 'MultiPart'; + } else if (forceAsync) { + action += 'Async'; } - exec(successCallback, errorCallback, "Echo", action, [message]); + + exec(successCallback, errorCallback, "Echo", action, args); }; @@ -5068,83 +5319,6 @@ module.exports = { }); -// file: lib/ios/plugin/ios/console.js -define("cordova/plugin/ios/console", function(require, exports, module) { - -var exec = require('cordova/exec'); - -/** - * create a nice string for an object - */ -function stringify(message) { - try { - if (typeof message === "object" && JSON && JSON.stringify) { - try { - return JSON.stringify(message); - } - catch (e) { - return "error JSON.stringify()ing argument: " + e; - } - } else { - return message.toString(); - } - } catch (e) { - return e.toString(); - } -} - -/** - * Wrapper one of the console logging methods, so that - * the Cordova logging native is called, then the original. - */ -function wrappedMethod(console, method) { - var origMethod = console[method]; - - return function(message) { - exec(null, null, - 'Debug Console', 'log', - [ stringify(message), { logLevel: method.toUpperCase() } ] - ); - - if (!origMethod) return; - - origMethod.apply(console, arguments); - }; -} - -var console = window.console || {}; - -// 2012-10-06 pmuellr - marking setLevel() method and logLevel property -// on console as deprecated; -// it didn't do anything useful, since the level constants weren't accessible -// to anyone - -console.setLevel = function() {}; -console.logLevel = 0; - -// wrapper the logging messages - -var methods = ["log", "debug", "info", "warn", "error"]; - -for (var i=0; i<methods.length; i++) { - var method = methods[i]; - - console[method] = wrappedMethod(console, method); -} - -module.exports = console; - -}); - -// file: lib/ios/plugin/ios/console/symbols.js -define("cordova/plugin/ios/console/symbols", function(require, exports, module) { - -var modulemapper = require('cordova/modulemapper'); - -modulemapper.clobbers('cordova/plugin/ios/console', 'console'); - -}); - // file: lib/ios/plugin/ios/contacts.js define("cordova/plugin/ios/contacts", function(require, exports, module) { @@ -5218,7 +5392,17 @@ define("cordova/plugin/ios/logger/plugininit", function(require, exports, module // use the native logger var logger = require("cordova/plugin/logger"); -logger.useConsole(false); +logger.useConsole(true); + +}); + +// file: lib/ios/plugin/ios/logger/symbols.js +define("cordova/plugin/ios/logger/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/logger', 'console'); }); @@ -5265,10 +5449,13 @@ var exec = require('cordova/exec'); var utils = require('cordova/utils'); var UseConsole = true; +var UseLogger = true; var Queued = []; var DeviceReady = false; var CurrentLevel; +var originalConsole = console; + /** * Logging levels */ @@ -5329,8 +5516,7 @@ logger.level = function (value) { * Getter/Setter for the useConsole functionality * * When useConsole is true, the logger will log via the - * browser 'console' object. Otherwise, it will use the - * native Logger plugin. + * browser 'console' object. */ logger.useConsole = function (value) { if (arguments.length) UseConsole = !!value; @@ -5355,6 +5541,18 @@ logger.useConsole = function (value) { }; /** + * Getter/Setter for the useLogger functionality + * + * When useLogger is true, the logger will log via the + * native Logger plugin. + */ +logger.useLogger = function (value) { + // Enforce boolean + if (arguments.length) UseLogger = !!value; + return UseLogger; +}; + +/** * Logs a message at the LOG level. * * Parameters passed after message are used applied to @@ -5406,10 +5604,10 @@ function logWithArgs(level, args) { * Parameters passed after message are used applied to * the message with utils.format() */ -logger.logLevel = function(level, message /* , ... */) { +logger.logLevel = function(level /* , ... */) { // format the message with the parameters - var formatArgs = [].slice.call(arguments, 2); - message = utils.vformat(message, formatArgs); + var formatArgs = [].slice.call(arguments, 1); + var message = logger.format.apply(logger.format, formatArgs); if (LevelsMap[level] === null) { throw new Error("invalid logging level: " + level); @@ -5423,27 +5621,115 @@ logger.logLevel = function(level, message /* , ... */) { return; } - // if not using the console, use the native logger - if (!UseConsole) { + // Log using the native logger if that is enabled + if (UseLogger) { exec(null, null, "Logger", "logLevel", [level, message]); - return; } - // make sure console is not using logger - if (console.__usingCordovaLogger) { - throw new Error("console and logger are too intertwingly"); - } + // Log using the console if that is enabled + if (UseConsole) { + // make sure console is not using logger + if (console.__usingCordovaLogger) { + throw new Error("console and logger are too intertwingly"); + } - // log to the console - switch (level) { - case logger.LOG: console.log(message); break; - case logger.ERROR: console.log("ERROR: " + message); break; - case logger.WARN: console.log("WARN: " + message); break; - case logger.INFO: console.log("INFO: " + message); break; - case logger.DEBUG: console.log("DEBUG: " + message); break; + // log to the console + switch (level) { + case logger.LOG: originalConsole.log(message); break; + case logger.ERROR: originalConsole.log("ERROR: " + message); break; + case logger.WARN: originalConsole.log("WARN: " + message); break; + case logger.INFO: originalConsole.log("INFO: " + message); break; + case logger.DEBUG: originalConsole.log("DEBUG: " + message); break; + } } }; + +/** + * Formats a string and arguments following it ala console.log() + * + * Any remaining arguments will be appended to the formatted string. + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +logger.format = function(formatString, args) { + return __format(arguments[0], [].slice.call(arguments,1)).join(' '); +}; + + +//------------------------------------------------------------------------------ +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * Returns an array containing the formatted string and any remaining + * arguments. + */ +function __format(formatString, args) { + if (formatString === null || formatString === undefined) return [""]; + if (arguments.length == 1) return [formatString.toString()]; + + if (typeof formatString != "string") + formatString = formatString.toString(); + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var match = pattern.exec(rest); + if (!match) break; + + var arg = args.shift(); + rest = match[3]; + result.push(match[1]); + + if (match[2] == '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(__formatted(arg, match[2])); + } + + result.push(rest); + + var remainingArgs = [].slice.call(args); + remainingArgs.unshift(result.join('')); + return remainingArgs; +} + +function __formatted(object, formatChar) { + + try { + switch(formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + + +//------------------------------------------------------------------------------ // when deviceready fires, log queued messages logger.__onDeviceReady = function() { if (DeviceReady) return; @@ -5572,6 +5858,7 @@ modulemapper.defaults('cordova/plugin/Connection', 'Connection'); define("cordova/plugin/notification", function(require, exports, module) { var exec = require('cordova/exec'); +var platform = require('cordova/platform'); /** * Provides access to notifications on the device. @@ -5600,15 +5887,55 @@ module.exports = { * @param {String} message Message to print in the body of the alert * @param {Function} resultCallback The callback that is called when user clicks on a button. * @param {String} title Title of the alert dialog (default: Confirm) - * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') + * @param {Array} buttonLabels Array of the labels of the buttons (default: ['OK', 'Cancel']) */ confirm: function(message, resultCallback, title, buttonLabels) { var _title = (title || "Confirm"); - var _buttonLabels = (buttonLabels || "OK,Cancel"); + var _buttonLabels = (buttonLabels || ["OK", "Cancel"]); + + // Strings are deprecated! + if (typeof _buttonLabels === 'string') { + console.log("Notification.confirm(string, function, string, string) is deprecated. Use Notification.confirm(string, function, string, array)."); + } + + // Some platforms take an array of button label names. + // Other platforms take a comma separated list. + // For compatibility, we convert to the desired type based on the platform. + if (platform.id == "android" || platform.id == "ios" || platform.id == "windowsphone" || platform.id == "blackberry10") { + if (typeof _buttonLabels === 'string') { + var buttonLabelString = _buttonLabels; + _buttonLabels = _buttonLabels.split(","); // not crazy about changing the var type here + } + } else { + if (Array.isArray(_buttonLabels)) { + var buttonLabelArray = _buttonLabels; + _buttonLabels = buttonLabelArray.toString(); + } + } exec(resultCallback, null, "Notification", "confirm", [message, _title, _buttonLabels]); }, /** + * Open a native prompt dialog, with a customizable title and button text. + * The following results are returned to the result callback: + * buttonIndex Index number of the button selected. + * input1 The text entered in the prompt dialog box. + * + * @param {String} message Dialog message to display (default: "Prompt message") + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the dialog (default: "Prompt") + * @param {Array} buttonLabels Array of strings for the button labels (default: ["OK","Cancel"]) + * @param {String} defaultText Textbox input value (default: "Default text") + */ + prompt: function(message, resultCallback, title, buttonLabels, defaultText) { + var _message = (message || "Prompt message"); + var _title = (title || "Prompt"); + var _buttonLabels = (buttonLabels || ["OK","Cancel"]); + var _defaultText = (defaultText || "Default text"); + exec(resultCallback, null, "Notification", "prompt", [_message, _title, _buttonLabels, _defaultText]); + }, + + /** * Causes the device to vibrate. * * @param {Integer} mills The number of milliseconds to vibrate for. @@ -5930,62 +6257,6 @@ utils.alert = function(msg) { } }; -/** - * Formats a string and arguments following it ala sprintf() - * - * see utils.vformat() for more information - */ -utils.format = function(formatString /* ,... */) { - var args = [].slice.call(arguments, 1); - return utils.vformat(formatString, args); -}; - -/** - * Formats a string and arguments following it ala vsprintf() - * - * format chars: - * %j - format arg as JSON - * %o - format arg as JSON - * %c - format arg as '' - * %% - replace with '%' - * any other char following % will format it's - * arg via toString(). - * - * for rationale, see FireBug's Console API: - * http://getfirebug.com/wiki/index.php/Console_API - */ -utils.vformat = function(formatString, args) { - if (formatString === null || formatString === undefined) return ""; - if (arguments.length == 1) return formatString.toString(); - if (typeof formatString != "string") return formatString.toString(); - - var pattern = /(.*?)%(.)(.*)/; - var rest = formatString; - var result = []; - - while (args.length) { - var arg = args.shift(); - var match = pattern.exec(rest); - - if (!match) break; - - rest = match[3]; - - result.push(match[1]); - - if (match[2] == '%') { - result.push('%'); - args.unshift(arg); - continue; - } - - result.push(formatted(arg, match[2])); - } - - result.push(rest); - - return result.join(''); -}; //------------------------------------------------------------------------------ function UUIDcreatePart(length) { @@ -6000,84 +6271,251 @@ function UUIDcreatePart(length) { return uuidpart; } -//------------------------------------------------------------------------------ -function formatted(object, formatChar) { - try { - switch(formatChar) { - case 'j': - case 'o': return JSON.stringify(object); - case 'c': return ''; +}); + +window.cordova = require('cordova'); +// file: lib/scripts/bootstrap.js + +(function (context) { + if (context._cordovaJsLoaded) { + throw new Error('cordova.js included multiple times.'); + } + context._cordovaJsLoaded = true; + + var channel = require('cordova/channel'); + var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; + + function logUnfiredChannels(arr) { + for (var i = 0; i < arr.length; ++i) { + if (arr[i].state != 2) { + console.log('Channel not fired: ' + arr[i].type); + } } } - catch (e) { - return "error JSON.stringify()ing argument: " + e; + + window.setTimeout(function() { + if (channel.onDeviceReady.state != 2) { + console.log('deviceready has not fired after 5 seconds.'); + logUnfiredChannels(platformInitChannelsArray); + logUnfiredChannels(channel.deviceReadyChannelsArray); + } + }, 5000); + + // Replace navigator before any modules are required(), to ensure it happens as soon as possible. + // We replace it so that properties that can't be clobbered can instead be overridden. + function replaceNavigator(origNavigator) { + var CordovaNavigator = function() {}; + CordovaNavigator.prototype = origNavigator; + var newNavigator = new CordovaNavigator(); + // This work-around really only applies to new APIs that are newer than Function.bind. + // Without it, APIs such as getGamepads() break. + if (CordovaNavigator.bind) { + for (var key in origNavigator) { + if (typeof origNavigator[key] == 'function') { + newNavigator[key] = origNavigator[key].bind(origNavigator); + } + } + } + return newNavigator; + } + if (context.navigator) { + context.navigator = replaceNavigator(context.navigator); } - if ((object === null) || (object === undefined)) { - return Object.prototype.toString.call(object); + // _nativeReady is global variable that the native side can set + // to signify that the native code is ready. It is a global since + // it may be called before any cordova JS is ready. + if (window._nativeReady) { + channel.onNativeReady.fire(); } - return object.toString(); -} + /** + * Create all cordova objects once native side is ready. + */ + channel.join(function() { + // Call the platform-specific initialization + require('cordova/platform').initialize(); -}); + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + // Fire onDeviceReady event once page has fully loaded, all + // constructors have run and cordova info has been received from native + // side. + // This join call is deliberately made after platform.initialize() in + // order that plugins may manipulate channel.deviceReadyChannelsArray + // if necessary. + channel.join(function() { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); -window.cordova = require('cordova'); + }, platformInitChannelsArray); -// file: lib/scripts/bootstrap.js +}(window)); -(function (context) { - // Replace navigator before any modules are required(), to ensure it happens as soon as possible. - // We replace it so that properties that can't be clobbered can instead be overridden. - if (context.navigator) { - var CordovaNavigator = function() {}; - CordovaNavigator.prototype = context.navigator; - context.navigator = new CordovaNavigator(); - } +// file: lib/scripts/bootstrap-ios.js - var channel = require("cordova/channel"), - _self = { - boot: function () { - /** - * Create all cordova objects once page has fully loaded and native side is ready. - */ - channel.join(function() { - var builder = require('cordova/builder'), - platform = require('cordova/platform'); +require('cordova/channel').onNativeReady.fire(); - builder.buildIntoButDoNotClobber(platform.defaults, context); - builder.buildIntoAndClobber(platform.clobbers, context); - builder.buildIntoAndMerge(platform.merges, context); +// file: lib/scripts/plugin_loader.js - // Call the platform-specific initialization - platform.initialize(); +// Tries to load all plugins' js-modules. +// This is an async process, but onDeviceReady is blocked on onPluginsReady. +// onPluginsReady is fired when there are no plugins to load, or they are all done. +(function (context) { + // To be populated with the handler by handlePluginsObject. + var onScriptLoadingComplete; + + var scriptCounter = 0; + function scriptLoadedCallback() { + scriptCounter--; + if (scriptCounter === 0) { + onScriptLoadingComplete && onScriptLoadingComplete(); + } + } - // Fire event to notify that all objects are created - channel.onCordovaReady.fire(); + function scriptErrorCallback(err) { + // Open Question: If a script path specified in cordova_plugins.js does not exist, do we fail for all? + // this is currently just continuing. + scriptCounter--; + if (scriptCounter === 0) { + onScriptLoadingComplete && onScriptLoadingComplete(); + } + } - // Fire onDeviceReady event once all constructors have run and - // cordova info has been received from native side. - channel.join(function() { - require('cordova').fireDocumentEvent('deviceready'); - }, channel.deviceReadyChannelsArray); + // Helper function to inject a <script> tag. + function injectScript(path) { + scriptCounter++; + var script = document.createElement("script"); + script.onload = scriptLoadedCallback; + script.onerror = scriptErrorCallback; + script.src = path; + document.head.appendChild(script); + } - }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); + // Called when: + // * There are plugins defined and all plugins are finished loading. + // * There are no plugins to load. + function finishPluginLoading() { + context.cordova.require('cordova/channel').onPluginsReady.fire(); + } + + // Handler for the cordova_plugins.js content. + // See plugman's plugin_loader.js for the details of this object. + // This function is only called if the really is a plugins array that isn't empty. + // Otherwise the onerror response handler will just call finishPluginLoading(). + function handlePluginsObject(modules, path) { + // First create the callback for when all plugins are loaded. + var mapper = context.cordova.require('cordova/modulemapper'); + onScriptLoadingComplete = function() { + // Loop through all the plugins and then through their clobbers and merges. + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + if (module) { + try { + if (module.clobbers && module.clobbers.length) { + for (var j = 0; j < module.clobbers.length; j++) { + mapper.clobbers(module.id, module.clobbers[j]); + } + } + + if (module.merges && module.merges.length) { + for (var k = 0; k < module.merges.length; k++) { + mapper.merges(module.id, module.merges[k]); + } + } + + // Finally, if runs is truthy we want to simply require() the module. + // This can be skipped if it had any merges or clobbers, though, + // since the mapper will already have required the module. + if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) { + context.cordova.require(module.id); + } + } + catch(err) { + // error with module, most likely clobbers, should we continue? + } + } } + + finishPluginLoading(); }; - // boot up once native side is ready - channel.onNativeReady.subscribe(_self.boot); + // Now inject the scripts. + for (var i = 0; i < modules.length; i++) { + injectScript(path + modules[i].file); + } + } - // _nativeReady is global variable that the native side can set - // to signify that the native code is ready. It is a global since - // it may be called before any cordova JS is ready. - if (window._nativeReady) { - channel.onNativeReady.fire(); + // Find the root of the app + var path = ''; + var scripts = document.getElementsByTagName('script'); + var term = 'cordova.js'; + for (var n = scripts.length-1; n>-1; n--) { + var src = scripts[n].src; + if (src.indexOf(term) == (src.length - term.length)) { + path = src.substring(0, src.length - term.length); + break; + } } + var plugins_json = path + 'cordova_plugins.json'; + var plugins_js = path + 'cordova_plugins.js'; + + // One some phones (Windows) this xhr.open throws an Access Denied exception + // So lets keep trying, but with a script tag injection technique instead of XHR + var injectPluginScript = function injectPluginScript() { + try { + var script = document.createElement("script"); + script.onload = function(){ + var list = cordova.require("cordova/plugin_list"); + handlePluginsObject(list,path); + }; + script.onerror = function() { + // Error loading cordova_plugins.js, file not found or something + // this is an acceptable error, pre-3.0.0, so we just move on. + finishPluginLoading(); + }; + script.src = plugins_js; + document.head.appendChild(script); + + } catch(err){ + finishPluginLoading(); + } + } + + + // Try to XHR the cordova_plugins.json file asynchronously. + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + // If the response is a JSON string which composes an array, call handlePluginsObject. + // If the request fails, or the response is not a JSON array, just call finishPluginLoading. + var obj; + try { + obj = (this.status == 0 || this.status == 200) && this.responseText && JSON.parse(this.responseText); + } catch (err) { + // obj will be undefined. + } + if (Array.isArray(obj) && obj.length > 0) { + handlePluginsObject(obj, path); + } else { + finishPluginLoading(); + } + }; + xhr.onerror = function() { + // In this case, the json file was not present, but XHR was allowed, + // so we should still try the script injection technique with the js file + // in case that is there. + injectPluginScript(); + }; + try { // we commented we were going to try, so let us actually try and catch + xhr.open('GET', plugins_json, true); // Async + xhr.send(); + } catch(err){ + injectPluginScript(); + } }(window)); -})();
\ No newline at end of file +})();var PhoneGap = cordova; diff --git a/iPhone/FixMyStreet.xcodeproj/project.pbxproj b/iPhone/FixMyStreet.xcodeproj/project.pbxproj index 7c78a5c..13d7065 100644 --- a/iPhone/FixMyStreet.xcodeproj/project.pbxproj +++ b/iPhone/FixMyStreet.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 1F766FE113BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDC13BBADB100FB74C0 /* Localizable.strings */; }; 1F766FE213BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDF13BBADB100FB74C0 /* Localizable.strings */; }; + 2425571F178DA08700E2E208 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2425571E178DA08700E2E208 /* ImageIO.framework */; }; + 24255721178DA08D00E2E208 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24255720178DA08D00E2E208 /* OpenAL.framework */; }; 244F330B1774574C00699B27 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 244F330A1774574C00699B27 /* Default.png */; }; 244F330D1774575000699B27 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 244F330C1774575000699B27 /* Default@2x.png */; }; 244F330F17746FC200699B27 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 244F330E17746FC200699B27 /* Default-568h@2x.png */; }; @@ -69,6 +71,8 @@ 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 1F766FDD13BBADB100FB74C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = "<group>"; }; 1F766FE013BBADB100FB74C0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Localizable.strings; sourceTree = "<group>"; }; + 2425571E178DA08700E2E208 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; + 24255720178DA08D00E2E208 /* OpenAL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenAL.framework; path = System/Library/Frameworks/OpenAL.framework; sourceTree = SDKROOT; }; 244F330A1774574C00699B27 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; }; 244F330C1774575000699B27 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; }; 244F330E17746FC200699B27 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; }; @@ -116,6 +120,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 24255721178DA08D00E2E208 /* OpenAL.framework in Frameworks */, + 2425571F178DA08700E2E208 /* ImageIO.framework in Frameworks */, 246C8F801700A4010052666F /* AssetsLibrary.framework in Frameworks */, 301BF552109A68D80062928A /* libCordova.a in Frameworks */, 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, @@ -178,6 +184,8 @@ 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( + 24255720178DA08D00E2E208 /* OpenAL.framework */, + 2425571E178DA08700E2E208 /* ImageIO.framework */, 244F330E17746FC200699B27 /* Default-568h@2x.png */, 244F330C1774575000699B27 /* Default@2x.png */, 244F330A1774574C00699B27 /* Default.png */, @@ -485,6 +493,19 @@ GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; + OTHER_LDFLAGS = ( + "-weak_framework", + CoreFoundation, + "-weak_framework", + UIKit, + "-weak_framework", + AVFoundation, + "-weak_framework", + CoreMedia, + "-weak-lSystem", + "-forceload", + "-ObjC", + ); PRODUCT_NAME = FixMyStreet; PROVISIONING_PROFILE = "292D4D07-F19C-44F9-A9C3-7EF8BBE5B672"; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "292D4D07-F19C-44F9-A9C3-7EF8BBE5B672"; @@ -506,6 +527,19 @@ GCC_VERSION = com.apple.compilers.llvm.clang.1_0; INFOPLIST_FILE = "FixMyStreet/FixMyStreet-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; + OTHER_LDFLAGS = ( + "-weak_framework", + CoreFoundation, + "-weak_framework", + UIKit, + "-weak_framework", + AVFoundation, + "-weak_framework", + CoreMedia, + "-weak-lSystem", + "-forceload", + "-ObjC", + ); PRODUCT_NAME = FixMyStreet; PROVISIONING_PROFILE = "487B7C0B-5882-4446-BB20-5FF010122FF3"; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "487B7C0B-5882-4446-BB20-5FF010122FF3"; diff --git a/iPhone/FixMyStreet/Classes/AppDelegate.m b/iPhone/FixMyStreet/Classes/AppDelegate.m index 77e991d..08c68ef 100644 --- a/iPhone/FixMyStreet/Classes/AppDelegate.m +++ b/iPhone/FixMyStreet/Classes/AppDelegate.m @@ -19,7 +19,7 @@ // // AppDelegate.m -// tmp_ios +// sample // // Created by ___FULLUSERNAME___ on ___DATE___. // Copyright ___ORGANIZATIONNAME___ ___YEAR___. All rights reserved. @@ -45,7 +45,11 @@ int cacheSizeMemory = 8 * 1024 * 1024; // 8MB int cacheSizeDisk = 32 * 1024 * 1024; // 32MB - NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"]; +#if __has_feature(objc_arc) + NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"]; +#else + NSURLCache* sharedCache = [[[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"] autorelease]; +#endif [NSURLCache setSharedURLCache:sharedCache]; self = [super init]; @@ -61,10 +65,18 @@ { CGRect screenBounds = [[UIScreen mainScreen] bounds]; - self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease]; +#if __has_feature(objc_arc) + self.window = [[UIWindow alloc] initWithFrame:screenBounds]; +#else + self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease]; +#endif self.window.autoresizesSubviews = YES; - self.viewController = [[[MainViewController alloc] init] autorelease]; +#if __has_feature(objc_arc) + self.viewController = [[MainViewController alloc] init]; +#else + self.viewController = [[[MainViewController alloc] init] autorelease]; +#endif self.viewController.useSplashScreen = YES; // Set your app's start page by setting the <content src='foo.html' /> tag in config.xml. @@ -81,7 +93,7 @@ } // this happens while we are running ( in the background, or from within our own app ) -// only valid if tmp_ios-Info.plist specifies a protocol to handle +// only valid if sample-Info.plist specifies a protocol to handle - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { if (!url) { @@ -99,8 +111,8 @@ } // repost the localnotification using the default NSNotificationCenter so multiple plugins may respond -- (void) application:(UIApplication*)application - didReceiveLocalNotification:(UILocalNotification*)notification +- (void) application:(UIApplication*)application + didReceiveLocalNotification:(UILocalNotification*)notification { // re-post ( broadcast ) [[NSNotificationCenter defaultCenter] postNotificationName:CDVLocalNotification object:notification]; diff --git a/iPhone/FixMyStreet/config.xml b/iPhone/FixMyStreet/config.xml index 086fb93..fc173e1 100644 --- a/iPhone/FixMyStreet/config.xml +++ b/iPhone/FixMyStreet/config.xml @@ -1,37 +1,120 @@ <?xml version="1.0" encoding="UTF-8"?> -<widget> - <preference name="AllowInlineMediaPlayback" value="false"/> - <preference name="AutoHideSplashScreen" value="true"/> - <preference name="EnableLocation" value="false"/> - <preference name="EnableViewportScale" value="false"/> - <preference name="MediaPlaybackRequiresUserAction" value="false"/> - <preference name="ShowSplashScreenSpinner" value="true"/> - <preference name="FadeSplashScreen" value="false" /> - <preference name="FadeSplashScreenDuration" value="0" /> - <preference name="TopActivityIndicator" value="gray"/> - <preference name="UIWebViewBounce" value="false"/> - <content src="index.html" /> - <plugins> - <plugin name="Accelerometer" value="CDVAccelerometer"/> - <plugin name="Battery" value="CDVBattery"/> - <plugin name="Camera" value="CDVCamera"/> - <plugin name="Capture" value="CDVCapture"/> - <plugin name="Compass" value="CDVLocation"/> - <plugin name="Contacts" value="CDVContacts"/> - <plugin name="Debug Console" value="CDVDebugConsole"/> - <plugin name="Device" value="CDVDevice"/> - <plugin name="File" value="CDVFile"/> - <plugin name="FileTransfer" value="CDVFileTransfer"/> - <plugin name="Geolocation" value="CDVLocation"/> - <plugin name="InAppBrowser" value="CDVInAppBrowser"/> - <plugin name="Media" value="CDVSound"/> - <plugin name="NetworkStatus" value="CDVConnection"/> - <plugin name="Notification" value="CDVNotification"/> - <plugin name="SplashScreen" value="CDVSplashScreen"/> - </plugins> - <access origin="*.tile.openstreetmap.org"/> - <access origin="*.tilma.mysociety.org"/> - <access origin="*.virtualearth.net"/> - <access origin="mapit.mysociety.org"/> - <access origin="struan.fixmystreet.dev.mysociety.org"/> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<widget xmlns = "http://www.w3.org/ns/widgets" + id = "io.cordova.helloCordova" + version = "2.0.0"> + <name>Hello Cordova</name> + + <description> + A sample Apache Cordova application that responds to the deviceready event. + </description> + + <author href="http://cordova.io" email="dev@cordova.apache.org"> + Apache Cordova Team + </author> + + <access origin="*.tile.openstreetmap.org"/> + <access origin="*.tilma.mysociety.org"/> + <access origin="*.virtualearth.net"/> + <access origin="mapit.mysociety.org"/> + <access origin="struan.fixmystreet.dev.mysociety.org"/> + + <!-- <content src="http://mysite.com/myapp.html" /> for external pages --> + <content src="index.html" /> + + <!-- Preferences for iOS --> + <preference name="AllowInlineMediaPlayback" value="false" /> + <preference name="AutoHideSplashScreen" value="true" /> + <preference name="BackupWebStorage" value="cloud" /> + <preference name="DisallowOverscroll" value="false" /> + <preference name="EnableLocation" value="false" /><!-- DEPRECATED --> + <preference name="EnableViewportScale" value="false" /> + <preference name="FadeSplashScreen" value="false" /> + <preference name="FadeSplashScreenDuration" value="0" /> + <preference name="HideKeyboardFormAccessoryBar" value="false" /> + <preference name="KeyboardDisplayRequiresUserAction" value="true" /> + <preference name="KeyboardShrinksView" value="false" /> + <preference name="MediaPlaybackRequiresUserAction" value="false" /> + <preference name="ShowSplashScreenSpinner" value="true" /> + <preference name="SuppressesIncrementalRendering" value="false" /> + <preference name="TopActivityIndicator" value="gray" /> + + + <feature name="Geolocation"> + <param name="ios-package" value="CDVLocation"/> + </feature> + <feature name="Device"> + <param name="ios-package" value="CDVDevice"/> + </feature> + <feature name="Accelerometer"> + <param name="ios-package" value="CDVAccelerometer"/> + </feature> + <feature name="Compass"> + <param name="ios-package" value="CDVLocation"/> + </feature> + <feature name="Media"> + <param name="ios-package" value="CDVSound"/> + </feature> + <feature name="Camera"> + <param name="ios-package" value="CDVCamera"/> + </feature> + <feature name="Contacts"> + <param name="ios-package" value="CDVContacts"/> + </feature> + <feature name="File"> + <param name="ios-package" value="CDVFile"/> + </feature> + <feature name="NetworkStatus"> + <param name="ios-package" value="CDVConnection"/> + </feature> + <feature name="Notification"> + <param name="ios-package" value="CDVNotification"/> + </feature> + <feature name="FileTransfer"> + <param name="ios-package" value="CDVFileTransfer"/> + </feature> + <feature name="Capture"> + <param name="ios-package" value="CDVCapture"/> + </feature> + <feature name="Battery"> + <param name="ios-package" value="CDVBattery"/> + </feature> + <feature name="SplashScreen"> + <param name="ios-package" value="CDVSplashScreen"/> + </feature> + <feature name="Echo"> + <param name="ios-package" value="CDVEcho"/> + </feature> + <feature name="Globalization"> + <param name="ios-package" value="CDVGlobalization"/> + </feature> + <feature name="InAppBrowser"> + <param name="ios-package" value="CDVInAppBrowser"/> + </feature> + <feature name="Logger"> + <param name="ios-package" value="CDVLogger"/> + </feature> + <feature name="LocalStorage"> + <param name="ios-package" value="CDVLocalStorage"/> + </feature> + <!-- Deprecated plugins element. REmove in 3.0 --> + <plugins> + </plugins> </widget> diff --git a/www/cordova-independent.js b/www/cordova-independent.js index 016a233..e35d16e 100644 --- a/www/cordova-independent.js +++ b/www/cordova-independent.js @@ -2,7 +2,7 @@ var scriptElement = document.createElement("script"); scriptElement.type = "text/javascript"; if (navigator.userAgent.match(/(iPhone|iPod|iPad)/)) { - scriptElement.src = 'cordova-ios-2.5.0.js'; + scriptElement.src = 'cordova-ios-2.9.0.js'; } else if (navigator.userAgent.match(/Android/)) { scriptElement.src = 'cordova-android-2.5.0.js'; } else { diff --git a/www/cordova-ios-2.9.0.js b/www/cordova-ios-2.9.0.js new file mode 100755 index 0000000..9bd85e2 --- /dev/null +++ b/www/cordova-ios-2.9.0.js @@ -0,0 +1,6521 @@ +// Platform: ios +// 2.9.0-0-g83dc4bd +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +;(function() { +var CORDOVA_JS_BUILD_LABEL = '2.9.0-0-g83dc4bd'; +// file: lib/scripts/require.js + +var require, + define; + +(function () { + var modules = {}, + // Stack of moduleIds currently being built. + requireStack = [], + // Map of module ID -> index into requireStack of modules currently being built. + inProgressModules = {}, + SEPERATOR = "."; + + + + function build(module) { + var factory = module.factory, + localRequire = function (id) { + var resultantId = id; + //Its a relative path, so lop off the last portion and add the id (minus "./") + if (id.charAt(0) === ".") { + resultantId = module.id.slice(0, module.id.lastIndexOf(SEPERATOR)) + SEPERATOR + id.slice(2); + } + return require(resultantId); + }; + module.exports = {}; + delete module.factory; + factory(localRequire, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw "module " + id + " not found"; + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw "Cycle in require graph: " + cycle; + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (modules[id]) { + throw "module " + id + " already defined"; + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + + define.moduleMap = modules; +})(); + +//Export for use in node +if (typeof module === "object" && typeof require === "function") { + module.exports.require = require; + module.exports.define = define; +} + +// file: lib/cordova.js +define("cordova", function(require, exports, module) { + + +var channel = require('cordova/channel'); + +/** + * Listen for DOMContentLoaded and notify our channel subscribers. + */ +document.addEventListener('DOMContentLoaded', function() { + channel.onDOMContentLoaded.fire(); +}, false); +if (document.readyState == 'complete' || document.readyState == 'interactive') { + channel.onDOMContentLoaded.fire(); +} + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}, + windowEventHandlers = {}; + +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] != 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] != 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] != "undefined") { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] != "undefined") { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent(type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + } + return event; +} + +if(typeof window.console === "undefined") { + window.console = { + log:function(){} + }; +} + +var cordova = { + define:define, + require:require, + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler:function(event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler:function(event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler:function(event) { + delete documentEventHandlers[event]; + }, + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function() { + return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, + 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; + }, + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function(type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] != 'undefined') { + if( bNoDetach ) { + documentEventHandlers[type].fire(evt); + } + else { + setTimeout(function() { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type == 'deviceready') { + document.dispatchEvent(evt); + } + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + fireWindowEvent: function(type, data) { + var evt = createEvent(type,data); + if (typeof windowEventHandlers[type] != 'undefined') { + setTimeout(function() { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + callbackSuccess: function(callbackId, args) { + try { + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning error result from an action. + */ + callbackError: function(callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + try { + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function(callbackId, success, status, args, keepCallback) { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (success && status == cordova.callbackStatus.OK) { + callback.success && callback.success.apply(null, args); + } else if (!success) { + callback.fail && callback.fail.apply(null, args); + } + + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + }, + addConstructor: function(func) { + channel.onCordovaReady.subscribe(function() { + try { + func(); + } catch(e) { + console.log("Failed to run constructor: " + e); + } + }); + } +}; + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +module.exports = cordova; + +}); + +// file: lib/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var moduleExports = module.exports; + +var typeMap = { + 'A': 'Array', + 'D': 'Date', + 'N': 'Number', + 'S': 'String', + 'F': 'Function', + 'O': 'Object' +}; + +function extractParamName(callee, argIndex) { + return (/.*?\((.*?)\)/).exec(callee)[1].split(', ')[argIndex]; +} + +function checkArgs(spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var typeName; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i), + cUpper = c.toUpperCase(), + arg = args[i]; + // Asterix means allow anything. + if (c == '*') { + continue; + } + typeName = utils.typeName(arg); + if ((arg === null || arg === undefined) && c == cUpper) { + continue; + } + if (typeName != typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + typeName + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running jake test. + if (typeof jasmine == 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +function getValue(value, defaultValue) { + return value === undefined ? defaultValue : value; +} + +moduleExports.checkArgs = checkArgs; +moduleExports.getValue = getValue; +moduleExports.enableChecks = true; + + +}); + +// file: lib/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each(objects, func, context) { + for (var prop in objects) { + if (objects.hasOwnProperty(prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber(obj, key, value) { + exports.replaceHookForTesting(obj, key); + obj[key] = value; + // Getters can only be overridden by getters. + if (obj[key] !== value) { + utils.defineGetter(obj, key, function() { + return value; + }); + } +} + +function assignOrWrapInDeprecateGetter(obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function() { + console.log(message); + delete obj[key]; + clobber(obj, key, value); + return value; + }); + } else { + clobber(obj, key, value); + } +} + +function include(parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] == 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch(e) { + utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge(target, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop)) { + if (target.prototype && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + clobber(target.prototype, prop, src[prop]); + } else { + if (typeof src[prop] === 'object' && typeof target[prop] === 'object') { + recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } +} + +exports.buildIntoButDoNotClobber = function(objects, target) { + include(target, objects, false, false); +}; +exports.buildIntoAndClobber = function(objects, target) { + include(target, objects, true, false); +}; +exports.buildIntoAndMerge = function(objects, target) { + include(target, objects, true, true); +}; +exports.recursiveMerge = recursiveMerge; +exports.assignOrWrapInDeprecateGetter = assignOrWrapInDeprecateGetter; +exports.replaceHookForTesting = function() {}; + +}); + +// file: lib/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'), + nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onCordovaInfoReady* Internal event fired when device properties are available. + * onCordovaConnectionReady* Internal event fired when the connection property has been set. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function(type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}, + channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function(h, c) { + var len = c.length, + i = len, + f = function() { + if (!(--i)) h(); + }; + for (var j=0; j<len; j++) { + if (c[j].state === 0) { + throw Error('Can only use join with sticky channels.'); + } + c[j].subscribe(f); + } + if (!len) h(); + }, + create: function(type) { + return channel[type] = new Channel(type, false); + }, + createSticky: function(type) { + return channel[type] = new Channel(type, true); + }, + + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray: [], + deviceReadyChannelsMap: {}, + + /** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ + waitForInitialization: function(feature) { + if (feature) { + var c = channel[feature] || this.createSticky(feature); + this.deviceReadyChannelsMap[feature] = c; + this.deviceReadyChannelsArray.push(c); + } + }, + + /** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ + initializationComplete: function(feature) { + var c = this.deviceReadyChannelsMap[feature]; + if (c) { + c.fire(); + } + } + }; + +function forceFunction(f) { + if (typeof f != 'function') throw "Function required as first argument!"; +} + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Channel.prototype.subscribe = function(f, c) { + // need a function to call + forceFunction(f); + if (this.state == 2) { + f.apply(c || this, this.fireArgs); + return; + } + + var func = f, + guid = f.observer_guid; + if (typeof c == "object") { func = utils.close(c, f); } + + if (!guid) { + // first time any channel has seen this subscriber + guid = '' + nextGuid++; + } + func.observer_guid = guid; + f.observer_guid = guid; + + // Don't add the same handler more than once. + if (!this.handlers[guid]) { + this.handlers[guid] = func; + this.numHandlers++; + if (this.numHandlers == 1) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Channel.prototype.unsubscribe = function(f) { + // need a function to unsubscribe + forceFunction(f); + + var guid = f.observer_guid, + handler = this.handlers[guid]; + if (handler) { + delete this.handlers[guid]; + this.numHandlers--; + if (this.numHandlers === 0) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Calls all functions subscribed to this channel. + */ +Channel.prototype.fire = function(e) { + var fail = false, + fireArgs = Array.prototype.slice.call(arguments); + // Apply stickiness. + if (this.state == 1) { + this.state = 2; + this.fireArgs = fireArgs; + } + if (this.numHandlers) { + // Copy the values first so that it is safe to modify it from within + // callbacks. + var toCall = []; + for (var item in this.handlers) { + toCall.push(this.handlers[item]); + } + for (var i = 0; i < toCall.length; ++i) { + toCall[i].apply(this, fireArgs); + } + if (this.state == 2 && this.numHandlers) { + this.numHandlers = 0; + this.handlers = {}; + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + + +// defining them here so they are ready super fast! +// DOM event that is received when the web page is loaded and parsed. +channel.createSticky('onDOMContentLoaded'); + +// Event to indicate the Cordova native side is ready. +channel.createSticky('onNativeReady'); + +// Event to indicate that all Cordova JavaScript objects have been created +// and it's time to run plugin constructors. +channel.createSticky('onCordovaReady'); + +// Event to indicate that device properties are available +channel.createSticky('onCordovaInfoReady'); + +// Event to indicate that the connection property has been set. +channel.createSticky('onCordovaConnectionReady'); + +// Event to indicate that all automatically loaded JS plugins are loaded and ready. +channel.createSticky('onPluginsReady'); + +// Event to indicate that Cordova is ready +channel.createSticky('onDeviceReady'); + +// Event to indicate a resume lifecycle event +channel.create('onResume'); + +// Event to indicate a pause lifecycle event +channel.create('onPause'); + +// Event to indicate a destroy lifecycle event +channel.createSticky('onDestroy'); + +// Channels that must fire before "deviceready" is fired. +channel.waitForInitialization('onCordovaReady'); +channel.waitForInitialization('onCordovaConnectionReady'); +channel.waitForInitialization('onDOMContentLoaded'); + +module.exports = channel; + +}); + +// file: lib/common/commandProxy.js +define("cordova/commandProxy", function(require, exports, module) { + + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add:function(id,proxyObj) { + console.log("adding proxy for " + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove:function(id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get:function(service,action) { + return ( CommandProxyMap[service] ? CommandProxyMap[service][action] : null ); + } +}; +}); + +// file: lib/ios/exec.js +define("cordova/exec", function(require, exports, module) { + +/** + * Creates a gap bridge iframe used to notify the native code about queued + * commands. + * + * @private + */ +var cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + jsToNativeModes = { + IFRAME_NAV: 0, + XHR_NO_PAYLOAD: 1, + XHR_WITH_PAYLOAD: 2, + XHR_OPTIONAL_PAYLOAD: 3 + }, + bridgeMode, + execIframe, + execXhr, + requestCount = 0, + vcHeaderValue = null, + commandQueue = [], // Contains pending JS->Native messages. + isInContextOfEvalJs = 0; + +function createExecIframe() { + var iframe = document.createElement("iframe"); + iframe.style.display = 'none'; + document.body.appendChild(iframe); + return iframe; +} + +function shouldBundleCommandJson() { + if (bridgeMode == jsToNativeModes.XHR_WITH_PAYLOAD) { + return true; + } + if (bridgeMode == jsToNativeModes.XHR_OPTIONAL_PAYLOAD) { + var payloadLength = 0; + for (var i = 0; i < commandQueue.length; ++i) { + payloadLength += commandQueue[i].length; + } + // The value here was determined using the benchmark within CordovaLibApp on an iPad 3. + return payloadLength < 4500; + } + return false; +} + +function massageArgsJsToNative(args) { + if (!args || utils.typeName(args) != 'Array') { + return args; + } + var ret = []; + var encodeArrayBufferAs8bitString = function(ab) { + return String.fromCharCode.apply(null, new Uint8Array(ab)); + }; + var encodeArrayBufferAsBase64 = function(ab) { + return window.btoa(encodeArrayBufferAs8bitString(ab)); + }; + args.forEach(function(arg, i) { + if (utils.typeName(arg) == 'ArrayBuffer') { + ret.push({ + 'CDVType': 'ArrayBuffer', + 'data': encodeArrayBufferAsBase64(arg) + }); + } else { + ret.push(arg); + } + }); + return ret; +} + +function massageMessageNativeToJs(message) { + if (message.CDVType == 'ArrayBuffer') { + var stringToArrayBuffer = function(str) { + var ret = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + ret[i] = str.charCodeAt(i); + } + return ret.buffer; + }; + var base64ToArrayBuffer = function(b64) { + return stringToArrayBuffer(atob(b64)); + }; + message = base64ToArrayBuffer(message.data); + } + return message; +} + +function convertMessageToArgsNativeToJs(message) { + var args = []; + if (!message || !message.hasOwnProperty('CDVType')) { + args.push(message); + } else if (message.CDVType == 'MultiPart') { + message.messages.forEach(function(e) { + args.push(massageMessageNativeToJs(e)); + }); + } else { + args.push(massageMessageNativeToJs(message)); + } + return args; +} + +function iOSExec() { + // XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices. + // XHR mode's main advantage is working around a bug in -webkit-scroll, which + // doesn't exist in 4.X devices anyways. + if (bridgeMode === undefined) { + bridgeMode = navigator.userAgent.indexOf(' 4_') == -1 ? jsToNativeModes.XHR_NO_PAYLOAD : jsToNativeModes.IFRAME_NAV; + } + + var successCallback, failCallback, service, action, actionArgs, splitCommand; + var callbackId = null; + if (typeof arguments[0] !== "string") { + // FORMAT ONE + successCallback = arguments[0]; + failCallback = arguments[1]; + service = arguments[2]; + action = arguments[3]; + actionArgs = arguments[4]; + + // Since we need to maintain backwards compatibility, we have to pass + // an invalid callbackId even if no callback was provided since plugins + // will be expecting it. The Cordova.exec() implementation allocates + // an invalid callbackId and passes it even if no callbacks were given. + callbackId = 'INVALID'; + } else { + // FORMAT TWO, REMOVED + try { + splitCommand = arguments[0].split("."); + action = splitCommand.pop(); + service = splitCommand.join("."); + actionArgs = Array.prototype.splice.call(arguments, 1); + + console.log('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + + "cordova.exec(null, null, \"" + service + "\", \"" + action + "\"," + JSON.stringify(actionArgs) + ");" + ); + return; + } catch (e) { + } + } + + // Register the callbacks and add the callbackId to the positional + // arguments if given. + if (successCallback || failCallback) { + callbackId = service + cordova.callbackId++; + cordova.callbacks[callbackId] = + {success:successCallback, fail:failCallback}; + } + + actionArgs = massageArgsJsToNative(actionArgs); + + var command = [callbackId, service, action, actionArgs]; + + // Stringify and queue the command. We stringify to command now to + // effectively clone the command arguments in case they are mutated before + // the command is executed. + commandQueue.push(JSON.stringify(command)); + + // If we're in the context of a stringByEvaluatingJavaScriptFromString call, + // then the queue will be flushed when it returns; no need for a poke. + // Also, if there is already a command in the queue, then we've already + // poked the native side, so there is no reason to do so again. + if (!isInContextOfEvalJs && commandQueue.length == 1) { + if (bridgeMode != jsToNativeModes.IFRAME_NAV) { + // This prevents sending an XHR when there is already one being sent. + // This should happen only in rare circumstances (refer to unit tests). + if (execXhr && execXhr.readyState != 4) { + execXhr = null; + } + // Re-using the XHR improves exec() performance by about 10%. + execXhr = execXhr || new XMLHttpRequest(); + // Changing this to a GET will make the XHR reach the URIProtocol on 4.2. + // For some reason it still doesn't work though... + // Add a timestamp to the query param to prevent caching. + execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true); + if (!vcHeaderValue) { + vcHeaderValue = /.*\((.*)\)/.exec(navigator.userAgent)[1]; + } + execXhr.setRequestHeader('vc', vcHeaderValue); + execXhr.setRequestHeader('rc', ++requestCount); + if (shouldBundleCommandJson()) { + execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages()); + } + execXhr.send(null); + } else { + execIframe = execIframe || createExecIframe(); + execIframe.src = "gap://ready"; + } + } +} + +iOSExec.jsToNativeModes = jsToNativeModes; + +iOSExec.setJsToNativeBridgeMode = function(mode) { + // Remove the iFrame since it may be no longer required, and its existence + // can trigger browser bugs. + // https://issues.apache.org/jira/browse/CB-593 + if (execIframe) { + execIframe.parentNode.removeChild(execIframe); + execIframe = null; + } + bridgeMode = mode; +}; + +iOSExec.nativeFetchMessages = function() { + // Each entry in commandQueue is a JSON string already. + if (!commandQueue.length) { + return ''; + } + var json = '[' + commandQueue.join(',') + ']'; + commandQueue.length = 0; + return json; +}; + +iOSExec.nativeCallback = function(callbackId, status, message, keepCallback) { + return iOSExec.nativeEvalAndFetch(function() { + var success = status === 0 || status === 1; + var args = convertMessageToArgsNativeToJs(message); + cordova.callbackFromNative(callbackId, success, status, args, keepCallback); + }); +}; + +iOSExec.nativeEvalAndFetch = function(func) { + // This shouldn't be nested, but better to be safe. + isInContextOfEvalJs++; + try { + func(); + return iOSExec.nativeFetchMessages(); + } finally { + isInContextOfEvalJs--; + } +}; + +module.exports = iOSExec; + +}); + +// file: lib/common/modulemapper.js +define("cordova/modulemapper", function(require, exports, module) { + +var builder = require('cordova/builder'), + moduleMap = define.moduleMap, + symbolList, + deprecationMap; + +exports.reset = function() { + symbolList = []; + deprecationMap = {}; +}; + +function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) { + if (!(moduleName in moduleMap)) { + throw new Error('Module ' + moduleName + ' does not exist.'); + } + symbolList.push(strategy, moduleName, symbolPath); + if (opt_deprecationMessage) { + deprecationMap[symbolPath] = opt_deprecationMessage; + } +} + +// Note: Android 2.3 does have Function.bind(). +exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('c', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('m', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('d', moduleName, symbolPath, opt_deprecationMessage); +}; + +function prepareNamespace(symbolPath, context) { + if (!symbolPath) { + return context; + } + var parts = symbolPath.split('.'); + var cur = context; + for (var i = 0, part; part = parts[i]; ++i) { + cur = cur[part] = cur[part] || {}; + } + return cur; +} + +exports.mapModules = function(context) { + var origSymbols = {}; + context.CDV_origSymbols = origSymbols; + for (var i = 0, len = symbolList.length; i < len; i += 3) { + var strategy = symbolList[i]; + var moduleName = symbolList[i + 1]; + var symbolPath = symbolList[i + 2]; + var lastDot = symbolPath.lastIndexOf('.'); + var namespace = symbolPath.substr(0, lastDot); + var lastName = symbolPath.substr(lastDot + 1); + + var module = require(moduleName); + var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null; + var parentObj = prepareNamespace(namespace, context); + var target = parentObj[lastName]; + + if (strategy == 'm' && target) { + builder.recursiveMerge(target, module); + } else if ((strategy == 'd' && !target) || (strategy != 'd')) { + if (!(symbolPath in origSymbols)) { + origSymbols[symbolPath] = target; + } + builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg); + } + } +}; + +exports.getOriginalSymbol = function(context, symbolPath) { + var origSymbols = context.CDV_origSymbols; + if (origSymbols && (symbolPath in origSymbols)) { + return origSymbols[symbolPath]; + } + var parts = symbolPath.split('.'); + var obj = context; + for (var i = 0; i < parts.length; ++i) { + obj = obj && obj[parts[i]]; + } + return obj; +}; + +exports.loadMatchingModules = function(matchingRegExp) { + for (var k in moduleMap) { + if (matchingRegExp.exec(k)) { + require(k); + } + } +}; + +exports.reset(); + + +}); + +// file: lib/ios/platform.js +define("cordova/platform", function(require, exports, module) { + +module.exports = { + id: "ios", + initialize:function() { + var modulemapper = require('cordova/modulemapper'); + + modulemapper.loadMatchingModules(/cordova.*\/plugininit$/); + + modulemapper.loadMatchingModules(/cordova.*\/symbols$/); + modulemapper.mapModules(window); + } +}; + + +}); + +// file: lib/common/plugin/Acceleration.js +define("cordova/plugin/Acceleration", function(require, exports, module) { + +var Acceleration = function(x, y, z, timestamp) { + this.x = x; + this.y = y; + this.z = z; + this.timestamp = timestamp || (new Date()).getTime(); +}; + +module.exports = Acceleration; + +}); + +// file: lib/common/plugin/Camera.js +define("cordova/plugin/Camera", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + Camera = require('cordova/plugin/CameraConstants'), + CameraPopoverHandle = require('cordova/plugin/CameraPopoverHandle'); + +var cameraExport = {}; + +// Tack on the Camera Constants to the base camera plugin. +for (var key in Camera) { + cameraExport[key] = Camera[key]; +} + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=FILE_URI. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +cameraExport.getPicture = function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'Camera.getPicture', arguments); + options = options || {}; + var getValue = argscheck.getValue; + + var quality = getValue(options.quality, 50); + var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI); + var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA); + var targetWidth = getValue(options.targetWidth, -1); + var targetHeight = getValue(options.targetHeight, -1); + var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG); + var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE); + var allowEdit = !!options.allowEdit; + var correctOrientation = !!options.correctOrientation; + var saveToPhotoAlbum = !!options.saveToPhotoAlbum; + var popoverOptions = getValue(options.popoverOptions, null); + var cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK); + + var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection]; + + exec(successCallback, errorCallback, "Camera", "takePicture", args); + return new CameraPopoverHandle(); +}; + +cameraExport.cleanup = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "Camera", "cleanup", []); +}; + +module.exports = cameraExport; + +}); + +// file: lib/common/plugin/CameraConstants.js +define("cordova/plugin/CameraConstants", function(require, exports, module) { + +module.exports = { + DestinationType:{ + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android) + NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS) + }, + EncodingType:{ + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image + }, + MediaType:{ + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types + }, + PictureSourceType:{ + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) + }, + PopoverArrowDirection:{ + ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants to specify arrow location on popover + ARROW_DOWN : 2, + ARROW_LEFT : 4, + ARROW_RIGHT : 8, + ARROW_ANY : 15 + }, + Direction:{ + BACK: 0, + FRONT: 1 + } +}; + +}); + +// file: lib/ios/plugin/CameraPopoverHandle.js +define("cordova/plugin/CameraPopoverHandle", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * A handle to an image picker popover. + */ +var CameraPopoverHandle = function() { + this.setPosition = function(popoverOptions) { + var args = [ popoverOptions ]; + exec(null, null, "Camera", "repositionPopover", args); + }; +}; + +module.exports = CameraPopoverHandle; + +}); + +// file: lib/common/plugin/CameraPopoverOptions.js +define("cordova/plugin/CameraPopoverOptions", function(require, exports, module) { + +var Camera = require('cordova/plugin/CameraConstants'); + +/** + * Encapsulates options for iOS Popover image picker + */ +var CameraPopoverOptions = function(x,y,width,height,arrowDir){ + // information of rectangle that popover should be anchored to + this.x = x || 0; + this.y = y || 32; + this.width = width || 320; + this.height = height || 480; + // The direction of the popover arrow + this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY; +}; + +module.exports = CameraPopoverOptions; + +}); + +// file: lib/common/plugin/CaptureAudioOptions.js +define("cordova/plugin/CaptureAudioOptions", function(require, exports, module) { + +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; +}; + +module.exports = CaptureAudioOptions; + +}); + +// file: lib/common/plugin/CaptureError.js +define("cordova/plugin/CaptureError", function(require, exports, module) { + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(c) { + this.code = c || null; +}; + +// Camera or microphone failed to capture image or sound. +CaptureError.CAPTURE_INTERNAL_ERR = 0; +// Camera application or audio capture application is currently serving other capture request. +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +// Invalid use of the API (e.g. limit parameter has value less than one). +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +// User exited camera application or audio capture application before capturing anything. +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +// The requested capture operation is not supported. +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +module.exports = CaptureError; + +}); + +// file: lib/common/plugin/CaptureImageOptions.js +define("cordova/plugin/CaptureImageOptions", function(require, exports, module) { + +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; +}; + +module.exports = CaptureImageOptions; + +}); + +// file: lib/common/plugin/CaptureVideoOptions.js +define("cordova/plugin/CaptureVideoOptions", function(require, exports, module) { + +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; +}; + +module.exports = CaptureVideoOptions; + +}); + +// file: lib/common/plugin/CompassError.js +define("cordova/plugin/CompassError", function(require, exports, module) { + +/** + * CompassError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var CompassError = function(err) { + this.code = (err !== undefined ? err : null); +}; + +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +module.exports = CompassError; + +}); + +// file: lib/common/plugin/CompassHeading.js +define("cordova/plugin/CompassHeading", function(require, exports, module) { + +var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { + this.magneticHeading = magneticHeading; + this.trueHeading = trueHeading; + this.headingAccuracy = headingAccuracy; + this.timestamp = timestamp || new Date().getTime(); +}; + +module.exports = CompassHeading; + +}); + +// file: lib/common/plugin/ConfigurationData.js +define("cordova/plugin/ConfigurationData", function(require, exports, module) { + +/** + * Encapsulates a set of parameters that the capture device supports. + */ +function ConfigurationData() { + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +} + +module.exports = ConfigurationData; + +}); + +// file: lib/common/plugin/Connection.js +define("cordova/plugin/Connection", function(require, exports, module) { + +/** + * Network status + */ +module.exports = { + UNKNOWN: "unknown", + ETHERNET: "ethernet", + WIFI: "wifi", + CELL_2G: "2g", + CELL_3G: "3g", + CELL_4G: "4g", + CELL:"cellular", + NONE: "none" +}; + +}); + +// file: lib/common/plugin/Contact.js +define("cordova/plugin/Contact", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'); + +/** +* Converts primitives into Complex Object +* Currently only used for Date fields +*/ +function convertIn(contact) { + var value = contact.birthday; + try { + contact.birthday = new Date(parseFloat(value)); + } catch (exception){ + console.log("Cordova Contact convertIn error: exception creating date."); + } + return contact; +} + +/** +* Converts Complex objects into primitives +* Only conversion at present is for Dates. +**/ + +function convertOut(contact) { + var value = contact.birthday; + if (value !== null) { + // try to make it a Date object if it is not already + if (!utils.isDate(value)){ + try { + value = new Date(value); + } catch(exception){ + value = null; + } + } + if (utils.isDate(value)){ + value = value.valueOf(); // convert to milliseconds + } + contact.birthday = value; + } + return contact; +} + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.<ContactField>} phoneNumbers array of phone numbers +* @param {Array.<ContactField>} emails array of email addresses +* @param {Array.<ContactAddress>} addresses array of addresses +* @param {Array.<ContactField>} ims instant messaging user ids +* @param {Array.<ContactOrganization>} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.<ContactField>} photos +* @param {Array.<ContactField>} categories +* @param {Array.<ContactField>} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + argscheck.checkArgs('FF', 'Contact.remove', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + if (this.id === null) { + fail(ContactError.UNKNOWN_ERROR); + } + else { + exec(successCB, fail, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = utils.clone(this); + clonedContact.id = null; + clonedContact.rawId = null; + + function nullIds(arr) { + if (arr) { + for (var i = 0; i < arr.length; ++i) { + arr[i].id = null; + } + } + } + + // Loop through and clear out any id's in phones, emails, etc. + nullIds(clonedContact.phoneNumbers); + nullIds(clonedContact.emails); + nullIds(clonedContact.addresses); + nullIds(clonedContact.ims); + nullIds(clonedContact.organizations); + nullIds(clonedContact.categories); + nullIds(clonedContact.photos); + nullIds(clonedContact.urls); + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + argscheck.checkArgs('FFO', 'Contact.save', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + var success = function(result) { + if (result) { + if (successCB) { + var fullContact = require('cordova/plugin/contacts').create(result); + successCB(convertIn(fullContact)); + } + } + else { + // no Entry object returned + fail(ContactError.UNKNOWN_ERROR); + } + }; + var dupContact = convertOut(utils.clone(this)); + exec(success, fail, "Contacts", "save", [dupContact]); +}; + + +module.exports = Contact; + +}); + +// file: lib/common/plugin/ContactAddress.js +define("cordova/plugin/ContactAddress", function(require, exports, module) { + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted // NOTE: not a W3C standard +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ + +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +module.exports = ContactAddress; + +}); + +// file: lib/common/plugin/ContactError.js +define("cordova/plugin/ContactError", function(require, exports, module) { + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var ContactError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +module.exports = ContactError; + +}); + +// file: lib/common/plugin/ContactField.js +define("cordova/plugin/ContactField", function(require, exports, module) { + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = (type && type.toString()) || null; + this.value = (value && value.toString()) || null; + this.pref = (typeof pref != 'undefined' ? pref : false); +}; + +module.exports = ContactField; + +}); + +// file: lib/common/plugin/ContactFindOptions.js +define("cordova/plugin/ContactFindOptions", function(require, exports, module) { + +/** + * ContactFindOptions. + * @constructor + * @param filter used to match contacts against + * @param multiple boolean used to determine if more than one contact should be returned + */ + +var ContactFindOptions = function(filter, multiple) { + this.filter = filter || ''; + this.multiple = (typeof multiple != 'undefined' ? multiple : false); +}; + +module.exports = ContactFindOptions; + +}); + +// file: lib/common/plugin/ContactName.js +define("cordova/plugin/ContactName", function(require, exports, module) { + +/** +* Contact name. +* @constructor +* @param formatted // NOTE: not part of W3C standard +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +module.exports = ContactName; + +}); + +// file: lib/common/plugin/ContactOrganization.js +define("cordova/plugin/ContactOrganization", function(require, exports, module) { + +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ + +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +module.exports = ContactOrganization; + +}); + +// file: lib/common/plugin/Coordinates.js +define("cordova/plugin/Coordinates", function(require, exports, module) { + +/** + * This class contains position information. + * @param {Object} lat + * @param {Object} lng + * @param {Object} alt + * @param {Object} acc + * @param {Object} head + * @param {Object} vel + * @param {Object} altacc + * @constructor + */ +var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { + /** + * The latitude of the position. + */ + this.latitude = lat; + /** + * The longitude of the position, + */ + this.longitude = lng; + /** + * The accuracy of the position. + */ + this.accuracy = acc; + /** + * The altitude of the position. + */ + this.altitude = (alt !== undefined ? alt : null); + /** + * The direction the device is moving at the position. + */ + this.heading = (head !== undefined ? head : null); + /** + * The velocity with which the device is moving at the position. + */ + this.speed = (vel !== undefined ? vel : null); + + if (this.speed === 0 || this.speed === null) { + this.heading = NaN; + } + + /** + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; +}; + +module.exports = Coordinates; + +}); + +// file: lib/common/plugin/DirectoryEntry.js +define("cordova/plugin/DirectoryEntry", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileError = require('cordova/plugin/FileError'), + DirectoryReader = require('cordova/plugin/DirectoryReader'); + +/** + * An interface representing a directory on the file system. + * + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * TODO: implement this!!! {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function(name, fullPath) { + DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath); +}; + +utils.extend(DirectoryEntry, Entry); + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function() { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or exclusively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments); + var win = successCallback && function(result) { + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or exclusively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments); + var win = successCallback && function(result) { + var FileEntry = require('cordova/plugin/FileEntry'); + var entry = new FileEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFile", [this.fullPath, path, options]); +}; + +module.exports = DirectoryEntry; + +}); + +// file: lib/common/plugin/DirectoryReader.js +define("cordova/plugin/DirectoryReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError') ; + +/** + * An interface that lists the files and directories in a directory. + */ +function DirectoryReader(path) { + this.path = path || null; +} + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i<result.length; i++) { + var entry = null; + if (result[i].isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result[i].isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result[i].isDirectory; + entry.isFile = result[i].isFile; + entry.name = result[i].name; + entry.fullPath = result[i].fullPath; + retVal.push(entry); + } + successCallback(retVal); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "readEntries", [this.path]); +}; + +module.exports = DirectoryReader; + +}); + +// file: lib/common/plugin/Entry.js +define("cordova/plugin/Entry", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + Metadata = require('cordova/plugin/Metadata'); + +/** + * Represents a file or directory on the local file system. + * + * @param isFile + * {boolean} true if Entry is a file (readonly) + * @param isDirectory + * {boolean} true if Entry is a directory (readonly) + * @param name + * {DOMString} name of the file or directory, excluding the path + * leading to it (readonly) + * @param fullPath + * {DOMString} the absolute full path to the file or directory + * (readonly) + */ +function Entry(isFile, isDirectory, name, fullPath, fileSystem) { + this.isFile = !!isFile; + this.isDirectory = !!isDirectory; + this.name = name || ''; + this.fullPath = fullPath || ''; + this.filesystem = fileSystem || null; +} + +/** + * Look up the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + */ +Entry.prototype.getMetadata = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getMetadata', arguments); + var success = successCallback && function(lastModified) { + var metadata = new Metadata(lastModified); + successCallback(metadata); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + exec(success, fail, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Set the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + * @param metadataObject + * {Object} keys and values to set + */ +Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) { + argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments); + exec(successCallback, errorCallback, "File", "setMetadata", [this.fullPath, metadataObject]); +}; + +/** + * Move a file or directory to a new location. + * + * @param parent + * {DirectoryEntry} the directory to which to move this entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new DirectoryEntry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "moveTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Copy a directory to a different location. + * + * @param parent + * {DirectoryEntry} the directory to which to copy the entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new Entry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + // success callback + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "copyTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Return a URL that can be used to identify this entry. + */ +Entry.prototype.toURL = function() { + // fullPath attribute contains the full URL + return this.fullPath; +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +Entry.prototype.toURI = function(mimeType) { + console.log("DEPRECATED: Update your code to use 'toURL'"); + // fullPath attribute contains the full URI + return this.toURL(); +}; + +/** + * Remove a file or directory. It is an error to attempt to delete a + * directory that is not empty. It is an error to attempt to delete a + * root directory of a file system. + * + * @param successCallback {Function} called with no parameters + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.remove = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.remove', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "remove", [this.fullPath]); +}; + +/** + * Look up the parent DirectoryEntry of this entry. + * + * @param successCallback {Function} called with the parent DirectoryEntry object + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.getParent = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getParent', arguments); + var win = successCallback && function(result) { + var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getParent", [this.fullPath]); +}; + +module.exports = Entry; + +}); + +// file: lib/common/plugin/File.js +define("cordova/plugin/File", function(require, exports, module) { + +/** + * Constructor. + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ + +var File = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || ''; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; + + // These store the absolute start and end for slicing the file. + this.start = 0; + this.end = this.size; +}; + +/** + * Returns a "slice" of the file. Since Cordova Files don't contain the actual + * content, this really returns a File with adjusted start and end. + * Slices of slices are supported. + * start {Number} The index at which to start the slice (inclusive). + * end {Number} The index at which to end the slice (exclusive). + */ +File.prototype.slice = function(start, end) { + var size = this.end - this.start; + var newStart = 0; + var newEnd = size; + if (arguments.length) { + if (start < 0) { + newStart = Math.max(size + start, 0); + } else { + newStart = Math.min(size, start); + } + } + + if (arguments.length >= 2) { + if (end < 0) { + newEnd = Math.max(size + end, 0); + } else { + newEnd = Math.min(end, size); + } + } + + var newFile = new File(this.name, this.fullPath, this.type, this.lastModifiedData, this.size); + newFile.start = this.start + newStart; + newFile.end = this.start + newEnd; + return newFile; +}; + + +module.exports = File; + +}); + +// file: lib/common/plugin/FileEntry.js +define("cordova/plugin/FileEntry", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileWriter = require('cordova/plugin/FileWriter'), + File = require('cordova/plugin/File'), + FileError = require('cordova/plugin/FileError'); + +/** + * An interface representing a file on the file system. + * + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the file resides (readonly) + */ +var FileEntry = function(name, fullPath) { + FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath]); +}; + +utils.extend(FileEntry, Entry); + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + errorCallback && errorCallback(new FileError(FileError.INVALID_STATE_ERR)); + } else { + successCallback && successCallback(writer); + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + var win = successCallback && function(f) { + var file = new File(f.name, f.fullPath, f.type, f.lastModifiedDate, f.size); + successCallback(file); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFileMetadata", [this.fullPath]); +}; + + +module.exports = FileEntry; + +}); + +// file: lib/common/plugin/FileError.js +define("cordova/plugin/FileError", function(require, exports, module) { + +/** + * FileError + */ +function FileError(error) { + this.code = error || null; +} + +// File error codes +// Found in DOMException +FileError.NOT_FOUND_ERR = 1; +FileError.SECURITY_ERR = 2; +FileError.ABORT_ERR = 3; + +// Added by File API specification +FileError.NOT_READABLE_ERR = 4; +FileError.ENCODING_ERR = 5; +FileError.NO_MODIFICATION_ALLOWED_ERR = 6; +FileError.INVALID_STATE_ERR = 7; +FileError.SYNTAX_ERR = 8; +FileError.INVALID_MODIFICATION_ERR = 9; +FileError.QUOTA_EXCEEDED_ERR = 10; +FileError.TYPE_MISMATCH_ERR = 11; +FileError.PATH_EXISTS_ERR = 12; + +module.exports = FileError; + +}); + +// file: lib/common/plugin/FileReader.js +define("cordova/plugin/FileReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + modulemapper = require('cordova/modulemapper'), + utils = require('cordova/utils'), + File = require('cordova/plugin/File'), + FileError = require('cordova/plugin/FileError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'), + origFileReader = modulemapper.getOriginalSymbol(this, 'FileReader'); + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + * @constructor + */ +var FileReader = function() { + this._readyState = 0; + this._error = null; + this._result = null; + this._fileName = ''; + this._realReader = origFileReader ? new origFileReader() : {}; +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +utils.defineGetter(FileReader.prototype, 'readyState', function() { + return this._fileName ? this._readyState : this._realReader.readyState; +}); + +utils.defineGetter(FileReader.prototype, 'error', function() { + return this._fileName ? this._error: this._realReader.error; +}); + +utils.defineGetter(FileReader.prototype, 'result', function() { + return this._fileName ? this._result: this._realReader.result; +}); + +function defineEvent(eventName) { + utils.defineGetterSetter(FileReader.prototype, eventName, function() { + return this._realReader[eventName] || null; + }, function(value) { + this._realReader[eventName] = value; + }); +} +defineEvent('onloadstart'); // When the read starts. +defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) +defineEvent('onload'); // When the read has successfully completed. +defineEvent('onerror'); // When the read has failed (see errors). +defineEvent('onloadend'); // When the request has completed (either in success or failure). +defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method. + +function initRead(reader, file) { + // Already loading something + if (reader.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + reader._result = null; + reader._error = null; + reader._readyState = FileReader.LOADING; + + if (typeof file.fullPath == 'string') { + reader._fileName = file.fullPath; + } else { + reader._fileName = ''; + return true; + } + + reader.onloadstart && reader.onloadstart(new ProgressEvent("loadstart", {target:reader})); +} + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + if (origFileReader && !this._fileName) { + return this._realReader.abort(); + } + this._result = null; + + if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) { + return; + } + + this._readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort === 'function') { + this.onabort(new ProgressEvent('abort', {target:this})); + } + // If load end callback + if (typeof this.onloadend === 'function') { + this.onloadend(new ProgressEvent('loadend', {target:this})); + } +}; + +/** + * Read text file. + * + * @param file {File} File object containing file properties + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + if (initRead(this, file)) { + return this._realReader.readAsText(file, encoding); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + var me = this; + var execArgs = [this._fileName, enc, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // DONE state + me._readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // null result + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsText", execArgs); +}; + + +/** + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[<mediatype>][;base64],<data> + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsDataURL(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsDataURL", execArgs); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsBinaryString(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsBinaryString", execArgs); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsArrayBuffer(file); + } + + var me = this; + var execArgs = [this._fileName, file.start, file.end]; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsArrayBuffer", execArgs); +}; + +module.exports = FileReader; + +}); + +// file: lib/common/plugin/FileSystem.js +define("cordova/plugin/FileSystem", function(require, exports, module) { + +var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function(name, root) { + this.name = name || null; + if (root) { + this.root = new DirectoryEntry(root.name, root.fullPath); + } +}; + +module.exports = FileSystem; + +}); + +// file: lib/common/plugin/FileTransfer.js +define("cordova/plugin/FileTransfer", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileTransferError = require('cordova/plugin/FileTransferError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +function newProgressEvent(result) { + var pe = new ProgressEvent(); + pe.lengthComputable = result.lengthComputable; + pe.loaded = result.loaded; + pe.total = result.total; + return pe; +} + +function getBasicAuthHeader(urlString) { + var header = null; + + if (window.btoa) { + // parse the url using the Location object + var url = document.createElement('a'); + url.href = urlString; + + var credentials = null; + var protocol = url.protocol + "//"; + var origin = protocol + url.host; + + // check whether there are the username:password credentials in the url + if (url.href.indexOf(origin) !== 0) { // credentials found + var atIndex = url.href.indexOf("@"); + credentials = url.href.substring(protocol.length, atIndex); + } + + if (credentials) { + var authHeader = "Authorization"; + var authHeaderValue = "Basic " + window.btoa(credentials); + + header = { + name : authHeader, + value : authHeaderValue + }; + } + } + + return header; +} + +var idCounter = 0; + +/** + * FileTransfer uploads a file to a remote server. + * @constructor + */ +var FileTransfer = function() { + this._id = ++idCounter; + this.onprogress = null; // optional callback +}; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { + argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments); + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + var chunkedMode = true; + var headers = null; + var httpMethod = null; + var basicAuthHeader = getBasicAuthHeader(server); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + headers = options.headers; + httpMethod = options.httpMethod || "POST"; + if (httpMethod.toUpperCase() == "PUT"){ + httpMethod = "PUT"; + } else { + httpMethod = "POST"; + } + if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { + chunkedMode = options.chunkedMode; + } + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); + errorCallback(error); + }; + + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + self.onprogress(newProgressEvent(result)); + } + } else { + successCallback && successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); +}; + +/** + * Downloads a file form a given URL and saves it to the specified directory. + * @param source {String} URL of the server to receive the file + * @param target {String} Full path of the file on the device + * @param successCallback (Function} Callback to be invoked when upload has completed + * @param errorCallback {Function} Callback to be invoked upon error + * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + * @param options {FileDownloadOptions} Optional parameters such as headers + */ +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { + argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); + var self = this; + + var basicAuthHeader = getBasicAuthHeader(source); + if (basicAuthHeader) { + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + var headers = null; + if (options) { + headers = options.headers || null; + } + + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else if (successCallback) { + var entry = null; + if (result.isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result.isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + successCallback(entry); + } + }; + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); + errorCallback(error); + }; + + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id, headers]); +}; + +/** + * Aborts the ongoing file transfer on this object. The original error + * callback for the file transfer will be called if necessary. + */ +FileTransfer.prototype.abort = function() { + exec(null, null, 'FileTransfer', 'abort', [this._id]); +}; + +module.exports = FileTransfer; + +}); + +// file: lib/common/plugin/FileTransferError.js +define("cordova/plugin/FileTransferError", function(require, exports, module) { + +/** + * FileTransferError + * @constructor + */ +var FileTransferError = function(code, source, target, status, body) { + this.code = code || null; + this.source = source || null; + this.target = target || null; + this.http_status = status || null; + this.body = body || null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; + +module.exports = FileTransferError; + +}); + +// file: lib/common/plugin/FileUploadOptions.js +define("cordova/plugin/FileUploadOptions", function(require, exports, module) { + +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + * @param headers {Object} Keys are header names, values are header values. Multiple + * headers of the same name are not supported. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; + this.headers = headers || null; + this.httpMethod = httpMethod || null; +}; + +module.exports = FileUploadOptions; + +}); + +// file: lib/common/plugin/FileUploadResult.js +define("cordova/plugin/FileUploadResult", function(require, exports, module) { + +/** + * FileUploadResult + * @constructor + */ +var FileUploadResult = function() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +module.exports = FileUploadResult; + +}); + +// file: lib/common/plugin/FileWriter.js +define("cordova/plugin/FileWriter", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // set error + this.error = new FileError(FileError.ABORT_ERR); + + this.readyState = FileWriter.DONE; + + // If abort callback + if (typeof this.onabort === "function") { + this.onabort(new ProgressEvent("abort", {"target":this})); + } + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend(new ProgressEvent("writeend", {"target":this})); + } +}; + +/** + * Writes data to the file + * + * @param data text or blob to be written + */ +FileWriter.prototype.write = function(data) { + + var isBinary = false; + + // If we don't have Blob or ArrayBuffer support, don't bother. + if (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined') { + + // Check to see if the incoming data is a blob + if (data instanceof Blob) { + var that=this; + var fileReader = new FileReader(); + fileReader.onload = function() { + // Call this method again, with the arraybuffer as argument + FileWriter.prototype.write.call(that, this.result); + }; + fileReader.readAsArrayBuffer(data); + return; + } + + // Mark data type for safer transport over the binary bridge + isBinary = (data instanceof ArrayBuffer); + } + + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":me})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + + me.length = me.position; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "write", [this.fileName, data, this.position, isBinary]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + if (!offset && offset !== 0) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger than file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":this})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "truncate", [this.fileName, size]); +}; + +module.exports = FileWriter; + +}); + +// file: lib/common/plugin/Flags.js +define("cordova/plugin/Flags", function(require, exports, module) { + +/** + * Supplies arguments to methods that lookup or create files and directories. + * + * @param create + * {boolean} file or directory if it doesn't exist + * @param exclusive + * {boolean} used with create; if true the command will fail if + * target path exists + */ +function Flags(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +} + +module.exports = Flags; + +}); + +// file: lib/common/plugin/GlobalizationError.js +define("cordova/plugin/GlobalizationError", function(require, exports, module) { + + +/** + * Globalization error object + * + * @constructor + * @param code + * @param message + */ +var GlobalizationError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +// Globalization error codes +GlobalizationError.UNKNOWN_ERROR = 0; +GlobalizationError.FORMATTING_ERROR = 1; +GlobalizationError.PARSING_ERROR = 2; +GlobalizationError.PATTERN_ERROR = 3; + +module.exports = GlobalizationError; + +}); + +// file: lib/common/plugin/InAppBrowser.js +define("cordova/plugin/InAppBrowser", function(require, exports, module) { + +var exec = require('cordova/exec'); +var channel = require('cordova/channel'); +var modulemapper = require('cordova/modulemapper'); + +function InAppBrowser() { + this.channels = { + 'loadstart': channel.create('loadstart'), + 'loadstop' : channel.create('loadstop'), + 'loaderror' : channel.create('loaderror'), + 'exit' : channel.create('exit') + }; +} + +InAppBrowser.prototype = { + _eventHandler: function (event) { + if (event.type in this.channels) { + this.channels[event.type].fire(event); + } + }, + close: function (eventname) { + exec(null, null, "InAppBrowser", "close", []); + }, + show: function (eventname) { + exec(null, null, "InAppBrowser", "show", []); + }, + addEventListener: function (eventname,f) { + if (eventname in this.channels) { + this.channels[eventname].subscribe(f); + } + }, + removeEventListener: function(eventname, f) { + if (eventname in this.channels) { + this.channels[eventname].unsubscribe(f); + } + }, + + executeScript: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]); + } else { + throw new Error('executeScript requires exactly one of code or file to be specified'); + } + }, + + insertCSS: function(injectDetails, cb) { + if (injectDetails.code) { + exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]); + } else if (injectDetails.file) { + exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]); + } else { + throw new Error('insertCSS requires exactly one of code or file to be specified'); + } + } +}; + +module.exports = function(strUrl, strWindowName, strWindowFeatures) { + var iab = new InAppBrowser(); + var cb = function(eventname) { + iab._eventHandler(eventname); + }; + + // Don't catch calls that write to existing frames (e.g. named iframes). + if (window.frames && window.frames[strWindowName]) { + var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open'); + return origOpenFunc.apply(window, arguments); + } + + exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); + return iab; +}; + + +}); + +// file: lib/common/plugin/LocalFileSystem.js +define("cordova/plugin/LocalFileSystem", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Represents a local file system. + */ +var LocalFileSystem = function() { + +}; + +LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence +LocalFileSystem.PERSISTENT = 1; //persistent + +module.exports = LocalFileSystem; + +}); + +// file: lib/common/plugin/Media.js +define("cordova/plugin/Media", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var mediaObjects = {}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @constructor + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + */ +var Media = function(src, successCallback, errorCallback, statusCallback) { + argscheck.checkArgs('SFFF', 'Media', arguments); + this.id = utils.createUUID(); + mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; + this._position = -1; + exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_POSITION = 3; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// "static" function to return existing objs. +Media.get = function(id) { + return mediaObjects[id]; +}; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function(options) { + exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + var me = this; + exec(function() { + me._position = 0; + }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Seek or jump to a new time in the track.. + */ +Media.prototype.seekTo = function(milliseconds) { + var me = this; + exec(function(p) { + me._position = p; + }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + */ +Media.prototype.getCurrentPosition = function(success, fail) { + var me = this; + exec(function(p) { + me._position = p; + success(p); + }, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + exec(null, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + exec(null, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + exec(null, this.errorCallback, "Media", "release", [this.id]); +}; + +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param msgType The 'type' of update this is + * @param value Use of value is determined by the msgType + */ +Media.onStatus = function(id, msgType, value) { + + var media = mediaObjects[id]; + + if(media) { + switch(msgType) { + case Media.MEDIA_STATE : + media.statusCallback && media.statusCallback(value); + if(value == Media.MEDIA_STOPPED) { + media.successCallback && media.successCallback(); + } + break; + case Media.MEDIA_DURATION : + media._duration = value; + break; + case Media.MEDIA_ERROR : + media.errorCallback && media.errorCallback(value); + break; + case Media.MEDIA_POSITION : + media._position = Number(value); + break; + default : + console.error && console.error("Unhandled Media.onStatus :: " + msgType); + break; + } + } + else { + console.error && console.error("Received Media.onStatus callback for unknown media :: " + id); + } + +}; + +module.exports = Media; + +}); + +// file: lib/common/plugin/MediaError.js +define("cordova/plugin/MediaError", function(require, exports, module) { + +/** + * This class contains information about any Media errors. +*/ +/* + According to :: http://dev.w3.org/html5/spec-author-view/video.html#mediaerror + We should never be creating these objects, we should just implement the interface + which has 1 property for an instance, 'code' + + instead of doing : + errorCallbackFunction( new MediaError(3,'msg') ); +we should simply use a literal : + errorCallbackFunction( {'code':3} ); + */ + + var _MediaError = window.MediaError; + + +if(!_MediaError) { + window.MediaError = _MediaError = function(code, msg) { + this.code = (typeof code != 'undefined') ? code : null; + this.message = msg || ""; // message is NON-standard! do not use! + }; +} + +_MediaError.MEDIA_ERR_NONE_ACTIVE = _MediaError.MEDIA_ERR_NONE_ACTIVE || 0; +_MediaError.MEDIA_ERR_ABORTED = _MediaError.MEDIA_ERR_ABORTED || 1; +_MediaError.MEDIA_ERR_NETWORK = _MediaError.MEDIA_ERR_NETWORK || 2; +_MediaError.MEDIA_ERR_DECODE = _MediaError.MEDIA_ERR_DECODE || 3; +_MediaError.MEDIA_ERR_NONE_SUPPORTED = _MediaError.MEDIA_ERR_NONE_SUPPORTED || 4; +// TODO: MediaError.MEDIA_ERR_NONE_SUPPORTED is legacy, the W3 spec now defines it as below. +// as defined by http://dev.w3.org/html5/spec-author-view/video.html#error-codes +_MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = _MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED || 4; + +module.exports = _MediaError; + +}); + +// file: lib/common/plugin/MediaFile.js +define("cordova/plugin/MediaFile", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + File = require('cordova/plugin/File'), + CaptureError = require('cordova/plugin/CaptureError'); +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + MediaFile.__super__.constructor.apply(this, arguments); +}; + +utils.extend(MediaFile, File); + +/** + * Request capture format data for a specific file and type + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { + if (typeof this.fullPath === "undefined" || this.fullPath === null) { + errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); + } else { + exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); + } +}; + +module.exports = MediaFile; + +}); + +// file: lib/common/plugin/MediaFileData.js +define("cordova/plugin/MediaFileData", function(require, exports, module) { + +/** + * MediaFileData encapsulates format information of a media file. + * + * @param {DOMString} codecs + * @param {long} bitrate + * @param {long} height + * @param {long} width + * @param {float} duration + */ +var MediaFileData = function(codecs, bitrate, height, width, duration){ + this.codecs = codecs || null; + this.bitrate = bitrate || 0; + this.height = height || 0; + this.width = width || 0; + this.duration = duration || 0; +}; + +module.exports = MediaFileData; + +}); + +// file: lib/common/plugin/Metadata.js +define("cordova/plugin/Metadata", function(require, exports, module) { + +/** + * Information about the state of the file or directory + * + * {Date} modificationTime (readonly) + */ +var Metadata = function(time) { + this.modificationTime = (typeof time != 'undefined'?new Date(time):null); +}; + +module.exports = Metadata; + +}); + +// file: lib/common/plugin/Position.js +define("cordova/plugin/Position", function(require, exports, module) { + +var Coordinates = require('cordova/plugin/Coordinates'); + +var Position = function(coords, timestamp) { + if (coords) { + this.coords = new Coordinates(coords.latitude, coords.longitude, coords.altitude, coords.accuracy, coords.heading, coords.velocity, coords.altitudeAccuracy); + } else { + this.coords = new Coordinates(); + } + this.timestamp = (timestamp !== undefined) ? timestamp : new Date(); +}; + +module.exports = Position; + +}); + +// file: lib/common/plugin/PositionError.js +define("cordova/plugin/PositionError", function(require, exports, module) { + +/** + * Position error object + * + * @constructor + * @param code + * @param message + */ +var PositionError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +module.exports = PositionError; + +}); + +// file: lib/common/plugin/ProgressEvent.js +define("cordova/plugin/ProgressEvent", function(require, exports, module) { + +// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill +// Feature test: See if we can instantiate a native ProgressEvent; +// if so, use that approach, +// otherwise fill-in with our own implementation. +// +// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview. +var ProgressEvent = (function() { + /* + var createEvent = function(data) { + var event = document.createEvent('Events'); + event.initEvent('ProgressEvent', false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + if (data.target) { + // TODO: cannot call <some_custom_object>.dispatchEvent + // need to first figure out how to implement EventTarget + } + } + return event; + }; + try { + var ev = createEvent({type:"abort",target:document}); + return function ProgressEvent(type, data) { + data.type = type; + return createEvent(data); + }; + } catch(e){ + */ + return function ProgressEvent(type, dict) { + this.type = type; + this.bubbles = false; + this.cancelBubble = false; + this.cancelable = false; + this.lengthComputable = false; + this.loaded = dict && dict.loaded ? dict.loaded : 0; + this.total = dict && dict.total ? dict.total : 0; + this.target = dict && dict.target ? dict.target : null; + }; + //} +})(); + +module.exports = ProgressEvent; + +}); + +// file: lib/common/plugin/accelerometer.js +define("cordova/plugin/accelerometer", function(require, exports, module) { + +/** + * This class provides access to device accelerometer data. + * @constructor + */ +var argscheck = require('cordova/argscheck'), + utils = require("cordova/utils"), + exec = require("cordova/exec"), + Acceleration = require('cordova/plugin/Acceleration'); + +// Is the accel sensor running? +var running = false; + +// Keeps reference to watchAcceleration calls. +var timers = {}; + +// Array of listeners; used to keep track of when we should call start and stop. +var listeners = []; + +// Last returned acceleration object from native +var accel = null; + +// Tells native to start. +function start() { + exec(function(a) { + var tempListeners = listeners.slice(0); + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].win(accel); + } + }, function(e) { + var tempListeners = listeners.slice(0); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].fail(e); + } + }, "Accelerometer", "start", []); + running = true; +} + +// Tells native to stop. +function stop() { + exec(null, null, "Accelerometer", "stop", []); + running = false; +} + +// Adds a callback pair to the listeners array +function createCallbackPair(win, fail) { + return {win:win, fail:fail}; +} + +// Removes a win/fail listener pair from the listeners array +function removeListeners(l) { + var idx = listeners.indexOf(l); + if (idx > -1) { + listeners.splice(idx, 1); + if (listeners.length === 0) { + stop(); + } + } +} + +var accelerometer = { + /** + * Asynchronously acquires the current acceleration. + * + * @param {Function} successCallback The function to call when the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + */ + getCurrentAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.getCurrentAcceleration', arguments); + + var p; + var win = function(a) { + removeListeners(p); + successCallback(a); + }; + var fail = function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }; + + p = createCallbackPair(win, fail); + listeners.push(p); + + if (!running) { + start(); + } + }, + + /** + * Asynchronously acquires the acceleration repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.watchAcceleration', arguments); + // Default interval (10 sec) + var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000; + + // Keep reference to watch id, and report accel readings as often as defined in frequency + var id = utils.createUUID(); + + var p = createCallbackPair(function(){}, function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }); + listeners.push(p); + + timers[id] = { + timer:window.setInterval(function() { + if (accel) { + successCallback(accel); + } + }, frequency), + listeners:p + }; + + if (running) { + // If we're already running then immediately invoke the success callback + // but only if we have retrieved a value, sample code does not check for null ... + if (accel) { + successCallback(accel); + } + } else { + start(); + } + + return id; + }, + + /** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ + clearWatch: function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + window.clearInterval(timers[id].timer); + removeListeners(timers[id].listeners); + delete timers[id]; + } + } +}; + +module.exports = accelerometer; + +}); + +// file: lib/common/plugin/accelerometer/symbols.js +define("cordova/plugin/accelerometer/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Acceleration', 'Acceleration'); +modulemapper.defaults('cordova/plugin/accelerometer', 'navigator.accelerometer'); + +}); + +// file: lib/common/plugin/battery.js +define("cordova/plugin/battery", function(require, exports, module) { + +/** + * This class contains information about the current battery status. + * @constructor + */ +var cordova = require('cordova'), + exec = require('cordova/exec'); + +function handlers() { + return battery.channels.batterystatus.numHandlers + + battery.channels.batterylow.numHandlers + + battery.channels.batterycritical.numHandlers; +} + +var Battery = function() { + this._level = null; + this._isPlugged = null; + // Create new event handlers on the window (returns a channel instance) + this.channels = { + batterystatus:cordova.addWindowEventHandler("batterystatus"), + batterylow:cordova.addWindowEventHandler("batterylow"), + batterycritical:cordova.addWindowEventHandler("batterycritical") + }; + for (var key in this.channels) { + this.channels[key].onHasSubscribersChange = Battery.onHasSubscribersChange; + } +}; +/** + * Event handlers for when callbacks get registered for the battery. + * Keep track of how many handlers we have so we can start and stop the native battery listener + * appropriately (and hopefully save on battery life!). + */ +Battery.onHasSubscribersChange = function() { + // If we just registered the first handler, make sure native listener is started. + if (this.numHandlers === 1 && handlers() === 1) { + exec(battery._status, battery._error, "Battery", "start", []); + } else if (handlers() === 0) { + exec(null, null, "Battery", "stop", []); + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = battery; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + cordova.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + cordova.fireWindowEvent("batterylow", info); + } + else { + cordova.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +var battery = new Battery(); + +module.exports = battery; + +}); + +// file: lib/common/plugin/battery/symbols.js +define("cordova/plugin/battery/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/battery', 'navigator.battery'); + +}); + +// file: lib/common/plugin/camera/symbols.js +define("cordova/plugin/camera/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Camera', 'navigator.camera'); +modulemapper.defaults('cordova/plugin/CameraConstants', 'Camera'); +modulemapper.defaults('cordova/plugin/CameraPopoverOptions', 'CameraPopoverOptions'); + +}); + +// file: lib/common/plugin/capture.js +define("cordova/plugin/capture", function(require, exports, module) { + +var exec = require('cordova/exec'), + MediaFile = require('cordova/plugin/MediaFile'); + +/** + * Launches a capture of different types. + * + * @param (DOMString} type + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +function _capture(type, successCallback, errorCallback, options) { + var win = function(pluginResult) { + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult[i].name; + mediaFile.fullPath = pluginResult[i].fullPath; + mediaFile.type = pluginResult[i].type; + mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; + mediaFile.size = pluginResult[i].size; + mediaFiles.push(mediaFile); + } + successCallback(mediaFiles); + }; + exec(win, errorCallback, "Capture", type, [options]); +} +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +} + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + _capture("captureAudio", successCallback, errorCallback, options); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + _capture("captureImage", successCallback, errorCallback, options); +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + _capture("captureVideo", successCallback, errorCallback, options); +}; + + +module.exports = new Capture(); + +}); + +// file: lib/common/plugin/capture/symbols.js +define("cordova/plugin/capture/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/CaptureError', 'CaptureError'); +modulemapper.clobbers('cordova/plugin/CaptureAudioOptions', 'CaptureAudioOptions'); +modulemapper.clobbers('cordova/plugin/CaptureImageOptions', 'CaptureImageOptions'); +modulemapper.clobbers('cordova/plugin/CaptureVideoOptions', 'CaptureVideoOptions'); +modulemapper.clobbers('cordova/plugin/ConfigurationData', 'ConfigurationData'); +modulemapper.clobbers('cordova/plugin/MediaFile', 'MediaFile'); +modulemapper.clobbers('cordova/plugin/MediaFileData', 'MediaFileData'); +modulemapper.clobbers('cordova/plugin/capture', 'navigator.device.capture'); + +}); + +// file: lib/common/plugin/compass.js +define("cordova/plugin/compass", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + utils = require('cordova/utils'), + CompassHeading = require('cordova/plugin/CompassHeading'), + CompassError = require('cordova/plugin/CompassError'), + timers = {}, + compass = { + /** + * Asynchronously acquires the current heading. + * @param {Function} successCallback The function to call when the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {CompassOptions} options The options for getting the heading data (not used). + */ + getCurrentHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.getCurrentHeading', arguments); + + var win = function(result) { + var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + successCallback(ch); + }; + var fail = errorCallback && function(code) { + var ce = new CompassError(code); + errorCallback(ce); + }; + + // Get heading + exec(win, fail, "Compass", "getHeading", [options]); + }, + + /** + * Asynchronously acquires the heading repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {HeadingOptions} options The options for getting the heading data + * such as timeout and the frequency of the watch. For iOS, filter parameter + * specifies to watch via a distance filter rather than time. + */ + watchHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.watchHeading', arguments); + // Default interval (100 msec) + var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; + var filter = (options !== undefined && options.filter !== undefined) ? options.filter : 0; + + var id = utils.createUUID(); + if (filter > 0) { + // is an iOS request for watch by filter, no timer needed + timers[id] = "iOS"; + compass.getCurrentHeading(successCallback, errorCallback, options); + } else { + // Start watch timer to get headings + timers[id] = window.setInterval(function() { + compass.getCurrentHeading(successCallback, errorCallback); + }, frequency); + } + + return id; + }, + + /** + * Clears the specified heading watch. + * @param {String} watchId The ID of the watch returned from #watchHeading. + */ + clearWatch:function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + if (timers[id] != "iOS") { + clearInterval(timers[id]); + } else { + // is iOS watch by filter so call into device to stop + exec(null, null, "Compass", "stopHeading", []); + } + delete timers[id]; + } + } + }; + +module.exports = compass; + +}); + +// file: lib/common/plugin/compass/symbols.js +define("cordova/plugin/compass/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/CompassHeading', 'CompassHeading'); +modulemapper.clobbers('cordova/plugin/CompassError', 'CompassError'); +modulemapper.clobbers('cordova/plugin/compass', 'navigator.compass'); + +}); + +// file: lib/common/plugin/console-via-logger.js +define("cordova/plugin/console-via-logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ + +var logger = require("cordova/plugin/logger"); +var utils = require("cordova/utils"); + +//------------------------------------------------------------------------------ +// object that we're exporting +//------------------------------------------------------------------------------ +var console = module.exports; + +//------------------------------------------------------------------------------ +// copy of the original console object +//------------------------------------------------------------------------------ +var WinConsole = window.console; + +//------------------------------------------------------------------------------ +// whether to use the logger +//------------------------------------------------------------------------------ +var UseLogger = false; + +//------------------------------------------------------------------------------ +// Timers +//------------------------------------------------------------------------------ +var Timers = {}; + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +function noop() {} + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +console.useLogger = function (value) { + if (arguments.length) UseLogger = !!value; + + if (UseLogger) { + if (logger.useConsole()) { + throw new Error("console and logger are too intertwingly"); + } + } + + return UseLogger; +}; + +//------------------------------------------------------------------------------ +console.log = function() { + if (logger.useConsole()) return; + logger.log.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.error = function() { + if (logger.useConsole()) return; + logger.error.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.warn = function() { + if (logger.useConsole()) return; + logger.warn.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.info = function() { + if (logger.useConsole()) return; + logger.info.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.debug = function() { + if (logger.useConsole()) return; + logger.debug.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.assert = function(expression) { + if (expression) return; + + var message = logger.format.apply(logger.format, [].slice.call(arguments, 1)); + console.log("ASSERT: " + message); +}; + +//------------------------------------------------------------------------------ +console.clear = function() {}; + +//------------------------------------------------------------------------------ +console.dir = function(object) { + console.log("%o", object); +}; + +//------------------------------------------------------------------------------ +console.dirxml = function(node) { + console.log(node.innerHTML); +}; + +//------------------------------------------------------------------------------ +console.trace = noop; + +//------------------------------------------------------------------------------ +console.group = console.log; + +//------------------------------------------------------------------------------ +console.groupCollapsed = console.log; + +//------------------------------------------------------------------------------ +console.groupEnd = noop; + +//------------------------------------------------------------------------------ +console.time = function(name) { + Timers[name] = new Date().valueOf(); +}; + +//------------------------------------------------------------------------------ +console.timeEnd = function(name) { + var timeStart = Timers[name]; + if (!timeStart) { + console.warn("unknown timer: " + name); + return; + } + + var timeElapsed = new Date().valueOf() - timeStart; + console.log(name + ": " + timeElapsed + "ms"); +}; + +//------------------------------------------------------------------------------ +console.timeStamp = noop; + +//------------------------------------------------------------------------------ +console.profile = noop; + +//------------------------------------------------------------------------------ +console.profileEnd = noop; + +//------------------------------------------------------------------------------ +console.count = noop; + +//------------------------------------------------------------------------------ +console.exception = console.log; + +//------------------------------------------------------------------------------ +console.table = function(data, columns) { + console.log("%o", data); +}; + +//------------------------------------------------------------------------------ +// return a new function that calls both functions passed as args +//------------------------------------------------------------------------------ +function wrappedOrigCall(orgFunc, newFunc) { + return function() { + var args = [].slice.call(arguments); + try { orgFunc.apply(WinConsole, args); } catch (e) {} + try { newFunc.apply(console, args); } catch (e) {} + }; +} + +//------------------------------------------------------------------------------ +// For every function that exists in the original console object, that +// also exists in the new console object, wrap the new console method +// with one that calls both +//------------------------------------------------------------------------------ +for (var key in console) { + if (typeof WinConsole[key] == "function") { + console[key] = wrappedOrigCall(WinConsole[key], console[key]); + } +} + +}); + +// file: lib/common/plugin/contacts.js +define("cordova/plugin/contacts", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'), + Contact = require('cordova/plugin/Contact'); + +/** +* Represents a group of Contacts. +* @constructor +*/ +var contacts = { + /** + * Returns an array of Contacts matching the search criteria. + * @param fields that should be searched + * @param successCB success callback + * @param errorCB error callback + * @param {ContactFindOptions} options that can be applied to contact searching + * @return array of Contacts matching search criteria + */ + find:function(fields, successCB, errorCB, options) { + argscheck.checkArgs('afFO', 'contacts.find', arguments); + if (!fields.length) { + errorCB && errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); + } else { + var win = function(result) { + var cs = []; + for (var i = 0, l = result.length; i < l; i++) { + cs.push(contacts.create(result[i])); + } + successCB(cs); + }; + exec(win, errorCB, "Contacts", "search", [fields, options]); + } + }, + + /** + * This function creates a new contact, but it does not persist the contact + * to device storage. To persist the contact to device storage, invoke + * contact.save(). + * @param properties an object whose properties will be examined to create a new Contact + * @returns new Contact object + */ + create:function(properties) { + argscheck.checkArgs('O', 'contacts.create', arguments); + var contact = new Contact(); + for (var i in properties) { + if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { + contact[i] = properties[i]; + } + } + return contact; + } +}; + +module.exports = contacts; + +}); + +// file: lib/common/plugin/contacts/symbols.js +define("cordova/plugin/contacts/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/contacts', 'navigator.contacts'); +modulemapper.clobbers('cordova/plugin/Contact', 'Contact'); +modulemapper.clobbers('cordova/plugin/ContactAddress', 'ContactAddress'); +modulemapper.clobbers('cordova/plugin/ContactError', 'ContactError'); +modulemapper.clobbers('cordova/plugin/ContactField', 'ContactField'); +modulemapper.clobbers('cordova/plugin/ContactFindOptions', 'ContactFindOptions'); +modulemapper.clobbers('cordova/plugin/ContactName', 'ContactName'); +modulemapper.clobbers('cordova/plugin/ContactOrganization', 'ContactOrganization'); + +}); + +// file: lib/common/plugin/device.js +define("cordova/plugin/device", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +// Tell cordova channel to wait on the CordovaInfoReady event +channel.waitForInitialization('onCordovaInfoReady'); + +/** + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * phone, etc. + * @constructor + */ +function Device() { + this.available = false; + this.platform = null; + this.version = null; + this.uuid = null; + this.cordova = null; + this.model = null; + + var me = this; + + channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + var buildLabel = info.cordova; + if (buildLabel != CORDOVA_JS_BUILD_LABEL) { + buildLabel += ' JS=' + CORDOVA_JS_BUILD_LABEL; + } + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.uuid = info.uuid; + me.cordova = buildLabel; + me.model = info.model; + channel.onCordovaInfoReady.fire(); + },function(e) { + me.available = false; + utils.alert("[ERROR] Error initializing Cordova: " + e); + }); + }); +} + +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + */ +Device.prototype.getInfo = function(successCallback, errorCallback) { + argscheck.checkArgs('fF', 'Device.getInfo', arguments); + exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +module.exports = new Device(); + +}); + +// file: lib/common/plugin/device/symbols.js +define("cordova/plugin/device/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/device', 'device'); + +}); + +// file: lib/common/plugin/echo.js +define("cordova/plugin/echo", function(require, exports, module) { + +var exec = require('cordova/exec'), + utils = require('cordova/utils'); + +/** + * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + * @param message The string to be echoed. + * @param forceAsync Whether to force an async return value (for testing native->js bridge). + */ +module.exports = function(successCallback, errorCallback, message, forceAsync) { + var action = 'echo'; + var messageIsMultipart = (utils.typeName(message) == "Array"); + var args = messageIsMultipart ? message : [message]; + + if (utils.typeName(message) == 'ArrayBuffer') { + if (forceAsync) { + console.warn('Cannot echo ArrayBuffer with forced async, falling back to sync.'); + } + action += 'ArrayBuffer'; + } else if (messageIsMultipart) { + if (forceAsync) { + console.warn('Cannot echo MultiPart Array with forced async, falling back to sync.'); + } + action += 'MultiPart'; + } else if (forceAsync) { + action += 'Async'; + } + + exec(successCallback, errorCallback, "Echo", action, args); +}; + + +}); + +// file: lib/ios/plugin/file/symbols.js +define("cordova/plugin/file/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'), + symbolshelper = require('cordova/plugin/file/symbolshelper'); + +symbolshelper(modulemapper.clobbers); +modulemapper.merges('cordova/plugin/ios/Entry', 'Entry'); + +}); + +// file: lib/common/plugin/file/symbolshelper.js +define("cordova/plugin/file/symbolshelper", function(require, exports, module) { + +module.exports = function(exportFunc) { + exportFunc('cordova/plugin/DirectoryEntry', 'DirectoryEntry'); + exportFunc('cordova/plugin/DirectoryReader', 'DirectoryReader'); + exportFunc('cordova/plugin/Entry', 'Entry'); + exportFunc('cordova/plugin/File', 'File'); + exportFunc('cordova/plugin/FileEntry', 'FileEntry'); + exportFunc('cordova/plugin/FileError', 'FileError'); + exportFunc('cordova/plugin/FileReader', 'FileReader'); + exportFunc('cordova/plugin/FileSystem', 'FileSystem'); + exportFunc('cordova/plugin/FileUploadOptions', 'FileUploadOptions'); + exportFunc('cordova/plugin/FileUploadResult', 'FileUploadResult'); + exportFunc('cordova/plugin/FileWriter', 'FileWriter'); + exportFunc('cordova/plugin/Flags', 'Flags'); + exportFunc('cordova/plugin/LocalFileSystem', 'LocalFileSystem'); + exportFunc('cordova/plugin/Metadata', 'Metadata'); + exportFunc('cordova/plugin/ProgressEvent', 'ProgressEvent'); + exportFunc('cordova/plugin/requestFileSystem', 'requestFileSystem'); + exportFunc('cordova/plugin/resolveLocalFileSystemURI', 'resolveLocalFileSystemURI'); +}; + +}); + +// file: lib/common/plugin/filetransfer/symbols.js +define("cordova/plugin/filetransfer/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/FileTransfer', 'FileTransfer'); +modulemapper.clobbers('cordova/plugin/FileTransferError', 'FileTransferError'); + +}); + +// file: lib/common/plugin/geolocation.js +define("cordova/plugin/geolocation", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + PositionError = require('cordova/plugin/PositionError'), + Position = require('cordova/plugin/Position'); + +var timers = {}; // list of timers in use + +// Returns default params, overrides if provided with values +function parseParameters(options) { + var opt = { + maximumAge: 0, + enableHighAccuracy: false, + timeout: Infinity + }; + + if (options) { + if (options.maximumAge !== undefined && !isNaN(options.maximumAge) && options.maximumAge > 0) { + opt.maximumAge = options.maximumAge; + } + if (options.enableHighAccuracy !== undefined) { + opt.enableHighAccuracy = options.enableHighAccuracy; + } + if (options.timeout !== undefined && !isNaN(options.timeout)) { + if (options.timeout < 0) { + opt.timeout = 0; + } else { + opt.timeout = options.timeout; + } + } + } + + return opt; +} + +// Returns a timeout failure, closed over a specified timeout value and error callback. +function createTimeout(errorCallback, timeout) { + var t = setTimeout(function() { + clearTimeout(t); + t = null; + errorCallback({ + code:PositionError.TIMEOUT, + message:"Position retrieval timed out." + }); + }, timeout); + return t; +} + +var geolocation = { + lastPosition:null, // reference to last known (cached) position returned + /** + * Asynchronously acquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ + getCurrentPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + // Timer var that will fire an error callback if no position is retrieved from native + // before the "timeout" param provided expires + var timeoutTimer = {timer:null}; + + var win = function(p) { + clearTimeout(timeoutTimer.timer); + if (!(timeoutTimer.timer)) { + // Timeout already happened, or native fired error callback for + // this geo request. + // Don't continue with success callback. + return; + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + var fail = function(e) { + clearTimeout(timeoutTimer.timer); + timeoutTimer.timer = null; + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + // Check our cached position, if its timestamp difference with current time is less than the maximumAge, then just + // fire the success callback with the cached position. + if (geolocation.lastPosition && options.maximumAge && (((new Date()).getTime() - geolocation.lastPosition.timestamp.getTime()) <= options.maximumAge)) { + successCallback(geolocation.lastPosition); + // If the cached position check failed and the timeout was set to 0, error out with a TIMEOUT error object. + } else if (options.timeout === 0) { + fail({ + code:PositionError.TIMEOUT, + message:"timeout value in PositionOptions set to 0 and no cached Position object available, or cached Position object's age exceeds provided PositionOptions' maximumAge parameter." + }); + // Otherwise we have to call into native to retrieve a position. + } else { + if (options.timeout !== Infinity) { + // If the timeout value was not set to Infinity (default), then + // set up a timeout function that will fire the error callback + // if no successful position was retrieved before timeout expired. + timeoutTimer.timer = createTimeout(fail, options.timeout); + } else { + // This is here so the check in the win function doesn't mess stuff up + // may seem weird but this guarantees timeoutTimer is + // always truthy before we call into native + timeoutTimer.timer = true; + } + exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.maximumAge]); + } + return timeoutTimer; + }, + /** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + var id = utils.createUUID(); + + // Tell device to get a position ASAP, and also retrieve a reference to the timeout timer generated in getCurrentPosition + timers[id] = geolocation.getCurrentPosition(successCallback, errorCallback, options); + + var fail = function(e) { + clearTimeout(timers[id].timer); + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + var win = function(p) { + clearTimeout(timers[id].timer); + if (options.timeout !== Infinity) { + timers[id].timer = createTimeout(fail, options.timeout); + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + + exec(win, fail, "Geolocation", "addWatch", [id, options.enableHighAccuracy]); + + return id; + }, + /** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ + clearWatch:function(id) { + if (id && timers[id] !== undefined) { + clearTimeout(timers[id].timer); + timers[id].timer = false; + exec(null, null, "Geolocation", "clearWatch", [id]); + } + } +}; + +module.exports = geolocation; + +}); + +// file: lib/common/plugin/geolocation/symbols.js +define("cordova/plugin/geolocation/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/geolocation', 'navigator.geolocation'); +modulemapper.clobbers('cordova/plugin/PositionError', 'PositionError'); +modulemapper.clobbers('cordova/plugin/Position', 'Position'); +modulemapper.clobbers('cordova/plugin/Coordinates', 'Coordinates'); + +}); + +// file: lib/common/plugin/globalization.js +define("cordova/plugin/globalization", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + GlobalizationError = require('cordova/plugin/GlobalizationError'); + +var globalization = { + +/** +* Returns the string identifier for the client's current language. +* It returns the language identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the language, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The language identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getPreferredLanguage(function (language) {alert('language:' + language.value + '\n');}, +* function () {}); +*/ +getPreferredLanguage:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getPreferredLanguage', arguments); + exec(successCB, failureCB, "Globalization","getPreferredLanguage", []); +}, + +/** +* Returns the string identifier for the client's current locale setting. +* It returns the locale identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the locale, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The locale identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getLocaleName(function (locale) {alert('locale:' + locale.value + '\n');}, +* function () {}); +*/ +getLocaleName:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getLocaleName', arguments); + exec(successCB, failureCB, "Globalization","getLocaleName", []); +}, + + +/** +* Returns a date formatted as a string according to the client's user preferences and +* calendar using the time zone of the client. It returns the formatted date string to the +* successCB callback with a properties object as a parameter. If there is an error +* formatting the date, then the errorCB callback is invoked. +* +* The defaults are: formatLenght="short" and selector="date and time" +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.value {String}: The localized date string +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.dateToString(new Date(), +* function (date) {alert('date:' + date.value + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {formatLength:'short'}); +*/ +dateToString:function(date, successCB, failureCB, options) { + argscheck.checkArgs('dfFO', 'Globalization.dateToString', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "dateToString", [{"date": dateValue, "options": options}]); +}, + + +/** +* Parses a date formatted as a string according to the client's user +* preferences and calendar using the time zone of the client and returns +* the corresponding date object. It returns the date to the successCB +* callback with a properties object as a parameter. If there is an error +* parsing the date string, then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {String} dateString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.year {Number}: The four digit year +* Object.month {Number}: The month from (0 - 11) +* Object.day {Number}: The day from (1 - 31) +* Object.hour {Number}: The hour from (0 - 23) +* Object.minute {Number}: The minute from (0 - 59) +* Object.second {Number}: The second from (0 - 59) +* Object.millisecond {Number}: The milliseconds (from 0 - 999), +* not available on all platforms +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToDate('4/11/2011', +* function (date) { alert('Month:' + date.month + '\n' + +* 'Day:' + date.day + '\n' + +* 'Year:' + date.year + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {selector:'date'}); +*/ +stringToDate:function(dateString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToDate', arguments); + exec(successCB, failureCB, "Globalization", "stringToDate", [{"dateString": dateString, "options": options}]); +}, + + +/** +* Returns a pattern string for formatting and parsing dates according to the client's +* user preferences. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, +* then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.pattern {String}: The date and time pattern for formatting and parsing dates. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.timezone {String}: The abbreviated name of the time zone on the client +* Object.utc_offset {Number}: The current difference in seconds between the client's +* time zone and coordinated universal time. +* Object.dst_offset {Number}: The current daylight saving time offset in seconds +* between the client's non-daylight saving's time zone +* and the client's daylight saving's time zone. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getDatePattern( +* function (date) {alert('pattern:' + date.pattern + '\n');}, +* function () {}, +* {formatLength:'short'}); +*/ +getDatePattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDatePattern', arguments); + exec(successCB, failureCB, "Globalization", "getDatePattern", [{"options": options}]); +}, + + +/** +* Returns an array of either the names of the months or days of the week +* according to the client's user preferences and calendar. It returns the array of names to the +* successCB callback with a properties object as a parameter. If there is an error obtaining the +* names, then the errorCB callback is invoked. +* +* The defaults are: type="wide" and item="months" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'narrow' or 'wide' +* item {String}: 'months', or 'days' +* +* @return Object.value {Array{String}}: The array of names starting from either +* the first month in the year or the +* first day of the week. +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getDateNames(function (names) { +* for(var i = 0; i < names.value.length; i++) { +* alert('Month:' + names.value[i] + '\n');}}, +* function () {}); +*/ +getDateNames:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDateNames', arguments); + exec(successCB, failureCB, "Globalization", "getDateNames", [{"options": options}]); +}, + +/** +* Returns whether daylight savings time is in effect for a given date using the client's +* time zone and calendar. It returns whether or not daylight savings time is in effect +* to the successCB callback with a properties object as a parameter. If there is an error +* reading the date, then the errorCB callback is invoked. +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.dst {Boolean}: The value "true" indicates that daylight savings time is +* in effect for the given date and "false" indicate that it is not. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.isDayLightSavingsTime(new Date(), +* function (date) {alert('dst:' + date.dst + '\n');} +* function () {}); +*/ +isDayLightSavingsTime:function(date, successCB, failureCB) { + argscheck.checkArgs('dfF', 'Globalization.isDayLightSavingsTime', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "isDayLightSavingsTime", [{"date": dateValue}]); +}, + +/** +* Returns the first day of the week according to the client's user preferences and calendar. +* The days of the week are numbered starting from 1 where 1 is considered to be Sunday. +* It returns the day to the successCB callback with a properties object as a parameter. +* If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {Number}: The number of the first day of the week. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getFirstDayOfWeek(function (day) +* { alert('Day:' + day.value + '\n');}, +* function () {}); +*/ +getFirstDayOfWeek:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getFirstDayOfWeek', arguments); + exec(successCB, failureCB, "Globalization", "getFirstDayOfWeek", []); +}, + + +/** +* Returns a number formatted as a string according to the client's user preferences. +* It returns the formatted number string to the successCB callback with a properties object as a +* parameter. If there is an error formatting the number, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Number} number +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {String}: The formatted number string. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.numberToString(3.25, +* function (number) {alert('number:' + number.value + '\n');}, +* function () {}, +* {type:'decimal'}); +*/ +numberToString:function(number, successCB, failureCB, options) { + argscheck.checkArgs('nfFO', 'Globalization.numberToString', arguments); + exec(successCB, failureCB, "Globalization", "numberToString", [{"number": number, "options": options}]); +}, + +/** +* Parses a number formatted as a string according to the client's user preferences and +* returns the corresponding number. It returns the number to the successCB callback with a +* properties object as a parameter. If there is an error parsing the number string, then +* the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {String} numberString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {Number}: The parsed number. +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToNumber('1234.56', +* function (number) {alert('Number:' + number.value + '\n');}, +* function () { alert('Error parsing number');}); +*/ +stringToNumber:function(numberString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToNumber', arguments); + exec(successCB, failureCB, "Globalization", "stringToNumber", [{"numberString": numberString, "options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing numbers according to the client's user +* preferences. It returns the pattern to the successCB callback with a properties object as a +* parameter. If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.pattern {String}: The number pattern for formatting and parsing numbers. +* The patterns follow Unicode Technical Standard #35. +* http://unicode.org/reports/tr35/tr35-4.html +* Object.symbol {String}: The symbol to be used when formatting and parsing +* e.g., percent or currency symbol. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting numbers. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.positive {String}: The symbol to use for positive numbers when parsing and formatting. +* Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getNumberPattern( +* function (pattern) {alert('Pattern:' + pattern.pattern + '\n');}, +* function () {}); +*/ +getNumberPattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getNumberPattern', arguments); + exec(successCB, failureCB, "Globalization", "getNumberPattern", [{"options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing currency values according to the client's +* user preferences and ISO 4217 currency code. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, then the errorCB +* callback is invoked. +* +* @param {String} currencyCode +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.pattern {String}: The currency pattern for formatting and parsing currency values. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.code {String}: The ISO 4217 currency code for the pattern. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting currency. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.getCurrencyPattern('EUR', +* function (currency) {alert('Pattern:' + currency.pattern + '\n');} +* function () {}); +*/ +getCurrencyPattern:function(currencyCode, successCB, failureCB) { + argscheck.checkArgs('sfF', 'Globalization.getCurrencyPattern', arguments); + exec(successCB, failureCB, "Globalization", "getCurrencyPattern", [{"currencyCode": currencyCode}]); +} + +}; + +module.exports = globalization; + +}); + +// file: lib/common/plugin/globalization/symbols.js +define("cordova/plugin/globalization/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/globalization', 'navigator.globalization'); +modulemapper.clobbers('cordova/plugin/GlobalizationError', 'GlobalizationError'); + +}); + +// file: lib/ios/plugin/inappbrowser/symbols.js +define("cordova/plugin/inappbrowser/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/InAppBrowser', 'open'); + +}); + +// file: lib/ios/plugin/ios/Contact.js +define("cordova/plugin/ios/Contact", function(require, exports, module) { + +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'); + +/** + * Provides iOS Contact.display API. + */ +module.exports = { + display : function(errorCB, options) { + /* + * Display a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + */ + + if (this.id === null) { + if (typeof errorCB === "function") { + var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); + errorCB(errorObj); + } + } + else { + exec(null, errorCB, "Contacts","displayContact", [this.id, options]); + } + } +}; + +}); + +// file: lib/ios/plugin/ios/Entry.js +define("cordova/plugin/ios/Entry", function(require, exports, module) { + +module.exports = { + toURL:function() { + // TODO: refactor path in a cross-platform way so we can eliminate + // these kinds of platform-specific hacks. + return "file://localhost" + this.fullPath; + }, + toURI: function() { + console.log("DEPRECATED: Update your code to use 'toURL'"); + return "file://localhost" + this.fullPath; + } +}; + +}); + +// file: lib/ios/plugin/ios/contacts.js +define("cordova/plugin/ios/contacts", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Provides iOS enhanced contacts API. + */ +module.exports = { + newContactUI : function(successCallback) { + /* + * Create a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * returns: the id of the created contact as param to successCallback + */ + exec(successCallback, null, "Contacts","newContact", []); + }, + chooseContact : function(successCallback, options) { + /* + * Select a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + * fields: array of fields to return in contact object (see ContactOptions.fields) + * + * @returns + * id of contact selected + * ContactObject + * if no fields provided contact contains just id information + * if fields provided contact object contains information for the specified fields + * + */ + var win = function(result) { + var fullContact = require('cordova/plugin/contacts').create(result); + successCallback(fullContact.id, fullContact); + }; + exec(win, null, "Contacts","chooseContact", [options]); + } +}; + +}); + +// file: lib/ios/plugin/ios/contacts/symbols.js +define("cordova/plugin/ios/contacts/symbols", function(require, exports, module) { + +require('cordova/plugin/contacts/symbols'); + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.merges('cordova/plugin/ios/contacts', 'navigator.contacts'); +modulemapper.merges('cordova/plugin/ios/Contact', 'Contact'); + +}); + +// file: lib/ios/plugin/ios/geolocation/symbols.js +define("cordova/plugin/ios/geolocation/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.merges('cordova/plugin/geolocation', 'navigator.geolocation'); + +}); + +// file: lib/ios/plugin/ios/logger/plugininit.js +define("cordova/plugin/ios/logger/plugininit", function(require, exports, module) { + +// use the native logger +var logger = require("cordova/plugin/logger"); +logger.useConsole(true); + +}); + +// file: lib/ios/plugin/ios/logger/symbols.js +define("cordova/plugin/ios/logger/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/logger', 'console'); + +}); + +// file: lib/ios/plugin/ios/notification.js +define("cordova/plugin/ios/notification", function(require, exports, module) { + +var Media = require('cordova/plugin/Media'); + +module.exports = { + beep:function(count) { + (new Media('beep.wav')).play(); + } +}; + +}); + +// file: lib/common/plugin/logger.js +define("cordova/plugin/logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ +// The logger module exports the following properties/functions: +// +// LOG - constant for the level LOG +// ERROR - constant for the level ERROR +// WARN - constant for the level WARN +// INFO - constant for the level INFO +// DEBUG - constant for the level DEBUG +// logLevel() - returns current log level +// logLevel(value) - sets and returns a new log level +// useConsole() - returns whether logger is using console +// useConsole(value) - sets and returns whether logger is using console +// log(message,...) - logs a message at level LOG +// error(message,...) - logs a message at level ERROR +// warn(message,...) - logs a message at level WARN +// info(message,...) - logs a message at level INFO +// debug(message,...) - logs a message at level DEBUG +// logLevel(level,message,...) - logs a message specified level +// +//------------------------------------------------------------------------------ + +var logger = exports; + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var UseConsole = true; +var UseLogger = true; +var Queued = []; +var DeviceReady = false; +var CurrentLevel; + +var originalConsole = console; + +/** + * Logging levels + */ + +var Levels = [ + "LOG", + "ERROR", + "WARN", + "INFO", + "DEBUG" +]; + +/* + * add the logging levels to the logger object and + * to a separate levelsMap object for testing + */ + +var LevelsMap = {}; +for (var i=0; i<Levels.length; i++) { + var level = Levels[i]; + LevelsMap[level] = i; + logger[level] = level; +} + +CurrentLevel = LevelsMap.WARN; + +/** + * Getter/Setter for the logging level + * + * Returns the current logging level. + * + * When a value is passed, sets the logging level to that value. + * The values should be one of the following constants: + * logger.LOG + * logger.ERROR + * logger.WARN + * logger.INFO + * logger.DEBUG + * + * The value used determines which messages get printed. The logging + * values above are in order, and only messages logged at the logging + * level or above will actually be displayed to the user. E.g., the + * default level is WARN, so only messages logged with LOG, ERROR, or + * WARN will be displayed; INFO and DEBUG messages will be ignored. + */ +logger.level = function (value) { + if (arguments.length) { + if (LevelsMap[value] === null) { + throw new Error("invalid logging level: " + value); + } + CurrentLevel = LevelsMap[value]; + } + + return Levels[CurrentLevel]; +}; + +/** + * Getter/Setter for the useConsole functionality + * + * When useConsole is true, the logger will log via the + * browser 'console' object. + */ +logger.useConsole = function (value) { + if (arguments.length) UseConsole = !!value; + + if (UseConsole) { + if (typeof console == "undefined") { + throw new Error("global console object is not defined"); + } + + if (typeof console.log != "function") { + throw new Error("global console object does not have a log function"); + } + + if (typeof console.useLogger == "function") { + if (console.useLogger()) { + throw new Error("console and logger are too intertwingly"); + } + } + } + + return UseConsole; +}; + +/** + * Getter/Setter for the useLogger functionality + * + * When useLogger is true, the logger will log via the + * native Logger plugin. + */ +logger.useLogger = function (value) { + // Enforce boolean + if (arguments.length) UseLogger = !!value; + return UseLogger; +}; + +/** + * Logs a message at the LOG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.log = function(message) { logWithArgs("LOG", arguments); }; + +/** + * Logs a message at the ERROR level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.error = function(message) { logWithArgs("ERROR", arguments); }; + +/** + * Logs a message at the WARN level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.warn = function(message) { logWithArgs("WARN", arguments); }; + +/** + * Logs a message at the INFO level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.info = function(message) { logWithArgs("INFO", arguments); }; + +/** + * Logs a message at the DEBUG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.debug = function(message) { logWithArgs("DEBUG", arguments); }; + +// log at the specified level with args +function logWithArgs(level, args) { + args = [level].concat([].slice.call(args)); + logger.logLevel.apply(logger, args); +} + +/** + * Logs a message at the specified level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.logLevel = function(level /* , ... */) { + // format the message with the parameters + var formatArgs = [].slice.call(arguments, 1); + var message = logger.format.apply(logger.format, formatArgs); + + if (LevelsMap[level] === null) { + throw new Error("invalid logging level: " + level); + } + + if (LevelsMap[level] > CurrentLevel) return; + + // queue the message if not yet at deviceready + if (!DeviceReady && !UseConsole) { + Queued.push([level, message]); + return; + } + + // Log using the native logger if that is enabled + if (UseLogger) { + exec(null, null, "Logger", "logLevel", [level, message]); + } + + // Log using the console if that is enabled + if (UseConsole) { + // make sure console is not using logger + if (console.__usingCordovaLogger) { + throw new Error("console and logger are too intertwingly"); + } + + // log to the console + switch (level) { + case logger.LOG: originalConsole.log(message); break; + case logger.ERROR: originalConsole.log("ERROR: " + message); break; + case logger.WARN: originalConsole.log("WARN: " + message); break; + case logger.INFO: originalConsole.log("INFO: " + message); break; + case logger.DEBUG: originalConsole.log("DEBUG: " + message); break; + } + } +}; + + +/** + * Formats a string and arguments following it ala console.log() + * + * Any remaining arguments will be appended to the formatted string. + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +logger.format = function(formatString, args) { + return __format(arguments[0], [].slice.call(arguments,1)).join(' '); +}; + + +//------------------------------------------------------------------------------ +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * Returns an array containing the formatted string and any remaining + * arguments. + */ +function __format(formatString, args) { + if (formatString === null || formatString === undefined) return [""]; + if (arguments.length == 1) return [formatString.toString()]; + + if (typeof formatString != "string") + formatString = formatString.toString(); + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var match = pattern.exec(rest); + if (!match) break; + + var arg = args.shift(); + rest = match[3]; + result.push(match[1]); + + if (match[2] == '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(__formatted(arg, match[2])); + } + + result.push(rest); + + var remainingArgs = [].slice.call(args); + remainingArgs.unshift(result.join('')); + return remainingArgs; +} + +function __formatted(object, formatChar) { + + try { + switch(formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + + +//------------------------------------------------------------------------------ +// when deviceready fires, log queued messages +logger.__onDeviceReady = function() { + if (DeviceReady) return; + + DeviceReady = true; + + for (var i=0; i<Queued.length; i++) { + var messageArgs = Queued[i]; + logger.logLevel(messageArgs[0], messageArgs[1]); + } + + Queued = null; +}; + +// add a deviceready event to log queued messages +document.addEventListener("deviceready", logger.__onDeviceReady, false); + +}); + +// file: lib/common/plugin/logger/symbols.js +define("cordova/plugin/logger/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/logger', 'cordova.logger'); + +}); + +// file: lib/ios/plugin/media/symbols.js +define("cordova/plugin/media/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Media', 'Media'); +modulemapper.clobbers('cordova/plugin/MediaError', 'MediaError'); + +}); + +// file: lib/common/plugin/network.js +define("cordova/plugin/network", function(require, exports, module) { + +var exec = require('cordova/exec'), + cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'); + +// Link the onLine property with the Cordova-supplied network info. +// This works because we clobber the naviagtor object with our own +// object in bootstrap.js. +if (typeof navigator != 'undefined') { + utils.defineGetter(navigator, 'onLine', function() { + return this.connection.type != 'none'; + }); +} + +function NetworkConnection() { + this.type = 'unknown'; +} + +/** + * Get connection info + * + * @param {Function} successCallback The function to call when the Connection data is available + * @param {Function} errorCallback The function to call when there is an error getting the Connection data. (OPTIONAL) + */ +NetworkConnection.prototype.getInfo = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "NetworkStatus", "getConnectionInfo", []); +}; + +var me = new NetworkConnection(); +var timerId = null; +var timeout = 500; + +channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + me.type = info; + if (info === "none") { + // set a timer if still offline at the end of timer send the offline event + timerId = setTimeout(function(){ + cordova.fireDocumentEvent("offline"); + timerId = null; + }, timeout); + } else { + // If there is a current offline event pending clear it + if (timerId !== null) { + clearTimeout(timerId); + timerId = null; + } + cordova.fireDocumentEvent("online"); + } + + // should only fire this once + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + }, + function (e) { + // If we can't get the network info we should still tell Cordova + // to fire the deviceready event. + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + console.log("Error initializing Network Connection: " + e); + }); +}); + +module.exports = me; + +}); + +// file: lib/common/plugin/networkstatus/symbols.js +define("cordova/plugin/networkstatus/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/network', 'navigator.network.connection', 'navigator.network.connection is deprecated. Use navigator.connection instead.'); +modulemapper.clobbers('cordova/plugin/network', 'navigator.connection'); +modulemapper.defaults('cordova/plugin/Connection', 'Connection'); + +}); + +// file: lib/common/plugin/notification.js +define("cordova/plugin/notification", function(require, exports, module) { + +var exec = require('cordova/exec'); +var platform = require('cordova/platform'); + +/** + * Provides access to notifications on the device. + */ + +module.exports = { + + /** + * Open a native alert dialog, with a customizable title and button text. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} completeCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Alert) + * @param {String} buttonLabel Label of the close button (default: OK) + */ + alert: function(message, completeCallback, title, buttonLabel) { + var _title = (title || "Alert"); + var _buttonLabel = (buttonLabel || "OK"); + exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]); + }, + + /** + * Open a native confirm dialog, with a customizable title and button text. + * The result that the user selects is returned to the result callback. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Confirm) + * @param {Array} buttonLabels Array of the labels of the buttons (default: ['OK', 'Cancel']) + */ + confirm: function(message, resultCallback, title, buttonLabels) { + var _title = (title || "Confirm"); + var _buttonLabels = (buttonLabels || ["OK", "Cancel"]); + + // Strings are deprecated! + if (typeof _buttonLabels === 'string') { + console.log("Notification.confirm(string, function, string, string) is deprecated. Use Notification.confirm(string, function, string, array)."); + } + + // Some platforms take an array of button label names. + // Other platforms take a comma separated list. + // For compatibility, we convert to the desired type based on the platform. + if (platform.id == "android" || platform.id == "ios" || platform.id == "windowsphone" || platform.id == "blackberry10") { + if (typeof _buttonLabels === 'string') { + var buttonLabelString = _buttonLabels; + _buttonLabels = _buttonLabels.split(","); // not crazy about changing the var type here + } + } else { + if (Array.isArray(_buttonLabels)) { + var buttonLabelArray = _buttonLabels; + _buttonLabels = buttonLabelArray.toString(); + } + } + exec(resultCallback, null, "Notification", "confirm", [message, _title, _buttonLabels]); + }, + + /** + * Open a native prompt dialog, with a customizable title and button text. + * The following results are returned to the result callback: + * buttonIndex Index number of the button selected. + * input1 The text entered in the prompt dialog box. + * + * @param {String} message Dialog message to display (default: "Prompt message") + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the dialog (default: "Prompt") + * @param {Array} buttonLabels Array of strings for the button labels (default: ["OK","Cancel"]) + * @param {String} defaultText Textbox input value (default: "Default text") + */ + prompt: function(message, resultCallback, title, buttonLabels, defaultText) { + var _message = (message || "Prompt message"); + var _title = (title || "Prompt"); + var _buttonLabels = (buttonLabels || ["OK","Cancel"]); + var _defaultText = (defaultText || "Default text"); + exec(resultCallback, null, "Notification", "prompt", [_message, _title, _buttonLabels, _defaultText]); + }, + + /** + * Causes the device to vibrate. + * + * @param {Integer} mills The number of milliseconds to vibrate for. + */ + vibrate: function(mills) { + exec(null, null, "Notification", "vibrate", [mills]); + }, + + /** + * Causes the device to beep. + * On Android, the default notification ringtone is played "count" times. + * + * @param {Integer} count The number of beeps. + */ + beep: function(count) { + exec(null, null, "Notification", "beep", [count]); + } +}; + +}); + +// file: lib/ios/plugin/notification/symbols.js +define("cordova/plugin/notification/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/notification', 'navigator.notification'); +modulemapper.merges('cordova/plugin/ios/notification', 'navigator.notification'); + +}); + +// file: lib/common/plugin/requestFileSystem.js +define("cordova/plugin/requestFileSystem", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + FileError = require('cordova/plugin/FileError'), + FileSystem = require('cordova/plugin/FileSystem'), + exec = require('cordova/exec'); + +/** + * Request a file system in which to store application data. + * @param type local file system type + * @param size indicates how much storage space, in bytes, the application expects to need + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + */ +var requestFileSystem = function(type, size, successCallback, errorCallback) { + argscheck.checkArgs('nnFF', 'requestFileSystem', arguments); + var fail = function(code) { + errorCallback && errorCallback(new FileError(code)); + }; + + if (type < 0 || type > 3) { + fail(FileError.SYNTAX_ERR); + } else { + // if successful, return a FileSystem object + var success = function(file_system) { + if (file_system) { + if (successCallback) { + // grab the name and root from the file system object + var result = new FileSystem(file_system.name, file_system.root); + successCallback(result); + } + } + else { + // no FileSystem object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + exec(success, fail, "File", "requestFileSystem", [type, size]); + } +}; + +module.exports = requestFileSystem; + +}); + +// file: lib/common/plugin/resolveLocalFileSystemURI.js +define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + DirectoryEntry = require('cordova/plugin/DirectoryEntry'), + FileEntry = require('cordova/plugin/FileEntry'), + FileError = require('cordova/plugin/FileError'), + exec = require('cordova/exec'); + +/** + * Look up file system Entry referred to by local URI. + * @param {DOMString} uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ +module.exports = function(uri, successCallback, errorCallback) { + argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments); + // error callback + var fail = function(error) { + errorCallback && errorCallback(new FileError(error)); + }; + // sanity check for 'not:valid:filename' + if(!uri || uri.split(":").length > 2) { + setTimeout( function() { + fail(FileError.ENCODING_ERR); + },0); + return; + } + // if successful, return either a file or directory entry + var success = function(entry) { + var result; + if (entry) { + if (successCallback) { + // create appropriate Entry object + result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); +}; + +}); + +// file: lib/common/plugin/splashscreen.js +define("cordova/plugin/splashscreen", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var splashscreen = { + show:function() { + exec(null, null, "SplashScreen", "show", []); + }, + hide:function() { + exec(null, null, "SplashScreen", "hide", []); + } +}; + +module.exports = splashscreen; + +}); + +// file: lib/common/plugin/splashscreen/symbols.js +define("cordova/plugin/splashscreen/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/splashscreen', 'navigator.splashscreen'); + +}); + +// file: lib/common/symbols.js +define("cordova/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +// Use merges here in case others symbols files depend on this running first, +// but fail to declare the dependency with a require(). +modulemapper.merges('cordova', 'cordova'); +modulemapper.clobbers('cordova/exec', 'cordova.exec'); +modulemapper.clobbers('cordova/exec', 'Cordova.exec'); + +}); + +// file: lib/common/utils.js +define("cordova/utils", function(require, exports, module) { + +var utils = exports; + +/** + * Defines a property getter / setter for obj[key]. + */ +utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) { + if (Object.defineProperty) { + var desc = { + get: getFunc, + configurable: true + }; + if (opt_setFunc) { + desc.set = opt_setFunc; + } + Object.defineProperty(obj, key, desc); + } else { + obj.__defineGetter__(key, getFunc); + if (opt_setFunc) { + obj.__defineSetter__(key, opt_setFunc); + } + } +}; + +/** + * Defines a property getter for obj[key]. + */ +utils.defineGetter = utils.defineGetterSetter; + +utils.arrayIndexOf = function(a, item) { + if (a.indexOf) { + return a.indexOf(item); + } + var len = a.length; + for (var i = 0; i < len; ++i) { + if (a[i] == item) { + return i; + } + } + return -1; +}; + +/** + * Returns whether the item was found in the array. + */ +utils.arrayRemove = function(a, item) { + var index = utils.arrayIndexOf(a, item); + if (index != -1) { + a.splice(index, 1); + } + return index != -1; +}; + +utils.typeName = function(val) { + return Object.prototype.toString.call(val).slice(8, -1); +}; + +/** + * Returns an indication of whether the argument is an array or not + */ +utils.isArray = function(a) { + return utils.typeName(a) == 'Array'; +}; + +/** + * Returns an indication of whether the argument is a Date or not + */ +utils.isDate = function(d) { + return utils.typeName(d) == 'Date'; +}; + +/** + * Does a deep clone of the object. + */ +utils.clone = function(obj) { + if(!obj || typeof obj == 'function' || utils.isDate(obj) || typeof obj != 'object') { + return obj; + } + + var retVal, i; + + if(utils.isArray(obj)){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(utils.clone(obj[i])); + } + return retVal; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] != obj[i]) { + retVal[i] = utils.clone(obj[i]); + } + } + return retVal; +}; + +/** + * Returns a wrapped version of the function + */ +utils.close = function(context, func, params) { + if (typeof params == 'undefined') { + return function() { + return func.apply(context, arguments); + }; + } else { + return function() { + return func.apply(context, params); + }; + } +}; + +/** + * Create a UUID + */ +utils.createUUID = function() { + return UUIDcreatePart(4) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(6); +}; + +/** + * Extends a child object from a parent object using classical inheritance + * pattern. + */ +utils.extend = (function() { + // proxy used to establish prototype chain + var F = function() {}; + // extend Child from Parent + return function(Child, Parent) { + F.prototype = Parent.prototype; + Child.prototype = new F(); + Child.__super__ = Parent.prototype; + Child.prototype.constructor = Child; + }; +}()); + +/** + * Alerts a message in any available way: alert or console.log. + */ +utils.alert = function(msg) { + if (window.alert) { + window.alert(msg); + } else if (console && console.log) { + console.log(msg); + } +}; + + +//------------------------------------------------------------------------------ +function UUIDcreatePart(length) { + var uuidpart = ""; + for (var i=0; i<length; i++) { + var uuidchar = parseInt((Math.random() * 256), 10).toString(16); + if (uuidchar.length == 1) { + uuidchar = "0" + uuidchar; + } + uuidpart += uuidchar; + } + return uuidpart; +} + + +}); + +window.cordova = require('cordova'); +// file: lib/scripts/bootstrap.js + +(function (context) { + if (context._cordovaJsLoaded) { + throw new Error('cordova.js included multiple times.'); + } + context._cordovaJsLoaded = true; + + var channel = require('cordova/channel'); + var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; + + function logUnfiredChannels(arr) { + for (var i = 0; i < arr.length; ++i) { + if (arr[i].state != 2) { + console.log('Channel not fired: ' + arr[i].type); + } + } + } + + window.setTimeout(function() { + if (channel.onDeviceReady.state != 2) { + console.log('deviceready has not fired after 5 seconds.'); + logUnfiredChannels(platformInitChannelsArray); + logUnfiredChannels(channel.deviceReadyChannelsArray); + } + }, 5000); + + // Replace navigator before any modules are required(), to ensure it happens as soon as possible. + // We replace it so that properties that can't be clobbered can instead be overridden. + function replaceNavigator(origNavigator) { + var CordovaNavigator = function() {}; + CordovaNavigator.prototype = origNavigator; + var newNavigator = new CordovaNavigator(); + // This work-around really only applies to new APIs that are newer than Function.bind. + // Without it, APIs such as getGamepads() break. + if (CordovaNavigator.bind) { + for (var key in origNavigator) { + if (typeof origNavigator[key] == 'function') { + newNavigator[key] = origNavigator[key].bind(origNavigator); + } + } + } + return newNavigator; + } + if (context.navigator) { + context.navigator = replaceNavigator(context.navigator); + } + + // _nativeReady is global variable that the native side can set + // to signify that the native code is ready. It is a global since + // it may be called before any cordova JS is ready. + if (window._nativeReady) { + channel.onNativeReady.fire(); + } + + /** + * Create all cordova objects once native side is ready. + */ + channel.join(function() { + // Call the platform-specific initialization + require('cordova/platform').initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once page has fully loaded, all + // constructors have run and cordova info has been received from native + // side. + // This join call is deliberately made after platform.initialize() in + // order that plugins may manipulate channel.deviceReadyChannelsArray + // if necessary. + channel.join(function() { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); + + }, platformInitChannelsArray); + +}(window)); + +// file: lib/scripts/bootstrap-ios.js + +require('cordova/channel').onNativeReady.fire(); + +// file: lib/scripts/plugin_loader.js + +// Tries to load all plugins' js-modules. +// This is an async process, but onDeviceReady is blocked on onPluginsReady. +// onPluginsReady is fired when there are no plugins to load, or they are all done. +(function (context) { + // To be populated with the handler by handlePluginsObject. + var onScriptLoadingComplete; + + var scriptCounter = 0; + function scriptLoadedCallback() { + scriptCounter--; + if (scriptCounter === 0) { + onScriptLoadingComplete && onScriptLoadingComplete(); + } + } + + function scriptErrorCallback(err) { + // Open Question: If a script path specified in cordova_plugins.js does not exist, do we fail for all? + // this is currently just continuing. + scriptCounter--; + if (scriptCounter === 0) { + onScriptLoadingComplete && onScriptLoadingComplete(); + } + } + + // Helper function to inject a <script> tag. + function injectScript(path) { + scriptCounter++; + var script = document.createElement("script"); + script.onload = scriptLoadedCallback; + script.onerror = scriptErrorCallback; + script.src = path; + document.head.appendChild(script); + } + + // Called when: + // * There are plugins defined and all plugins are finished loading. + // * There are no plugins to load. + function finishPluginLoading() { + context.cordova.require('cordova/channel').onPluginsReady.fire(); + } + + // Handler for the cordova_plugins.js content. + // See plugman's plugin_loader.js for the details of this object. + // This function is only called if the really is a plugins array that isn't empty. + // Otherwise the onerror response handler will just call finishPluginLoading(). + function handlePluginsObject(modules, path) { + // First create the callback for when all plugins are loaded. + var mapper = context.cordova.require('cordova/modulemapper'); + onScriptLoadingComplete = function() { + // Loop through all the plugins and then through their clobbers and merges. + for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + if (module) { + try { + if (module.clobbers && module.clobbers.length) { + for (var j = 0; j < module.clobbers.length; j++) { + mapper.clobbers(module.id, module.clobbers[j]); + } + } + + if (module.merges && module.merges.length) { + for (var k = 0; k < module.merges.length; k++) { + mapper.merges(module.id, module.merges[k]); + } + } + + // Finally, if runs is truthy we want to simply require() the module. + // This can be skipped if it had any merges or clobbers, though, + // since the mapper will already have required the module. + if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) { + context.cordova.require(module.id); + } + } + catch(err) { + // error with module, most likely clobbers, should we continue? + } + } + } + + finishPluginLoading(); + }; + + // Now inject the scripts. + for (var i = 0; i < modules.length; i++) { + injectScript(path + modules[i].file); + } + } + + // Find the root of the app + var path = ''; + var scripts = document.getElementsByTagName('script'); + var term = 'cordova.js'; + for (var n = scripts.length-1; n>-1; n--) { + var src = scripts[n].src; + if (src.indexOf(term) == (src.length - term.length)) { + path = src.substring(0, src.length - term.length); + break; + } + } + + var plugins_json = path + 'cordova_plugins.json'; + var plugins_js = path + 'cordova_plugins.js'; + + // One some phones (Windows) this xhr.open throws an Access Denied exception + // So lets keep trying, but with a script tag injection technique instead of XHR + var injectPluginScript = function injectPluginScript() { + try { + var script = document.createElement("script"); + script.onload = function(){ + var list = cordova.require("cordova/plugin_list"); + handlePluginsObject(list,path); + }; + script.onerror = function() { + // Error loading cordova_plugins.js, file not found or something + // this is an acceptable error, pre-3.0.0, so we just move on. + finishPluginLoading(); + }; + script.src = plugins_js; + document.head.appendChild(script); + + } catch(err){ + finishPluginLoading(); + } + } + + + // Try to XHR the cordova_plugins.json file asynchronously. + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + // If the response is a JSON string which composes an array, call handlePluginsObject. + // If the request fails, or the response is not a JSON array, just call finishPluginLoading. + var obj; + try { + obj = (this.status == 0 || this.status == 200) && this.responseText && JSON.parse(this.responseText); + } catch (err) { + // obj will be undefined. + } + if (Array.isArray(obj) && obj.length > 0) { + handlePluginsObject(obj, path); + } else { + finishPluginLoading(); + } + }; + xhr.onerror = function() { + // In this case, the json file was not present, but XHR was allowed, + // so we should still try the script injection technique with the js file + // in case that is there. + injectPluginScript(); + }; + try { // we commented we were going to try, so let us actually try and catch + xhr.open('GET', plugins_json, true); // Async + xhr.send(); + } catch(err){ + injectPluginScript(); + } +}(window)); + + +})();var PhoneGap = cordova; |