2016-07-17 20:04:06 +02:00
import { get } from '../util' ;
2016-09-21 15:04:46 -05:00
import { Observable } from 'rxjs/Observable' ;
2015-11-28 18:26:55 -06:00
declare var window ;
declare var Promise ;
2015-11-30 12:34:54 -06:00
2016-03-12 18:52:25 -05:00
/**
* @private
* @param pluginRef
* @returns {null|*}
*/
2016-04-25 06:22:17 -04:00
export const getPlugin = function ( pluginRef : string ) : any {
2015-11-29 17:20:11 -06:00
return get ( window , pluginRef ) ;
2016-03-06 15:27:26 -05:00
} ;
2016-03-12 18:52:25 -05:00
/**
* @private
* @param pluginObj
* @param method
*/
2016-09-25 18:17:03 -05:00
export const pluginWarn = function ( pluginObj : any , method? : string ) {
2016-10-27 12:48:50 -05:00
let pluginName = pluginObj . pluginName , plugin = pluginObj . plugin ;
2016-07-18 11:39:53 -04:00
if ( method ) {
console . warn ( 'Native: tried calling ' + pluginName + '.' + method + ', but the ' + pluginName + ' plugin is not installed.' ) ;
} else {
console . warn ( 'Native: tried accessing the ' + pluginName + ' plugin but it\'s not installed.' ) ;
}
2016-06-11 10:35:41 -04:00
console . warn ( 'Install the ' + pluginName + ' plugin: \'ionic plugin add ' + plugin + '\'' ) ;
2016-03-06 15:27:26 -05:00
} ;
2016-03-12 18:52:25 -05:00
/**
* @private
* @param pluginName
* @param method
*/
2016-04-25 06:22:17 -04:00
export const cordovaWarn = function ( pluginName : string , method : string ) {
2016-07-18 11:39:53 -04:00
if ( method ) {
console . warn ( 'Native: tried calling ' + pluginName + '.' + method + ', but Cordova is not available. Make sure to include cordova.js or run in a device/simulator' ) ;
} else {
console . warn ( 'Native: tried accessing the ' + pluginName + ' plugin but Cordova is not available. Make sure to include cordova.js or run in a device/simulator' ) ;
}
2016-03-06 15:27:26 -05:00
} ;
2016-07-17 20:04:06 +02:00
function setIndex ( args : any [ ] , opts : any = { } , resolve? : Function , reject? : Function ) : any {
2016-10-08 16:07:01 -03:00
// ignore resolve and reject in case sync
if ( opts . sync ) {
return args ;
}
2016-04-29 23:56:49 -04:00
// If the plugin method expects myMethod(success, err, options)
if ( opts . callbackOrder === 'reverse' ) {
// Get those arguments in the order [resolve, reject, ...restOfArgs]
args . unshift ( reject ) ;
args . unshift ( resolve ) ;
2016-08-19 14:08:05 +01:00
} else if ( opts . callbackStyle === 'node' ) {
args . push ( ( err , result ) = > {
if ( err ) {
reject ( err ) ;
} else {
resolve ( result ) ;
}
} ) ;
2016-08-27 14:43:57 -04:00
} else if ( opts . callbackStyle === 'object' && opts . successName && opts . errorName ) {
let obj : any = { } ;
obj [ opts . successName ] = resolve ;
obj [ opts . errorName ] = reject ;
args . push ( obj ) ;
2016-04-29 23:56:49 -04:00
} else if ( typeof opts . successIndex !== 'undefined' || typeof opts . errorIndex !== 'undefined' ) {
2016-10-27 07:59:33 -04:00
const setSuccessIndex = ( ) = > {
// If we've specified a success/error index
if ( opts . successIndex > args . length ) {
args [ opts . successIndex ] = resolve ;
} else {
args . splice ( opts . successIndex , 0 , resolve ) ;
}
} ;
2016-10-05 23:19:45 -04:00
2016-10-27 07:59:33 -04:00
const setErrorIndex = ( ) = > {
// We don't want that the reject cb gets spliced into the position of an optional argument that has not been defined and thus causing non expected behaviour.
if ( opts . errorIndex > args . length ) {
args [ opts . errorIndex ] = reject ; // insert the reject fn at the correct specific index
} else {
args . splice ( opts . errorIndex , 0 , reject ) ; // otherwise just splice it into the array
}
} ;
2016-08-17 13:34:11 +02:00
2016-10-27 09:17:11 -04:00
if ( opts . successIndex > opts . errorIndex ) {
2016-10-27 07:59:33 -04:00
setErrorIndex ( ) ;
setSuccessIndex ( ) ;
2016-08-17 13:34:11 +02:00
} else {
2016-10-27 07:59:33 -04:00
setSuccessIndex ( ) ;
setErrorIndex ( ) ;
2016-08-17 13:34:11 +02:00
}
2016-10-27 07:59:33 -04:00
2016-04-29 23:56:49 -04:00
} else {
// Otherwise, let's tack them on to the end of the argument list
// which is 90% of cases
2016-10-08 16:07:01 -03:00
args . push ( resolve ) ;
args . push ( reject ) ;
2016-04-29 23:56:49 -04:00
}
return args ;
2016-03-29 06:50:03 -04:00
}
2016-03-29 17:53:51 -04:00
2016-07-17 20:04:06 +02:00
function callCordovaPlugin ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } , resolve? : Function , reject? : Function ) {
2015-11-30 12:34:54 -06:00
// Try to figure out where the success/error callbacks need to be bound
// to our promise resolve/reject handlers.
2016-07-17 20:04:06 +02:00
args = setIndex ( args , opts , resolve , reject ) ;
2015-11-30 12:34:54 -06:00
let pluginInstance = getPlugin ( pluginObj . pluginRef ) ;
2016-04-29 23:56:49 -04:00
if ( ! pluginInstance ) {
2016-04-29 21:46:35 -04:00
// Do this check in here in the case that the Web API for this plugin is available (for example, Geolocation).
2016-04-29 23:56:49 -04:00
if ( ! window . cordova ) {
2016-10-27 12:48:50 -05:00
cordovaWarn ( pluginObj . pluginName , methodName ) ;
2016-04-29 21:46:35 -04:00
return {
error : 'cordova_not_available'
2016-04-29 23:56:49 -04:00
} ;
2016-04-29 21:46:35 -04:00
}
2015-11-30 12:34:54 -06:00
2016-04-29 21:46:35 -04:00
pluginWarn ( pluginObj , methodName ) ;
return {
error : 'plugin_not_installed'
} ;
}
2015-11-30 12:34:54 -06:00
2015-11-30 13:27:25 -06:00
// TODO: Illegal invocation needs window context
return get ( window , pluginObj . pluginRef ) [ methodName ] . apply ( pluginInstance , args ) ;
2015-11-30 12:34:54 -06:00
}
2016-12-04 11:47:28 -05:00
/**
* @private
*/
export function getPromise ( cb ) {
2016-09-02 18:16:57 +01:00
const tryNativePromise = ( ) = > {
if ( window . Promise ) {
return new Promise ( ( resolve , reject ) = > {
cb ( resolve , reject ) ;
} ) ;
} else {
console . error ( 'No Promise support or polyfill found. To enable Ionic Native support, please add the es6-promise polyfill before this script, or run with a library like Angular 1/2 or on a recent browser.' ) ;
}
} ;
2016-07-31 19:24:56 +01:00
if ( window . angular ) {
2016-09-02 18:16:57 +01:00
let injector = window . angular . element ( document . querySelector ( '[ng-app]' ) || document . body ) . injector ( ) ;
if ( injector ) {
let $q = injector . get ( '$q' ) ;
return $q ( ( resolve , reject ) = > {
cb ( resolve , reject ) ;
} ) ;
} else {
console . warn ( 'Angular 1 was detected but $q couldn\'t be retrieved. This is usually when the app is not bootstrapped on the html or body tag. Falling back to native promises which won\'t trigger an automatic digest when promises resolve.' ) ;
return tryNativePromise ( ) ;
}
2015-12-01 13:33:08 -06:00
} else {
2016-09-02 18:16:57 +01:00
return tryNativePromise ( ) ;
2015-12-01 13:33:08 -06:00
}
}
2016-07-17 20:04:06 +02:00
function wrapPromise ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } ) {
2016-04-27 13:00:57 -05:00
let pluginResult , rej ;
const p = getPromise ( ( resolve , reject ) = > {
pluginResult = callCordovaPlugin ( pluginObj , methodName , args , opts , resolve , reject ) ;
rej = reject ;
} ) ;
// Angular throws an error on unhandled rejection, but in this case we have already printed
// a warning that Cordova is undefined or the plugin is uninstalled, so there is no reason
// to error
if ( pluginResult && pluginResult . error ) {
2016-07-17 20:04:06 +02:00
p . catch ( ( ) = > { } ) ;
2016-04-27 13:00:57 -05:00
rej ( pluginResult . error ) ;
}
return p ;
2015-11-30 12:34:54 -06:00
}
2016-07-18 10:51:39 -04:00
function wrapOtherPromise ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } ) {
return getPromise ( ( resolve , reject ) = > {
let pluginResult = callCordovaPlugin ( pluginObj , methodName , args , opts ) ;
if ( pluginResult && pluginResult . error ) {
reject ( pluginResult . error ) ;
}
pluginResult . then ( resolve ) . catch ( reject ) ;
} ) ;
}
2016-04-29 23:56:49 -04:00
function wrapObservable ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } ) {
2015-11-30 13:27:25 -06:00
return new Observable ( observer = > {
2015-11-30 14:38:52 -06:00
let pluginResult = callCordovaPlugin ( pluginObj , methodName , args , opts , observer . next . bind ( observer ) , observer . error . bind ( observer ) ) ;
2016-04-27 13:00:57 -05:00
if ( pluginResult && pluginResult . error ) {
observer . error ( pluginResult . error ) ;
}
2015-11-30 12:34:54 -06:00
return ( ) = > {
2015-11-30 14:38:52 -06:00
try {
2016-06-10 14:00:35 -05:00
if ( opts . clearFunction ) {
if ( opts . clearWithArgs ) {
return get ( window , pluginObj . pluginRef ) [ opts . clearFunction ] . apply ( pluginObj , args ) ;
}
return get ( window , pluginObj . pluginRef ) [ opts . clearFunction ] . call ( pluginObj , pluginResult ) ;
2016-02-08 12:42:42 -06:00
}
2016-04-29 23:56:49 -04:00
} catch ( e ) {
2016-10-27 12:48:50 -05:00
console . warn ( 'Unable to clear the previous observable watch for' , pluginObj . pluginName , methodName ) ;
2016-03-04 13:53:59 -06:00
console . error ( e ) ;
2015-11-30 14:38:52 -06:00
}
2016-04-29 23:56:49 -04:00
} ;
2015-11-30 12:34:54 -06:00
} ) ;
}
2016-04-29 23:56:49 -04:00
function callInstance ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } , resolve? : Function , reject? : Function ) {
args = setIndex ( args , opts , resolve , reject ) ;
return pluginObj . _objectInstance [ methodName ] . apply ( pluginObj . _objectInstance , args ) ;
2016-03-29 06:50:03 -04:00
}
2016-07-17 20:04:06 +02:00
function wrapInstance ( pluginObj : any , methodName : string , opts : any = { } ) {
2016-04-29 21:46:35 -04:00
return ( . . . args ) = > {
2016-04-25 06:22:17 -04:00
if ( opts . sync ) {
2016-07-14 10:42:56 -05:00
// Sync doesn't wrap the plugin with a promise or observable, it returns the result as-is
2016-04-29 21:46:35 -04:00
return callInstance ( pluginObj , methodName , args , opts ) ;
2016-04-25 06:22:17 -04:00
} else if ( opts . observable ) {
2016-04-29 21:46:35 -04:00
return new Observable ( observer = > {
2016-04-29 23:56:49 -04:00
let pluginResult = callInstance ( pluginObj , methodName , args , opts , observer . next . bind ( observer ) , observer . error . bind ( observer ) ) ;
2016-04-29 21:46:35 -04:00
return ( ) = > {
try {
2016-04-29 23:56:49 -04:00
if ( opts . clearWithArgs ) {
2016-04-29 21:46:35 -04:00
return pluginObj . _objectInstance [ opts . clearFunction ] . apply ( pluginObj . _objectInstance , args ) ;
2016-04-25 06:22:17 -04:00
}
2016-04-29 21:46:35 -04:00
return pluginObj . _objectInstance [ opts . clearFunction ] . call ( pluginObj , pluginResult ) ;
2016-04-29 23:56:49 -04:00
} catch ( e ) {
2016-10-27 12:48:50 -05:00
console . warn ( 'Unable to clear the previous observable watch for' , pluginObj . pluginName , methodName ) ;
2016-04-29 21:46:35 -04:00
console . error ( e ) ;
2016-03-29 17:49:53 -04:00
}
2016-04-29 23:56:49 -04:00
} ;
2016-04-29 21:46:35 -04:00
} ) ;
2016-07-18 11:39:53 -04:00
} else if ( opts . otherPromise ) {
return getPromise ( ( resolve , reject ) = > {
let result = callInstance ( pluginObj , methodName , args , opts , resolve , reject ) ;
result . then ( resolve , reject ) ;
} ) ;
2016-04-25 06:22:17 -04:00
} else {
2016-04-29 21:46:35 -04:00
return getPromise ( ( resolve , reject ) = > {
callInstance ( pluginObj , methodName , args , opts , resolve , reject ) ;
} ) ;
2016-04-25 06:22:17 -04:00
}
2016-04-29 23:56:49 -04:00
} ;
2016-03-29 06:50:03 -04:00
}
2016-03-13 15:30:21 -04:00
/**
* Wrap the event with an observable
* @param event
* @returns {Observable}
*/
2016-07-17 20:04:06 +02:00
function wrapEventObservable ( event : string ) : Observable < any > {
2016-03-13 15:30:21 -04:00
return new Observable ( observer = > {
2016-07-17 08:54:39 -04:00
window . addEventListener ( event , observer . next . bind ( observer ) , false ) ;
return ( ) = > window . removeEventListener ( event , observer . next . bind ( observer ) , false ) ;
2016-03-13 15:30:21 -04:00
} ) ;
}
2016-09-24 17:26:23 -05:00
/**
* Certain plugins expect the user to override methods in the plugin. For example,
* window.cordova.plugins.backgroundMode.onactivate = function() { ... }.
*
* Unfortunately, this is brittle and would be better wrapped as an Observable. overrideFunction
* does just this.
*/
function overrideFunction ( pluginObj : any , methodName : string , args : any [ ] , opts : any = { } ) : Observable < any > {
return new Observable ( observer = > {
let pluginInstance = getPlugin ( pluginObj . pluginRef ) ;
if ( ! pluginInstance ) {
// Do this check in here in the case that the Web API for this plugin is available (for example, Geolocation).
if ( ! window . cordova ) {
2016-10-27 12:48:50 -05:00
cordovaWarn ( pluginObj . pluginName , methodName ) ;
2016-09-24 17:26:23 -05:00
observer . error ( {
error : 'cordova_not_available'
} ) ;
}
pluginWarn ( pluginObj , methodName ) ;
observer . error ( {
error : 'plugin_not_installed'
} ) ;
return ;
}
let method = pluginInstance [ methodName ] ;
if ( ! method ) {
observer . error ( {
error : 'no_such_method'
} ) ;
observer . complete ( ) ;
return ;
}
pluginInstance [ methodName ] = observer . next . bind ( observer ) ;
} ) ;
}
2016-03-12 18:52:25 -05:00
/**
* @private
* @param pluginObj
* @param methodName
* @param opts
* @returns {function(...[any]): (undefined|*|Observable|*|*)}
*/
2016-07-17 20:04:06 +02:00
export const wrap = function ( pluginObj : any , methodName : string , opts : any = { } ) {
2015-11-30 12:34:54 -06:00
return ( . . . args ) = > {
2016-07-18 11:39:53 -04:00
if ( opts . sync ) {
2016-07-14 10:42:56 -05:00
// Sync doesn't wrap the plugin with a promise or observable, it returns the result as-is
2016-02-09 14:45:57 -06:00
return callCordovaPlugin ( pluginObj , methodName , args , opts ) ;
2016-07-18 11:39:53 -04:00
} else if ( opts . observable ) {
2015-11-30 12:34:54 -06:00
return wrapObservable ( pluginObj , methodName , args , opts ) ;
2016-07-18 11:39:53 -04:00
} else if ( opts . eventObservable && opts . event ) {
2016-03-13 15:30:21 -04:00
return wrapEventObservable ( opts . event ) ;
2016-07-18 11:39:53 -04:00
} else if ( opts . otherPromise ) {
2016-07-18 10:51:39 -04:00
return wrapOtherPromise ( pluginObj , methodName , args , opts ) ;
2016-07-18 11:39:53 -04:00
} else {
2015-11-30 12:34:54 -06:00
return wrapPromise ( pluginObj , methodName , args , opts ) ;
2016-07-18 11:39:53 -04:00
}
2016-04-29 23:56:49 -04:00
} ;
2016-03-06 15:27:26 -05:00
} ;
2015-11-28 18:26:55 -06:00
2015-11-29 19:54:45 -06:00
/**
2016-03-12 18:52:25 -05:00
* @private
*
2015-11-29 19:54:45 -06:00
* Class decorator specifying Plugin metadata. Required for all plugins.
2016-03-12 18:30:16 -05:00
*
* @usage
2016-07-20 17:17:09 +02:00
* ```typescript
2016-03-12 18:30:16 -05:00
* @Plugin({
2016-10-27 12:48:50 -05:00
* pluginName: 'MyPlugin',
2016-03-12 18:30:16 -05:00
* plugin: 'cordova-plugin-myplugin',
* pluginRef: 'window.myplugin'
* })
* export class MyPlugin {
*
* // Plugin wrappers, properties, and functions go here ...
*
* }
* ```
2015-11-29 19:54:45 -06:00
*/
2015-11-28 18:26:55 -06:00
export function Plugin ( config ) {
2016-04-25 06:22:17 -04:00
return function ( cls ) {
2015-11-28 18:26:55 -06:00
// Add these fields to the class
2016-02-09 14:45:57 -06:00
for ( let k in config ) {
2015-11-28 18:26:55 -06:00
cls [ k ] = config [ k ] ;
}
2016-09-25 18:17:03 -05:00
cls [ 'installed' ] = function ( printWarning? : boolean ) {
2015-12-01 13:33:08 -06:00
return ! ! getPlugin ( config . pluginRef ) ;
2016-03-06 15:27:26 -05:00
} ;
2015-12-01 13:33:08 -06:00
2016-09-25 17:59:56 -05:00
cls [ 'getPlugin' ] = function ( ) {
return getPlugin ( config . pluginRef ) ;
} ;
2016-09-25 18:17:03 -05:00
cls [ 'checkInstall' ] = function ( ) {
let pluginInstance = getPlugin ( config . pluginRef ) ;
if ( ! pluginInstance ) {
pluginWarn ( cls ) ;
return false ;
}
return true ;
} ;
2015-11-28 18:26:55 -06:00
return cls ;
2016-04-29 23:56:49 -04:00
} ;
2015-11-28 18:26:55 -06:00
}
2015-11-29 19:54:45 -06:00
/**
2016-03-12 18:52:25 -05:00
* @private
*
2015-11-29 19:54:45 -06:00
* Wrap a stub function in a call to a Cordova plugin, checking if both Cordova
* and the required plugin are installed.
*/
2016-04-29 23:56:49 -04:00
export function Cordova ( opts : any = { } ) {
2016-04-25 06:22:17 -04:00
return ( target : Object , methodName : string , descriptor : TypedPropertyDescriptor < any > ) = > {
2015-11-29 19:54:45 -06:00
return {
2016-04-25 06:22:17 -04:00
value : function ( . . . args : any [ ] ) {
2015-11-29 21:50:58 -06:00
return wrap ( this , methodName , opts ) . apply ( this , args ) ;
2015-11-29 19:54:45 -06:00
}
2016-04-29 23:56:49 -04:00
} ;
} ;
2015-11-28 18:26:55 -06:00
}
2015-11-29 17:20:11 -06:00
2016-03-29 17:49:53 -04:00
/**
* @private
*
* Wrap an instance method
*/
2016-04-29 23:56:49 -04:00
export function CordovaInstance ( opts : any = { } ) {
return ( target : Object , methodName : string ) = > {
return {
value : function ( . . . args : any [ ] ) {
return wrapInstance ( this , methodName , opts ) . apply ( this , args ) ;
}
} ;
} ;
2016-03-29 06:50:03 -04:00
}
2015-11-29 19:54:45 -06:00
/**
2016-03-12 18:52:25 -05:00
* @private
*
*
2015-11-29 19:54:45 -06:00
* Before calling the original method, ensure Cordova and the plugin are installed.
*/
2016-12-04 11:42:30 -05:00
export function CordovaProperty ( target : Function , key : string ) {
let exists : Function = function ( ) : boolean {
2016-04-29 23:56:49 -04:00
if ( ! window . cordova ) {
2015-11-29 17:20:11 -06:00
cordovaWarn ( this . name , null ) ;
2016-12-04 11:42:30 -05:00
return false ;
2015-11-29 17:20:11 -06:00
}
2016-12-04 11:42:30 -05:00
let pluginInstance = getPlugin ( this . pluginRef ) ;
2016-04-29 23:56:49 -04:00
if ( ! pluginInstance ) {
2016-03-04 13:52:57 -06:00
pluginWarn ( this , key ) ;
2016-12-04 11:42:30 -05:00
return false ;
2015-11-29 17:20:11 -06:00
}
2016-12-04 11:42:30 -05:00
return true ;
2016-03-06 15:27:26 -05:00
} ;
2015-11-29 17:20:11 -06:00
2016-12-04 11:42:30 -05:00
Object . defineProperty ( target , key , {
get : function ( ) {
if ( exists ) {
return this . pluginRef [ key ] ;
} else {
return { } ;
}
} ,
set : function ( value ) {
if ( exists ) {
this . pluginRef [ key ] = value ;
}
}
} ) ;
2015-11-29 17:20:11 -06:00
}
2016-04-30 13:31:10 -04:00
/**
* @private
* @param target
* @param key
* @constructor
*/
2016-12-04 11:42:30 -05:00
export function InstanceProperty ( target : any , key : string ) {
Object . defineProperty ( target , key , {
get : function ( ) {
return this . _objectInstance [ key ] ;
} ,
set : function ( value ) {
this . _objectInstance [ key ] = value ;
}
} ) ;
2016-05-20 16:59:18 -04:00
}
2016-09-24 17:26:23 -05:00
/**
* @private
*
* Wrap a stub function in a call to a Cordova plugin, checking if both Cordova
* and the required plugin are installed.
*/
export function CordovaFunctionOverride ( opts : any = { } ) {
return ( target : Object , methodName : string , descriptor : TypedPropertyDescriptor < any > ) = > {
return {
value : function ( . . . args : any [ ] ) {
return overrideFunction ( this , methodName , opts ) ;
}
} ;
} ;
}