Définition
Apache Cordova permet la création d'applications pour téléphones en utilisant les technologies web HTML5, Typescript, Angular et CSS3 pour les plateformes Android et iOS.
Environnements
MacOS |
- compilation pour Android
- compilation pour iOS
- compilation pour Windows avec virtualisation
|
Windows |
- compilation pour Android, si Hyper-V ou Virtual Extension n'est pas lancé
car ils n'autorisent pas d'autres plateforme de virtualisation comme l'émulateur Android
- pas de compilation pour iOS sans Mac
- compilation pour Windows
|
Linux |
- compilation pour Android
- pas de compilation pour iOS sans Mac
- compilation pour Windows avec virtualisation
|
Installation
|
npm -g install cordova
# Tools for Apache COrdova
npm -g install taco-cli
npm -g install ionic
|
- Android Studio
- Start a new project
- AVD Manager (Android Virtual Device)
- Create a Virtual Device
- XCode
- Visual Studio Code
|
# créer un dossier myApp avec la structure de fichier pour une application Cordova
cordova create myApp
cd myApp
# lister les palteformes installées et disponibles
cordova platforms ls
# ajouter des plateformes
cordova platform add browser
cordova platform add android
# tester si l'environnement est prêt
cordova requirements
# build pour android
cordova build android --release
# test dans un emulateur android
cordova emulate android
# test dans un téléphone physique
cordova run android --device
# lister les targets (devices et emulateurs)
cordova run android --list
# cibler une target (devices ou emulateurs)
cordova run android --target=<target_name>
|
Signer le paquet *.apk
|
# créer la clé keystore
& 'C:\Program Files\Java\jdk1.8.0_162\bin\keytool.exe' -genkey -v -keystore myapp.keystore -alias MyApp -keyalg RSA -keysize 2048 -validity 10000
# signer le paquet apk avec la clé keystore
& 'C:\Program Files\Java\jdk1.8.0_162\bin\jarsigner.exe' -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore .\myapp.keystore .\android-release-unsigned.apk MyApp
|
|
/c/Program\ Files/Java/jdk1.8.0_162/bin/keytool.exe -genkey -v -keystore myapp.keystore -alias MyApp -keyalg RSA -keysize 2048 -validity 10000
cd MobileApp/platforms/android/build/outputs/apk/release
# Signer le fichier apk
/c/Program\ Files/Java/jdk1.8.0_162/bin/jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore myapp.keystore android-release-unsigned.apk MyApp
/c/Users/llobera/AppData/Local/Android/Sdk/build-tools/27.0.3/zipalign.exe -v 4 android-release-unsigned.apk myapp.apk
|
Recherche de plugins
|
cordova plugin add <plugin-name>
cordova plugin remove <plugin-name>
|
package.json
|
"dependencies": {
"cordova-plugin-device": "^2.0.1"
},
"cordova": {
"plugins": {
"cordova-plugin-device": {}
}
}
|
cordova-plugin-device |
information sur le device
|
config.xml
|
<hook type="after_prepare" src="scripts/myScript.js" />
|
scripts/myScript.js
|
module.exports = function(ctx) {
// make sure android platform is part of build
if (ctx.opts.platforms.indexOf('android') < 0) {
return;
}
// deferral.reject('Operation failed');
// deferral.resolve();
return deferral.promise;
};
|
Requêtes HTTP
|
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.setRequestHeader('Authorization', 'Bearer ' + accessToken);
req.onload = function(e) {
if (e.target.status >= 200 && e.target.status < 300) {
console.log("ok");
return;
}
console.log('Data request failed: ' + e.target.response);
};
req.onerror = function(e) {
console.log('Data request failed: ' + e.target.status);
}
req.onreadystatechange = function (oEvent) {
if (req.readyState === 4) {
if (req.status === 200) {
console.log(req.responseText)
} else {
console.log("Error", req.statusText);
}
}
};
req.send();
|
|
cordova.plugin.http.get(myUri, null,
{ Authorization: 'Bearer ' + accessToken },
function(response) {
console.log("Ok: " + response.status);
data = JSON.parse(response.data);
}, function(response) {
console.log("Ko: " + response.error);
});
|
|
cordova plugin add cordova-plugin-advanced-http
|
Angular
Code
|
<!DOCTYPE html>
<html>
<head>
<base href=".">
<title>Hello World</title>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
</head>
<body>
<app>
Loading ...
</app>
<script src="cordova.js"></script>
<script src="dist/bundle.js"></script>
</body>
</html>
|
Installation
|
# dependencies
npm install --save @angular/common @angular/compiler @angular/core @angular/forms @angular/forms @angular/http @angular/platform-browser @angular/platform-browser-dynamic @angular/router @angular/upgrade angular-in-memory-web-api core-js reflect-metadata rxjs systemjs zone.js
# dev dependencies
npm install --save-dev @types/core-js @types/node concurrently lite-server typescript systemjs-plugin-text
|
package.json
|
{
"scripts": {
"start": "tsc && cordova run android",
"tsc": "tsc"
},
"dependencies": {
"@angular/common": "^5.2.9",
"@angular/compiler": "^5.2.9",
"@angular/core": "^5.2.9",
"@angular/forms": "^5.2.9",
"@angular/http": "^5.2.9",
"@angular/platform-browser": "^5.2.9",
"@angular/platform-browser-dynamic": "^5.2.9",
"@angular/router": "^5.2.9",
"@angular/upgrade": "^5.2.9",
"angular-in-memory-web-api": "^0.6.0",
"core-js": "^2.5.5",
"reflect-metadata": "^0.1.12",
"rxjs": "^5.5.8",
"systemjs": "^0.21.2",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@types/core-js": "^0.9.46",
"@types/node": "^9.6.2",
"concurrently": "^3.5.1",
"lite-server": "^2.3.0",
"systemjs-plugin-text": "0.0.11",
"typescript": "^2.8.1"
}
}
|
tsconfig.json
|
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "none",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": true,
"noImplicitAny": false,
"allowJs": false,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"plugins",
"platforms",
"node_modules",
"hooks"
]
}
|
Android Studio
- SDK build tools %LocalAppData%\Android\Sdk\build-tools\27.0.3
- SDK platform tools %LocalAppData%\Android\Sdk\platform-tools
- JDK C:\Program Files\Java\jdk1.8.0_162\bin
- Android Virtual Device Manager: Bar de menu → droite → AVD Manager
Copier un fichier sur l'émulateur
Glisser-déposer le fichier à copier.
VScode
|
# installer les dépendances npm
npm install
# Run app on target platform device or simulator
npm run cordova:run <ios/android>
# Erreur (node:18008) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CordovaError: No platforms added to this project. Please use `cordova platform add <platform>`.
cordova platform add android
# bash: cordova: command not found
npm install -g cordova
|
|
- Installer Java SE Development Kit, puis ajouter C:\Program Files\Java\jdk1.8.0_162\bin au PATH
- Installer Android Studio
|
Debug Android
- Chrome → chrome://inspect → inspect link
- Sources → file:// → android_asset/www → js → index.js → ajouter un breakpoint
TACO - Tools for Apache COrdova
Debugger Android ou iOS device ou emulateur avec Visual Studio Code:
- VSCode → Extensions → Cordova Tools
- Debug → icône engrenage → Cordova (créé un fichier .vscode/launch.json)
- Debug → Run Android on emulator
- DEBUG CONSOLE
Exemple
www/index.html
|
<button id="populateDetailsButton">Populate</button>
<div id="deviceDetails"></div>
|
www/js/index.js
|
var app = {
initialize: function() {
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
document.getElementById("populateDetailsButton").addEventListener(
"click", this.populateDeviceDetails
);
},
populateDeviceDetails: function() {
var deviceDetails = "";
deviceDetails += "<br/>Cordova:" + device.cordova;
deviceDetails += "<br/>Model:" + device.model;
deviceDetails += "<br/>Platform:" + device.platform;
document.querySelector("#deviceDetails").innerHTML = deviceDetails;
},
|
config.xml
|
<widget xmlns:android="http://schemas.android.com/apk/res/android">
<platform name="android">
<allow-intent href="market:*" />
<config-file parent="/*" target="AndroidManifest.xml">
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
</config-file>
</platform>
|
myApp\platforms\android\app\src\main\AndroidManifest.xml
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
|
cordova plugin add cordova-plugin-clipboard2
|
|
cordova.plugins.clipboard.copy(text);
cordova.plugins.clipboard.paste(function (text) { alert(text); });
|
Azure AD Cordova getting started
|
# necessary for invoking the Graph API
cordova plugin add cordova-plugin-whitelist
cordova plugin add cordova-plugin-ms-adal
|
|
// Shows the user authentication dialog box if required
authenticate: function (authCompletedCallback) {
app.context = new Microsoft.ADAL.AuthenticationContext(authority);
app.context.tokenCache.readItems().then(function (items) {
if (items.length > 0) {
authority = items[0].authority;
app.context = new Microsoft.ADAL.AuthenticationContext(authority);
}
// Attempt to authorize the user silently
app.context.acquireTokenSilentAsync(resourceUri, clientId)
.then(authCompletedCallback, function () {
// We require user credentials, so this triggers the authentication dialog box
app.context.acquireTokenAsync(resourceUri, clientId, redirectUri)
.then(authCompletedCallback, function (err) {
document.querySelector("#signInDetails").innerHTML = "Failed to authenticate: " + err;
app.error("Failed to authenticate: " + err);
});
});
});
},
// Makes an API call to receive the user profile
requestData: function (authResult, searchText) {
var req = new XMLHttpRequest();
var url = resourceUri + "/v1.0/me";
req.open("GET", url, true);
req.setRequestHeader('Authorization', 'Bearer ' + authResult.accessToken);
req.onload = function(e) {
if (e.target.status >= 200 && e.target.status < 300) {
app.renderData(JSON.parse(e.target.response));
return;
}
app.error('Data request failed: ' + e.target.response);
};
req.onerror = function(e) {
app.error('Data request failed: ' + e.error);
}
req.send();
}
|
Authentification avec Azure AD, InTune et un broker
|
var redirectUri = "msauth://io.cordova.myapp/xxx-Signature-xxx",
Microsoft.ADAL.AuthenticationSettings.setUseBroker(true)
.then(function() {
app.authenticate(function(authResponse) {
app.log("Token acquired!<br/>Token will expire on: " + authResponse.expiresOn);
app.requestData(authResponse);
});
});
|
|
Pour connaitre les redirectUri utiliser le script brokerRedirectPrint.ps1.
Il génère 2 uris, une uri de debug correspondant au keystore android C:\Users\$env:USERNAME\.android\debug.keystore
et une uri de release correspondant au keystore de l'application. |
com.microsoft.intune.mam.apppackager.utils.AppPackagerException: Failed to update this app's supported SDK versions.
com.microsoft.intune.mam.apppackager.utils.AppPackagerException: This app's target SDK is above the MAM target SDK. Please upgrade to the newest version of the App Wrapping Tool.
Wrapping failed for android
config.xml
|
<platform name="android">
<preference name="android-targetSdkVersion" value="24" />
|
Erreurs
ERROR in Error during template compile of 'ɵa'
Function calls are not supported in decorators but 'ɵmakeDecorator' was called in 'Injectable'
'Injectable' calls 'ɵmakeDecorator'.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
tsconfig.app.json
|
{
"compilerOptions": {
"paths": { "@angular/*": ["../node_modules/@angular/*"] }
}
}
|
list-devices is not recognized as an internal or external command
Available browser devices:
'myApp\platforms\browser\cordova\lib\list-devices' is not recognized as an internal or external command,
operable program or batch file.
An unexpected error has occured while running list-devices with code 1: Error: cmd: Command failed with exit code 1
Available browser virtual devices:
'myApp\platforms\browser\cordova\lib\list-emulator-images' is not recognized as an internal or external command,
operable program or batch file.
An unexpected error has occured while running list-emulator-images with code 1: Error: cmd: Command failed with exit code 1
|
# Spécifier la plateforme android, par défaut c'est browser qui es tutilisé
cordova run android --list
|
ERROR: TypeError: Cannot read property 'semver' of null
Bug qui se produit avec les images android > 8.0 dans l'émulateur.
Supprimer ces images corrige le problème.