├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── PhotoLibrary.d.ts ├── README.md ├── package.json ├── plugin.xml ├── src ├── android │ ├── PhotoLibrary.java │ ├── PhotoLibraryGetLibraryOptions.java │ └── PhotoLibraryService.java ├── browser │ ├── .eslintrc.json │ ├── PhotoLibraryProxy.js │ └── jsconfig.json └── ios │ ├── PhotoLibrary.swift │ ├── PhotoLibraryGetLibraryOptions.swift │ ├── PhotoLibraryProtocol.swift │ └── PhotoLibraryService.swift ├── tests ├── .eslintrc.json ├── README.md ├── android │ └── wait-for-emulator-boot.sh ├── blueimp-canvastoblob │ └── js │ │ └── canvas-to-blob.min.js ├── es5-shim │ └── es5-shim.min.js ├── es6-shim │ └── es6-shim.min.js ├── es7-shim │ └── dist │ │ └── es7-shim.min.js ├── ionic-tests.ts ├── ios │ ├── get-booted-simulator.sh │ └── grant-simulator-permissions.sh ├── plugin.xml ├── test-images │ ├── Landscape_1.jpg │ ├── Landscape_2.jpg │ ├── Landscape_3.jpg │ ├── Landscape_4.jpg │ ├── Landscape_5.jpg │ ├── Landscape_6.jpg │ ├── Landscape_7.jpg │ ├── Landscape_8.jpg │ ├── Portrait_1.jpg │ ├── Portrait_2.jpg │ ├── Portrait_3.jpg │ ├── Portrait_4.jpg │ ├── Portrait_5.jpg │ ├── Portrait_6.jpg │ ├── Portrait_7.jpg │ ├── Portrait_8.jpg │ └── geotagged.jpg ├── test-utils.js └── tests.js └── www ├── PhotoLibrary.js └── async └── dist ├── async.min.js └── async.min.map /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ 8 | "error", 9 | 2 10 | ], 11 | "quotes": [ 12 | "error", 13 | "single" 14 | ], 15 | "semi": [ 16 | "error", 17 | "always" 18 | ] 19 | }, 20 | "globals": { 21 | "cordova": true, 22 | "require": true, 23 | "exports": true, 24 | "module": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | tcc.db-shm 4 | tcc.db-wal 5 | osx-local-error.txt 6 | iPod Photo Cache/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | #- android 3 | - objective-c 4 | 5 | os: 6 | # The value of the $TRAVIS_OS_NAME variable is set to linux or osx according to the operating system a particular build is running on 7 | # Usage: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then command; fi 8 | #- linux 9 | - osx 10 | 11 | # jdk is for Android only, commend on osx to prevent error 12 | # jdk: 13 | # - oraclejdk8 14 | 15 | osx_image: xcode8.2 16 | 17 | env: 18 | global: 19 | # To get a list of available targets run the command android list targets 20 | - EMULATOR_API_LEVEL=23 21 | - EMULATOR_TARGET=android-$EMULATOR_API_LEVEL 22 | - EMULATOR_ABI=google_apis/armeabi-v7a # armeabi-v7a 23 | 24 | - SIMULATOR_TYPE=iPhone-7-Plus 25 | - IOS_VERSION=10.2 # 10.1 for xcode8.1 26 | 27 | sudo: false 28 | 29 | android: 30 | components: 31 | # To get a list of available exact component names and descriptions run the command android list sdk --no-ui --all --extended 32 | 33 | # We want to use the latest revision of Android SDK Tools 34 | - tools # to get the new `repository-11.xml` 35 | - platform-tools 36 | - tools # to install Android SDK tools 25.x.x, see https://github.com/travis-ci/travis-ci/issues/6040#issuecomment-219367943 37 | 38 | # The BuildTools version used by your project 39 | - build-tools-25.1.7 40 | 41 | # The SDK version used to compile your project 42 | - android-24 43 | 44 | # Needed by emulator 45 | - android-$EMULATOR_API_LEVEL 46 | 47 | # Additional components 48 | # - extra-google-google_play_services 49 | # - extra-google-m2repository 50 | # - extra-android-m2repository 51 | # - addon-google_apis-google-19 52 | 53 | # Specify at least one system image, if you need to run emulator(s) during your tests 54 | #- sys-img-armeabi-v7a-android-$EMULATOR_API_LEVEL 55 | - sys-img-armeabi-v7a-google_apis-$EMULATOR_API_LEVEL 56 | 57 | before_install: 58 | #- sudo apt-get update -qq 59 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then android list targets; fi 60 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi 61 | - nvm install 6 62 | - node --version 63 | # Workaround for 8.2 causes multiple simulators error: https://github.com/travis-ci/travis-ci/issues/7031 64 | # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcrun simctl delete 7B6F8C6B-B67A-4F64-BB70-AE1FF077ACC2; fi # 7-plus 65 | # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcrun simctl delete 9464677E-9962-4C3B-91E8-C969B6337A68; fi # 7-plus 66 | # - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcrun simctl delete 2AAA645C-1882-4F81-B866-6241B900C185; fi # 5s 67 | 68 | install: 69 | - npm install 70 | - npm install -g cordova 71 | - cordova --version 72 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm install -g ios-deploy; fi 73 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm install -g ios-sim; fi 74 | 75 | before_script: 76 | - pwd 77 | - echo $PATH 78 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo no | android create avd --force -n test -t "$EMULATOR_TARGET" --abi $EMULATOR_ABI; fi 79 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then emulator -avd test -no-audio -no-window -memory 1024 & fi 80 | #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then timeout 540 android-wait-for-emulator; fi 81 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash tests/android/wait-for-emulator-boot.sh; fi 82 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb shell input keyevent 82; fi #KEYCODE_MENU 83 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm run copy-test-images:android:emulator; fi 84 | #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cat ./node_modules/adb-ci/adb-ci.txt; fi 85 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ios-sim start --devicetypeid "com.apple.CoreSimulator.SimDeviceType.$SIMULATOR_TYPE, $IOS_VERSION"; fi 86 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm run copy-test-images:ios:simulator; fi 87 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then bash tests/ios/grant-simulator-permissions.sh; fi 88 | 89 | script: 90 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then timeout 540 npm run test:android; fi 91 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then npm run test:ios -- --target "'$SIMULATOR_TYPE,\\ $IOS_VERSION'"; fi 92 | 93 | # TODO: remove 94 | after_success: 95 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb shell /system/bin/screencap -p /sdcard/screenshot.png; fi 96 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb pull /sdcard/screenshot.png screenshot.png; fi 97 | - ls -l 98 | #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cat ./node_modules/adb-ci/adb-ci.txt; fi 99 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb logcat -d; fi 100 | 101 | after_failure: 102 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb shell /system/bin/screencap -p /sdcard/screenshot.png; fi 103 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb pull /sdcard/screenshot.png screenshot.png; fi 104 | #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cat ./node_modules/adb-ci/adb-ci.txt; fi 105 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then adb logcat -d; fi 106 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cat ~/Library/Logs/CoreSimulator/CoreSimulator.log; fi 107 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cat ~/Library/Logs/CoreSimulator/$(bash tests/ios/get-booted-simulator.sh)/system.log; fi 108 | 109 | before_cache: 110 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 111 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 112 | cache: 113 | directories: 114 | - $HOME/.gradle/caches/ 115 | - $HOME/.gradle/wrapper/ 116 | 117 | # addons: 118 | # artifacts: 119 | # s3_region: "eu-west-1" 120 | # paths: 121 | # - screenshot.png 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Terikon Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PhotoLibrary.d.ts: -------------------------------------------------------------------------------- 1 | declare module PhotoLibraryCordova { 2 | 3 | export interface Plugin { 4 | 5 | getLibrary(success: (chunk: { library: LibraryItem[], isLastChunk: boolean }) => void, error: (err: any) => void, options?: GetLibraryOptions): void; 6 | 7 | requestAuthorization(success: () => void, error: (err: any) => void, options?: RequestAuthorizationOptions): void; 8 | 9 | getAlbums(success: (result: AlbumItem[]) => void, error: (err:any) => void): void; 10 | isAuthorized(success: (result: boolean) => void, error: (err:any) => void): void; 11 | 12 | getThumbnailURL(photoId: string, success: (result: string) => void, error: (err: any) => void, options?: GetThumbnailOptions): void; 13 | getThumbnailURL(libraryItem: LibraryItem, success: (result: string) => void, error: (err: any) => void, options?: GetThumbnailOptions): void; 14 | getThumbnailURL(photoId: string, options?: GetThumbnailOptions): string; // Will not work in browser 15 | getThumbnailURL(libraryItem: LibraryItem, options?: GetThumbnailOptions): string; // Will not work in browser 16 | 17 | getPhotoURL(photoId: string, success: (result: string) => void, error: (err: any) => void, options?: GetPhotoOptions): void; 18 | getPhotoURL(libraryItem: LibraryItem, success: (result: string) => void, error: (err: any) => void, options?: GetPhotoOptions): void; 19 | getPhotoURL(photoId: string, options?: GetPhotoOptions): string; // Will not work in browser 20 | getPhotoURL(libraryItem: LibraryItem, options?: GetPhotoOptions): string; // Will not work in browser 21 | 22 | getThumbnail(photoId: string, success: (result: Blob) => void, error: (err: any) => void, options?: GetThumbnailOptions): void; 23 | getThumbnail(libraryItem: LibraryItem, success: (result: Blob) => void, error: (err: any) => void, options?: GetThumbnailOptions): void; 24 | 25 | getPhoto(photoId: string, success: (result: Blob) => void, error: (err: any) => void, options?: GetPhotoOptions): void; 26 | getPhoto(libraryItem: LibraryItem, success: (result: Blob) => void, error: (err: any) => void, options?: GetPhotoOptions): void; 27 | getLibraryItem(libraryItem: LibraryItem, success: (result: Blob) => void, error: (err: any) => void, options?: GetPhotoOptions): void; 28 | 29 | stopCaching(success: () => void, error: (err: any) => void): void; 30 | 31 | saveImage(url: string, album: AlbumItem | string, success: (libraryItem: LibraryItem) => void, error: (err: any) => void, options?: GetThumbnailOptions): void; 32 | 33 | saveVideo(url: string, album: AlbumItem | string, success: () => void, error: (err: any) => void): void; 34 | 35 | } 36 | 37 | export interface LibraryItem { 38 | id: string; 39 | photoURL: string; 40 | thumbnailURL: string; 41 | fileName: string; 42 | width: number; 43 | height: number; 44 | creationDate: Date; 45 | latitude?: number; 46 | longitude?: number; 47 | albumIds?: string[]; 48 | } 49 | 50 | export interface AlbumItem { 51 | id: string; 52 | title: string; 53 | } 54 | 55 | export interface GetLibraryOptions { 56 | thumbnailWidth?: number; 57 | thumbnailHeight?: number; 58 | quality?: number; 59 | itemsInChunk?: number; 60 | chunkTimeSec?: number; 61 | useOriginalFileNames?: boolean; 62 | includeImages?: boolean; 63 | includeAlbumData?: boolean; 64 | includeCloudData?: boolean; 65 | includeVideos?: boolean; 66 | maxItems?: number; 67 | } 68 | 69 | export interface RequestAuthorizationOptions { 70 | read?: boolean; 71 | write?: boolean; 72 | } 73 | 74 | export interface GetThumbnailOptions { 75 | thumbnailWidth?: number; 76 | thumbnailHeight?: number; 77 | quality?: number; 78 | } 79 | 80 | export interface GetPhotoOptions { 81 | } 82 | 83 | } 84 | 85 | interface CordovaPlugins { 86 | photoLibrary: PhotoLibraryCordova.Plugin; 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Haven't worked on this project for a while and even became a maintainer for the main library. 2 | 3 | The code diverged too much though so this will still be maintained, and I will merge your PRs and test them if they solve any of the issues. 4 | 5 | # What is this? 6 | 7 | Basically, it's a fork/replacement for https://github.com/terikon/cordova-plugin-photo-library. 8 | 9 | # Why? 10 | 11 | Well, that one is not updated anymore much, and I have no repo access for that. 12 | 13 | There are many things that our projects needed that go beyond what the main plugin offers, like latest Swift support, etc. 14 | 15 | So, to go beyond that, this repo will always be updated as long as I am working on the projects it is dependent on. 16 | 17 | # What I Use It For, and thus what will almost always work 18 | - Get permissions to access gallery/camera roll. 19 | - Save image to gallery, even with query strings. 20 | - Save video to gallery from external URL (even https:// URL 😎) 21 | 22 | # How to Use? 23 | 24 | Just use this as a drop-in replacement for that plugin. 25 | 26 | Anyway, instead of installing the original plugin, install this one. 27 | 28 | So follow the instructions from that one, but instead use this: 29 | 30 | ```bash 31 | cordova plugin add cordova-plugin-photo-library-sism 32 | npm i --save cordova-plugin-photo-library-sism 33 | ``` 34 | 35 | or if you have the old one installed already: 36 | 37 | ```bash 38 | cordova plugin rm cordova-plugin-photo-library 39 | cordova plugin add cordova-plugin-photo-library-sism 40 | npm i --save cordova-plugin-photo-library-sism 41 | ``` 42 | 43 | Yeah, you will still need the "@ionic-native/photo-library" plugin. 44 | 45 | # Capacitor 46 | ``` 47 | npm i --save @ionic-native/photo-library@4 48 | npm i --save cordova-plugin-photo-library-sism 49 | ``` 50 | 51 | # I've Got Issues 52 | 53 | Write it on the Issues page and I'll see what I can do. :P 54 | 55 | PR's are nice as well. 56 | 57 | # Release Notes 58 | ## 2.2.99 59 | - Updated to work with latest Swift UIApplication stuff 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-photo-library-sism", 3 | "version": "2.2.99", 4 | "description": "Plugin that saves photos to the gallery / camera roll. Actually updated so it almost always works.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test:android": "cordova-paramedic --platform android --plugin . --verbose", 8 | "test:ios": "cordova-paramedic --platform ios --plugin . --verbose", 9 | "test": "echo \"Run tests with https://github.com/terikon/cordova-plugin-photo-library-tester, or run test:android or test:ios.\" && exit 1", 10 | "copy-test-images:android:emulator": "adb -e push tests/test-images /mnt/sdcard/DCIM && adb -e shell am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard", 11 | "copy-test-images:ios:simulator": "xcrun simctl addmedia booted tests/test-images/*" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/xmarkclx/cordova-plugin-photo-library-sism" 16 | }, 17 | "author": { 18 | "name": "Roman Viskin", 19 | "email": "npm@terikon.com", 20 | "url": "http://il.linkedin.com/in/romanviskin" 21 | }, 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/terikon/cordova-plugin-photo-library-sism/issues" 25 | }, 26 | "homepage": "https://github.com/terikon/cordova-plugin-photo-library-sism#readme", 27 | "devDependencies": { 28 | "eslint": "^3.14.1", 29 | "@types/cordova": "^0.0.34", 30 | "@types/jasmine": "^2.5.41", 31 | "es5-shim": "4.5.9", 32 | "es6-shim": "0.35.3", 33 | "es7-shim": "6.0.0", 34 | "blueimp-canvastoblob": "2.1.0", 35 | "cordova-paramedic": "https://github.com/apache/cordova-paramedic.git" 36 | }, 37 | "dependencies": { 38 | "async": "^2.1.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Photo Library 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | $PHOTO_LIBRARY_USAGE_DESCRIPTION 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/android/PhotoLibrary.java: -------------------------------------------------------------------------------- 1 | package com.terikon.cordova.photolibrary; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.net.Uri; 7 | import android.util.Base64; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import org.apache.cordova.*; 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | public class PhotoLibrary extends CordovaPlugin { 23 | 24 | public static final String PHOTO_LIBRARY_PROTOCOL = "cdvphotolibrary"; 25 | 26 | public static final int DEFAULT_WIDTH = 512; 27 | public static final int DEFAULT_HEIGHT = 384; 28 | public static final double DEFAULT_QUALITY = 0.5; 29 | 30 | public static final String ACTION_GET_LIBRARY = "getLibrary"; 31 | public static final String ACTION_GET_ALBUMS = "getAlbums"; 32 | public static final String ACTION_GET_THUMBNAIL = "getThumbnail"; 33 | public static final String ACTION_GET_PHOTO = "getPhoto"; 34 | public static final String ACTION_STOP_CACHING = "stopCaching"; 35 | public static final String ACTION_REQUEST_AUTHORIZATION = "requestAuthorization"; 36 | public static final String ACTION_SAVE_IMAGE = "saveImage"; 37 | public static final String ACTION_SAVE_VIDEO = "saveVideo"; 38 | 39 | public CallbackContext callbackContext; 40 | 41 | @Override 42 | protected void pluginInitialize() { 43 | super.pluginInitialize(); 44 | 45 | service = PhotoLibraryService.getInstance(); 46 | 47 | } 48 | 49 | @Override 50 | public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { 51 | 52 | this.callbackContext = callbackContext; 53 | 54 | try { 55 | 56 | if (ACTION_GET_LIBRARY.equals(action)) { 57 | cordova.getThreadPool().execute(new Runnable() { 58 | public void run() { 59 | try { 60 | 61 | final JSONObject options = args.optJSONObject(0); 62 | final int itemsInChunk = options.getInt("itemsInChunk"); 63 | final double chunkTimeSec = options.getDouble("chunkTimeSec"); 64 | final boolean includeAlbumData = options.getBoolean("includeAlbumData"); 65 | 66 | if (!cordova.hasPermission(READ_EXTERNAL_STORAGE)) { 67 | callbackContext.error(service.PERMISSION_ERROR); 68 | return; 69 | } 70 | 71 | PhotoLibraryGetLibraryOptions getLibraryOptions = new PhotoLibraryGetLibraryOptions(itemsInChunk, chunkTimeSec, includeAlbumData); 72 | 73 | service.getLibrary(getContext(), getLibraryOptions, new PhotoLibraryService.ChunkResultRunnable() { 74 | @Override 75 | public void run(ArrayList library, int chunkNum, boolean isLastChunk) { 76 | try { 77 | 78 | JSONObject result = createGetLibraryResult(library, chunkNum, isLastChunk); 79 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); 80 | pluginResult.setKeepCallback(!isLastChunk); 81 | callbackContext.sendPluginResult(pluginResult); 82 | 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | callbackContext.error(e.getMessage()); 86 | } 87 | } 88 | }); 89 | 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | callbackContext.error(e.getMessage()); 93 | } 94 | } 95 | }); 96 | return true; 97 | 98 | } else if (ACTION_GET_ALBUMS.equals(action)) { 99 | cordova.getThreadPool().execute(new Runnable() { 100 | public void run() { 101 | try { 102 | 103 | if (!cordova.hasPermission(READ_EXTERNAL_STORAGE)) { 104 | callbackContext.error(service.PERMISSION_ERROR); 105 | return; 106 | } 107 | 108 | ArrayList albums = service.getAlbums(getContext()); 109 | 110 | callbackContext.success(createGetAlbumsResult(albums)); 111 | 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | callbackContext.error(e.getMessage()); 115 | } 116 | } 117 | }); 118 | return true; 119 | 120 | } else if (ACTION_GET_THUMBNAIL.equals(action)) { 121 | cordova.getThreadPool().execute(new Runnable() { 122 | public void run() { 123 | try { 124 | 125 | final String photoId = args.getString(0); 126 | final JSONObject options = args.optJSONObject(1); 127 | final int thumbnailWidth = options.getInt("thumbnailWidth"); 128 | final int thumbnailHeight = options.getInt("thumbnailHeight"); 129 | final double quality = options.getDouble("quality"); 130 | 131 | if (!cordova.hasPermission(READ_EXTERNAL_STORAGE)) { 132 | callbackContext.error(service.PERMISSION_ERROR); 133 | return; 134 | } 135 | 136 | PhotoLibraryService.PictureData thumbnail = service.getThumbnail(getContext(), photoId, thumbnailWidth, thumbnailHeight, quality); 137 | callbackContext.sendPluginResult(createMultipartPluginResult(PluginResult.Status.OK, thumbnail)); 138 | 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | callbackContext.error(e.getMessage()); 142 | } 143 | } 144 | }); 145 | return true; 146 | 147 | } else if (ACTION_GET_PHOTO.equals(action)) { 148 | 149 | cordova.getThreadPool().execute(new Runnable() { 150 | public void run() { 151 | try { 152 | 153 | final String photoId = args.getString(0); 154 | 155 | if (!cordova.hasPermission(READ_EXTERNAL_STORAGE)) { 156 | callbackContext.error(service.PERMISSION_ERROR); 157 | return; 158 | } 159 | 160 | PhotoLibraryService.PictureData photo = service.getPhoto(getContext(), photoId); 161 | callbackContext.sendPluginResult(createMultipartPluginResult(PluginResult.Status.OK, photo)); 162 | 163 | } catch (Exception e) { 164 | e.printStackTrace(); 165 | callbackContext.error(e.getMessage()); 166 | } 167 | } 168 | }); 169 | return true; 170 | 171 | } else if (ACTION_STOP_CACHING.equals(action)) { 172 | 173 | // Nothing to do - it's ios only functionality 174 | callbackContext.success(); 175 | return true; 176 | 177 | } else if (ACTION_REQUEST_AUTHORIZATION.equals(action)) { 178 | try { 179 | 180 | final JSONObject options = args.optJSONObject(0); 181 | final boolean read = options.getBoolean("read"); 182 | final boolean write = options.getBoolean("write"); 183 | 184 | if (read && !cordova.hasPermission(READ_EXTERNAL_STORAGE) 185 | || write && !cordova.hasPermission(WRITE_EXTERNAL_STORAGE)) { 186 | requestAuthorization(read, write); 187 | } else { 188 | callbackContext.success(); 189 | } 190 | } catch (Exception e) { 191 | e.printStackTrace(); 192 | callbackContext.error(e.getMessage()); 193 | } 194 | return true; 195 | 196 | } else if (ACTION_SAVE_IMAGE.equals(action)) { 197 | cordova.getThreadPool().execute(new Runnable() { 198 | public void run() { 199 | try { 200 | 201 | final String url = args.getString(0); 202 | final String album = args.getString(1); 203 | 204 | if (!cordova.hasPermission(WRITE_EXTERNAL_STORAGE)) { 205 | callbackContext.error(service.PERMISSION_ERROR); 206 | return; 207 | } 208 | 209 | service.saveImage(getContext(), cordova, url, album, new PhotoLibraryService.JSONObjectRunnable() { 210 | @Override 211 | public void run(JSONObject result) { 212 | callbackContext.success(result); 213 | } 214 | }); 215 | 216 | } catch (Exception e) { 217 | e.printStackTrace(); 218 | callbackContext.error(e.getMessage()); 219 | } 220 | } 221 | }); 222 | return true; 223 | 224 | } else if (ACTION_SAVE_VIDEO.equals(action)) { 225 | cordova.getThreadPool().execute(new Runnable() { 226 | public void run() { 227 | try { 228 | 229 | final String url = args.getString(0); 230 | final String album = args.getString(1); 231 | 232 | if (!cordova.hasPermission(WRITE_EXTERNAL_STORAGE)) { 233 | callbackContext.error(service.PERMISSION_ERROR); 234 | return; 235 | } 236 | 237 | service.saveVideo(getContext(), cordova, url, album); 238 | 239 | callbackContext.success(); 240 | 241 | } catch (Exception e) { 242 | e.printStackTrace(); 243 | callbackContext.error(e.getMessage()); 244 | } 245 | } 246 | }); 247 | return true; 248 | 249 | } 250 | 251 | return false; 252 | 253 | } catch (Exception e) { 254 | e.printStackTrace(); 255 | callbackContext.error(e.getMessage()); 256 | return false; 257 | } 258 | } 259 | 260 | @Override 261 | public Uri remapUri(Uri uri) { 262 | 263 | if (!PHOTO_LIBRARY_PROTOCOL.equals(uri.getScheme())) { 264 | return null; 265 | } 266 | return toPluginUri(uri); 267 | 268 | } 269 | 270 | @Override 271 | public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException { 272 | 273 | Uri origUri = fromPluginUri(uri); 274 | 275 | boolean isThumbnail = origUri.getHost().toLowerCase().equals("thumbnail") && origUri.getPath().isEmpty(); 276 | boolean isPhoto = origUri.getHost().toLowerCase().equals("photo") && origUri.getPath().isEmpty(); 277 | 278 | if (!isThumbnail && !isPhoto) { 279 | throw new FileNotFoundException("URI not supported by PhotoLibrary"); 280 | } 281 | 282 | String photoId = origUri.getQueryParameter("photoId"); 283 | if (photoId == null || photoId.isEmpty()) { 284 | throw new FileNotFoundException("Missing 'photoId' query parameter"); 285 | } 286 | 287 | if (isThumbnail) { 288 | 289 | String widthStr = origUri.getQueryParameter("width"); 290 | int width; 291 | try { 292 | width = widthStr == null || widthStr.isEmpty() ? DEFAULT_WIDTH : Integer.parseInt(widthStr); 293 | } catch (NumberFormatException e) { 294 | throw new FileNotFoundException("Incorrect 'width' query parameter"); 295 | } 296 | 297 | String heightStr = origUri.getQueryParameter("height"); 298 | int height; 299 | try { 300 | height = heightStr == null || heightStr.isEmpty() ? DEFAULT_HEIGHT : Integer.parseInt(heightStr); 301 | } catch (NumberFormatException e) { 302 | throw new FileNotFoundException("Incorrect 'height' query parameter"); 303 | } 304 | 305 | String qualityStr = origUri.getQueryParameter("quality"); 306 | double quality; 307 | try { 308 | quality = qualityStr == null || qualityStr.isEmpty() ? DEFAULT_QUALITY : Double.parseDouble(qualityStr); 309 | } catch (NumberFormatException e) { 310 | throw new FileNotFoundException("Incorrect 'quality' query parameter"); 311 | } 312 | 313 | PhotoLibraryService.PictureData thumbnailData = service.getThumbnail(getContext(), photoId, width, height, quality); 314 | 315 | if (thumbnailData == null) { 316 | throw new FileNotFoundException("Could not create thumbnail"); 317 | } 318 | 319 | InputStream is = new ByteArrayInputStream(thumbnailData.bytes); 320 | 321 | return new CordovaResourceApi.OpenForReadResult(uri, is, thumbnailData.mimeType, is.available(), null); 322 | 323 | } else { // isPhoto == true 324 | 325 | PhotoLibraryService.PictureAsStream pictureAsStream = service.getPhotoAsStream(getContext(), photoId); 326 | InputStream is = pictureAsStream.getStream(); 327 | 328 | return new CordovaResourceApi.OpenForReadResult(uri, is, pictureAsStream.getMimeType(), is.available(), null); 329 | 330 | } 331 | 332 | } 333 | 334 | @Override 335 | public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { 336 | super.onRequestPermissionResult(requestCode, permissions, grantResults); 337 | 338 | for (int r : grantResults) { 339 | if (r == PackageManager.PERMISSION_DENIED) { 340 | this.callbackContext.error(PhotoLibraryService.PERMISSION_ERROR); 341 | return; 342 | } 343 | } 344 | 345 | this.callbackContext.success(); 346 | } 347 | 348 | private static final String READ_EXTERNAL_STORAGE = android.Manifest.permission.READ_EXTERNAL_STORAGE; 349 | private static final String WRITE_EXTERNAL_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE; 350 | private static final int REQUEST_AUTHORIZATION_REQ_CODE = 0; 351 | 352 | private PhotoLibraryService service; 353 | 354 | private Context getContext() { 355 | 356 | return this.cordova.getActivity().getApplicationContext(); 357 | 358 | } 359 | 360 | private PluginResult createMultipartPluginResult(PluginResult.Status status, PhotoLibraryService.PictureData pictureData) throws JSONException { 361 | 362 | // As cordova-android 6.x uses EVAL_BRIDGE, and it breaks support for multipart result, we will encode result by ourselves. 363 | // see encodeAsJsMessage method of https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/NativeToJsMessageQueue.java 364 | 365 | JSONObject resultJSON = new JSONObject(); 366 | resultJSON.put("data", Base64.encodeToString(pictureData.bytes, Base64.NO_WRAP)); 367 | resultJSON.put("mimeType", pictureData.mimeType); 368 | 369 | return new PluginResult(status, resultJSON); 370 | 371 | // This is old good code that worked with cordova-android 5.x 372 | // return new PluginResult(status, 373 | // Arrays.asList( 374 | // new PluginResult(status, pictureData.getBytes()), 375 | // new PluginResult(status, pictureData.getMimeType()))); 376 | 377 | } 378 | 379 | private void requestAuthorization(boolean read, boolean write) { 380 | 381 | List permissions = new ArrayList(); 382 | 383 | if (read) { 384 | permissions.add(READ_EXTERNAL_STORAGE); 385 | } 386 | 387 | if (write) { 388 | permissions.add(WRITE_EXTERNAL_STORAGE); 389 | } 390 | 391 | cordova.requestPermissions(this, REQUEST_AUTHORIZATION_REQ_CODE, permissions.toArray(new String[0])); 392 | } 393 | 394 | private static JSONArray createGetAlbumsResult(ArrayList albums) throws JSONException { 395 | return new JSONArray(albums); 396 | } 397 | 398 | private static JSONObject createGetLibraryResult(ArrayList library, int chunkNum, boolean isLastChunk) throws JSONException { 399 | JSONObject result = new JSONObject(); 400 | result.put("chunkNum", chunkNum); 401 | result.put("isLastChunk", isLastChunk); 402 | result.put("library", new JSONArray(library)); 403 | return result; 404 | } 405 | 406 | } 407 | -------------------------------------------------------------------------------- /src/android/PhotoLibraryGetLibraryOptions.java: -------------------------------------------------------------------------------- 1 | package com.terikon.cordova.photolibrary; 2 | 3 | public class PhotoLibraryGetLibraryOptions { 4 | 5 | public final int itemsInChunk; 6 | public final double chunkTimeSec; 7 | public final boolean includeAlbumData; 8 | 9 | public PhotoLibraryGetLibraryOptions(int itemsInChunk, double chunkTimeSec, boolean includeAlbumData) { 10 | this.itemsInChunk = itemsInChunk; 11 | this.chunkTimeSec = chunkTimeSec; 12 | this.includeAlbumData = includeAlbumData; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/android/PhotoLibraryService.java: -------------------------------------------------------------------------------- 1 | package com.terikon.cordova.photolibrary; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Matrix; 8 | import android.media.ExifInterface; 9 | import android.media.MediaScannerConnection; 10 | import android.media.ThumbnailUtils; 11 | import android.net.Uri; 12 | import android.os.Environment; 13 | import android.os.SystemClock; 14 | import android.provider.MediaStore; 15 | import android.util.Base64; 16 | 17 | import org.apache.cordova.CordovaInterface; 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.File; 25 | import java.io.FileInputStream; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | import java.net.URI; 31 | import java.net.URL; 32 | import java.net.URISyntaxException; 33 | import java.text.SimpleDateFormat; 34 | import java.util.ArrayList; 35 | import java.util.Calendar; 36 | import java.util.Date; 37 | import java.util.HashMap; 38 | import java.util.Iterator; 39 | import java.util.Map; 40 | import java.util.regex.Matcher; 41 | import java.util.regex.Pattern; 42 | import java.util.TimeZone; 43 | 44 | public class PhotoLibraryService { 45 | 46 | // TODO: implement cache 47 | //int cacheSize = 4 * 1024 * 1024; // 4MB 48 | //private LruCache imageCache = new LruCache(cacheSize); 49 | 50 | protected PhotoLibraryService() { 51 | dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 52 | dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); 53 | } 54 | 55 | public static final String PERMISSION_ERROR = "Permission Denial: This application is not allowed to access Photo data."; 56 | 57 | public static PhotoLibraryService getInstance() { 58 | if (instance == null) { 59 | synchronized (PhotoLibraryService.class) { 60 | if (instance == null) { 61 | instance = new PhotoLibraryService(); 62 | } 63 | } 64 | } 65 | return instance; 66 | } 67 | 68 | public void getLibrary(Context context, PhotoLibraryGetLibraryOptions options, ChunkResultRunnable completion) throws JSONException { 69 | 70 | String whereClause = ""; 71 | queryLibrary(context, options.itemsInChunk, options.chunkTimeSec, options.includeAlbumData, whereClause, completion); 72 | 73 | } 74 | 75 | public ArrayList getAlbums(Context context) throws JSONException { 76 | 77 | // All columns here: https://developer.android.com/reference/android/provider/MediaStore.Images.ImageColumns.html, 78 | // https://developer.android.com/reference/android/provider/MediaStore.MediaColumns.html 79 | JSONObject columns = new JSONObject() {{ 80 | put("id", MediaStore.Images.ImageColumns.BUCKET_ID); 81 | put("title", MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME); 82 | }}; 83 | 84 | final ArrayList queryResult = queryContentProvider(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, "1) GROUP BY 1,(2"); 85 | 86 | return queryResult; 87 | 88 | } 89 | 90 | public PictureData getThumbnail(Context context, String photoId, int thumbnailWidth, int thumbnailHeight, double quality) throws IOException { 91 | 92 | Bitmap bitmap = null; 93 | 94 | String imageURL = getImageURL(photoId); 95 | File imageFile = new File(imageURL); 96 | 97 | // TODO: maybe it never worth using MediaStore.Images.Thumbnails.getThumbnail, as it returns sizes less than 512x384? 98 | if (thumbnailWidth == 512 && thumbnailHeight == 384) { // In such case, thumbnail will be cached by MediaStore 99 | int imageId = getImageId(photoId); 100 | // For some reason and against documentation, MINI_KIND image can be returned in size different from 512x384, so the image will be scaled later if needed 101 | bitmap = MediaStore.Images.Thumbnails.getThumbnail( 102 | context.getContentResolver(), 103 | imageId , 104 | MediaStore.Images.Thumbnails.MINI_KIND, 105 | (BitmapFactory.Options) null); 106 | } 107 | 108 | if (bitmap == null) { // No free caching here 109 | Uri imageUri = Uri.fromFile(imageFile); 110 | BitmapFactory.Options options = new BitmapFactory.Options(); 111 | 112 | options.inJustDecodeBounds = true; 113 | InputStream is = context.getContentResolver().openInputStream(imageUri); 114 | BitmapFactory.decodeStream(is, null, options); 115 | 116 | // get bitmap with size of closest power of 2 117 | options.inSampleSize = calculateInSampleSize(options, thumbnailWidth, thumbnailHeight); 118 | options.inJustDecodeBounds = false; 119 | is = context.getContentResolver().openInputStream(imageUri); 120 | bitmap = BitmapFactory.decodeStream(is, null, options); 121 | is.close(); 122 | } 123 | 124 | if (bitmap != null) { 125 | 126 | // correct image orientation 127 | int orientation = getImageOrientation(imageFile); 128 | Bitmap rotatedBitmap = rotateImage(bitmap, orientation); 129 | if (bitmap != rotatedBitmap) { 130 | bitmap.recycle(); 131 | } 132 | 133 | Bitmap thumbnailBitmap = ThumbnailUtils.extractThumbnail(rotatedBitmap, thumbnailWidth, thumbnailHeight); 134 | if (rotatedBitmap != thumbnailBitmap) { 135 | rotatedBitmap.recycle(); 136 | } 137 | 138 | // TODO: cache bytes for performance 139 | 140 | byte[] bytes = getJpegBytesFromBitmap(thumbnailBitmap, quality); 141 | String mimeType = "image/jpeg"; 142 | 143 | thumbnailBitmap.recycle(); 144 | 145 | return new PictureData(bytes, mimeType); 146 | 147 | } 148 | 149 | return null; 150 | 151 | } 152 | 153 | public PictureAsStream getPhotoAsStream(Context context, String photoId) throws IOException { 154 | 155 | int imageId = getImageId(photoId); 156 | String imageURL = getImageURL(photoId); 157 | File imageFile = new File(imageURL); 158 | Uri imageUri = Uri.fromFile(imageFile); 159 | 160 | String mimeType = queryMimeType(context, imageId); 161 | 162 | InputStream is = context.getContentResolver().openInputStream(imageUri); 163 | 164 | if (mimeType.equals("image/jpeg")) { 165 | int orientation = getImageOrientation(imageFile); 166 | if (orientation > 1) { // Image should be rotated 167 | 168 | Bitmap bitmap = BitmapFactory.decodeStream(is, null, null); 169 | is.close(); 170 | 171 | Bitmap rotatedBitmap = rotateImage(bitmap, orientation); 172 | 173 | bitmap.recycle(); 174 | 175 | // Here we perform conversion with data loss, but it seems better than handling orientation in JavaScript. 176 | // Converting to PNG can be an option to prevent data loss, but in price of very large files. 177 | byte[] bytes = getJpegBytesFromBitmap(rotatedBitmap, 1.0); // minimize data loss with 1.0 quality 178 | 179 | is = new ByteArrayInputStream(bytes); 180 | } 181 | } 182 | 183 | return new PictureAsStream(is, mimeType); 184 | 185 | } 186 | 187 | public PictureData getPhoto(Context context, String photoId) throws IOException { 188 | 189 | PictureAsStream pictureAsStream = getPhotoAsStream(context, photoId); 190 | 191 | byte[] bytes = readBytes(pictureAsStream.getStream()); 192 | pictureAsStream.getStream().close(); 193 | 194 | return new PictureData(bytes, pictureAsStream.getMimeType()); 195 | 196 | } 197 | 198 | public void saveImage(final Context context, final CordovaInterface cordova, final String url, String album, final JSONObjectRunnable completion) 199 | throws IOException, URISyntaxException { 200 | 201 | saveMedia(context, cordova, url, album, imageMimeToExtension, new FilePathRunnable() { 202 | @Override 203 | public void run(String filePath) { 204 | try { 205 | // Find the saved image in the library and return it as libraryItem 206 | String whereClause = MediaStore.MediaColumns.DATA + " = \"" + filePath + "\""; 207 | queryLibrary(context, whereClause, new ChunkResultRunnable() { 208 | @Override 209 | public void run(ArrayList chunk, int chunkNum, boolean isLastChunk) { 210 | completion.run(chunk.size() == 1 ? chunk.get(0) : null); 211 | } 212 | }); 213 | } catch (Exception e) { 214 | completion.run(null); 215 | } 216 | } 217 | }); 218 | 219 | } 220 | 221 | public void saveVideo(final Context context, final CordovaInterface cordova, String url, String album) 222 | throws IOException, URISyntaxException { 223 | 224 | saveMedia(context, cordova, url, album, videMimeToExtension, new FilePathRunnable() { 225 | @Override 226 | public void run(String filePath) { 227 | // TODO: call queryLibrary and return libraryItem of what was saved 228 | } 229 | }); 230 | 231 | } 232 | 233 | public class PictureData { 234 | 235 | public final byte[] bytes; 236 | public final String mimeType; 237 | 238 | public PictureData(byte[] bytes, String mimeType) { 239 | this.bytes = bytes; 240 | this.mimeType = mimeType; 241 | } 242 | 243 | } 244 | 245 | public class PictureAsStream { 246 | 247 | public PictureAsStream(InputStream stream, String mimeType) { 248 | this.stream = stream; 249 | this.mimeType = mimeType; 250 | } 251 | 252 | public InputStream getStream() { return this.stream; } 253 | 254 | public String getMimeType() { return this.mimeType; } 255 | 256 | private InputStream stream; 257 | private String mimeType; 258 | 259 | } 260 | 261 | private static PhotoLibraryService instance = null; 262 | 263 | private SimpleDateFormat dateFormatter; 264 | 265 | private Pattern dataURLPattern = Pattern.compile("^data:(.+?)/(.+?);base64,"); 266 | 267 | private ArrayList queryContentProvider(Context context, Uri collection, JSONObject columns, String whereClause) throws JSONException { 268 | 269 | final ArrayList columnNames = new ArrayList(); 270 | final ArrayList columnValues = new ArrayList(); 271 | 272 | Iterator iteratorFields = columns.keys(); 273 | 274 | while (iteratorFields.hasNext()) { 275 | String column = iteratorFields.next(); 276 | 277 | columnNames.add(column); 278 | columnValues.add("" + columns.getString(column)); 279 | } 280 | 281 | final String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC"; 282 | 283 | final Cursor cursor = context.getContentResolver().query( 284 | collection, 285 | columnValues.toArray(new String[columns.length()]), 286 | whereClause, null, sortOrder); 287 | 288 | final ArrayList buffer = new ArrayList(); 289 | 290 | if (cursor.moveToFirst()) { 291 | do { 292 | JSONObject item = new JSONObject(); 293 | 294 | for (String column : columnNames) { 295 | int columnIndex = cursor.getColumnIndex(columns.get(column).toString()); 296 | 297 | if (column.startsWith("int.")) { 298 | item.put(column.substring(4), cursor.getInt(columnIndex)); 299 | if (column.substring(4).equals("width") && item.getInt("width") == 0) { 300 | System.err.println("cursor: " + cursor.getInt(columnIndex)); 301 | } 302 | } else if (column.startsWith("float.")) { 303 | item.put(column.substring(6), cursor.getFloat(columnIndex)); 304 | } else if (column.startsWith("date.")) { 305 | long intDate = cursor.getLong(columnIndex); 306 | Date date = new Date(intDate); 307 | item.put(column.substring(5), dateFormatter.format(date)); 308 | } else { 309 | item.put(column, cursor.getString(columnIndex)); 310 | } 311 | } 312 | buffer.add(item); 313 | 314 | // TODO: return partial result 315 | 316 | } 317 | while (cursor.moveToNext()); 318 | } 319 | 320 | cursor.close(); 321 | 322 | return buffer; 323 | 324 | } 325 | 326 | private void queryLibrary(Context context, String whereClause, ChunkResultRunnable completion) throws JSONException { 327 | queryLibrary(context, 0, 0, false, whereClause, completion); 328 | } 329 | 330 | private void queryLibrary(Context context, int itemsInChunk, double chunkTimeSec, boolean includeAlbumData, String whereClause, ChunkResultRunnable completion) 331 | throws JSONException { 332 | 333 | // All columns here: https://developer.android.com/reference/android/provider/MediaStore.Images.ImageColumns.html, 334 | // https://developer.android.com/reference/android/provider/MediaStore.MediaColumns.html 335 | JSONObject columns = new JSONObject() {{ 336 | put("int.id", MediaStore.Images.Media._ID); 337 | put("fileName", MediaStore.Images.ImageColumns.DISPLAY_NAME); 338 | put("int.width", MediaStore.Images.ImageColumns.WIDTH); 339 | put("int.height", MediaStore.Images.ImageColumns.HEIGHT); 340 | put("albumId", MediaStore.Images.ImageColumns.BUCKET_ID); 341 | put("date.creationDate", MediaStore.Images.ImageColumns.DATE_TAKEN); 342 | put("float.latitude", MediaStore.Images.ImageColumns.LATITUDE); 343 | put("float.longitude", MediaStore.Images.ImageColumns.LONGITUDE); 344 | put("nativeURL", MediaStore.MediaColumns.DATA); // will not be returned to javascript 345 | }}; 346 | 347 | final ArrayList queryResults = queryContentProvider(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, whereClause); 348 | 349 | ArrayList chunk = new ArrayList(); 350 | 351 | long chunkStartTime = SystemClock.elapsedRealtime(); 352 | int chunkNum = 0; 353 | 354 | for (int i=0; i 0 && chunk.size() == itemsInChunk) || (chunkTimeSec > 0 && (SystemClock.elapsedRealtime() - chunkStartTime) >= chunkTimeSec*1000)) { 389 | completion.run(chunk, chunkNum, false); 390 | chunkNum += 1; 391 | chunk = new ArrayList(); 392 | chunkStartTime = SystemClock.elapsedRealtime(); 393 | } 394 | 395 | } 396 | 397 | } 398 | 399 | private String queryMimeType(Context context, int imageId) { 400 | 401 | Cursor cursor = context.getContentResolver().query( 402 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 403 | new String[] { MediaStore.Images.ImageColumns.MIME_TYPE }, 404 | MediaStore.MediaColumns._ID + "=?", 405 | new String[] {Integer.toString(imageId)}, null); 406 | 407 | if (cursor != null && cursor.moveToFirst()) { 408 | String mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); 409 | cursor.close(); 410 | 411 | return mimeType; 412 | 413 | } 414 | 415 | cursor.close(); 416 | return null; 417 | } 418 | 419 | // From https://developer.android.com/training/displaying-bitmaps/load-bitmap.html 420 | private static int calculateInSampleSize( 421 | 422 | BitmapFactory.Options options, int reqWidth, int reqHeight) { 423 | // Raw height and width of image 424 | final int height = options.outHeight; 425 | final int width = options.outWidth; 426 | int inSampleSize = 1; 427 | 428 | if (height > reqHeight || width > reqWidth) { 429 | 430 | final int halfHeight = height / 2; 431 | final int halfWidth = width / 2; 432 | 433 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 434 | // height and width larger than the requested height and width. 435 | while ((halfHeight / inSampleSize) >= reqHeight 436 | && (halfWidth / inSampleSize) >= reqWidth) { 437 | inSampleSize *= 2; 438 | } 439 | } 440 | 441 | return inSampleSize; 442 | 443 | } 444 | 445 | private static byte[] getJpegBytesFromBitmap(Bitmap bitmap, double quality) { 446 | 447 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 448 | bitmap.compress(Bitmap.CompressFormat.JPEG, (int)(quality * 100), stream); 449 | 450 | return stream.toByteArray(); 451 | 452 | } 453 | 454 | private static void copyStream(InputStream source, OutputStream target) throws IOException { 455 | 456 | int bufferSize = 1024; 457 | byte[] buffer = new byte[bufferSize]; 458 | 459 | int len; 460 | while ((len = source.read(buffer)) != -1) { 461 | target.write(buffer, 0, len); 462 | } 463 | 464 | } 465 | 466 | private static byte[] readBytes(InputStream inputStream) throws IOException { 467 | 468 | ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 469 | 470 | int bufferSize = 1024; 471 | byte[] buffer = new byte[bufferSize]; 472 | 473 | int len; 474 | while ((len = inputStream.read(buffer)) != -1) { 475 | byteBuffer.write(buffer, 0, len); 476 | } 477 | 478 | return byteBuffer.toByteArray(); 479 | 480 | } 481 | 482 | // photoId is in format "imageid;imageurl;[swap]" 483 | private static int getImageId(String photoId) { 484 | return Integer.parseInt(photoId.split(";")[0]); 485 | } 486 | 487 | // photoId is in format "imageid;imageurl;[swap]" 488 | private static String getImageURL(String photoId) { 489 | return photoId.split(";")[1]; 490 | } 491 | 492 | private static int getImageOrientation(File imageFile) throws IOException { 493 | 494 | ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); 495 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 496 | 497 | return orientation; 498 | 499 | } 500 | 501 | // see http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ 502 | private static Bitmap rotateImage(Bitmap source, int orientation) { 503 | 504 | Matrix matrix = new Matrix(); 505 | 506 | switch (orientation) { 507 | case ExifInterface.ORIENTATION_NORMAL: // 1 508 | return source; 509 | case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: // 2 510 | matrix.setScale(-1, 1); 511 | break; 512 | case ExifInterface.ORIENTATION_ROTATE_180: // 3 513 | matrix.setRotate(180); 514 | break; 515 | case ExifInterface.ORIENTATION_FLIP_VERTICAL: // 4 516 | matrix.setRotate(180); 517 | matrix.postScale(-1, 1); 518 | break; 519 | case ExifInterface.ORIENTATION_TRANSPOSE: // 5 520 | matrix.setRotate(90); 521 | matrix.postScale(-1, 1); 522 | break; 523 | case ExifInterface.ORIENTATION_ROTATE_90: // 6 524 | matrix.setRotate(90); 525 | break; 526 | case ExifInterface.ORIENTATION_TRANSVERSE: // 7 527 | matrix.setRotate(-90); 528 | matrix.postScale(-1, 1); 529 | break; 530 | case ExifInterface.ORIENTATION_ROTATE_270: // 8 531 | matrix.setRotate(-90); 532 | break; 533 | default: 534 | return source; 535 | } 536 | 537 | return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, false); 538 | 539 | } 540 | 541 | // Returns true if orientation rotates image by 90 or 270 degrees. 542 | private static boolean isOrientationSwapsDimensions(int orientation) { 543 | return orientation == ExifInterface.ORIENTATION_TRANSPOSE // 5 544 | || orientation == ExifInterface.ORIENTATION_ROTATE_90 // 6 545 | || orientation == ExifInterface.ORIENTATION_TRANSVERSE // 7 546 | || orientation == ExifInterface.ORIENTATION_ROTATE_270; // 8 547 | } 548 | 549 | private static File makeAlbumInPhotoLibrary(String album) { 550 | File albumDirectory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), album); 551 | if (!albumDirectory.exists()) { 552 | albumDirectory.mkdirs(); 553 | } 554 | return albumDirectory; 555 | } 556 | 557 | private File getImageFileName(File albumDirectory, String extension) { 558 | Calendar calendar = Calendar.getInstance(); 559 | String dateStr = calendar.get(Calendar.YEAR) + 560 | "-" + calendar.get(Calendar.MONTH) + 561 | "-" + calendar.get(Calendar.DAY_OF_MONTH); 562 | int i = 1; 563 | File result; 564 | do { 565 | String fileName = dateStr + "-" + i + extension; 566 | i += 1; 567 | result = new File(albumDirectory, fileName); 568 | } while (result.exists()); 569 | return result; 570 | } 571 | 572 | private void addFileToMediaLibrary(Context context, File file, final FilePathRunnable completion) { 573 | 574 | String filePath = file.getAbsolutePath(); 575 | 576 | MediaScannerConnection.scanFile(context, new String[]{filePath}, null, new MediaScannerConnection.OnScanCompletedListener() { 577 | @Override 578 | public void onScanCompleted(String path, Uri uri) { 579 | completion.run(path); 580 | } 581 | }); 582 | 583 | } 584 | 585 | private Map imageMimeToExtension = new HashMap(){{ 586 | put("jpeg", ".jpg"); 587 | }}; 588 | 589 | private Map videMimeToExtension = new HashMap(){{ 590 | put("quicktime", ".mov"); 591 | put("ogg", ".ogv"); 592 | }}; 593 | 594 | private void saveMedia(Context context, CordovaInterface cordova, String url, String album, Map mimeToExtension, FilePathRunnable completion) 595 | throws IOException, URISyntaxException { 596 | 597 | File albumDirectory = makeAlbumInPhotoLibrary(album); 598 | File targetFile; 599 | 600 | if (url.startsWith("data:")) { 601 | 602 | Matcher matcher = dataURLPattern.matcher(url); 603 | if (!matcher.find()) { 604 | throw new IllegalArgumentException("The dataURL is in incorrect format"); 605 | } 606 | String mime = matcher.group(2); 607 | int dataPos = matcher.end(); 608 | 609 | String base64 = url.substring(dataPos); // Use substring and not replace to keep memory footprint small 610 | byte[] decoded = Base64.decode(base64, Base64.DEFAULT); 611 | 612 | if (decoded == null) { 613 | throw new IllegalArgumentException("The dataURL could not be decoded"); 614 | } 615 | 616 | String extension = mimeToExtension.get(mime); 617 | if (extension == null) { 618 | extension = "." + mime; 619 | } 620 | 621 | targetFile = getImageFileName(albumDirectory, extension); 622 | 623 | FileOutputStream os = new FileOutputStream(targetFile); 624 | 625 | os.write(decoded); 626 | 627 | os.flush(); 628 | os.close(); 629 | 630 | } else { 631 | 632 | String noQ = url.split("\\?")[0]; //no q 633 | String extension = url.contains(".") ? noQ.substring(noQ.lastIndexOf(".")) : ""; 634 | targetFile = getImageFileName(albumDirectory, extension); 635 | 636 | InputStream is; 637 | FileOutputStream os = new FileOutputStream(targetFile); 638 | 639 | if(url.startsWith("file:///android_asset/")) { 640 | String assetUrl = url.replace("file:///android_asset/", ""); 641 | is = cordova.getActivity().getApplicationContext().getAssets().open(assetUrl); 642 | } else { 643 | is = new URL(url).openStream(); 644 | } 645 | 646 | copyStream(is, os); 647 | 648 | os.flush(); 649 | os.close(); 650 | is.close(); 651 | 652 | } 653 | 654 | addFileToMediaLibrary(context, targetFile, completion); 655 | 656 | } 657 | 658 | public interface ChunkResultRunnable { 659 | 660 | void run(ArrayList chunk, int chunkNum, boolean isLastChunk); 661 | 662 | } 663 | 664 | public interface FilePathRunnable { 665 | 666 | void run(String filePath); 667 | 668 | } 669 | 670 | public interface JSONObjectRunnable { 671 | 672 | void run(JSONObject result); 673 | 674 | } 675 | 676 | } 677 | -------------------------------------------------------------------------------- /src/browser/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/browser/PhotoLibraryProxy.js: -------------------------------------------------------------------------------- 1 | // Assume browser supports lambdas 2 | 3 | var async = cordova.require('cordova-plugin-photo-library-sism.async'); 4 | 5 | var photoLibraryProxy = { 6 | 7 | getLibrary: function (success, error, [options]) { 8 | 9 | let processFiles = (chunkFiles, filesElement) => { 10 | files2Library(chunkFiles, options.includeAlbumData, options.itemsInChunk, options.chunkTimeSec, (library, chunkNum, isLastChunk) => { 11 | if (filesElement && isLastChunk) { 12 | removeFilesElement(filesElement); 13 | files = null; 14 | } 15 | success({ library: library, chunkNum: chunkNum, isLastChunk: isLastChunk }, {keepCallback: !isLastChunk}); 16 | }); 17 | }; 18 | 19 | if (files) { 20 | 21 | processFiles(files); 22 | 23 | } else { 24 | 25 | checkSupported(); 26 | 27 | let filesElement = createFilesElement(); 28 | 29 | filesElement.addEventListener('change', (evt) => { 30 | 31 | files = getFiles(evt.target); 32 | 33 | processFiles(files, filesElement); 34 | 35 | }, false); 36 | } 37 | 38 | }, 39 | 40 | getAlbums: function (success, error) { 41 | setTimeout(() => { 42 | success( ['browser'] ); 43 | }, 0); 44 | }, 45 | 46 | _getThumbnailURLBrowser: function (success, error, [photoId, options]) { 47 | photoLibraryProxy.getThumbnail( 48 | imageData => { 49 | let thumbnailURL = URL.createObjectURL(imageData.data); 50 | success(thumbnailURL); 51 | }, 52 | error, 53 | [photoId, options]); 54 | }, 55 | 56 | _getPhotoURLBrowser: function (success, error, [photoId, options]) { 57 | photoLibraryProxy.getPhoto( 58 | imageData => { 59 | let photoURL = URL.createObjectURL(imageData.data); 60 | success(photoURL); 61 | }, 62 | error, 63 | [photoId, options]); 64 | }, 65 | 66 | getThumbnail: function (success, error, [photoId, options]) { 67 | 68 | let staticItem = staticLibrary.get(photoId); 69 | if (!staticItem) { 70 | error(`Photo with id ${photoId} not found in the library`); 71 | return; 72 | } 73 | //let libraryItem = staticItem.libraryItem; 74 | 75 | let {thumbnailWidth, thumbnailHeight, quality} = options; 76 | 77 | readDataURLAsImage(staticItem.dataURL).then(image => { 78 | let canvas = document.createElement('canvas'); 79 | let context = canvas.getContext('2d'); 80 | canvas.width = thumbnailWidth; 81 | canvas.height = thumbnailHeight; 82 | context.drawImage(image, 0, 0, thumbnailWidth, thumbnailHeight); 83 | canvas.toBlob((blob) => { 84 | success({ data: blob, mimeType: blob.type }); 85 | }, 'image/jpeg', quality); 86 | }); 87 | 88 | }, 89 | 90 | getPhoto: function (success, error, [photoId, options]) { 91 | 92 | let staticItem = staticLibrary.get(photoId); 93 | if (!staticItem) { 94 | error(`Photo with id ${photoId} not found in the library`); 95 | return; 96 | } 97 | //let libraryItem = staticItem.libraryItem; 98 | 99 | let blob = dataURLToBlob(staticItem.dataURL); 100 | success({ data: blob, mimeType: blob.type }); 101 | 102 | }, 103 | 104 | stopCaching: function (success, error) { 105 | // Nothing to do 106 | success(); 107 | }, 108 | 109 | requestAuthorization: function (success, error) { 110 | // Nothing to do 111 | success(); 112 | }, 113 | 114 | saveImage: function (success, error, [url, album]) { 115 | // TODO - implement saving on browser 116 | error('not implemented'); 117 | }, 118 | 119 | saveVideo: function (success, error, [url, album]) { 120 | // TODO - implement saving on browser 121 | error('not implemented'); 122 | }, 123 | 124 | }; 125 | 126 | module.exports = photoLibraryProxy; 127 | 128 | require('cordova/exec/proxy').add('PhotoLibrary', photoLibraryProxy); 129 | 130 | const HIGHEST_POSSIBLE_Z_INDEX = 2147483647; 131 | 132 | var staticLibrary = new Map(); 133 | var counter = 0; 134 | 135 | var files = null; // files are stored, so multiple calls to getLibrary won't require multiple files selections 136 | var idCache = {}; // cache of ids 137 | 138 | function checkSupported() { 139 | // Check for the various File API support. 140 | if (!(window.File && window.FileReader && window.FileList && window.Blob)) { 141 | throw ('The File APIs are not fully supported in this browser.'); 142 | } 143 | } 144 | 145 | function createFilesElement() { 146 | var filesElement = document.createElement('input'); 147 | filesElement.type = 'file'; 148 | filesElement.name = 'files[]'; 149 | filesElement.multiple = true; 150 | filesElement.style.zIndex = HIGHEST_POSSIBLE_Z_INDEX; 151 | filesElement.style.position = 'relative'; 152 | filesElement.className = 'cordova-photo-library-select'; 153 | document.body.appendChild(filesElement); 154 | return filesElement; 155 | } 156 | 157 | function removeFilesElement(filesElement) { 158 | filesElement.parentNode.removeChild(filesElement); 159 | } 160 | 161 | function getFiles(filesElement) { 162 | //convert from array-like to real array 163 | let files = Array.from(filesElement.files); // FileList object 164 | return files.filter(f => f.type.match('image.*')); 165 | } 166 | 167 | function readFileAsDataURL(file) { 168 | return new Promise((resolve, reject) => { 169 | let reader = new FileReader(); 170 | reader.onload = (e) => { 171 | resolve(e.target.result); 172 | }; 173 | reader.readAsDataURL(file); 174 | }); 175 | } 176 | 177 | function readDataURLAsImage(dataURL) { 178 | return new Promise((resolve, reject) => { 179 | var imageObj = new Image(); 180 | imageObj.onload = () => { 181 | resolve(imageObj); 182 | }; 183 | imageObj.src = dataURL; 184 | }); 185 | } 186 | 187 | function files2Library(files, includeAlbumData, itemsInChunk, chunkTimeSec, success) { 188 | 189 | let chunk = []; 190 | let chunkStartTime = new Date().getTime(); 191 | let chunkNum = 0; 192 | 193 | async.eachOfSeries(files, (file, index, done) => { 194 | 195 | readFileAsDataURL(file) 196 | .then(dataURL => { 197 | return readDataURLAsImage(dataURL).then(image => { 198 | return { dataURL, image }; 199 | }); 200 | }) 201 | .then(dataURLwithImage => { 202 | let {image, dataURL} = dataURLwithImage; 203 | let {width, height} = image; 204 | let id = idCache[file.name]; 205 | if (!id) { 206 | id = `${counter}#${file.name}`; 207 | idCache[file.name] = id; 208 | } 209 | 210 | let libraryItem = { 211 | id: id, 212 | fileName: file.name, 213 | width: width, 214 | height: height, 215 | creationDate: file.lastModifiedDate.toISOString(), // file contains only lastModifiedDate 216 | //TODO: latitude, using exif-js 217 | //TODO: longitude 218 | }; 219 | if (includeAlbumData) { 220 | libraryItem.albumIds = [ 'browser' ]; 221 | } 222 | counter += 1; 223 | 224 | staticLibrary.set(libraryItem.id, { libraryItem: libraryItem, dataURL: dataURL }); 225 | 226 | chunk.push(libraryItem); 227 | 228 | if (index === files.length - 1) { 229 | success(chunk, chunkNum, true); 230 | } else if ((itemsInChunk > 0 && chunk.length === itemsInChunk) || (chunkTimeSec > 0 && (new Date().getTime() - chunkStartTime) >= chunkTimeSec*1000)) { 231 | success(chunk, chunkNum, false); 232 | chunkNum += 1; 233 | chunk = []; 234 | chunkStartTime = new Date().getTime(); 235 | } 236 | 237 | done(); 238 | 239 | }); 240 | 241 | }); 242 | 243 | } 244 | 245 | // From here: https://gist.github.com/davoclavo/4424731 246 | function dataURLToBlob(dataURL) { 247 | // convert base64 to raw binary data held in a string 248 | var byteString = atob(dataURL.split(',')[1]); 249 | 250 | // separate out the mime component 251 | var mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]; 252 | 253 | // write the bytes of the string to an ArrayBuffer 254 | var arrayBuffer = new ArrayBuffer(byteString.length); 255 | var _ia = new Uint8Array(arrayBuffer); 256 | for (var i = 0; i < byteString.length; i++) { 257 | _ia[i] = byteString.charCodeAt(i); 258 | } 259 | 260 | var dataView = new DataView(arrayBuffer); 261 | var blob = new Blob([dataView], { type: mimeString }); 262 | return blob; 263 | } 264 | -------------------------------------------------------------------------------- /src/browser/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/ios/PhotoLibrary.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(PhotoLibrary) class PhotoLibrary : CDVPlugin { 4 | 5 | lazy var concurrentQueue: DispatchQueue = DispatchQueue(label: "photo-library.queue.plugin", qos: DispatchQoS.utility, attributes: [.concurrent]) 6 | 7 | override func pluginInitialize() { 8 | 9 | // Do not call PhotoLibraryService here, as it will cause permission prompt to appear on app start. 10 | 11 | URLProtocol.registerClass(PhotoLibraryProtocol.self) 12 | 13 | } 14 | 15 | override func onMemoryWarning() { 16 | // self.service.stopCaching() 17 | NSLog("-- MEMORY WARNING --") 18 | } 19 | 20 | 21 | // Will sort by creation date 22 | @objc(getLibrary:) func getLibrary(_ command: CDVInvokedUrlCommand) { 23 | concurrentQueue.async { 24 | 25 | if !PhotoLibraryService.hasPermission() { 26 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 27 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 28 | return 29 | } 30 | 31 | let service = PhotoLibraryService.instance 32 | 33 | let options = command.arguments[0] as! NSDictionary 34 | 35 | 36 | let thumbnailWidth = options["thumbnailWidth"] as! Int 37 | let thumbnailHeight = options["thumbnailHeight"] as! Int 38 | let itemsInChunk = options["itemsInChunk"] as! Int 39 | let chunkTimeSec = options["chunkTimeSec"] as! Double 40 | let useOriginalFileNames = options["useOriginalFileNames"] as! Bool 41 | let includeAlbumData = options["includeAlbumData"] as! Bool 42 | let includeCloudData = options["includeCloudData"] as! Bool 43 | let includeVideos = options["includeVideos"] as! Bool 44 | let includeImages = options["includeImages"] as! Bool 45 | let maxItems = options["maxItems"] as! Int 46 | 47 | func createResult (library: [NSDictionary], chunkNum: Int, isLastChunk: Bool) -> [String: AnyObject] { 48 | let result: NSDictionary = [ 49 | "chunkNum": chunkNum, 50 | "isLastChunk": isLastChunk, 51 | "library": library 52 | ] 53 | return result as! [String: AnyObject] 54 | } 55 | 56 | let getLibraryOptions = PhotoLibraryGetLibraryOptions(thumbnailWidth: thumbnailWidth, 57 | thumbnailHeight: thumbnailHeight, 58 | itemsInChunk: itemsInChunk, 59 | chunkTimeSec: chunkTimeSec, 60 | useOriginalFileNames: useOriginalFileNames, 61 | includeImages: includeImages, 62 | includeAlbumData: includeAlbumData, 63 | includeCloudData: includeCloudData, 64 | includeVideos: includeVideos, 65 | maxItems: maxItems) 66 | 67 | service.getLibrary(getLibraryOptions, 68 | completion: { (library, chunkNum, isLastChunk) in 69 | 70 | let result = createResult(library: library, chunkNum: chunkNum, isLastChunk: isLastChunk) 71 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result) 72 | pluginResult!.setKeepCallbackAs(!isLastChunk) 73 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 74 | } 75 | ) 76 | } 77 | } 78 | 79 | @objc(getAlbums:) func getAlbums(_ command: CDVInvokedUrlCommand) { 80 | concurrentQueue.async { 81 | 82 | if !PhotoLibraryService.hasPermission() { 83 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 84 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 85 | return 86 | } 87 | 88 | let service = PhotoLibraryService.instance 89 | 90 | let albums = service.getAlbums() 91 | 92 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: albums) 93 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 94 | 95 | } 96 | } 97 | 98 | @objc(getPhotosFromAlbum:) func getPhotosFromAlbum(_ command: CDVInvokedUrlCommand) { 99 | print("C getPhotosFromAlbum 0"); 100 | concurrentQueue.async { 101 | 102 | print("C getPhotosFromAlbum 1"); 103 | 104 | if !PhotoLibraryService.hasPermission() { 105 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 106 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 107 | return 108 | } 109 | 110 | print("C getPhotosFromAlbum 2"); 111 | 112 | let service = PhotoLibraryService.instance 113 | 114 | let albumTitle = command.arguments[0] as! String 115 | 116 | let photos = service.getPhotosFromAlbum(albumTitle); 117 | 118 | print("C getPhotosFromAlbum 3"); 119 | 120 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: photos) 121 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 122 | 123 | } 124 | } 125 | 126 | 127 | @objc(isAuthorized:) func isAuthorized(_ command: CDVInvokedUrlCommand) { 128 | concurrentQueue.async { 129 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: PhotoLibraryService.hasPermission()) 130 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 131 | } 132 | } 133 | 134 | 135 | @objc(getThumbnail:) func getThumbnail(_ command: CDVInvokedUrlCommand) { 136 | concurrentQueue.async { 137 | 138 | if !PhotoLibraryService.hasPermission() { 139 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 140 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 141 | return 142 | } 143 | 144 | let service = PhotoLibraryService.instance 145 | 146 | let photoId = command.arguments[0] as! String 147 | let options = command.arguments[1] as! NSDictionary 148 | let thumbnailWidth = options["thumbnailWidth"] as! Int 149 | let thumbnailHeight = options["thumbnailHeight"] as! Int 150 | let quality = options["quality"] as! Float 151 | 152 | service.getThumbnail(photoId, thumbnailWidth: thumbnailWidth, thumbnailHeight: thumbnailHeight, quality: quality) { (imageData) in 153 | 154 | let pluginResult = imageData != nil ? 155 | CDVPluginResult( 156 | status: CDVCommandStatus_OK, 157 | messageAsMultipart: [imageData!.data, imageData!.mimeType]) 158 | : 159 | CDVPluginResult( 160 | status: CDVCommandStatus_ERROR, 161 | messageAs: "Could not fetch the thumbnail") 162 | 163 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 164 | 165 | } 166 | 167 | } 168 | } 169 | 170 | @objc(getPhoto:) func getPhoto(_ command: CDVInvokedUrlCommand) { 171 | concurrentQueue.async { 172 | 173 | if !PhotoLibraryService.hasPermission() { 174 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 175 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 176 | return 177 | } 178 | 179 | let service = PhotoLibraryService.instance 180 | 181 | let photoId = command.arguments[0] as! String 182 | 183 | service.getPhoto(photoId) { (imageData) in 184 | 185 | let pluginResult = imageData != nil ? 186 | CDVPluginResult( 187 | status: CDVCommandStatus_OK, 188 | messageAsMultipart: [imageData!.data, imageData!.mimeType]) 189 | : 190 | CDVPluginResult( 191 | status: CDVCommandStatus_ERROR, 192 | messageAs: "Could not fetch the image") 193 | 194 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 195 | } 196 | 197 | } 198 | } 199 | 200 | @objc(getLibraryItem:) func getLibraryItem(_ command: CDVInvokedUrlCommand) { 201 | concurrentQueue.async { 202 | 203 | if !PhotoLibraryService.hasPermission() { 204 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 205 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 206 | return 207 | } 208 | 209 | let service = PhotoLibraryService.instance 210 | let info = command.arguments[0] as! NSDictionary 211 | let mime_type = info["mimeType"] as! String 212 | service.getLibraryItem(info["id"] as! String, mimeType: mime_type, completion: { (base64: String?) in 213 | self.returnPictureData(callbackId: command.callbackId, base64: base64, mimeType: mime_type) 214 | }) 215 | } 216 | } 217 | 218 | 219 | func returnPictureData(callbackId : String, base64: String?, mimeType: String?) { 220 | let pluginResult = (base64 != nil) ? 221 | CDVPluginResult( 222 | status: CDVCommandStatus_OK, 223 | messageAsMultipart: [base64!, mimeType!]) 224 | : 225 | CDVPluginResult( 226 | status: CDVCommandStatus_ERROR, 227 | messageAs: "Could not fetch the image") 228 | 229 | self.commandDelegate!.send(pluginResult, callbackId: callbackId) 230 | 231 | } 232 | 233 | 234 | @objc(stopCaching:) func stopCaching(_ command: CDVInvokedUrlCommand) { 235 | 236 | let service = PhotoLibraryService.instance 237 | 238 | service.stopCaching() 239 | 240 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) 241 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 242 | 243 | } 244 | 245 | @objc(requestAuthorization:) func requestAuthorization(_ command: CDVInvokedUrlCommand) { 246 | 247 | let service = PhotoLibraryService.instance 248 | 249 | service.requestAuthorization({ 250 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) 251 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 252 | }, failure: { (err) in 253 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: err) 254 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 255 | }) 256 | 257 | } 258 | 259 | @objc(saveImage:) func saveImage(_ command: CDVInvokedUrlCommand) { 260 | concurrentQueue.async { 261 | 262 | if !PhotoLibraryService.hasPermission() { 263 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 264 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 265 | return 266 | } 267 | 268 | let service = PhotoLibraryService.instance 269 | 270 | let url = command.arguments[0] as! String 271 | let album = command.arguments[1] as! String 272 | 273 | service.saveImage(url, album: album) { (libraryItem: NSDictionary?, error: String?) in 274 | if (error != nil) { 275 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: error) 276 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 277 | } else { 278 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: libraryItem as! [String: AnyObject]?) 279 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 280 | } 281 | } 282 | 283 | } 284 | } 285 | 286 | @objc(saveVideo:) func saveVideo(_ command: CDVInvokedUrlCommand) { 287 | concurrentQueue.async { 288 | 289 | if !PhotoLibraryService.hasPermission() { 290 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) 291 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 292 | return 293 | } 294 | 295 | let service = PhotoLibraryService.instance 296 | 297 | let url = command.arguments[0] as! String 298 | let album = command.arguments[1] as! String 299 | 300 | service.saveVideo(url, album: album) { (assetId: String?, error: String?) in 301 | if (error != nil) { 302 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: error) 303 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) 304 | } else { 305 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: assetId) 306 | self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) 307 | } 308 | } 309 | 310 | } 311 | } 312 | 313 | } 314 | -------------------------------------------------------------------------------- /src/ios/PhotoLibraryGetLibraryOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PhotoLibraryGetLibraryOptions { 4 | let thumbnailWidth: Int 5 | let thumbnailHeight: Int 6 | let itemsInChunk: Int 7 | let chunkTimeSec: Double 8 | let useOriginalFileNames: Bool 9 | let includeImages: Bool 10 | let includeAlbumData: Bool 11 | let includeCloudData: Bool 12 | let includeVideos: Bool 13 | let maxItems: Int 14 | } 15 | -------------------------------------------------------------------------------- /src/ios/PhotoLibraryProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc(PhotoLibraryProtocol) class PhotoLibraryProtocol : CDVURLProtocol { 4 | 5 | static let PHOTO_LIBRARY_PROTOCOL = "cdvphotolibrary" 6 | static let DEFAULT_WIDTH = "512" 7 | static let DEFAULT_HEIGHT = "384" 8 | static let DEFAULT_QUALITY = "0.5" 9 | 10 | lazy var concurrentQueue: OperationQueue = { 11 | var queue = OperationQueue() 12 | queue.name = "PhotoLibrary Protocol Queue" 13 | queue.qualityOfService = .background 14 | queue.maxConcurrentOperationCount = 4 15 | return queue 16 | }() 17 | 18 | override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { 19 | super.init(request: request, cachedResponse: cachedResponse, client: client) 20 | } 21 | 22 | override class func canInit(with request: URLRequest) -> Bool { 23 | let scheme = request.url?.scheme 24 | 25 | if scheme?.lowercased() == PHOTO_LIBRARY_PROTOCOL { 26 | return true 27 | } 28 | 29 | return false 30 | } 31 | 32 | override func startLoading() { 33 | 34 | if let url = self.request.url { 35 | if url.path == "" { 36 | 37 | let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) 38 | let queryItems = urlComponents?.queryItems 39 | 40 | // Errors are 404 as android plugin only supports returning 404 41 | 42 | let photoId = queryItems?.filter({$0.name == "photoId"}).first?.value 43 | if photoId == nil { 44 | self.sendErrorResponse(404, error: "Missing 'photoId' query parameter") 45 | return 46 | } 47 | 48 | if !PhotoLibraryService.hasPermission() { 49 | self.sendErrorResponse(404, error: PhotoLibraryService.PERMISSION_ERROR) 50 | return 51 | } 52 | 53 | let service = PhotoLibraryService.instance 54 | 55 | if url.host?.lowercased() == "thumbnail" { 56 | 57 | let widthStr = queryItems?.filter({$0.name == "width"}).first?.value ?? PhotoLibraryProtocol.DEFAULT_WIDTH 58 | let width = Int(widthStr) 59 | if width == nil { 60 | self.sendErrorResponse(404, error: "Incorrect 'width' query parameter") 61 | return 62 | } 63 | 64 | let heightStr = queryItems?.filter({$0.name == "height"}).first?.value ?? PhotoLibraryProtocol.DEFAULT_HEIGHT 65 | let height = Int(heightStr) 66 | if height == nil { 67 | self.sendErrorResponse(404, error: "Incorrect 'height' query parameter") 68 | return 69 | } 70 | 71 | let qualityStr = queryItems?.filter({$0.name == "quality"}).first?.value ?? PhotoLibraryProtocol.DEFAULT_QUALITY 72 | let quality = Float(qualityStr) 73 | if quality == nil { 74 | self.sendErrorResponse(404, error: "Incorrect 'quality' query parameter") 75 | return 76 | } 77 | 78 | concurrentQueue.addOperation { 79 | service.getThumbnail(photoId!, thumbnailWidth: width!, thumbnailHeight: height!, quality: quality!) { (imageData) in 80 | if (imageData == nil) { 81 | self.sendErrorResponse(404, error: PhotoLibraryService.PERMISSION_ERROR) 82 | return 83 | } 84 | self.sendResponseWithResponseCode(200, data: imageData!.data, mimeType: imageData!.mimeType) 85 | } 86 | } 87 | 88 | return 89 | 90 | } else if url.host?.lowercased() == "photo" { 91 | 92 | concurrentQueue.addOperation { 93 | service.getPhoto(photoId!) { (imageData) in 94 | if (imageData == nil) { 95 | self.sendErrorResponse(404, error: PhotoLibraryService.PERMISSION_ERROR) 96 | return 97 | } 98 | self.sendResponseWithResponseCode(200, data: imageData!.data, mimeType: imageData!.mimeType) 99 | } 100 | } 101 | 102 | return 103 | 104 | } else if url.host?.lowercased() == "video" { 105 | 106 | concurrentQueue.addOperation { 107 | service.getVideo(photoId!) { (videoData) in 108 | if (videoData == nil) { 109 | self.sendErrorResponse(404, error: PhotoLibraryService.PERMISSION_ERROR) 110 | return 111 | } 112 | self.sendResponseWithResponseCode(200, data: videoData!.data, mimeType: videoData!.mimeType) 113 | } 114 | } 115 | 116 | return 117 | 118 | } 119 | 120 | } 121 | } 122 | 123 | let body = "URI not supported by PhotoLibrary" 124 | self.sendResponseWithResponseCode(404, data: body.data(using: String.Encoding.ascii), mimeType: nil) 125 | 126 | } 127 | 128 | 129 | override func stopLoading() { 130 | // do any cleanup here 131 | } 132 | 133 | fileprivate func sendErrorResponse(_ statusCode: Int, error: String) { 134 | self.sendResponseWithResponseCode(statusCode, data: error.data(using: String.Encoding.ascii), mimeType: nil) 135 | } 136 | 137 | // Cannot use sendResponseWithResponseCode from CDVURLProtocol, so copied one here. 138 | fileprivate func sendResponseWithResponseCode(_ statusCode: Int, data: Data?, mimeType: String?) { 139 | 140 | var mimeType = mimeType 141 | if mimeType == nil { 142 | mimeType = "text/plain" 143 | } 144 | 145 | let encodingName: String? = mimeType == "text/plain" ? "UTF-8" : nil 146 | 147 | let response: CDVHTTPURLResponse = CDVHTTPURLResponse(url: self.request.url!, mimeType: mimeType, expectedContentLength: data?.count ?? 0, textEncodingName: encodingName) 148 | response.statusCode = statusCode 149 | 150 | self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: URLCache.StoragePolicy.notAllowed) 151 | 152 | if (data != nil) { 153 | self.client?.urlProtocol(self, didLoad: data!) 154 | } 155 | self.client?.urlProtocolDidFinishLoading(self) 156 | 157 | } 158 | 159 | class CDVHTTPURLResponse: HTTPURLResponse { 160 | var _statusCode: Int = 0 161 | override var statusCode: Int { 162 | get { 163 | return _statusCode 164 | } 165 | set { 166 | _statusCode = newValue 167 | } 168 | } 169 | 170 | override var allHeaderFields: [AnyHashable: Any] { 171 | get { 172 | return [:] 173 | } 174 | } 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jasmine": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | Automatic and manual tests for cordova-plugin-photo-library. 2 | Tests are written with [cordova-plugin-test-framework](https://github.com/apache/cordova-plugin-test-framework). Automatic tests run with [jasmine](https://jasmine.github.io/). 3 | 4 | # Running tests 5 | 6 | Please use [cordova-plugin-photo-library-tester](https://github.com/terikon/cordova-plugin-photo-library-tester) to run the tests. 7 | 8 | # Shims 9 | 10 | Tests plugin adds es5, es6 and es7 shims. If needed, update esX-shim.min.js files from node_modules/esX-shim folder. 11 | -------------------------------------------------------------------------------- /tests/android/wait-for-emulator-boot.sh: -------------------------------------------------------------------------------- 1 | adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' 2 | -------------------------------------------------------------------------------- /tests/blueimp-canvastoblob/js/canvas-to-blob.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;h0||-1)*Math.floor(Math.abs(r))}return r},ToPrimitive:function ToPrimitive(t){var r,e,n;if(J(t)){return t}e=t.valueOf;if(D(e)){r=e.call(t);if(J(r)){return r}}n=t.toString;if(D(n)){r=n.call(t);if(J(r)){return r}}throw new TypeError},ToObject:function(t){if(t==null){throw new TypeError("can't convert "+t+" to object")}return e(t)},ToUint32:function ToUint32(t){return t>>>0}};var z=function Empty(){};$(a,{bind:function bind(t){var r=this;if(!D(r)){throw new TypeError("Function.prototype.bind called on incompatible "+r)}var n=s.call(arguments,1);var a;var o=function(){if(this instanceof a){var i=g.call(r,this,p.call(n,s.call(arguments)));if(e(i)===i){return i}return this}else{return g.call(r,t,p.call(n,s.call(arguments)))}};var f=w(0,r.length-n.length);var u=[];for(var l=0;l1){a=arguments[1]}if(!D(t)){throw new TypeError("Array.prototype.forEach callback must be a function")}while(++n1){o=arguments[1]}if(!D(r)){throw new TypeError("Array.prototype.map callback must be a function")}for(var f=0;f1){o=arguments[1]}if(!D(t)){throw new TypeError("Array.prototype.filter callback must be a function")}for(var f=0;f1){i=arguments[1]}if(!D(t)){throw new TypeError("Array.prototype.every callback must be a function")}for(var a=0;a1){i=arguments[1]}if(!D(t)){throw new TypeError("Array.prototype.some callback must be a function")}for(var a=0;a=2){a=arguments[1]}else{do{if(i in e){a=e[i++];break}if(++i>=n){throw new TypeError("reduce of empty array with no initial value")}}while(true)}for(;i=2){i=arguments[1]}else{do{if(a in e){i=e[a--];break}if(--a<0){throw new TypeError("reduceRight of empty array with no initial value")}}while(true)}if(a<0){return i}do{if(a in e){i=t(i,e[a],a,r)}}while(a--);return i}},!at);var ot=r.indexOf&&[0,1].indexOf(1,2)!==-1;$(r,{indexOf:function indexOf(t){var r=et&&C(this)?X(this,""):Z.ToObject(this);var e=Z.ToUint32(r.length);if(e===0){return-1}var n=0;if(arguments.length>1){n=Z.ToInteger(arguments[1])}n=n>=0?n:w(0,e+n);for(;n1){n=b(n,Z.ToInteger(arguments[1]))}n=n>=0?n:e-Math.abs(n);for(;n>=0;n--){if(n in r&&t===r[n]){return n}}return-1}},ft);var ut=function(){var t=[1,2];var r=t.splice();return t.length===2&&_(r)&&r.length===0}();$(r,{splice:function splice(t,r){if(arguments.length===0){return[]}else{return c.apply(this,arguments)}}},!ut);var lt=function(){var t={};r.splice.call(t,0,0,1);return t.length===1}();$(r,{splice:function splice(t,r){if(arguments.length===0){return[]}var e=arguments;this.length=w(Z.ToInteger(this.length),0);if(arguments.length>0&&typeof r!=="number"){e=H(arguments);if(e.length<2){K(e,this.length-t)}else{e[1]=Z.ToInteger(r)}}return c.apply(this,e)}},!lt);var st=function(){var r=new t(1e5);r[8]="x";r.splice(1,1);return r.indexOf("x")===7}();var ct=function(){var t=256;var r=[];r[t]="a";r.splice(t+1,0,"b");return r[t]==="a"}();$(r,{splice:function splice(t,r){var e=Z.ToObject(this);var n=[];var i=Z.ToUint32(e.length);var a=Z.ToInteger(t);var f=a<0?w(i+a,0):b(a,i);var u=b(w(Z.ToInteger(r),0),i-f);var l=0;var s;while(ly){delete e[l-1];l-=1}}else if(v>u){l=i-u;while(l>f){s=o(l+u-1);h=o(l+v-1);if(G(e,s)){e[h]=e[s]}else{delete e[h]}l-=1}}l=f;for(var d=0;d=0&&!_(t)&&D(t.callee)};var Ct=Ft(arguments)?Ft:Nt;$(e,{keys:function keys(t){var r=D(t);var e=Ct(t);var n=t!==null&&typeof t==="object";var i=n&&C(t);if(!n&&!r&&!e){throw new TypeError("Object.keys called on a non-object")}var a=[];var f=St&&r;if(i&&xt||e){for(var u=0;u11){return t+1}return t},getMonth:function getMonth(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Bt(this);var r=Ht(this);if(t<0&&r>11){return 0}return r},getDate:function getDate(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Bt(this);var r=Ht(this);var e=Wt(this);if(t<0&&r>11){if(r===12){return e}var n=nr(0,t+1);return n-e+1}return e},getUTCFullYear:function getUTCFullYear(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Lt(this);if(t<0&&Xt(this)>11){return t+1}return t},getUTCMonth:function getUTCMonth(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Lt(this);var r=Xt(this);if(t<0&&r>11){return 0}return r},getUTCDate:function getUTCDate(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Lt(this);var r=Xt(this);var e=qt(this);if(t<0&&r>11){if(r===12){return e}var n=nr(0,t+1);return n-e+1}return e}},Pt);$(Date.prototype,{toUTCString:function toUTCString(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=Kt(this);var r=qt(this);var e=Xt(this);var n=Lt(this);var i=Qt(this);var a=Vt(this);var o=_t(this);return rr[t]+", "+(r<10?"0"+r:r)+" "+er[e]+" "+n+" "+(i<10?"0"+i:i)+":"+(a<10?"0"+a:a)+":"+(o<10?"0"+o:o)+" GMT"}},Pt||Yt);$(Date.prototype,{toDateString:function toDateString(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();return rr[t]+" "+er[e]+" "+(r<10?"0"+r:r)+" "+n}},Pt||Zt);if(Pt||zt){Date.prototype.toString=function toString(){if(!this||!(this instanceof Date)){throw new TypeError("this is not a Date object.")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();var i=this.getHours();var a=this.getMinutes();var o=this.getSeconds();var f=this.getTimezoneOffset();var u=Math.floor(Math.abs(f)/60);var l=Math.floor(Math.abs(f)%60);return rr[t]+" "+er[e]+" "+(r<10?"0"+r:r)+" "+n+" "+(i<10?"0"+i:i)+":"+(a<10?"0"+a:a)+":"+(o<10?"0"+o:o)+" GMT"+(f>0?"-":"+")+(u<10?"0"+u:u)+(l<10?"0"+l:l)};if(P){e.defineProperty(Date.prototype,"toString",{configurable:true,enumerable:false,writable:true})}}var ir=-621987552e5;var ar="-000001";var or=Date.prototype.toISOString&&new Date(ir).toISOString().indexOf(ar)===-1;var fr=Date.prototype.toISOString&&new Date(-1).toISOString()!=="1969-12-31T23:59:59.999Z";var ur=d.bind(Date.prototype.getTime);$(Date.prototype,{toISOString:function toISOString(){if(!isFinite(this)||!isFinite(ur(this))){throw new RangeError("Date.prototype.toISOString called on non-finite value.")}var t=Lt(this);var r=Xt(this);t+=Math.floor(r/12);r=(r%12+12)%12;var e=[r+1,qt(this),Qt(this),Vt(this),_t(this)];t=(t<0?"-":t>9999?"+":"")+L("00000"+Math.abs(t),0<=t&&t<=9999?-4:-6);for(var n=0;n=7&&l>hr){var p=Math.floor(l/hr)*hr;var y=Math.floor(p/1e3);v+=y;h-=y*1e3}c=s===1&&o(e)===e?new t(r.parse(e)):s>=7?new t(e,n,i,a,f,v,h):s>=6?new t(e,n,i,a,f,v):s>=5?new t(e,n,i,a,f):s>=4?new t(e,n,i,a):s>=3?new t(e,n,i):s>=2?new t(e,n):s>=1?new t(e instanceof t?+e:e):new t}else{c=t.apply(this,arguments)}if(!J(c)){$(c,{constructor:r},true)}return c};var e=new RegExp("^"+"(\\d{4}|[+-]\\d{6})"+"(?:-(\\d{2})"+"(?:-(\\d{2})"+"(?:"+"T(\\d{2})"+":(\\d{2})"+"(?:"+":(\\d{2})"+"(?:(\\.\\d{1,}))?"+")?"+"("+"Z|"+"(?:"+"([-+])"+"(\\d{2})"+":(\\d{2})"+")"+")?)?)?)?"+"$");var n=[0,31,59,90,120,151,181,212,243,273,304,334,365];var i=function dayFromMonth(t,r){var e=r>1?1:0;return n[r]+Math.floor((t-1969+e)/4)-Math.floor((t-1901+e)/100)+Math.floor((t-1601+e)/400)+365*(t-1970)};var a=function toUTC(r){var e=0;var n=r;if(pr&&n>hr){var i=Math.floor(n/hr)*hr;var a=Math.floor(i/1e3);e+=a;n-=a*1e3}return u(new t(1970,0,1,0,0,e,n))};for(var f in t){if(G(t,f)){r[f]=t[f]}}$(r,{now:t.now,UTC:t.UTC},true);r.prototype=t.prototype;$(r.prototype,{constructor:r},true);var l=function parse(r){var n=e.exec(r);if(n){var o=u(n[1]),f=u(n[2]||1)-1,l=u(n[3]||1)-1,s=u(n[4]||0),c=u(n[5]||0),v=u(n[6]||0),h=Math.floor(u(n[7]||0)*1e3),p=Boolean(n[4]&&!n[8]),y=n[9]==="-"?1:-1,d=u(n[10]||0),g=u(n[11]||0),w;var b=c>0||v>0||h>0;if(s<(b?24:25)&&c<60&&v<60&&h<1e3&&f>-1&&f<12&&d<24&&g<60&&l>-1&&l=0){e+=dr.data[r];dr.data[r]=Math.floor(e/t);e=e%t*dr.base}},numToString:function numToString(){var t=dr.size;var r="";while(--t>=0){if(r!==""||t===0||dr.data[t]!==0){var e=o(dr.data[t]);if(r===""){r=e}else{r+=L("0000000",0,7-e.length)+e}}}return r},pow:function pow(t,r,e){return r===0?e:r%2===1?pow(t,r-1,e*t):pow(t*t,r/2,e)},log:function log(t){var r=0;var e=t;while(e>=4096){r+=12;e/=4096}while(e>=2){r+=1;e/=2}return r}};var gr=function toFixed(t){var r,e,n,i,a,f,l,s;r=u(t);r=Y(r)?0:Math.floor(r);if(r<0||r>20){throw new RangeError("Number.toFixed called with invalid number of decimals")}e=u(this);if(Y(e)){return"NaN"}if(e<=-1e21||e>=1e21){return o(e)}n="";if(e<0){n="-";e=-e}i="0";if(e>1e-21){a=dr.log(e*dr.pow(2,69,1))-69;f=a<0?e*dr.pow(2,-a,1):e/dr.pow(2,a,1);f*=4503599627370496;a=52-a;if(a>0){dr.multiply(0,f);l=r;while(l>=7){dr.multiply(1e7,0);l-=7}dr.multiply(dr.pow(10,l,1),0);l=a-1;while(l>=23){dr.divide(1<<23);l-=23}dr.divide(1<0){s=i.length;if(s<=r){i=n+L("0.0000000000000000000",0,r-s+2)+i}else{i=n+L(i,0,s-r)+"."+L(i,s-r)}}else{i=n+i}return i};$(l,{toFixed:gr},yr);var wr=function(){try{return 1..toPrecision(undefined)==="1"}catch(t){return true}}();var br=l.toPrecision;$(l,{toPrecision:function toPrecision(t){return typeof t==="undefined"?br.call(this):br.call(this,t)}},wr);if("ab".split(/(?:ab)*/).length!==2||".".split(/(.?)(.?)/).length!==4||"tesst".split(/(s)*/)[1]==="t"||"test".split(/(?:)/,-1).length!==4||"".split(/.?/).length||".".split(/()()/).length>1){(function(){var t=typeof/()??/.exec("")[1]==="undefined";var r=Math.pow(2,32)-1;f.split=function(e,n){var i=String(this);if(typeof e==="undefined"&&n===0){return[]}if(!M(e)){return X(this,e,n)}var a=[];var o=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),f=0,u,l,s,c;var h=new RegExp(e.source,o+"g");if(!t){u=new RegExp("^"+h.source+"$(?!\\s)",o)}var p=typeof n==="undefined"?r:Z.ToUint32(n);l=h.exec(i);while(l){s=l.index+l[0].length;if(s>f){K(a,L(i,f,l.index));if(!t&&l.length>1){l[0].replace(u,function(){for(var t=1;t1&&l.index=p){break}}if(h.lastIndex===l.index){h.lastIndex++}l=h.exec(i)}if(f===i.length){if(c||!h.test("")){K(a,"")}}else{K(a,L(i,f))}return a.length>p?H(a,0,p):a}})()}else if("0".split(void 0,0).length){f.split=function split(t,r){if(typeof t==="undefined"&&r===0){return[]}return X(this,t,r)}}var Tr=f.replace;var mr=function(){var t=[];"x".replace(/x(.)?/g,function(r,e){K(t,e)});return t.length===1&&typeof t[0]==="undefined"}();if(!mr){f.replace=function replace(t,r){var e=D(r);var n=M(t)&&/\)[*?]/.test(t.source);if(!e||!n){return Tr.call(this,t,r)}else{var i=function(e){var n=arguments.length;var i=t.lastIndex;t.lastIndex=0;var a=t.exec(e)||[];t.lastIndex=i;K(a,arguments[n-2],arguments[n-1]);return r.apply(this,a)};return Tr.call(this,t,i)}}}var Dr=f.substr;var Sr="".substr&&"0b".substr(-1)!=="b";$(f,{substr:function substr(t,r){var e=t;if(t<0){e=w(this.length+t,0)}return Dr.call(this,e,r)}},Sr);var xr=" \n\x0B\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003"+"\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028"+"\u2029\ufeff";var Or="\u200b";var jr="["+xr+"]";var Er=new RegExp("^"+jr+jr+"*");var Ir=new RegExp(jr+jr+"*$");var Mr=f.trim&&(xr.trim()||!Or.trim());$(f,{trim:function trim(){if(typeof this==="undefined"||this===null){throw new TypeError("can't convert "+this+" to object")}return o(this).replace(Er,"").replace(Ir,"")}},Mr);var Ur=d.bind(String.prototype.trim);var Fr=f.lastIndexOf&&"abc\u3042\u3044".lastIndexOf("\u3042\u3044",2)!==-1;$(f,{lastIndexOf:function lastIndexOf(t){if(typeof this==="undefined"||this===null){throw new TypeError("can't convert "+this+" to object")}var r=o(this);var e=o(t);var n=arguments.length>1?u(arguments[1]):NaN;var i=Y(n)?Infinity:Z.ToInteger(n);var a=b(w(i,0),r.length);var f=e.length;var l=a+f;while(l>0){l=w(0,l-f);var s=q(L(r,l,a+f),e);if(s!==-1){return l+s}}return-1}},Fr);var Nr=f.lastIndexOf;$(f,{lastIndexOf:function lastIndexOf(t){return Nr.apply(this,arguments)}},f.lastIndexOf.length!==1);if(parseInt(xr+"08")!==8||parseInt(xr+"0x16")!==22){parseInt=function(t){var r=/^[\-+]?0[xX]/;return function parseInt(e,n){var i=Ur(String(e));var a=u(n)||(r.test(i)?16:10);return t(i,a)}}(parseInt)}if(1/parseFloat("-0")!==-Infinity){parseFloat=function(t){return function parseFloat(r){var e=Ur(String(r));var n=t(e);return n===0&&L(e,0,1)==="-"?-0:n}}(parseFloat)}if(String(new RangeError("test"))!=="RangeError: test"){var Cr=function toString(){if(typeof this==="undefined"||this===null){throw new TypeError("can't convert "+this+" to object")}var t=this.name;if(typeof t==="undefined"){t="Error"}else if(typeof t!=="string"){t=o(t)}var r=this.message;if(typeof r==="undefined"){r=""}else if(typeof r!=="string"){r=o(r)}if(!t){return r}if(!r){return t}return t+": "+r};Error.prototype.toString=Cr}if(P){var kr=function(t,r){if(Q(t,r)){var e=Object.getOwnPropertyDescriptor(t,r);if(e.configurable){e.enumerable=false;Object.defineProperty(t,r,e)}}};kr(Error.prototype,"message");if(Error.prototype.message!==""){Error.prototype.message=""}kr(Error.prototype,"name")}if(String(/a/gim)!=="/a/gim"){var Ar=function toString(){var t="/"+this.source+"/";if(this.global){t+="g"}if(this.ignoreCase){t+="i"}if(this.multiline){t+="m"}return t};RegExp.prototype.toString=Ar}}); 7 | //# sourceMappingURL=es5-shim.map 8 | -------------------------------------------------------------------------------- /tests/es7-shim/dist/es7-shim.min.js: -------------------------------------------------------------------------------- 1 | (function t(e,r,n){function i(p,u){if(!r[p]){if(!e[p]){var a=typeof require=="function"&&require;if(!u&&a)return a(p,!0);if(o)return o(p,!0);var l=new Error("Cannot find module '"+p+"'");throw l.code="MODULE_NOT_FOUND",l}var s=r[p]={exports:{}};e[p][0].call(s.exports,function(t){var r=e[p][1][t];return i(r?r:t)},s,s.exports,t,e,r,n)}return r[p].exports}var o=typeof require=="function"&&require;for(var p=0;p1?n.ToInteger(arguments[1]):0;if(p&&!i(e)&&o(r)&&typeof e!=="undefined"){return p.apply(this,arguments)>-1}var u=n.ToObject(this);var a=n.ToLength(u.length);if(a===0){return false}var l=r>=0?r:Math.max(0,a+r);while(l2?arguments[2]:{};var p=n(e);if(o){p=p.concat(Object.getOwnPropertySymbols(e))}i(p,function(n){s(t,n,e[n],r[n])})};c.supportsDescriptors=!!l;e.exports=c},{foreach:26,"object-keys":33}],16:[function(t,e,r){"use strict";var n=Number.isNaN||function(t){return t!==t};var i=t("./helpers/isFinite");var o=t("./helpers/sign");var p=t("./helpers/mod");var u=t("is-callable");var a=t("es-to-primitive/es5");var l={ToPrimitive:a,ToBoolean:function t(e){return Boolean(e)},ToNumber:function t(e){return Number(e)},ToInteger:function t(e){var r=this.ToNumber(e);if(n(r)){return 0}if(r===0||!i(r)){return r}return o(r)*Math.floor(Math.abs(r))},ToInt32:function t(e){return this.ToNumber(e)>>0},ToUint32:function t(e){return this.ToNumber(e)>>>0},ToUint16:function t(e){var r=this.ToNumber(e);if(n(r)||r===0||!i(r)){return 0}var u=o(r)*Math.floor(Math.abs(r));return p(u,65536)},ToString:function t(e){return String(e)},ToObject:function t(e){this.CheckObjectCoercible(e);return Object(e)},CheckObjectCoercible:function t(e,r){if(e==null){throw new TypeError(r||"Cannot call method on "+e)}return e},IsCallable:u,SameValue:function t(e,r){if(e===r){if(e===0){return 1/e===1/r}return true}return n(e)&&n(r)}};e.exports=l},{"./helpers/isFinite":19,"./helpers/mod":21,"./helpers/sign":22,"es-to-primitive/es5":23,"is-callable":29}],17:[function(t,e,r){"use strict";var n=Object.prototype.toString;var i=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol";var o=i?Symbol.prototype.toString:n;var p=Number.isNaN||function(t){return t!==t};var u=t("./helpers/isFinite");var a=Number.MAX_SAFE_INTEGER||Math.pow(2,53)-1;var l=t("./helpers/assign");var s=t("./helpers/sign");var c=t("./helpers/mod");var f=t("./helpers/isPrimitive");var y=t("es-to-primitive/es6");var v=parseInt;var m=t("function-bind");var b=m.call(Function.call,String.prototype.slice);var d=m.call(Function.call,RegExp.prototype.test,/^0b[01]+$/i);var h=m.call(Function.call,RegExp.prototype.test,/^0o[0-7]+$/i);var g=["\x85","\u200b","\ufffe"].join("");var S=new RegExp("["+g+"]","g");var j=m.call(Function.call,RegExp.prototype.test,S);var x=/^[\-\+]0x[0-9a-f]+$/i;var O=m.call(Function.call,RegExp.prototype.test,x);var w=["\t\n\v\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003","\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028","\u2029\ufeff"].join("");var T=new RegExp("(^["+w+"]+)|(["+w+"]+$)","g");var F=m.call(Function.call,String.prototype.replace);var P=function(t){return F(t,T,"")};var E=t("./es5");var N=t("is-regex");var A=l(l({},E),{Call:function t(e,r){var n=arguments.length>2?arguments[2]:[];if(!this.IsCallable(e)){throw new TypeError(e+" is not a function")}return e.apply(r,n)},ToPrimitive:y,ToNumber:function t(e){var r=f(e)?e:y(e,"number");if(typeof r==="symbol"){throw new TypeError("Cannot convert a Symbol value to a number")}if(typeof r==="string"){if(d(r)){return this.ToNumber(v(b(r,2),2))}else if(h(r)){return this.ToNumber(v(b(r,2),8))}else if(j(r)||O(r)){return NaN}else{var n=P(r);if(n!==r){return this.ToNumber(n)}}}return Number(r)},ToInt16:function t(e){var r=this.ToUint16(e);return r>=32768?r-65536:r},ToInt8:function t(e){var r=this.ToUint8(e);return r>=128?r-256:r},ToUint8:function t(e){var r=this.ToNumber(e);if(p(r)||r===0||!u(r)){return 0}var n=s(r)*Math.floor(Math.abs(r));return c(n,256)},ToUint8Clamp:function t(e){var r=this.ToNumber(e);if(p(r)||r<=0){return 0}if(r>=255){return 255}var n=Math.floor(e);if(n+.5a){return a}return r},CanonicalNumericIndexString:function t(e){if(n.call(e)!=="[object String]"){throw new TypeError("must be a string")}if(e==="-0"){return-0}var r=this.ToNumber(e);if(this.SameValue(this.ToString(r),e)){return r}},RequireObjectCoercible:E.CheckObjectCoercible,IsArray:Array.isArray||function t(e){return n.call(e)==="[object Array]"},IsConstructor:function t(e){return this.IsCallable(e)},IsExtensible:function t(e){if(!Object.preventExtensions){return true}if(f(e)){return false}return Object.isExtensible(e)},IsInteger:function t(e){if(typeof e!=="number"||p(e)||!u(e)){return false}var r=Math.abs(e);return Math.floor(r)===r},IsPropertyKey:function t(e){return typeof e==="string"||typeof e==="symbol"},IsRegExp:function t(e){if(!e||typeof e!=="object"){return false}if(i){var r=RegExp[Symbol.match];if(typeof r!=="undefined"){return E.ToBoolean(r)}}return N(e)},SameValueZero:function t(e,r){return e===r||p(e)&&p(r)}});delete A.CheckObjectCoercible;e.exports=A},{"./es5":16,"./helpers/assign":18,"./helpers/isFinite":19,"./helpers/isPrimitive":20,"./helpers/mod":21,"./helpers/sign":22,"es-to-primitive/es6":24,"function-bind":28,"is-regex":31}],18:[function(t,e,r){var n=Object.prototype.hasOwnProperty;e.exports=Object.assign||function t(e,r){for(var i in r){if(n.call(r,i)){e[i]=r[i]}}return e}},{}],19:[function(t,e,r){var n=Number.isNaN||function(t){return t!==t};e.exports=Number.isFinite||function(t){return typeof t==="number"&&!n(t)&&t!==Infinity&&t!==-Infinity}},{}],20:[function(t,e,r){e.exports=function t(e){return e===null||typeof e!=="function"&&typeof e!=="object"}},{}],21:[function(t,e,r){e.exports=function t(e,r){var n=e%r;return Math.floor(n>=0?n:n+r)}},{}],22:[function(t,e,r){e.exports=function t(e){return e>=0?1:-1}},{}],23:[function(t,e,r){"use strict";var n=Object.prototype.toString;var i=t("./helpers/isPrimitive");var o=t("is-callable");var p={"[[DefaultValue]]":function(t,e){var r=e||(n.call(t)==="[object Date]"?String:Number);if(r===String||r===Number){var p=r===String?["toString","valueOf"]:["valueOf","toString"];var u,a;for(a=0;a1){if(r===String){o="string"}else if(r===Number){o="number"}}var s;if(n){if(Symbol.toPrimitive){s=l(e,Symbol.toPrimitive)}else if(u(e)){s=Symbol.prototype.valueOf}}if(typeof s!=="undefined"){var c=s.call(e,o);if(i(c)){return c}throw new TypeError("unable to convert exotic object to primitive")}if(o==="default"&&(p(e)||u(e))){o="string"}return a(e,o==="default"?"number":o)}},{"./helpers/isPrimitive":25,"is-callable":29,"is-date-object":30,"is-symbol":32}],25:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],26:[function(t,e,r){var n=Object.prototype.hasOwnProperty;var i=Object.prototype.toString;e.exports=function t(e,r,o){if(i.call(r)!=="[object Function]"){throw new TypeError("iterator must be a function")}var p=e.length;if(p===+p){for(var u=0;u0&&!n.call(e,0)){for(var m=0;m0){for(var b=0;b=0&&n.call(e.callee)==="[object Function]"}return i}},{}],35:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return Array.prototype.includes||n}},{"./implementation":13}],36:[function(t,e,r){"use strict";var n=t("define-properties");var i=t("./polyfill");e.exports=function t(){var e=i();if(Array.prototype.includes!==e){n(Array.prototype,{includes:e})}return e}},{"./polyfill":35,"define-properties":15}],37:[function(t,e,r){"use strict";var n=t("es-abstract/es7");var i=t("has");var o=t("function-bind");var p=o.call(Function.call,Object.prototype.propertyIsEnumerable);e.exports=function t(e){var r=n.RequireObjectCoercible(e);var o=[];for(var u in r){if(i(r,u)&&p(r,u)){o.push([u,r[u]])}}return o}},{"es-abstract/es7":42,"function-bind":53,has:54}],38:[function(t,e,r){"use strict";var n=t("define-properties");var i=t("./implementation");var o=t("./polyfill");var p=t("./shim");n(i,{getPolyfill:o,implementation:i,shim:p});e.exports=i},{"./implementation":37,"./polyfill":61,"./shim":62,"define-properties":39}],39:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:51,"object-keys":59}],40:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":44,"./helpers/mod":46,"./helpers/sign":47,dup:16,"es-to-primitive/es5":48,"is-callable":55}],41:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":40,"./helpers/assign":43,"./helpers/isFinite":44,"./helpers/isPrimitive":45,"./helpers/mod":46,"./helpers/sign":47,dup:17,"es-to-primitive/es6":49,"function-bind":53,"is-regex":57}],42:[function(t,e,r){"use strict";var n=t("./es6");var i=t("./helpers/assign");var o=i(n,{SameValueNonNumber:function t(e,r){if(typeof e==="number"||typeof e!==typeof r){throw new TypeError("SameValueNonNumber requires two non-number values of the same type.")}return this.SameValue(e,r)}});e.exports=o},{"./es6":41,"./helpers/assign":43}],43:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],44:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],45:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],46:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],47:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],48:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":50,dup:23,"is-callable":55}],49:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":50,dup:24,"is-callable":55,"is-date-object":56,"is-symbol":58}],50:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],51:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],52:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],53:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":52,dup:28}],54:[function(t,e,r){var n=t("function-bind");e.exports=n.call(Function.call,Object.prototype.hasOwnProperty)},{"function-bind":53}],55:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],56:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],57:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],58:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],59:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":60,dup:33}],60:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],61:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return typeof Object.entries==="function"?Object.entries:n}},{"./implementation":37}],62:[function(t,e,r){"use strict";var n=t("./polyfill");var i=t("define-properties");e.exports=function t(){var e=n();i(Object,{entries:e},{entries:function(){return Object.entries!==e}});return e}},{"./polyfill":61,"define-properties":39}],63:[function(t,e,r){"use strict";var n=t("es-abstract/es7");var i=Object.defineProperty;var o=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var u=Object.getOwnPropertySymbols;var a=Function.call.bind(Array.prototype.concat);var l=Function.call.bind(Array.prototype.reduce);var s=u?function(t){return a(p(t),u(t))}:p;var c=n.IsCallable(o)&&n.IsCallable(p);var f=function t(e,r,n){if(i&&r in e){i(e,r,{configurable:true,enumerable:true,value:n,writable:true})}else{e[r]=n}};e.exports=function t(e){n.RequireObjectCoercible(e);if(!c){throw new TypeError("getOwnPropertyDescriptors requires Object.getOwnPropertyDescriptor")}var r=n.ToObject(e);return l(s(r),function(t,e){f(t,e,o(r,e));return t},{})}},{"es-abstract/es7":68}],64:[function(t,e,r){arguments[4][38][0].apply(r,arguments)},{"./implementation":63,"./polyfill":86,"./shim":87,"define-properties":65,dup:38}],65:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:77,"object-keys":84}],66:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":70,"./helpers/mod":72,"./helpers/sign":73,dup:16,"es-to-primitive/es5":74,"is-callable":80}],67:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":66,"./helpers/assign":69,"./helpers/isFinite":70,"./helpers/isPrimitive":71,"./helpers/mod":72,"./helpers/sign":73,dup:17,"es-to-primitive/es6":75,"function-bind":79,"is-regex":82}],68:[function(t,e,r){arguments[4][42][0].apply(r,arguments)},{"./es6":67,"./helpers/assign":69,dup:42}],69:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],70:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],71:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],72:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],73:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],74:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":76,dup:23,"is-callable":80}],75:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":76,dup:24,"is-callable":80,"is-date-object":81,"is-symbol":83}],76:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],77:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],78:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],79:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":78,dup:28}],80:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],81:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],82:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],83:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],84:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":85,dup:33}],85:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],86:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return typeof Object.getOwnPropertyDescriptors==="function"?Object.getOwnPropertyDescriptors:n}},{"./implementation":63}],87:[function(t,e,r){"use strict";var n=t("./polyfill");var i=t("define-properties");e.exports=function t(){var e=n();i(Object,{getOwnPropertyDescriptors:e},{getOwnPropertyDescriptors:function(){return Object.getOwnPropertyDescriptors!==e}});return e}},{"./polyfill":86,"define-properties":65}],88:[function(t,e,r){"use strict";var n=t("es-abstract/es7");var i=t("has");var o=t("function-bind");var p=o.call(Function.call,Object.prototype.propertyIsEnumerable);e.exports=function t(e){var r=n.RequireObjectCoercible(e);var o=[];for(var u in r){if(i(r,u)&&p(r,u)){o.push(r[u])}}return o}},{"es-abstract/es7":93,"function-bind":104,has:105}],89:[function(t,e,r){arguments[4][38][0].apply(r,arguments)},{"./implementation":88,"./polyfill":112,"./shim":113,"define-properties":90,dup:38}],90:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:102,"object-keys":110}],91:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":95,"./helpers/mod":97,"./helpers/sign":98,dup:16,"es-to-primitive/es5":99,"is-callable":106}],92:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":91,"./helpers/assign":94,"./helpers/isFinite":95,"./helpers/isPrimitive":96,"./helpers/mod":97,"./helpers/sign":98,dup:17,"es-to-primitive/es6":100,"function-bind":104,"is-regex":108}],93:[function(t,e,r){arguments[4][42][0].apply(r,arguments)},{"./es6":92,"./helpers/assign":94,dup:42}],94:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],95:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],96:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],97:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],98:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],99:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":101,dup:23,"is-callable":106}],100:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":101,dup:24,"is-callable":106,"is-date-object":107,"is-symbol":109}],101:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],102:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],103:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],104:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":103,dup:28}],105:[function(t,e,r){arguments[4][54][0].apply(r,arguments)},{dup:54,"function-bind":104}],106:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],107:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],108:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],109:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],110:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":111,dup:33}],111:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],112:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return typeof Object.values==="function"?Object.values:n}},{"./implementation":88}],113:[function(t,e,r){"use strict";var n=t("./polyfill");var i=t("define-properties");e.exports=function t(){var e=n();i(Object,{values:e},{values:function(){return Object.values!==e}});return e}},{"./polyfill":112,"define-properties":90}],114:[function(t,e,r){"use strict";var n=t("define-properties");var i=t("es-abstract/es7");var o=t("function-bind");var p=function t(e){i.RequireObjectCoercible(this);var r=i.ToObject(this);var n=i.ToString(r);var o=i.ToInteger(e);var p=n.length;if(o<0||o>=p){return""}var u=n.charCodeAt(o);var a;var l=o+1;var s=1;var c=u>=55296&&u<=56319;if(c&&p>l){a=n.charCodeAt(l);if(a>=56320&&a<=57343){s=2}}return n.slice(o,o+s)};var u=o.call(Function.call,p);n(u,{method:p,shim:function t(){n(String.prototype,{at:p});return String.prototype.at}});e.exports=u},{"define-properties":115,"es-abstract/es7":118,"function-bind":129}],115:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:127,"object-keys":134}],116:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":120,"./helpers/mod":122,"./helpers/sign":123,dup:16,"es-to-primitive/es5":124,"is-callable":130}],117:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":116,"./helpers/assign":119,"./helpers/isFinite":120,"./helpers/isPrimitive":121,"./helpers/mod":122,"./helpers/sign":123,dup:17,"es-to-primitive/es6":125,"function-bind":129,"is-regex":132}],118:[function(t,e,r){arguments[4][42][0].apply(r,arguments)},{"./es6":117,"./helpers/assign":119,dup:42}],119:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],120:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],121:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],122:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],123:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],124:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":126,dup:23,"is-callable":130}],125:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":126,dup:24,"is-callable":130,"is-date-object":131,"is-symbol":133}],126:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],127:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],128:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],129:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":128,dup:28}],130:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],131:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],132:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],133:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],134:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":135,dup:33}],135:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],136:[function(t,e,r){"use strict";var n=t("function-bind");var i=t("es-abstract/es7");var o=n.call(Function.call,String.prototype.slice);e.exports=function t(e){var r=i.RequireObjectCoercible(this);var n=i.ToString(r);var p=i.ToLength(n.length);var u;if(arguments.length>1){u=arguments[1]}var a=typeof u==="undefined"?"":i.ToString(u);if(a===""){a=" "}var l=i.ToLength(e);if(l<=p){return n}var s=l-p;while(a.lengthf?o(a,0,f):a}var y=a.length>s?o(a,0,s):a;return n+y}},{"es-abstract/es7":141,"function-bind":152}],137:[function(t,e,r){"use strict";var n=t("function-bind");var i=t("define-properties");var o=t("es-abstract/es7");var p=t("./implementation");var u=t("./polyfill");var a=t("./shim");var l=n.call(Function.apply,p);var s=function t(e,r){o.RequireObjectCoercible(e);var n=[r];if(arguments.length>2){n.push(arguments[2])}return l(e,n)};i(s,{getPolyfill:u,implementation:p,shim:a});e.exports=s},{"./implementation":136,"./polyfill":159,"./shim":160,"define-properties":138,"es-abstract/es7":141,"function-bind":152}],138:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:150,"object-keys":157}],139:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":143,"./helpers/mod":145,"./helpers/sign":146,dup:16,"es-to-primitive/es5":147,"is-callable":153}],140:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":139,"./helpers/assign":142,"./helpers/isFinite":143,"./helpers/isPrimitive":144,"./helpers/mod":145,"./helpers/sign":146,dup:17,"es-to-primitive/es6":148,"function-bind":152,"is-regex":155}],141:[function(t,e,r){arguments[4][42][0].apply(r,arguments)},{"./es6":140,"./helpers/assign":142,dup:42}],142:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],143:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],144:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],145:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],146:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],147:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":149,dup:23,"is-callable":153}],148:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":149,dup:24,"is-callable":153,"is-date-object":154,"is-symbol":156}],149:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],150:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],151:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],152:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":151,dup:28}],153:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],154:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],155:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],156:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],157:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":158,dup:33}],158:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],159:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return typeof String.prototype.padEnd==="function"?String.prototype.padEnd:n}},{"./implementation":136}],160:[function(t,e,r){"use strict";var n=t("./polyfill");var i=t("define-properties");e.exports=function t(){var e=n();i(String.prototype,{padEnd:e},{padEnd:function(){return String.prototype.padEnd!==e}});return e}},{"./polyfill":159,"define-properties":138}],161:[function(t,e,r){"use strict";var n=t("function-bind");var i=t("es-abstract/es7");var o=n.call(Function.call,String.prototype.slice);e.exports=function t(e){var r=i.RequireObjectCoercible(this);var n=i.ToString(r);var p=i.ToLength(n.length);var u;if(arguments.length>1){u=arguments[1]}var a=typeof u==="undefined"?"":i.ToString(u);if(a===""){a=" "}var l=i.ToLength(e);if(l<=p){return n}var s=l-p;while(a.lengthf?o(a,0,f):a}var y=a.length>s?o(a,0,s):a;return y+n}},{"es-abstract/es7":166,"function-bind":177}],162:[function(t,e,r){"use strict";var n=t("function-bind");var i=t("define-properties");var o=t("es-abstract/es7");var p=t("./implementation");var u=t("./polyfill");var a=t("./shim");var l=n.call(Function.apply,p);var s=function t(e,r){o.RequireObjectCoercible(e);var n=[r];if(arguments.length>2){ 7 | n.push(arguments[2])}return l(e,n)};i(s,{getPolyfill:u,implementation:p,shim:a});e.exports=s},{"./implementation":161,"./polyfill":184,"./shim":185,"define-properties":163,"es-abstract/es7":166,"function-bind":177}],163:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:175,"object-keys":182}],164:[function(t,e,r){arguments[4][16][0].apply(r,arguments)},{"./helpers/isFinite":168,"./helpers/mod":170,"./helpers/sign":171,dup:16,"es-to-primitive/es5":172,"is-callable":178}],165:[function(t,e,r){arguments[4][17][0].apply(r,arguments)},{"./es5":164,"./helpers/assign":167,"./helpers/isFinite":168,"./helpers/isPrimitive":169,"./helpers/mod":170,"./helpers/sign":171,dup:17,"es-to-primitive/es6":173,"function-bind":177,"is-regex":180}],166:[function(t,e,r){arguments[4][42][0].apply(r,arguments)},{"./es6":165,"./helpers/assign":167,dup:42}],167:[function(t,e,r){arguments[4][18][0].apply(r,arguments)},{dup:18}],168:[function(t,e,r){arguments[4][19][0].apply(r,arguments)},{dup:19}],169:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],170:[function(t,e,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],171:[function(t,e,r){arguments[4][22][0].apply(r,arguments)},{dup:22}],172:[function(t,e,r){arguments[4][23][0].apply(r,arguments)},{"./helpers/isPrimitive":174,dup:23,"is-callable":178}],173:[function(t,e,r){arguments[4][24][0].apply(r,arguments)},{"./helpers/isPrimitive":174,dup:24,"is-callable":178,"is-date-object":179,"is-symbol":181}],174:[function(t,e,r){arguments[4][20][0].apply(r,arguments)},{dup:20}],175:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],176:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],177:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":176,dup:28}],178:[function(t,e,r){arguments[4][29][0].apply(r,arguments)},{dup:29}],179:[function(t,e,r){arguments[4][30][0].apply(r,arguments)},{dup:30}],180:[function(t,e,r){arguments[4][31][0].apply(r,arguments)},{dup:31}],181:[function(t,e,r){arguments[4][32][0].apply(r,arguments)},{dup:32}],182:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":183,dup:33}],183:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],184:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){return typeof String.prototype.padStart==="function"?String.prototype.padStart:n}},{"./implementation":161}],185:[function(t,e,r){"use strict";var n=t("./polyfill");var i=t("define-properties");e.exports=function t(){var e=n();i(String.prototype,{padStart:e},{padStart:function(){return String.prototype.padStart!==e}});return e}},{"./polyfill":184,"define-properties":163}],186:[function(t,e,r){"use strict";var n=t("function-bind");var i=n.call(Function.call,String.prototype.replace);var o=/^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]*/;e.exports=function t(){return i(this,o,"")}},{"function-bind":191}],187:[function(t,e,r){"use strict";var n=t("function-bind");var i=t("define-properties");var o=t("./implementation");var p=t("./polyfill");var u=t("./shim");var a=n.call(Function.call,p());i(a,{getPolyfill:p,implementation:o,shim:u});e.exports=a},{"./implementation":186,"./polyfill":194,"./shim":195,"define-properties":188,"function-bind":191}],188:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:189,"object-keys":192}],189:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],190:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],191:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":190,dup:28}],192:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":193,dup:33}],193:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],194:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){if(!String.prototype.trimLeft){return n}var e="\u200b";if(e.trimLeft()!==e){return n}return String.prototype.trimLeft}},{"./implementation":186}],195:[function(t,e,r){"use strict";var n=t("define-properties");var i=t("./polyfill");e.exports=function t(){var e=i();n(String.prototype,{trimLeft:e},{trimLeft:function(){return String.prototype.trimLeft!==e}});return e}},{"./polyfill":194,"define-properties":188}],196:[function(t,e,r){"use strict";var n=t("function-bind");var i=n.call(Function.call,String.prototype.replace);var o=/[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]*$/;e.exports=function t(){return i(this,o,"")}},{"function-bind":201}],197:[function(t,e,r){arguments[4][187][0].apply(r,arguments)},{"./implementation":196,"./polyfill":204,"./shim":205,"define-properties":198,dup:187,"function-bind":201}],198:[function(t,e,r){arguments[4][15][0].apply(r,arguments)},{dup:15,foreach:199,"object-keys":202}],199:[function(t,e,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],200:[function(t,e,r){arguments[4][27][0].apply(r,arguments)},{dup:27}],201:[function(t,e,r){arguments[4][28][0].apply(r,arguments)},{"./implementation":200,dup:28}],202:[function(t,e,r){arguments[4][33][0].apply(r,arguments)},{"./isArguments":203,dup:33}],203:[function(t,e,r){arguments[4][34][0].apply(r,arguments)},{dup:34}],204:[function(t,e,r){"use strict";var n=t("./implementation");e.exports=function t(){if(!String.prototype.trimRight){return n}var e="\u200b";if(e.trimRight()!==e){return n}return String.prototype.trimRight}},{"./implementation":196}],205:[function(t,e,r){"use strict";var n=t("define-properties");var i=t("./polyfill");e.exports=function t(){var e=i();n(String.prototype,{trimRight:e},{trimRight:function(){return String.prototype.trimRight!==e}});return e}},{"./polyfill":204,"define-properties":198}],206:[function(t,e,r){"use strict";e.exports=t("./es7-shim").shim()},{"./es7-shim":12}]},{},[206]); 8 | //# sourceMappingURL=dist/es7-shim.map 9 | -------------------------------------------------------------------------------- /tests/ionic-tests.ts: -------------------------------------------------------------------------------- 1 | // TODO: make it real test 2 | 3 | import { PhotoLibrary, LibraryItem } from 'ionic-native'; 4 | 5 | class Test { 6 | 7 | test () { 8 | 9 | PhotoLibrary.requestAuthorization({ read: true, write: true }) 10 | .then(() => { 11 | console.log('permission granted'); 12 | return this.getLibrary(); 13 | }) 14 | .then((library: LibraryItem[]) => { 15 | return this.getThumbnailURL(library[0]).then(() => library); 16 | }) 17 | .then((library: LibraryItem[]) => { 18 | return this.getPhotoURL(library[0]).then(() => library); 19 | }) 20 | .then((library: LibraryItem[]) => { 21 | return this.getThumbnail(library[0]).then(() => library); 22 | }) 23 | .then((library: LibraryItem[]) => { 24 | return this.getPhoto(library[0]).then(() => library); 25 | }) 26 | .then(() => { 27 | return this.getAlbums(); 28 | }) 29 | .then(() => { 30 | return this.saveImage(); 31 | }) 32 | .catch(err => { 33 | console.log(err); 34 | }); 35 | 36 | } 37 | 38 | getLibrary() { 39 | return new Promise((resolve, reject) => { 40 | let library = []; 41 | PhotoLibrary.getLibrary({ itemsInChunk: 1000 }).subscribe({ 42 | next: chunk => { 43 | console.log(`chunk arrived: ${chunk.length}`); 44 | library = library.concat(chunk); 45 | }, error: err => { 46 | console.log('getLibrary error: ' + err); 47 | reject(err); 48 | }, complete: () => { 49 | console.log(`completed: ${library.length}`); 50 | resolve(library); 51 | } 52 | }); 53 | }); 54 | } 55 | 56 | getAlbums() { 57 | return PhotoLibrary.getAlbums().then(albums => { 58 | albums.forEach(album => console.log(JSON.stringify(album))); 59 | }); 60 | } 61 | 62 | getThumbnailURL(libraryItem: LibraryItem) { 63 | return PhotoLibrary.getThumbnailURL(libraryItem).then(url => console.log(url)); 64 | } 65 | 66 | getPhotoURL(libraryItem: LibraryItem) { 67 | return PhotoLibrary.getPhotoURL(libraryItem).then(url => console.log(url)); 68 | } 69 | 70 | getThumbnail(libraryItem: LibraryItem) { 71 | return PhotoLibrary.getThumbnail(libraryItem).then(url => console.log(url)); 72 | } 73 | 74 | getPhoto(libraryItem: LibraryItem) { 75 | return PhotoLibrary.getPhoto(libraryItem).then(url => console.log(url)); 76 | } 77 | 78 | saveImage() { 79 | var canvas = document.createElement('canvas'); 80 | canvas.width = 150; 81 | canvas.height = 150; 82 | var ctx = canvas.getContext('2d'); 83 | ctx.fillRect(25, 25, 100, 100); 84 | ctx.clearRect(45, 45, 60, 60); 85 | ctx.strokeRect(50, 50, 50, 50); 86 | var dataURL = canvas.toDataURL('image/jpg'); 87 | 88 | return PhotoLibrary.saveImage(dataURL, 'ionic-native').then(libraryItem => console.log(JSON.stringify(libraryItem))); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /tests/ios/get-booted-simulator.sh: -------------------------------------------------------------------------------- 1 | xcrun simctl list | grep Booted | awk -F "[()]" '{ for (i=2; i 2 | 6 | Photo Library Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/test-images/Landscape_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_1.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_2.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_3.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_4.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_5.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_6.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_7.jpg -------------------------------------------------------------------------------- /tests/test-images/Landscape_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Landscape_8.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_1.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_2.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_3.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_4.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_5.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_6.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_7.jpg -------------------------------------------------------------------------------- /tests/test-images/Portrait_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/Portrait_8.jpg -------------------------------------------------------------------------------- /tests/test-images/geotagged.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmarkclx/cordova-plugin-photo-library-sism/aa0b7cdd08cccbea94aecea0fa082d8e7bc6d6b1/tests/test-images/geotagged.jpg -------------------------------------------------------------------------------- /tests/test-utils.js: -------------------------------------------------------------------------------- 1 | exports.resolveLocalFileSystemURL = function (fileSystem) { 2 | 3 | return new Promise(function (resolve, reject) { 4 | 5 | window.resolveLocalFileSystemURL(fileSystem, resolve, reject); 6 | 7 | }); 8 | 9 | }; 10 | 11 | exports.createFile = function (dirEntry, fileName) { 12 | 13 | return new Promise(function (resolve, reject) { 14 | 15 | dirEntry.getFile(fileName, {create: true, exclusive: false}, resolve, reject); 16 | 17 | }); 18 | 19 | }; 20 | 21 | exports.writeFile = function (fileEntry, dataObj) { 22 | 23 | return new Promise(function (resolve, reject) { 24 | 25 | fileEntry.createWriter(function (fileWriter) { 26 | 27 | fileWriter.onwriteend = function () { resolve (fileEntry); }; 28 | fileWriter.onerror = reject; 29 | 30 | fileWriter.write(dataObj); 31 | 32 | }); 33 | 34 | }); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | // Tests should be written on JavaScript version that supported on both iOS and Android WebView (no lambdas). 2 | // But functionality provided by esshims can be used :) 3 | 4 | // Include shims for useful javascript functions to work on all devices 5 | cordova.require('cordova-plugin-photo-library-tests.es5-shim'); 6 | cordova.require('cordova-plugin-photo-library-tests.es6-shim'); 7 | cordova.require('cordova-plugin-photo-library-tests.es7-shim'); 8 | cordova.require('cordova-plugin-photo-library-tests.blueimp-canvastoblob'); 9 | 10 | var testUtils = cordova.require('cordova-plugin-photo-library-tests.test-utils'); 11 | 12 | var expectedImages = [ 13 | { fileName: 'Landscape_1.jpg', width: 600, height: 450, }, 14 | { fileName: 'Landscape_2.jpg', width: 600, height: 450, }, 15 | { fileName: 'Landscape_3.jpg', width: 600, height: 450, }, 16 | { fileName: 'Landscape_4.jpg', width: 600, height: 450, }, 17 | { fileName: 'Landscape_5.jpg', width: 600, height: 450, }, 18 | { fileName: 'Landscape_6.jpg', width: 600, height: 450, }, 19 | { fileName: 'Landscape_7.jpg', width: 600, height: 450, }, 20 | { fileName: 'Landscape_8.jpg', width: 600, height: 450, }, 21 | { fileName: 'Portrait_1.jpg', width: 450, height: 600, }, 22 | { fileName: 'Portrait_2.jpg', width: 450, height: 600, }, 23 | { fileName: 'Portrait_3.jpg', width: 450, height: 600, }, 24 | { fileName: 'Portrait_4.jpg', width: 450, height: 600, }, 25 | { fileName: 'Portrait_5.jpg', width: 450, height: 600, }, 26 | { fileName: 'Portrait_6.jpg', width: 450, height: 600, }, 27 | { fileName: 'Portrait_7.jpg', width: 450, height: 600, }, 28 | { fileName: 'Portrait_8.jpg', width: 450, height: 600, }, 29 | ]; 30 | 31 | exports.defineAutoTests = function () { 32 | 33 | // Configure jasmine 34 | var jasmineEnv = jasmine.getEnv(); 35 | jasmineEnv.catchExceptions(true); // without this, syntax error will hang a test, instead of reporting it failed 36 | 37 | describe('cordova.plugins', function () { 38 | 39 | it('photoLibrary should exist', function () { 40 | expect(cordova.plugins.photoLibrary).toBeDefined(); 41 | }); 42 | 43 | describe('cordova.plugins.photoLibrary', function () { 44 | 45 | var library = null; 46 | var libraryError = null; 47 | var getLibraryResultCalledTimes = 0; 48 | var getLibraryIsLastChunk = null; 49 | 50 | beforeAll(function (done) { 51 | cordova.plugins.photoLibrary.getLibrary(function (result) { 52 | library = result.library; 53 | getLibraryResultCalledTimes += 1; 54 | getLibraryIsLastChunk = result.isLastChunk; 55 | done(); 56 | }, 57 | function (err) { 58 | libraryError = err; 59 | done.fail(err); 60 | }, 61 | { 62 | useOriginalFileNames: true, // We want to compare file names in test 63 | includeAlbumData: true, // We want to check albums 64 | }); 65 | }, 20000); // In browser platform, gives a time to select photos. 66 | 67 | it('should not fail', function () { 68 | expect(libraryError).toBeNull('getLibrary failed with error: ' + libraryError); 69 | }); 70 | 71 | it('should load library', function () { 72 | expect(library).not.toBeNull(); 73 | }); 74 | 75 | it('result callback should be executed exactly once', function () { 76 | expect(getLibraryResultCalledTimes).toEqual(1); 77 | }); 78 | 79 | it('result callback should be treated as last chunk', function () { 80 | expect(getLibraryIsLastChunk).toBeTruthy(); 81 | }); 82 | 83 | describe('cordova.plugins.photoLibrary.getAlbums', function () { 84 | 85 | var albums = null; 86 | var getAlbumsError = null; 87 | 88 | beforeAll(function (done) { 89 | cordova.plugins.photoLibrary.getAlbums(function (albs) { 90 | albums = albs; 91 | done(); 92 | }, 93 | function (err) { 94 | getAlbumsError = err; 95 | done.fail(err); 96 | }); 97 | }); 98 | 99 | it('should not fail', function() { 100 | expect(getAlbumsError).toBeNull('getAlbums failed with error: ' + getAlbumsError); 101 | }); 102 | 103 | it('should return an array', function() { 104 | expect(albums).toEqual(jasmine.any(Array)); 105 | }); 106 | 107 | it('shoud return at least one album', function() { 108 | expect(albums.length).toBeGreaterThan(0); 109 | }); 110 | 111 | }); 112 | 113 | describe('cordova.plugins.photoLibrary.getLibrary', function () { 114 | 115 | it('should return multiple photos', function () { 116 | expect(library.length).toBeGreaterThan(0); 117 | }); 118 | 119 | expectedImages.forEach(function (expectedImage) { 120 | 121 | describe('test-images/' + expectedImage.fileName, function () { 122 | 123 | beforeEach(function () { 124 | this.libraryItem = library.find(function (libraryItem) { return libraryItem.fileName === expectedImage.fileName }); 125 | }); 126 | 127 | it('should exist', function () { 128 | expect(this.libraryItem).toBeDefined(); 129 | }); 130 | 131 | it('should have not-empty id', function () { 132 | expect(this.libraryItem.id).toEqual(jasmine.any(String)); 133 | expect(this.libraryItem.id.length).not.toEqual(0); 134 | }); 135 | 136 | it('should have not-empty photoURL', function () { 137 | expect(this.libraryItem.photoURL).toEqual(jasmine.any(String)); 138 | expect(this.libraryItem.photoURL.length).not.toEqual(0); 139 | }); 140 | 141 | it('should have not-empty thumbnailURL', function () { 142 | expect(this.libraryItem.thumbnailURL).toEqual(jasmine.any(String)); 143 | expect(this.libraryItem.thumbnailURL.length).not.toEqual(0); 144 | }); 145 | 146 | it('should have right width', function () { 147 | expect(this.libraryItem.width).toBe(expectedImage.width); 148 | }); 149 | 150 | it('should have right height', function () { 151 | expect(this.libraryItem.height).toBe(expectedImage.height); 152 | }); 153 | 154 | it('should have creationDate', function () { 155 | expect(this.libraryItem.creationDate).toEqual(jasmine.any(Date)); 156 | expect(this.libraryItem.creationDate.getFullYear()).toBeGreaterThan(2015); 157 | }); 158 | 159 | it('should have albumIds array', function() { 160 | expect(this.libraryItem.albumIds).toEqual(jasmine.any(Array)); 161 | }); 162 | 163 | it('albumIds array should contain at least one album', function() { 164 | expect(this.libraryItem.albumIds.length).toBeGreaterThan(0); 165 | }); 166 | 167 | }); 168 | 169 | }); 170 | 171 | describe('geotagged image', function() { 172 | 173 | beforeEach(function () { 174 | this.libraryItem = library.find(function (libraryItem) { return libraryItem.fileName === 'geotagged.jpg'; }); 175 | }); 176 | 177 | it('should have correct latitude', function() { 178 | expect(this.libraryItem.latitude).toBeCloseTo(32.517078, 5); // 32' 31'' 1.482''' 179 | }); 180 | 181 | it('should have correct longitude', function() { 182 | expect(this.libraryItem.longitude).toBeCloseTo(34.955096, 5); // 34' 57'' 18.348''' 183 | }); 184 | 185 | }); 186 | 187 | }); 188 | 189 | describe('cordova.plugins.photoLibrary.getThumbnailURL', function () { 190 | 191 | var thumbnailURL = null; 192 | var getThumbnailURLError = null; 193 | 194 | beforeAll(function (done) { 195 | cordova.plugins.photoLibrary.getThumbnailURL( 196 | library[0], 197 | function (url) { 198 | thumbnailURL = url; 199 | done(); 200 | }, 201 | function (err) { 202 | getThumbnailURLError = err; 203 | done.fail(err); 204 | }, { 205 | thumbnailWidth: 123, 206 | thumbnailHeight: 234, 207 | quality: 0.25 208 | }); 209 | }); 210 | 211 | it('should not fail', function () { 212 | expect(getThumbnailURLError).toBeNull('failed with error: ' + getThumbnailURLError); 213 | }); 214 | 215 | it('should return non-empty url', function () { 216 | expect(thumbnailURL).toEqual(jasmine.any(String)); 217 | expect(thumbnailURL.length).not.toEqual(0); 218 | }); 219 | 220 | it('thumbnailURL should contain requested width', function () { 221 | expect(thumbnailURL).toContain('width=123'); 222 | }); 223 | 224 | it('thumbnailURL should contain requested height', function () { 225 | expect(thumbnailURL).toContain('height=234'); 226 | }); 227 | 228 | it('thumbnailURL should contain requested quality', function () { 229 | expect(thumbnailURL).toContain('quality=0.25'); 230 | }); 231 | 232 | }); 233 | 234 | describe('cordova.plugins.photoLibrary.getPhotoURL', function () { 235 | 236 | var photoURL = null; 237 | var getPhotoURLError = null; 238 | 239 | beforeAll(function (done) { 240 | cordova.plugins.photoLibrary.getPhotoURL( 241 | library[0], 242 | function (url) { 243 | photoURL = url; 244 | done(); 245 | }, 246 | function (err) { 247 | getPhotoURLError = err; 248 | done.fail(err); 249 | }); 250 | }); 251 | 252 | it('should not fail', function () { 253 | expect(getPhotoURLError).toBeNull('failed with error: ' + getPhotoURLError); 254 | }); 255 | 256 | it('should return non-empty url', function () { 257 | expect(photoURL).toEqual(jasmine.any(String)); 258 | expect(photoURL.length).not.toEqual(0); 259 | }); 260 | 261 | }); 262 | 263 | describe('cordova.plugins.photoLibrary.getThumbnail', function () { 264 | 265 | var thumbnailBlob = null; 266 | var getThumbnailError = null; 267 | 268 | beforeAll(function (done) { 269 | cordova.plugins.photoLibrary.getThumbnail( 270 | library[0].id, 271 | function (blob) { 272 | thumbnailBlob = blob; 273 | done(); 274 | }, 275 | function (err) { 276 | getThumbnailError = err; 277 | done.fail(err); 278 | }, { 279 | thumbnailWidth: 123, 280 | thumbnailHeight: 234, 281 | quality: 0.25 282 | }); 283 | }); 284 | 285 | it('should not fail', function () { 286 | expect(getThumbnailError).toBeNull('failed with error: ' + getThumbnailError); 287 | }); 288 | 289 | it('should return non-empty blob', function () { 290 | expect(thumbnailBlob).toEqual(jasmine.any(Blob)); 291 | expect(thumbnailBlob.size).not.toEqual(0); 292 | }); 293 | 294 | describe('thumbnailBlob', function () { 295 | 296 | var imageObj; 297 | 298 | beforeAll(function (done) { 299 | imageObj = new Image(); 300 | imageObj.onload = function () { 301 | done(); 302 | }; 303 | var dataURL = URL.createObjectURL(thumbnailBlob); 304 | imageObj.src = dataURL; 305 | }); 306 | 307 | it('width should be as requested', function () { 308 | expect(imageObj.width).toBe(123); 309 | }); 310 | 311 | it('height should be as requested', function () { 312 | expect(imageObj.height).toBe(234); 313 | }); 314 | 315 | }); 316 | 317 | }); 318 | 319 | describe('cordova.plugins.photoLibrary.getPhoto', function () { 320 | 321 | var photoBlob = null; 322 | var getPhotoError = null; 323 | 324 | beforeAll(function (done) { 325 | cordova.plugins.photoLibrary.getPhoto( 326 | library[0], 327 | function (blob) { 328 | photoBlob = blob; 329 | done(); 330 | }, 331 | function (err) { 332 | getPhotoError = err; 333 | done.fail(err); 334 | }); 335 | }); 336 | 337 | it('should not fail', function () { 338 | expect(getPhotoError).toBeNull('failed with error: ' + getPhotoError); 339 | }); 340 | 341 | it('should return non-empty blob', function () { 342 | expect(photoBlob).toEqual(jasmine.any(Blob)); 343 | expect(photoBlob.size).not.toEqual(0); 344 | }); 345 | 346 | }); 347 | 348 | describe('cordova.plugins.photoLibrary.requestAuthorization', function () { 349 | 350 | it('should be defined', function () { 351 | expect(cordova.plugins.photoLibrary.requestAuthorization).toEqual(jasmine.any(Function)); 352 | }); 353 | 354 | // Use manual tests to check it working 355 | 356 | }); 357 | 358 | var chunkOptionsArray = [{itemsInChunk: 1, chunkTimeSec: 0}, {itemsInChunk: 0, chunkTimeSec: 0.000000001}]; 359 | 360 | chunkOptionsArray.forEach(function (chunkOptions) { 361 | 362 | describe('chunked output by ' + (chunkOptions.itemsInChunk > 0? 'itemsInChunk' : 'chunkTimeSec'), function () { 363 | var libraryChunks = []; 364 | var chunkedError = null; 365 | 366 | beforeAll(function (done) { 367 | cordova.plugins.photoLibrary.getLibrary(function (result) { 368 | libraryChunks.push(result.library); 369 | if (result.isLastChunk) { 370 | done(); 371 | } 372 | }, 373 | function (err) { 374 | chunkedError = err; 375 | done.fail(err); 376 | }, 377 | { 378 | itemsInChunk: chunkOptions.itemsInChunk, 379 | chunkTimeSec: chunkOptions.chunkTimeSec, 380 | useOriginalFileNames: true, 381 | }); 382 | }, 20000); // In browser platform, gives a time to select photos. 383 | 384 | it('should not fail', function () { 385 | expect(chunkedError).toBeNull('chunked getLibrary failed with error: ' + chunkedError); 386 | }); 387 | 388 | if (chunkOptions.itemsInChunk > 0) { 389 | it('should return correct number of chunks', function () { 390 | expect(libraryChunks.length).toEqual(library.length); 391 | }); 392 | } 393 | 394 | if (chunkOptions.chunkTimeSec > 0) { 395 | it('should return multiple chunks', function () { 396 | expect(libraryChunks.length).toBeGreaterThan(0); 397 | }); 398 | } 399 | 400 | it('should return same photos in chunks as without chunks', function () { 401 | var unchunkedNames = library.map(function(item) { return item.id; }); 402 | var flattenedChunks = [].concat.apply([], libraryChunks); 403 | var chunkedNames = flattenedChunks.map(function(item) { return item.id; }); 404 | expect(chunkedNames).toEqual(unchunkedNames); 405 | }); 406 | 407 | }); 408 | 409 | }); 410 | 411 | }); 412 | 413 | describe('cordova.plugins.photoLibrary.saveImage', function () { 414 | 415 | it('should be defined', function () { 416 | expect(cordova.plugins.photoLibrary.saveImage).toEqual(jasmine.any(Function)); 417 | }); 418 | 419 | describe('saving image as dataURL', function() { 420 | 421 | var saveImageLibraryItem = null; 422 | var saveImageError = null; 423 | 424 | beforeAll(function(done) { 425 | var canvas = document.createElement('canvas'); 426 | canvas.width = 150; 427 | canvas.height = 150; 428 | var ctx = canvas.getContext('2d'); 429 | ctx.fillRect(25, 25, 100, 100); 430 | ctx.clearRect(45, 45, 60, 60); 431 | ctx.strokeRect(50, 50, 50, 50); 432 | var dataURL = canvas.toDataURL('image/jpg'); 433 | 434 | cordova.plugins.photoLibrary.saveImage(dataURL, 'PhotoLibraryTests', 435 | function(libraryItem) { 436 | saveImageLibraryItem = libraryItem; 437 | done(); 438 | }, 439 | function(err) { 440 | saveImageError = err; 441 | done.fail(err); 442 | }); 443 | }); 444 | 445 | it('should not fail', function() { 446 | expect(saveImageError).toBeNull('failed with error: ' + saveImageError); 447 | }); 448 | 449 | it('should return valid library item', function() { 450 | expect(saveImageLibraryItem).not.toBeNull(); 451 | expect(saveImageLibraryItem.id).toBeDefined(); 452 | }); 453 | 454 | }); 455 | 456 | describe('saving image from local URL', function() { 457 | 458 | var saveImageLibraryItem = null; 459 | var saveImageError = null; 460 | 461 | beforeAll(function(done) { 462 | var canvas = document.createElement('canvas'); 463 | canvas.width = 150; 464 | canvas.height = 150; 465 | var ctx = canvas.getContext('2d'); 466 | ctx.fillRect(25, 25, 100, 100); 467 | ctx.clearRect(45, 45, 60, 60); 468 | ctx.strokeStyle = '#87CEEB'; // Sky blue 469 | ctx.strokeRect(50, 50, 50, 50); 470 | 471 | testUtils.resolveLocalFileSystemURL(cordova.file.cacheDirectory) 472 | .then(function (dirEntry) { 473 | 474 | return testUtils.createFile(dirEntry, 'test-image.jpg'); 475 | 476 | }) 477 | .then(function (fileEntry) { 478 | 479 | return new Promise(function (resolve, reject) { 480 | canvas.toBlob(function(blob) { 481 | resolve({fileEntry: fileEntry, blob: blob}); 482 | }, 'image/jpeg'); 483 | }); 484 | 485 | }) 486 | .then(function (result) { 487 | 488 | var fileEntry = result.fileEntry; 489 | var blob = result.blob; 490 | return testUtils.writeFile(fileEntry, blob); 491 | 492 | }) 493 | .then(function(fileEntry) { 494 | 495 | var localURL = fileEntry.toURL(); 496 | return new Promise(function (resolve, reject) { 497 | cordova.plugins.photoLibrary.saveImage(localURL, 'PhotoLibraryTests', 498 | function(libraryItem) { 499 | saveImageLibraryItem = libraryItem; 500 | resolve(); 501 | }, 502 | function(err) { 503 | saveImageError = err; 504 | reject(err); 505 | }); 506 | }); 507 | 508 | }) 509 | .then(done) 510 | .catch(function (err) { done.fail(err); }); 511 | }); 512 | 513 | it('should not fail', function() { 514 | expect(saveImageError).toBeNull('failed with error: ' + saveImageError); 515 | }); 516 | 517 | it('should return valid library item', function() { 518 | expect(saveImageLibraryItem).not.toBeNull(); 519 | expect(saveImageLibraryItem.id).toBeDefined(); 520 | }); 521 | 522 | }); 523 | 524 | describe('saving image from remote URL', function() { 525 | 526 | var saveImageLibraryItem = null; 527 | var saveImageError = null; 528 | 529 | beforeAll(function(done) { 530 | var remoteURL = 'http://openphoto.net/volumes/nmarchildon/20041218/opl_imgp0196.jpg'; 531 | 532 | cordova.plugins.photoLibrary.saveImage(remoteURL, 'PhotoLibraryTests', 533 | function(libraryItem) { 534 | saveImageLibraryItem = libraryItem; 535 | done(); 536 | }, 537 | function(err) { 538 | saveImageError = err; 539 | done.fail(err); 540 | }); 541 | }); 542 | 543 | it('should not fail', function() { 544 | expect(saveImageError).toBeNull('failed with error: ' + saveImageError); 545 | }); 546 | 547 | it('should return valid library item', function() { 548 | expect(saveImageLibraryItem).not.toBeNull(); 549 | expect(saveImageLibraryItem.id).toBeDefined(); 550 | }); 551 | 552 | }); 553 | 554 | }); 555 | 556 | describe('cordova.plugins.photoLibrary.saveVideo', function () { 557 | 558 | it('should be defined', function () { 559 | expect(cordova.plugins.photoLibrary.saveVideo).toEqual(jasmine.any(Function)); 560 | }); 561 | 562 | // TODO: add more tests 563 | 564 | }); 565 | 566 | }); 567 | 568 | }; 569 | 570 | exports.defineManualTests = function (contentEl, createActionButton) { 571 | 572 | var logMessage = function (message, color) { 573 | var log = document.getElementById('info'); 574 | var logLine = document.createElement('div'); 575 | if (color) { 576 | logLine.style.color = color; 577 | } 578 | logLine.innerHTML = message; 579 | log.appendChild(logLine); 580 | }; 581 | 582 | var clearLog = function () { 583 | var log = document.getElementById('info'); 584 | log.innerHTML = ''; 585 | }; 586 | 587 | var photo_library_tests = '

Press requestAuthorization button to authorize storage

' + 588 | '
' + 589 | 'Expected result: If authorized, this fact will be logged. On iOS: settings page will open. On Android: confirmation prompt will open.' + 590 | 591 | '

Press the button to visually inspect test-images

' + 592 | '
' + 593 | 'Expected result: All the images should be rotated right way' + 594 | 595 | '

Press the button to visually inspect thumbnails of test-images

' + 596 | '
' + 597 | 'Expected result: All the images should be rotated right way' + 598 | 599 | '

Press the button to measure speed of getLibrary

' + 600 | '
' + 601 | 'Expected result: Time per image should be adequate' + 602 | 603 | '

Press the button to display albums

' + 604 | '
' + 605 | 'Expected result: Should return all the albums' 606 | ; 607 | 608 | contentEl.innerHTML = '
' + photo_library_tests; 609 | 610 | createActionButton('requestAuthorization', function () { 611 | clearLog(); 612 | cordova.plugins.photoLibrary.requestAuthorization( 613 | function () { 614 | logMessage('User gave us permission to his library'); 615 | }, 616 | function (err) { 617 | logMessage('User denied the access: ' + err); 618 | }, 619 | { 620 | read: true, 621 | write: true // Needed for saveImage tests 622 | } 623 | ); 624 | }, 'request_authorization'); 625 | 626 | createActionButton('inspect test images', function () { 627 | clearLog(); 628 | cordova.plugins.photoLibrary.getLibrary( 629 | function (result) { 630 | var found = 0; 631 | result.library.forEach(function (libraryItem) { 632 | if (expectedImages.some(function (expectedImage) { return expectedImage.fileName === libraryItem.fileName; })) { 633 | found += 1; 634 | logMessage(''); 635 | } 636 | }); 637 | if (found < expectedImages.length) { 638 | logMessage('Some test-images are missing. Please put photos from test-images folder to device library.') 639 | } 640 | }, 641 | function (err) { 642 | logMessage('Error occured in getLibrary: ' + err); 643 | }, 644 | { 645 | useOriginalFileNames: true, 646 | } 647 | ); 648 | }, 'inspect_test_images'); 649 | 650 | createActionButton('inspect thumbnail test images', function () { 651 | clearLog(); 652 | cordova.plugins.photoLibrary.getLibrary( 653 | function (result) { 654 | var found = 0; 655 | result.library.forEach(function (libraryItem) { 656 | if (expectedImages.some(function (expectedImage) { return expectedImage.fileName === libraryItem.fileName; })) { 657 | found += 1; 658 | logMessage(''); 659 | } 660 | }); 661 | if (found < expectedImages.length) { 662 | logMessage('Some test-images are missing. Please put photos from test-images folder to device library.') 663 | } 664 | }, 665 | function (err) { 666 | logMessage('Error occured in getLibrary: ' + err); 667 | }, 668 | { 669 | thumbnailWidth: 256, 670 | thumbnailHeight: 256, 671 | useOriginalFileNames: true, 672 | } 673 | ); 674 | }, 'inspect_thumbnail_test_images'); 675 | 676 | createActionButton('measure', function () { 677 | clearLog(); 678 | logMessage('measuring, please wait...'); 679 | var start = performance.now(); 680 | cordova.plugins.photoLibrary.getLibrary( 681 | function (result) { 682 | var library = result.library; 683 | var end = performance.now(); 684 | var elapsedMs = end - start; 685 | logMessage('getLibrary returned ' + library.length + ' items.'); 686 | logMessage('it took ' + Math.round(elapsedMs) + ' ms.'); 687 | logMessage('time per photo is ' + Math.round(elapsedMs / library.length) + ' ms.'); 688 | }, 689 | function (err) { 690 | logMessage('Error occured in getLibrary: ' + err); 691 | } 692 | ); 693 | }, 'measure_get_library_speed'); 694 | 695 | createActionButton('display albums', function () { 696 | clearLog(); 697 | cordova.plugins.photoLibrary.getAlbums( 698 | function (albums) { 699 | albums.forEach(function(album) { 700 | logMessage(JSON.stringify(album)); 701 | }); 702 | }, 703 | function (err) { 704 | logMessage('Error occured in getAlbums: ' + err); 705 | } 706 | ); 707 | }, 'display_albums'); 708 | 709 | }; 710 | -------------------------------------------------------------------------------- /www/PhotoLibrary.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | var async = cordova.require('cordova-plugin-photo-library-sism.async'); 4 | 5 | var defaultThumbnailWidth = 512; // optimal for android 6 | var defaultThumbnailHeight = 384; // optimal for android 7 | 8 | var defaultQuality = 0.5; 9 | 10 | var isBrowser = cordova.platformId == 'browser'; 11 | 12 | var photoLibrary = {}; 13 | 14 | // Will start caching for specified size 15 | photoLibrary.getLibrary = function (success, error, options) { 16 | 17 | if (!options) { 18 | options = {}; 19 | } 20 | 21 | options = { 22 | thumbnailWidth: options.thumbnailWidth || defaultThumbnailWidth, 23 | thumbnailHeight: options.thumbnailHeight || defaultThumbnailHeight, 24 | quality: options.quality || defaultQuality, 25 | itemsInChunk: options.itemsInChunk || 0, 26 | chunkTimeSec: options.chunkTimeSec || 0, 27 | useOriginalFileNames: options.useOriginalFileNames || false, 28 | includeImages: options.includeImages !== undefined ? options.includeImages : true, 29 | includeAlbumData: options.includeAlbumData || false, 30 | includeCloudData: options.includeCloudData !== undefined ? options.includeCloudData : true, 31 | includeVideos: options.includeVideos || false, 32 | maxItems: options.maxItems || 0 33 | }; 34 | 35 | // queue that keeps order of async processing 36 | var q = async.queue(function(chunk, done) { 37 | 38 | var library = chunk.library; 39 | var isLastChunk = chunk.isLastChunk; 40 | 41 | processLibrary(library, function(library) { 42 | var result = { library: library, isLastChunk: isLastChunk }; 43 | success(result); 44 | done(); 45 | }, options); 46 | 47 | }); 48 | 49 | var chunksToProcess = []; // chunks are stored in its index 50 | var currentChunkNum = 0; 51 | 52 | cordova.exec( 53 | function (chunk) { 54 | // callbacks arrive from cordova.exec not in order, restoring the order here 55 | if (chunk.chunkNum === currentChunkNum) { 56 | // the chunk arrived in order 57 | q.push(chunk); 58 | currentChunkNum += 1; 59 | while (chunksToProcess[currentChunkNum]) { 60 | q.push(chunksToProcess[currentChunkNum]); 61 | delete chunksToProcess[currentChunkNum]; 62 | currentChunkNum += 1; 63 | } 64 | } else { 65 | // the chunk arrived not in order 66 | chunksToProcess[chunk.chunkNum] = chunk; 67 | } 68 | }, 69 | error, 70 | 'PhotoLibrary', 71 | 'getLibrary', [options] 72 | ); 73 | 74 | }; 75 | 76 | photoLibrary.getAlbums = function (success, error) { 77 | 78 | cordova.exec( 79 | function (result) { 80 | success(result); 81 | }, 82 | error, 83 | 'PhotoLibrary', 84 | 'getAlbums', [] 85 | ); 86 | 87 | }; 88 | 89 | photoLibrary.getPhotosFromAlbum = function (albumTitle, success, error) { 90 | 91 | cordova.exec( 92 | function (result) { 93 | success(result); 94 | }, 95 | error, 96 | 'PhotoLibrary', 97 | 'getPhotosFromAlbum', [albumTitle] 98 | ); 99 | 100 | }; 101 | 102 | photoLibrary.isAuthorized = function (success, error) { 103 | 104 | cordova.exec( 105 | function (result) { 106 | success(result); 107 | }, 108 | error, 109 | 'PhotoLibrary', 110 | 'isAuthorized', [] 111 | ); 112 | 113 | }; 114 | 115 | // Generates url that can be accessed directly, so it will work more efficiently than getThumbnail, which does base64 encode/decode. 116 | // If success callback not provided, will return value immediately, but use overload with success as it browser-friendly 117 | photoLibrary.getThumbnailURL = function (photoIdOrLibraryItem, success, error, options) { 118 | 119 | var photoId = typeof photoIdOrLibraryItem.id !== 'undefined' ? photoIdOrLibraryItem.id : photoIdOrLibraryItem; 120 | 121 | if (typeof success !== 'function' && typeof options === 'undefined') { 122 | options = success; 123 | success = undefined; 124 | } 125 | 126 | options = getThumbnailOptionsWithDefaults(options); 127 | 128 | var urlParams = 'photoId=' + fixedEncodeURIComponent(photoId) + 129 | '&width=' + fixedEncodeURIComponent(options.thumbnailWidth) + 130 | '&height=' + fixedEncodeURIComponent(options.thumbnailHeight) + 131 | '&quality=' + fixedEncodeURIComponent(options.quality); 132 | var thumbnailURL = 'cdvphotolibrary://thumbnail?' + urlParams; 133 | 134 | if (success) { 135 | if (isBrowser) { 136 | cordova.exec(function(thumbnailURL) { success(thumbnailURL + '#' + urlParams); }, error, 'PhotoLibrary', '_getThumbnailURLBrowser', [photoId, options]); 137 | } else { 138 | success(thumbnailURL); 139 | } 140 | } else { 141 | return thumbnailURL; 142 | } 143 | 144 | }; 145 | 146 | // Generates url that can be accessed directly, so it will work more efficiently than getPhoto, which does base64 encode/decode. 147 | // If success callback not provided, will return value immediately, but use overload with success as it browser-friendly 148 | photoLibrary.getPhotoURL = function (photoIdOrLibraryItem, success, error, options) { 149 | 150 | var photoId = typeof photoIdOrLibraryItem.id !== 'undefined' ? photoIdOrLibraryItem.id : photoIdOrLibraryItem; 151 | 152 | if (typeof success !== 'function' && typeof options === 'undefined') { 153 | options = success; 154 | success = undefined; 155 | } 156 | 157 | if (!options) { 158 | options = {}; 159 | } 160 | 161 | var urlParams = 'photoId=' + fixedEncodeURIComponent(photoId); 162 | var photoURL = 'cdvphotolibrary://photo?' + urlParams; 163 | 164 | if (success) { 165 | if (isBrowser) { 166 | cordova.exec(function(photoURL) { success(photoURL + '#' + urlParams); }, error, 'PhotoLibrary', '_getPhotoURLBrowser', [photoId, options]); 167 | } else { 168 | success(photoURL); 169 | } 170 | } else { 171 | return photoURL; 172 | } 173 | 174 | }; 175 | 176 | // Provide same size as when calling getLibrary for better performance 177 | photoLibrary.getThumbnail = function (photoIdOrLibraryItem, success, error, options) { 178 | 179 | var photoId = typeof photoIdOrLibraryItem.id !== 'undefined' ? photoIdOrLibraryItem.id : photoIdOrLibraryItem; 180 | 181 | options = getThumbnailOptionsWithDefaults(options); 182 | 183 | cordova.exec( 184 | function (data, mimeType) { 185 | var blob = dataAndMimeTypeToBlob(data, mimeType); 186 | success(blob); 187 | }, 188 | error, 189 | 'PhotoLibrary', 190 | 'getThumbnail', [photoId, options] 191 | ); 192 | 193 | }; 194 | 195 | photoLibrary.getPhoto = function (photoIdOrLibraryItem, success, error, options) { 196 | 197 | var photoId = typeof photoIdOrLibraryItem.id !== 'undefined' ? photoIdOrLibraryItem.id : photoIdOrLibraryItem; 198 | 199 | if (!options) { 200 | options = {}; 201 | } 202 | 203 | cordova.exec( 204 | function (data, mimeType) { 205 | var blob = dataAndMimeTypeToBlob(data, mimeType); 206 | success(blob); 207 | }, 208 | error, 209 | 'PhotoLibrary', 210 | 'getPhoto', [photoId, options] 211 | ); 212 | 213 | }; 214 | 215 | photoLibrary.getLibraryItem = function (libraryItem, success, error, options) { 216 | 217 | if (!options) { 218 | options = {}; 219 | } 220 | 221 | cordova.exec( 222 | function (data, mimeType) { 223 | var blob = dataAndMimeTypeToBlob(data, mimeType); 224 | success(blob); 225 | }, 226 | error, 227 | 'PhotoLibrary', 228 | 'getLibraryItem', [libraryItem, options] 229 | ); 230 | 231 | }; 232 | 233 | // Call when thumbnails are not longer needed for better performance 234 | photoLibrary.stopCaching = function (success, error) { 235 | 236 | cordova.exec( 237 | success, 238 | error, 239 | 'PhotoLibrary', 240 | 'stopCaching', [] 241 | ); 242 | 243 | }; 244 | 245 | // Call when getting errors that begin with 'Permission Denial' 246 | photoLibrary.requestAuthorization = function (success, error, options) { 247 | 248 | options = getRequestAuthenticationOptionsWithDefaults(options); 249 | 250 | cordova.exec( 251 | success, 252 | error, 253 | 'PhotoLibrary', 254 | 'requestAuthorization', [options] 255 | ); 256 | 257 | }; 258 | 259 | // url is file url or dataURL 260 | photoLibrary.saveImage = function (url, album, success, error, options) { 261 | 262 | options = getThumbnailOptionsWithDefaults(options); 263 | 264 | if (album.title) { 265 | album = album.title; 266 | } 267 | 268 | cordova.exec( 269 | function (libraryItem) { 270 | var library = libraryItem ? [libraryItem] : []; 271 | 272 | processLibrary(library, function(library) { 273 | success(library[0] || null); 274 | }, options); 275 | 276 | }, 277 | error, 278 | 'PhotoLibrary', 279 | 'saveImage', [url, album] 280 | ); 281 | 282 | }; 283 | 284 | // url is file url or dataURL 285 | photoLibrary.saveVideo = function (url, album, success, error) { 286 | 287 | if (album.title) { 288 | album = album.title; 289 | } 290 | 291 | cordova.exec( 292 | success, 293 | error, 294 | 'PhotoLibrary', 295 | 'saveVideo', [url, album] 296 | ); 297 | 298 | }; 299 | 300 | module.exports = photoLibrary; 301 | 302 | var getThumbnailOptionsWithDefaults = function (options) { 303 | 304 | if (!options) { 305 | options = {}; 306 | } 307 | 308 | options = { 309 | thumbnailWidth: options.thumbnailWidth || defaultThumbnailWidth, 310 | thumbnailHeight: options.thumbnailHeight || defaultThumbnailHeight, 311 | quality: options.quality || defaultQuality, 312 | }; 313 | 314 | return options; 315 | 316 | }; 317 | 318 | var getRequestAuthenticationOptionsWithDefaults = function (options) { 319 | 320 | if (!options) { 321 | options = {}; 322 | } 323 | 324 | options = { 325 | read: options.read || true, 326 | write: options.write || false, 327 | }; 328 | 329 | return options; 330 | 331 | }; 332 | 333 | var processLibrary = function (library, success, options) { 334 | 335 | parseDates(library); 336 | 337 | addUrlsToLibrary(library, success, options); 338 | 339 | }; 340 | 341 | var parseDates = function (library) { 342 | var i; 343 | for (i = 0; i < library.length; i++) { 344 | var libraryItem = library[i]; 345 | if (libraryItem.creationDate) { 346 | libraryItem.creationDate = new Date(libraryItem.creationDate); 347 | } 348 | } 349 | }; 350 | 351 | var addUrlsToLibrary = function (library, callback, options) { 352 | 353 | var urlsLeft = library.length; 354 | 355 | var handlePhotoURL = function (libraryItem, photoURL) { 356 | libraryItem.photoURL = photoURL; 357 | urlsLeft -= 1; 358 | if (urlsLeft === 0) { 359 | callback(library); 360 | } 361 | }; 362 | 363 | var handleThumbnailURL = function (libraryItem, thumbnailURL) { 364 | libraryItem.thumbnailURL = thumbnailURL; 365 | photoLibrary.getPhotoURL(libraryItem, handlePhotoURL.bind(null, libraryItem), handleUrlError); 366 | }; 367 | 368 | var handleUrlError = function () {}; // Should never happen 369 | 370 | var i; 371 | for (i = 0; i < library.length; i++) { 372 | var libraryItem = library[i]; 373 | photoLibrary.getThumbnailURL(libraryItem, handleThumbnailURL.bind(null, libraryItem), handleUrlError, options); 374 | } 375 | 376 | }; 377 | 378 | var dataAndMimeTypeToBlob = function (data, mimeType) { 379 | if (!mimeType && data.data && data.mimeType) { 380 | // workaround for browser platform cannot return multipart result 381 | mimeType = data.mimeType; 382 | data = data.data; 383 | } 384 | if (typeof data === 'string') { 385 | // workaround for data arrives as base64 instead of arrayBuffer, with cordova-android 6.x 386 | data = cordova.require('cordova/base64').toArrayBuffer(data); 387 | } 388 | var blob = new Blob([data], { 389 | type: mimeType 390 | }); 391 | 392 | return blob; 393 | }; 394 | 395 | // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 396 | function fixedEncodeURIComponent(str) { 397 | return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { 398 | return '%' + c.charCodeAt(0).toString(16); 399 | }); 400 | } 401 | -------------------------------------------------------------------------------- /www/async/dist/async.min.js: -------------------------------------------------------------------------------- 1 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.async=n.async||{})}(this,function(n){"use strict";function t(n,t,e){switch(e.length){case 0:return n.call(t);case 1:return n.call(t,e[0]);case 2:return n.call(t,e[0],e[1]);case 3:return n.call(t,e[0],e[1],e[2])}return n.apply(t,e)}function e(n,e,r){return e=rt(void 0===e?n.length-1:e,0),function(){for(var u=arguments,o=-1,i=rt(u.length-e,0),c=Array(i);++o-1&&n%1==0&&n<=kt}function p(n){return null!=n&&s(n.length)&&!l(n)}function h(){}function y(n){return function(){if(null!==n){var t=n;n=null,t.apply(this,arguments)}}}function v(n,t){for(var e=-1,r=Array(n);++e-1&&n%1==0&&nu?0:u+t),e=e>u?u:e,e<0&&(e+=u),u=t>e?0:e-t>>>0,t>>>=0;for(var o=Array(u);++r=r?n:K(n,t,e)}function Y(n,t){for(var e=n.length;e--&&Q(t,n[e],0)>-1;);return e}function Z(n,t){for(var e=-1,r=n.length;++e-1;);return e}function nn(n){return n.split("")}function tn(n){return We.test(n)}function en(n){return n.match(fr)||[]}function rn(n){return tn(n)?en(n):nn(n)}function un(n){return null==n?"":J(n)}function on(n,t,e){if(n=un(n),n&&(e||void 0===t))return n.replace(ar,"");if(!n||!(t=J(t)))return n;var r=rn(n),u=rn(t),o=Z(r,u),i=Y(r,u)+1;return X(r,o,i).join("")}function cn(n){return n=n.toString().replace(hr,""),n=n.match(lr)[2].replace(" ",""),n=n?n.split(sr):[],n=n.map(function(n){return on(n.replace(pr,""))})}function fn(n,t){var e={};C(n,function(n,t){function r(t,e){var r=G(u,function(n){return t[n]});r.push(e),n.apply(null,r)}var u;if(Ft(n))u=n.slice(0,-1),n=n[n.length-1],e[t]=u.concat(u.length>0?r:n);else if(1===n.length)e[t]=n;else{if(u=cn(n),0===n.length&&0===u.length)throw new Error("autoInject task functions require explicit parameters.");u.pop(),e[t]=u.concat(r)}}),Me(e,t)}function an(n){setTimeout(n,0)}function ln(n){return u(function(t,e){n(function(){t.apply(null,e)})})}function sn(){this.head=this.tail=null,this.length=0}function pn(n,t){n.length=1,n.head=n.tail=t}function hn(n,t,e){function r(n,t,e){if(null!=e&&"function"!=typeof e)throw new Error("task callback must be a function");if(f.started=!0,Ft(n)||(n=[n]),0===n.length&&f.idle())return dr(function(){f.drain()});for(var r=0,u=n.length;r=0&&c.splice(o),u.callback.apply(u,t),null!=t[0]&&f.error(t[0],u.data)}i<=f.concurrency-f.buffer&&f.unsaturated(),f.idle()&&f.drain(),f.process()})}if(null==t)t=1;else if(0===t)throw new Error("Concurrency must not be zero");var i=0,c=[],f={_tasks:new sn,concurrency:t,payload:e,saturated:h,unsaturated:h,buffer:t/4,empty:h,drain:h,error:h,started:!1,paused:!1,push:function(n,t){r(n,!1,t)},kill:function(){f.drain=h,f._tasks.empty()},unshift:function(n,t){r(n,!0,t)},process:function(){for(;!f.paused&&i3?(i=i||h,n(r,u,f,c)):(i=o,i=i||h,o=u,n(r,f,c))}}function bn(n,t){return t}function jn(n){return u(function(t,e){t.apply(null,e.concat([u(function(t,e){"object"==typeof console&&(t?console.error&&console.error(t):console[n]&&D(e,function(t){console[n](t)}))})]))})}function Sn(n,t,e){function r(t,r){return t?e(t):r?void n(o):e(null)}e=I(e||h);var o=u(function(n,u){return n?e(n):(u.push(r),void t.apply(this,u))});r(null,!0)}function kn(n,t,e){e=I(e||h);var r=u(function(u,o){return u?e(u):t.apply(this,o)?n(r):void e.apply(null,[null].concat(o))});n(r)}function wn(n,t,e){kn(n,function(){return!t.apply(this,arguments)},e)}function xn(n,t,e){function r(t){return t?e(t):void n(u)}function u(n,u){return n?e(n):u?void t(r):e(null)}e=I(e||h),n(u)}function On(n){return function(t,e,r){return n(t,r)}}function En(n,t,e){Ee(n,On(t),e)}function Ln(n,t,e,r){_(t)(n,On(e),r)}function An(n){return ut(function(t,e){var r=!0;t.push(function(){var n=arguments;r?dr(function(){e.apply(null,n)}):e.apply(null,n)}),n.apply(this,t),r=!1})}function Tn(n){return!n}function Fn(n){return function(t){return null==t?void 0:t[n]}}function In(n,t,e,r){var u=new Array(t.length);n(t,function(n,t,r){e(n,function(n,e){u[t]=!!e,r(n)})},function(n){if(n)return r(n);for(var e=[],o=0;o1&&(r=t),e(null,{value:r})}})),n.apply(this,t)})}function Wn(n,t,e,r){Bn(n,t,function(n,t){e(n,function(n,e){t(n,!e)})},r)}function Nn(n){var t;return Ft(n)?t=G(n,$n):(t={},C(n,function(n,e){t[e]=$n.call(this,n)})),t}function Qn(n){return function(){return n}}function Gn(n,t,e){function r(n,t){if("object"==typeof t)n.times=+t.times||o,n.intervalFunc="function"==typeof t.interval?t.interval:Qn(+t.interval||i),n.errorFilter=t.errorFilter;else{if("number"!=typeof t&&"string"!=typeof t)throw new Error("Invalid arguments for async.retry");n.times=+t||o}}function u(){t(function(n){n&&f++r?1:0}Le(n,function(n,e){t(n,function(t,r){return t?e(t):void e(null,{value:n,criteria:r})})},function(n,t){return n?e(n):void e(null,G(t.sort(r),Fn("value")))})}function Kn(n,t,e){function r(){c||(o.apply(null,arguments),clearTimeout(i))}function u(){var t=n.name||"anonymous",r=new Error('Callback function "'+t+'" timed out.');r.code="ETIMEDOUT",e&&(r.info=e),c=!0,o(r)}var o,i,c=!1;return ut(function(e,c){o=c,i=setTimeout(u,t),n.apply(null,e.concat(r))})}function Xn(n,t,e,r){for(var u=-1,o=Kr(Jr((t-n)/(e||1)),0),i=Array(o);o--;)i[r?o:++u]=n,n+=e;return i}function Yn(n,t,e,r){Te(Xn(0,n,1),t,e,r)}function Zn(n,t,e,r){3===arguments.length&&(r=e,e=t,t=Ft(n)?[]:{}),r=y(r||h),Ee(n,function(n,r,u){e(t,n,r,u)},function(n){r(n,t)})}function nt(n){return function(){return(n.unmemoized||n).apply(null,arguments)}}function tt(n,t,e){if(e=I(e||h),!n())return e(null);var r=u(function(u,o){return u?e(u):n()?t(r):void e.apply(null,[null].concat(o))});t(r)}function et(n,t,e){tt(function(){return!n.apply(this,arguments)},t,e)}var rt=Math.max,ut=function(n){return u(function(t){var e=t.pop();n.call(this,t,e)})},ot="object"==typeof global&&global&&global.Object===Object&&global,it="object"==typeof self&&self&&self.Object===Object&&self,ct=ot||it||Function("return this")(),ft=ct.Symbol,at=Object.prototype,lt=at.hasOwnProperty,st=at.toString,pt=ft?ft.toStringTag:void 0,ht=Object.prototype,yt=ht.toString,vt="[object Null]",dt="[object Undefined]",mt=ft?ft.toStringTag:void 0,gt="[object AsyncFunction]",bt="[object Function]",jt="[object GeneratorFunction]",St="[object Proxy]",kt=9007199254740991,wt="function"==typeof Symbol&&Symbol.iterator,xt=function(n){return wt&&n[wt]&&n[wt]()},Ot="[object Arguments]",Et=Object.prototype,Lt=Et.hasOwnProperty,At=Et.propertyIsEnumerable,Tt=m(function(){return arguments}())?m:function(n){return d(n)&&Lt.call(n,"callee")&&!At.call(n,"callee")},Ft=Array.isArray,It="object"==typeof n&&n&&!n.nodeType&&n,_t=It&&"object"==typeof module&&module&&!module.nodeType&&module,Bt=_t&&_t.exports===It,Mt=Bt?ct.Buffer:void 0,Ut=Mt?Mt.isBuffer:void 0,zt=Ut||g,Vt=9007199254740991,Pt=/^(?:0|[1-9]\d*)$/,qt="[object Arguments]",Dt="[object Array]",Rt="[object Boolean]",Ct="[object Date]",$t="[object Error]",Wt="[object Function]",Nt="[object Map]",Qt="[object Number]",Gt="[object Object]",Ht="[object RegExp]",Jt="[object Set]",Kt="[object String]",Xt="[object WeakMap]",Yt="[object ArrayBuffer]",Zt="[object DataView]",ne="[object Float32Array]",te="[object Float64Array]",ee="[object Int8Array]",re="[object Int16Array]",ue="[object Int32Array]",oe="[object Uint8Array]",ie="[object Uint8ClampedArray]",ce="[object Uint16Array]",fe="[object Uint32Array]",ae={};ae[ne]=ae[te]=ae[ee]=ae[re]=ae[ue]=ae[oe]=ae[ie]=ae[ce]=ae[fe]=!0,ae[qt]=ae[Dt]=ae[Yt]=ae[Rt]=ae[Zt]=ae[Ct]=ae[$t]=ae[Wt]=ae[Nt]=ae[Qt]=ae[Gt]=ae[Ht]=ae[Jt]=ae[Kt]=ae[Xt]=!1;var le,se="object"==typeof n&&n&&!n.nodeType&&n,pe=se&&"object"==typeof module&&module&&!module.nodeType&&module,he=pe&&pe.exports===se,ye=he&&ot.process,ve=function(){try{return ye&&ye.binding("util")}catch(n){}}(),de=ve&&ve.isTypedArray,me=de?S(de):j,ge=Object.prototype,be=ge.hasOwnProperty,je=Object.prototype,Se=x(Object.keys,Object),ke=Object.prototype,we=ke.hasOwnProperty,xe={},Oe=M(B,1/0),Ee=function(n,t,e){var r=p(n)?U:Oe;r(n,t,e)},Le=z(V),Ae=o(Le),Te=P(V),Fe=M(Te,1),Ie=o(Fe),_e=u(function(n,t){return u(function(e){return n.apply(null,t.concat(e))})}),Be=R(),Me=function(n,t,e){function r(n,t){b.push(function(){f(n,t)})}function o(){if(0===b.length&&0===d)return e(null,v);for(;b.length&&d1?o(v,r):o(r)}}function a(){for(var n,t=0;j.length;)n=j.pop(),t++,D(l(n),function(n){0===--S[n]&&j.push(n)});if(t!==p)throw new Error("async.auto cannot execute tasks due to a recursive dependency")}function l(t){var e=[];return C(n,function(n,r){Ft(n)&&Q(n,t,0)>=0&&e.push(r)}),e}"function"==typeof t&&(e=t,t=null),e=y(e||h);var s=E(n),p=s.length;if(!p)return e(null);t||(t=p);var v={},d=0,m=!1,g={},b=[],j=[],S={};C(n,function(t,e){if(!Ft(t))return r(e,[t]),void j.push(e);var u=t.slice(0,t.length-1),o=u.length;return 0===o?(r(e,t),void j.push(e)):(S[e]=o,void D(u,function(c){if(!n[c])throw new Error("async.auto task `"+e+"` has a non-existent dependency in "+u.join(", "));i(c,function(){o--,0===o&&r(e,t)})}))}),a(),o()},Ue="[object Symbol]",ze=1/0,Ve=ft?ft.prototype:void 0,Pe=Ve?Ve.toString:void 0,qe="\\ud800-\\udfff",De="\\u0300-\\u036f\\ufe20-\\ufe23",Re="\\u20d0-\\u20f0",Ce="\\ufe0e\\ufe0f",$e="\\u200d",We=RegExp("["+$e+qe+De+Re+Ce+"]"),Ne="\\ud800-\\udfff",Qe="\\u0300-\\u036f\\ufe20-\\ufe23",Ge="\\u20d0-\\u20f0",He="\\ufe0e\\ufe0f",Je="["+Ne+"]",Ke="["+Qe+Ge+"]",Xe="\\ud83c[\\udffb-\\udfff]",Ye="(?:"+Ke+"|"+Xe+")",Ze="[^"+Ne+"]",nr="(?:\\ud83c[\\udde6-\\uddff]){2}",tr="[\\ud800-\\udbff][\\udc00-\\udfff]",er="\\u200d",rr=Ye+"?",ur="["+He+"]?",or="(?:"+er+"(?:"+[Ze,nr,tr].join("|")+")"+ur+rr+")*",ir=ur+rr+or,cr="(?:"+[Ze+Ke+"?",Ke,nr,tr,Je].join("|")+")",fr=RegExp(Xe+"(?="+Xe+")|"+cr+ir,"g"),ar=/^\s+|\s+$/g,lr=/^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m,sr=/,/,pr=/(=.+)?(\s*)$/,hr=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,yr="function"==typeof setImmediate&&setImmediate,vr="object"==typeof process&&"function"==typeof process.nextTick;le=yr?setImmediate:vr?process.nextTick:an;var dr=ln(le);sn.prototype.removeLink=function(n){return n.prev?n.prev.next=n.next:this.head=n.next,n.next?n.next.prev=n.prev:this.tail=n.prev,n.prev=n.next=null,this.length-=1,n},sn.prototype.empty=sn,sn.prototype.insertAfter=function(n,t){t.prev=n,t.next=n.next,n.next?n.next.prev=t:this.tail=t,n.next=t,this.length+=1},sn.prototype.insertBefore=function(n,t){t.prev=n.prev,t.next=n,n.prev?n.prev.next=t:this.head=t,n.prev=t,this.length+=1},sn.prototype.unshift=function(n){this.head?this.insertBefore(this.head,n):pn(this,n)},sn.prototype.push=function(n){this.tail?this.insertAfter(this.tail,n):pn(this,n)},sn.prototype.shift=function(){return this.head&&this.removeLink(this.head)},sn.prototype.pop=function(){return this.tail&&this.removeLink(this.tail)};var mr,gr=M(B,1),br=u(function(n){return u(function(t){var e=this,r=t[t.length-1];"function"==typeof r?t.pop():r=h,vn(n,t,function(n,t,r){t.apply(e,n.concat([u(function(n,t){r(n,t)})]))},function(n,t){r.apply(e,[n].concat(t))})})}),jr=u(function(n){return br.apply(null,n.reverse())}),Sr=z(dn),kr=mn(dn),wr=u(function(n){var t=[null].concat(n);return ut(function(n,e){return e.apply(this,t)})}),xr=gn(Ee,r,bn),Or=gn(B,r,bn),Er=gn(gr,r,bn),Lr=jn("dir"),Ar=M(Ln,1),Tr=gn(Ee,Tn,Tn),Fr=gn(B,Tn,Tn),Ir=M(Fr,1),_r=z(Bn),Br=P(Bn),Mr=M(Br,1),Ur=jn("log"),zr=M(Un,1/0),Vr=M(Un,1);mr=vr?process.nextTick:yr?setImmediate:an;var Pr=ln(mr),qr=function(n,t){return hn(function(t,e){n(t[0],e)},t,1)},Dr=function(n,t){var e=qr(n,t);return e.push=function(n,t,r){if(null==r&&(r=h),"function"!=typeof r)throw new Error("task callback must be a function");if(e.started=!0,Ft(n)||(n=[n]),0===n.length)return dr(function(){e.drain()});t=t||0;for(var u=e._tasks.head;u&&t>=u.priority;)u=u.next;for(var o=0,i=n.length;o