Exemples de Développement Mobile Apache Cordova

Exemples de développement d'application mobile hybride Apache Cordova incluant l'utilisation de plugins, les APIs d'appareil, le déploiement multiplateforme et l'intégration avec les systèmes hérités

Key Facts

Category
Mobile Development
Items
3
Format Families
text

Sample Overview

Exemples de développement d'application mobile hybride Apache Cordova incluant l'utilisation de plugins, les APIs d'appareil, le déploiement multiplateforme et l'intégration avec les systèmes hérités This sample set belongs to Mobile Development and can be used to test related workflows inside Elysia Tools.

💻 Implémentation de Plugins Core Cordova text

🟢 simple

Implémenter les plugins essentiels Cordova pour les fonctionnalités d'appareil incluant caméra, géolocalisation, notifications, système de fichiers et informations d'appareil

// config.xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.cordovaapp" version="1.0.0" xmlns="http://www.w3.org/ns/widgets">
    <name>CordovaDemo</name>
    <description>Apache Cordova Application</description>
    <author email="[email protected]" href="http://example.com">
        Example Developer
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />

    <!-- Core Plugins -->
    <plugin name="cordova-plugin-camera" spec="^6.0.0" />
    <plugin name="cordova-plugin-geolocation" spec="^4.1.0" />
    <plugin name="cordova-plugin-device" spec="^2.1.0" />
    <plugin name="cordova-plugin-file" spec="^6.0.2" />
    <plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
    <plugin name="cordova-plugin-dialogs" spec="^2.0.2" />
    <plugin name="cordova-plugin-vibration" spec="^3.1.1" />
    <plugin name="cordova-plugin-network-information" spec="^2.0.2" />
    <plugin name="cordova-plugin-inappbrowser" spec="^3.2.0" />
    <plugin name="cordova-plugin-media-capture" spec="^3.0.3" />
    <plugin name="cordova-plugin-splashscreen" spec="^5.0.4" />
    <plugin name="cordova-plugin-statusbar" spec="^2.4.3" />
    <plugin name="cordova-plugin-whitelist" spec="^1.3.4" />

    <!-- Platform-specific preferences -->
    <platform name="android">
        <preference name="android-minSdkVersion" value="22" />
        <preference name="android-targetSdkVersion" value="30" />
        <preference name="SplashMaintainAspectRatio" value="true" />
        <preference name="SplashShowOnlyFirstTime" value="false" />
        <preference name="SplashScreenDelay" value="3000" />
        <preference name="StatusBarOverlaysWebView" value="false" />
        <preference name="StatusBarBackgroundColor" value="#000000" />
        <preference name="StatusBarStyle" value="lightcontent" />
    </platform>

    <platform name="ios">
        <preference name="SplashMaintainAspectRatio" value="true" />
        <preference name="SplashScreenDelay" value="3000" />
        <preference name="StatusBarOverlaysWebView" value="false" />
        <preference name="StatusBarBackgroundColor" value="#000000" />
        <preference name="StatusBarStyle" value="lightcontent" />
        <preference name="BackupWebStorage" value="none" />
    </platform>
</widget>

// js/services/CordovaDeviceService.js
class CordovaDeviceService {
    constructor() {
        this.isDeviceReady = false;
        this.initializeCordova();
    }

    initializeCordova() {
        document.addEventListener('deviceready', () => {
            console.log('Device is ready!');
            this.isDeviceReady = true;
            this.onDeviceReady();
        }, false);
    }

    onDeviceReady() {
        // Hide splash screen
        if (navigator.splashscreen) {
            navigator.splashscreen.hide();
        }

        // Setup status bar
        if (window.StatusBar) {
            StatusBar.backgroundColorByHexString("#000000");
            StatusBar.styleLightContent();
        }

        // Setup network monitoring
        this.setupNetworkMonitoring();
    }

    // Device Information
    getDeviceInfo() {
        if (!this.isDeviceReady) {
            console.warn('Device not ready');
            return null;
        }

        return {
            platform: device.platform,
            version: device.version,
            model: device.model,
            manufacturer: device.manufacturer,
            uuid: device.uuid,
            serial: device.serial,
            isVirtual: device.isVirtual
        };
    }

    // Camera Operations
    takePhoto(options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !navigator.camera) {
                reject(new Error('Camera not available'));
                return;
            }

            const defaultOptions = {
                quality: 80,
                destinationType: Camera.DestinationType.FILE_URI,
                sourceType: Camera.PictureSourceType.CAMERA,
                encodingType: Camera.EncodingType.JPEG,
                mediaType: Camera.MediaType.PICTURE,
                allowEdit: false,
                correctOrientation: true,
                saveToPhotoAlbum: false
            };

            const cameraOptions = { ...defaultOptions, ...options };

            navigator.camera.getPicture(
                (imageUri) => {
                    resolve({
                        success: true,
                        imageUri: imageUri,
                        message: 'Photo captured successfully'
                    });
                },
                (error) => {
                    reject(new Error(`Camera error: ${error}`));
                },
                cameraOptions
            );
        });
    }

    getPhotoFromGallery(options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !navigator.camera) {
                reject(new Error('Camera not available'));
                return;
            }

            const defaultOptions = {
                quality: 80,
                destinationType: Camera.DestinationType.FILE_URI,
                sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
                encodingType: Camera.EncodingType.JPEG,
                mediaType: Camera.MediaType.PICTURE
            };

            const cameraOptions = { ...defaultOptions, ...options };

            navigator.camera.getPicture(
                (imageUri) => {
                    resolve({
                        success: true,
                        imageUri: imageUri,
                        message: 'Photo selected successfully'
                    });
                },
                (error) => {
                    reject(new Error(`Gallery error: ${error}`));
                },
                cameraOptions
            );
        });
    }

    // Geolocation Operations
    getCurrentPosition(options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !navigator.geolocation) {
                reject(new Error('Geolocation not available'));
                return;
            }

            const defaultOptions = {
                maximumAge: 3000,
                timeout: 5000,
                enableHighAccuracy: true
            };

            const geolocationOptions = { ...defaultOptions, ...options };

            navigator.geolocation.getCurrentPosition(
                (position) => {
                    resolve({
                        success: true,
                        latitude: position.coords.latitude,
                        longitude: position.coords.longitude,
                        accuracy: position.coords.accuracy,
                        altitude: position.coords.altitude,
                        altitudeAccuracy: position.coords.altitudeAccuracy,
                        heading: position.coords.heading,
                        speed: position.coords.speed,
                        timestamp: position.timestamp
                    });
                },
                (error) => {
                    reject(new Error(`Geolocation error: ${error.message}`));
                },
                geolocationOptions
            );
        });
    }

    watchPosition(callback, options = {}) {
        if (!this.isDeviceReady || !navigator.geolocation) {
            throw new Error('Geolocation not available');
        }

        const defaultOptions = {
            maximumAge: 3000,
            timeout: 5000,
            enableHighAccuracy: true
        };

        const geolocationOptions = { ...defaultOptions, ...options };

        return navigator.geolocation.watchPosition(
            (position) => {
                callback({
                    success: true,
                    latitude: position.coords.latitude,
                    longitude: position.coords.longitude,
                    accuracy: position.coords.accuracy,
                    timestamp: position.timestamp
                });
            },
            (error) => {
                callback({
                    success: false,
                    error: error.message
                });
            },
            geolocationOptions
        );
    }

    // File System Operations
    readFile(path, options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !window.resolveLocalFileSystemURL) {
                reject(new Error('File system not available'));
                return;
            }

            window.resolveLocalFileSystemURL(
                path,
                (fileEntry) => {
                    fileEntry.file(
                        (file) => {
                            const reader = new FileReader();

                            reader.onloadend = function() {
                                resolve({
                                    success: true,
                                    data: this.result,
                                    name: file.name,
                                    size: file.size,
                                    type: file.type,
                                    lastModified: file.lastModified
                                });
                            };

                            reader.onerror = function() {
                                reject(new Error('Failed to read file'));
                            };

                            // Read based on options
                            if (options.asText) {
                                reader.readAsText(file);
                            } else if (options.asDataURL) {
                                reader.readAsDataURL(file);
                            } else {
                                reader.readAsArrayBuffer(file);
                            }
                        },
                        (error) => {
                            reject(new Error(`File error: ${error.code}`));
                        }
                    );
                },
                (error) => {
                    reject(new Error(`File system error: ${error.code}`));
                }
            );
        });
    }

    writeFile(path, data, options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !window.resolveLocalFileSystemURL) {
                reject(new Error('File system not available'));
                return;
            }

            const defaultOptions = {
                create: true,
                exclusive: false
            };

            const fileOptions = { ...defaultOptions, ...options };

            window.resolveLocalFileSystemURL(
                path.substring(0, path.lastIndexOf('/')),
                (directoryEntry) => {
                    directoryEntry.getFile(
                        path.substring(path.lastIndexOf('/') + 1),
                        fileOptions,
                        (fileEntry) => {
                            fileEntry.createWriter(
                                (writer) => {
                                    writer.onwriteend = () => {
                                        resolve({
                                            success: true,
                                            path: fileEntry.toURL(),
                                            message: 'File written successfully'
                                        });
                                    };

                                    writer.onerror = (error) => {
                                        reject(new Error(`Write error: ${error}`));
                                    };

                                    if (typeof data === 'string') {
                                        writer.write(data);
                                    } else if (data instanceof Blob) {
                                        writer.write(data);
                                    } else {
                                        const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
                                        writer.write(blob);
                                    }
                                },
                                (error) => {
                                    reject(new Error(`Writer error: ${error.code}`));
                                }
                            );
                        },
                        (error) => {
                            reject(new Error(`File creation error: ${error.code}`));
                        }
                    );
                },
                (error) => {
                    reject(new Error(`Directory error: ${error.code}`));
                }
            );
        });
    }

    // Dialog Operations
    showAlert(message, title = 'Alert', buttonName = 'OK') {
        return new Promise((resolve) => {
            if (!this.isDeviceReady || !navigator.notification) {
                // Fallback to browser alert
                alert(message);
                resolve();
                return;
            }

            navigator.notification.alert(
                message,
                () => resolve(),
                title,
                buttonName
            );
        });
    }

    showConfirm(message, title = 'Confirm', buttonLabels = ['OK', 'Cancel']) {
        return new Promise((resolve) => {
            if (!this.isDeviceReady || !navigator.notification) {
                // Fallback to browser confirm
                const result = confirm(message);
                resolve(result ? 1 : 2);
                return;
            }

            navigator.notification.confirm(
                message,
                (buttonIndex) => resolve(buttonIndex),
                title,
                buttonLabels
            );
        });
    }

    showPrompt(message, title = 'Prompt', buttonLabels = ['OK', 'Cancel'], defaultText = '') {
        return new Promise((resolve) => {
            if (!this.isDeviceReady || !navigator.notification) {
                // Fallback to browser prompt
                const result = prompt(message, defaultText);
                resolve(result ? { input1: result } : false);
                return;
            }

            navigator.notification.prompt(
                message,
                (results) => resolve(results),
                title,
                buttonLabels,
                defaultText
            );
        });
    }

    // Vibration
    vibrate(pattern) {
        if (!this.isDeviceReady || !navigator.notification || !navigator.vibrate) {
            console.warn('Vibration not available');
            return;
        }

        if (Array.isArray(pattern)) {
            navigator.notification.vibrateWithPattern(pattern);
        } else {
            navigator.vibrate(pattern || 1000);
        }
    }

    // Network Information
    setupNetworkMonitoring() {
        if (!this.isDeviceReady || !navigator.connection) {
            console.warn('Network information not available');
            return;
        }

        document.addEventListener('online', () => {
            console.log('Network connection established');
            this.onNetworkChange(true);
        });

        document.addEventListener('offline', () => {
            console.log('Network connection lost');
            this.onNetworkChange(false);
        });
    }

    onNetworkChange(isOnline) {
        // Override this method to handle network changes
        console.log(`Network status changed: ${isOnline ? 'online' : 'offline'}`);
    }

    getNetworkInfo() {
        if (!this.isDeviceReady || !navigator.connection) {
            return {
                online: navigator.onLine,
                type: 'unknown'
            };
        }

        const connection = navigator.connection;
        return {
            online: navigator.onLine,
            type: connection.type,
            effectiveType: connection.effectiveType,
            downlink: connection.downlink,
            rtt: connection.rtt
        };
    }

    // In App Browser
    openInAppBrowser(url, options = {}) {
        if (!this.isDeviceReady || !cordova.InAppBrowser) {
            // Fallback to opening in same window
            window.open(url, '_blank');
            return null;
        }

        const defaultOptions = {
            location: 'yes',
            clearcache: 'yes',
            toolbar: 'yes',
            closebuttoncaption: 'Close'
        };

        const browserOptions = { ...defaultOptions, ...options };
        const target = options.target || '_blank';

        return cordova.InAppBrowser.open(url, target, browserOptions);
    }

    // Media Capture
    captureAudio(options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !navigator.device.capture) {
                reject(new Error('Media capture not available'));
                return;
            }

            const defaultOptions = {
                limit: 1,
                duration: 30
            };

            const captureOptions = { ...defaultOptions, ...options };

            navigator.device.capture.captureAudio(
                (mediaFiles) => {
                    resolve({
                        success: true,
                        files: mediaFiles.map(file => ({
                            name: file.name,
                            fullPath: file.fullPath,
                            type: file.type,
                            lastModifiedDate: file.lastModifiedDate,
                            size: file.size
                        }))
                    });
                },
                (error) => {
                    reject(new Error(`Audio capture error: ${error.code}`));
                },
                captureOptions
            );
        });
    }

    captureImage(options = {}) {
        return new Promise((resolve, reject) => {
            if (!this.isDeviceReady || !navigator.device.capture) {
                reject(new Error('Media capture not available'));
                return;
            }

            const defaultOptions = {
                limit: 1
            };

            const captureOptions = { ...defaultOptions, ...options };

            navigator.device.capture.captureImage(
                (mediaFiles) => {
                    resolve({
                        success: true,
                        files: mediaFiles.map(file => ({
                            name: file.name,
                            fullPath: file.fullPath,
                            type: file.type,
                            lastModifiedDate: file.lastModifiedDate,
                            size: file.size
                        }))
                    });
                },
                (error) => {
                    reject(new Error(`Image capture error: ${error.code}`));
                },
                captureOptions
            );
        });
    }
}

// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
    module.exports = CordovaDeviceService;
}

// js/app.js (Main Application)
const deviceService = new CordovaDeviceService();

// Example usage in your app
class AppController {
    constructor() {
        this.initializeEventListeners();
    }

    initializeEventListeners() {
        document.addEventListener('DOMContentLoaded', () => {
            this.setupUI();
        });

        // Wait for device ready
        if (deviceService.isDeviceReady) {
            this.onDeviceReady();
        } else {
            document.addEventListener('deviceready', () => {
                this.onDeviceReady();
            });
        }
    }

    onDeviceReady() {
        console.log('App is ready!');
        this.displayDeviceInfo();
        this.checkNetworkStatus();
    }

    async displayDeviceInfo() {
        const deviceInfo = deviceService.getDeviceInfo();
        if (deviceInfo) {
            const infoElement = document.getElementById('device-info');
            if (infoElement) {
                infoElement.innerHTML = `
                    <p><strong>Platform:</strong> ${deviceInfo.platform}</p>
                    <p><strong>Version:</strong> ${deviceInfo.version}</p>
                    <p><strong>Model:</strong> ${deviceInfo.model}</p>
                    <p><strong>UUID:</strong> ${deviceInfo.uuid}</p>
                `;
            }
        }
    }

    checkNetworkStatus() {
        const networkInfo = deviceService.getNetworkInfo();
        const statusElement = document.getElementById('network-status');
        if (statusElement) {
            statusElement.innerHTML = `
                <p><strong>Status:</strong> ${networkInfo.online ? 'Online' : 'Offline'}</p>
                <p><strong>Type:</strong> ${networkInfo.type}</p>
            `;
            statusElement.className = networkInfo.online ? 'online' : 'offline';
        }
    }

    async takePicture() {
        try {
            const result = await deviceService.takePhoto({
                quality: 90,
                saveToPhotoAlbum: true
            });

            if (result.success) {
                const imageElement = document.getElementById('captured-image');
                if (imageElement) {
                    imageElement.src = result.imageUri;
                    imageElement.style.display = 'block';
                }

                await deviceService.showAlert(
                    'Photo captured successfully!',
                    'Success',
                    'OK'
                );
            }
        } catch (error) {
            console.error('Camera error:', error);
            await deviceService.showAlert(
                'Failed to capture photo: ' + error.message,
                'Error',
                'OK'
            );
        }
    }

    async getCurrentLocation() {
        try {
            const position = await deviceService.getCurrentPosition();

            if (position.success) {
                const locationElement = document.getElementById('location');
                if (locationElement) {
                    locationElement.innerHTML = `
                        <p><strong>Latitude:</strong> ${position.latitude}</p>
                        <p><strong>Longitude:</strong> ${position.longitude}</p>
                        <p><strong>Accuracy:</strong> ${position.accuracy}m</p>
                    `;
                }
            }
        } catch (error) {
            console.error('Geolocation error:', error);
            await deviceService.showAlert(
                'Failed to get location: ' + error.message,
                'Error',
                'OK'
            );
        }
    }

    async showConfirmation() {
        const result = await deviceService.showConfirm(
            'Do you want to continue?',
            'Confirmation',
            ['Yes', 'No']
        );

        if (result === 1) {
            await deviceService.showAlert('You chose Yes!', 'Result', 'OK');
        } else {
            await deviceService.showAlert('You chose No!', 'Result', 'OK');
        }
    }
}

// Initialize the app
const app = new AppController();

💻 Développement de Plugins Personnalisés Cordova text

🟡 intermediate

Créer des plugins personnalisés Cordova avec des implémentations de code natif pour iOS (Objective-C/Swift) et Android (Java/Kotlin)

// Plugin Creation Command
// cordova plugin create cordova-plugin-myplugin --name "My Plugin" --plugin_id cordova-plugin-myplugin --plugin_version 1.0.0

// www/myplugin.js (JavaScript Interface)
var exec = require('cordova/exec');

var MyPlugin = {
    /**
     * Perform a custom operation
     * @param {Function} successCallback - Success callback
     * @param {Function} errorCallback - Error callback
     * @param {Object} options - Options object
     */
    customOperation: function(successCallback, errorCallback, options) {
        var defaultOptions = {
            value: '',
            mode: 'normal'
        };

        var mergedOptions = Object.assign(defaultOptions, options || {});

        exec(
            successCallback,
            errorCallback,
            'MyPlugin',
            'customOperation',
            [mergedOptions]
        );
    },

    /**
     * Get device specific information
     * @param {Function} successCallback - Success callback
     * @param {Function} errorCallback - Error callback
     */
    getDeviceInfo: function(successCallback, errorCallback) {
        exec(
            successCallback,
            errorCallback,
            'MyPlugin',
            'getDeviceInfo',
            []
        );
    },

    /**
     * Start monitoring a custom event
     * @param {Function} callback - Event callback
     */
    startMonitoring: function(callback) {
        exec(
            callback,
            callback,
            'MyPlugin',
            'startMonitoring',
            []
        );
    },

    /**
     * Stop monitoring custom events
     */
    stopMonitoring: function() {
        exec(
            function() {},
            function() {},
            'MyPlugin',
            'stopMonitoring',
            []
        );
    }
};

module.exports = MyPlugin;

// src/ios/MyPlugin.m (Objective-C Implementation)
#import <Cordova/CDV.h>
#import "MyPlugin.h"

@implementation MyPlugin

- (void)customOperation:(CDVInvokedUrlCommand*)command {
    CDVPluginResult* pluginResult = nil;
    NSString* value = nil;
    NSString* mode = @"normal";

    @try {
        // Get parameters
        NSDictionary* options = [command.arguments objectAtIndex:0];

        if (options) {
            value = [options objectForKey:@"value"];
            mode = [options objectForKey:@"mode"];

            if (!mode) {
                mode = @"normal";
            }
        }

        if (!value) {
            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Missing required parameter: value"];
        } else {
            // Process the operation
            NSString* processedValue = [NSString stringWithFormat:@"%@: %@",
                                      [mode isEqualToString:@"advanced"] ? @"ADVANCED" : @"NORMAL",
                                      value];

            // Create response dictionary
            NSDictionary* result = @{
                @"success": @YES,
                @"processedValue": processedValue,
                @"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000),
                @"platform": @"iOS"
            };

            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
        }
    } @catch (NSException* exception) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[exception reason]];
    }

    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)getDeviceInfo:(CDVInvokedUrlCommand*)command {
    CDVPluginResult* pluginResult = nil;

    @try {
        UIDevice* device = [UIDevice currentDevice];

        NSDictionary* deviceInfo = @{
            @"platform": @"iOS",
            @"systemVersion": device.systemVersion,
            @"model": device.model,
            @"name": device.name,
            @"customProperty": @"ios-specific-value",
            @"batteryLevel": @(device.batteryLevel),
            @"batteryState": @(device.batteryState)
        };

        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:deviceInfo];
    } @catch (NSException* exception) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[exception reason]];
    }

    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)startMonitoring:(CDVInvokedUrlCommand*)command {
    // Start monitoring custom events
    self.monitoringCallbackId = command.callbackId;

    // Example: Monitor battery changes
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(batteryLevelDidChange:)
                                                 name:UIDeviceBatteryLevelDidChangeNotification
                                               object:nil];

    // Enable battery monitoring
    [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];

    // Keep the command alive for monitoring
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [pluginResult setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)stopMonitoring:(CDVInvokedUrlCommand*)command {
    // Stop monitoring
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];

    self.monitoringCallbackId = nil;

    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)batteryLevelDidChange:(NSNotification*)notification {
    if (self.monitoringCallbackId) {
        UIDevice* device = [UIDevice currentDevice];

        NSDictionary* batteryInfo = @{
            @"eventType": @"batteryChange",
            @"level": @(device.batteryLevel),
            @"state": @(device.batteryState),
            @"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000)
        };

        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:batteryInfo];
        [pluginResult setKeepCallbackAsBool:YES];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:self.monitoringCallbackId];
    }
}

- (void)dispose {
    [self stopMonitoring:nil];
    [super dispose];
}

@end

// src/android/MyPlugin.java (Android Implementation)
package com.example.myplugin;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.util.Log;

public class MyPlugin extends CordovaPlugin {
    private static final String TAG = "MyPlugin";
    private CallbackContext monitoringCallback;

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if ("customOperation".equals(action)) {
            this.customOperation(args, callbackContext);
            return true;
        } else if ("getDeviceInfo".equals(action)) {
            this.getDeviceInfo(callbackContext);
            return true;
        } else if ("startMonitoring".equals(action)) {
            this.startMonitoring(callbackContext);
            return true;
        } else if ("stopMonitoring".equals(action)) {
            this.stopMonitoring(callbackContext);
            return true;
        } else {
            callbackContext.error("Invalid action: " + action);
            return false;
        }
    }

    private void customOperation(JSONArray args, CallbackContext callbackContext) {
        try {
            if (args.length() < 1) {
                callbackContext.error("Missing options parameter");
                return;
            }

            JSONObject options = args.getJSONObject(0);
            String value = options.optString("value", "");
            String mode = options.optString("mode", "normal");

            if (value.isEmpty()) {
                callbackContext.error("Missing required parameter: value");
                return;
            }

            // Process the operation
            String processedValue = "advanced".equals(mode)
                ? "ADVANCED: " + value
                : "NORMAL: " + value;

            // Create response
            JSONObject result = new JSONObject();
            result.put("success", true);
            result.put("processedValue", processedValue);
            result.put("timestamp", System.currentTimeMillis());
            result.put("platform", "Android");

            callbackContext.success(result);
        } catch (JSONException e) {
            Log.e(TAG, "JSON Exception", e);
            callbackContext.error("JSON Exception: " + e.getMessage());
        }
    }

    private void getDeviceInfo(CallbackContext callbackContext) {
        try {
            Context context = cordova.getActivity().getApplicationContext();

            JSONObject deviceInfo = new JSONObject();
            deviceInfo.put("platform", "Android");
            deviceInfo.put("version", android.os.Build.VERSION.RELEASE);
            deviceInfo.put("model", android.os.Build.MODEL);
            deviceInfo.put("manufacturer", android.os.Build.MANUFACTURER);
            deviceInfo.put("customProperty", "android-specific-value");

            // Battery information
            IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            Intent batteryStatus = context.registerReceiver(null, ifilter);

            if (batteryStatus != null) {
                int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                float batteryPct = level / (float) scale;
                int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

                deviceInfo.put("batteryLevel", batteryPct);
                deviceInfo.put("batteryStatus", status);
            }

            callbackContext.success(deviceInfo);
        } catch (JSONException e) {
            Log.e(TAG, "JSON Exception", e);
            callbackContext.error("JSON Exception: " + e.getMessage());
        }
    }

    private void startMonitoring(CallbackContext callbackContext) {
        this.monitoringCallback = callbackContext;

        // Create a result that stays alive for monitoring
        PluginResult result = new PluginResult(PluginResult.Status.OK);
        result.setKeepCallback(true);
        callbackContext.sendPluginResult(result);

        // Start a background thread for monitoring
        startBackgroundMonitoring();
    }

    private void stopMonitoring(CallbackContext callbackContext) {
        this.monitoringCallback = null;
        stopBackgroundMonitoring();

        callbackContext.success();
    }

    private void startBackgroundMonitoring() {
        cordova.getThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                while (monitoringCallback != null) {
                    try {
                        // Example: Monitor battery every 5 seconds
                        Thread.sleep(5000);

                        if (monitoringCallback != null) {
                            Context context = cordova.getActivity().getApplicationContext();
                            IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
                            Intent batteryStatus = context.registerReceiver(null, ifilter);

                            if (batteryStatus != null) {
                                int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                                int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                                float batteryPct = level / (float) scale;

                                JSONObject batteryInfo = new JSONObject();
                                try {
                                    batteryInfo.put("eventType", "batteryChange");
                                    batteryInfo.put("level", batteryPct);
                                    batteryInfo.put("timestamp", System.currentTimeMillis());

                                    PluginResult result = new PluginResult(PluginResult.Status.OK, batteryInfo);
                                    result.setKeepCallback(true);
                                    monitoringCallback.sendPluginResult(result);
                                } catch (JSONException e) {
                                    Log.e(TAG, "JSON Exception", e);
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        Log.d(TAG, "Monitoring interrupted", e);
                        break;
                    }
                }
            }
        });
    }

    private void stopBackgroundMonitoring() {
        // Cleanup background monitoring
        // This would typically involve stopping any background threads
    }

    @Override
    public void onDestroy() {
        stopMonitoring(null);
        super.onDestroy();
    }
}

// src/android/MyPlugin.gradle (Gradle Build File)
dependencies {
    implementation 'org.apache.cordova:framework:9.0.0'
}

// package.json (Plugin Package Configuration)
{
  "name": "cordova-plugin-myplugin",
  "version": "1.0.0",
  "description": "My Custom Cordova Plugin",
  "cordova": {
    "id": "cordova-plugin-myplugin",
    "platforms": [
      "android",
      "ios"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/example/cordova-plugin-myplugin.git"
  },
  "keywords": [
    "cordova",
    "plugin",
    "ecosystem:cordova",
    "cordova-android",
    "cordova-ios"
  ],
  "author": "Example Developer",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/example/cordova-plugin-myplugin/issues"
  },
  "homepage": "https://github.com/example/cordova-plugin-myplugin#readme"
}

// Usage in Cordova App
// js/app.js
document.addEventListener('deviceready', function() {
    // Use the custom plugin
    MyPlugin.customOperation(
        function(result) {
            console.log('Success:', result);
        },
        function(error) {
            console.error('Error:', error);
        },
        {
            value: 'Hello World',
            mode: 'advanced'
        }
    );

    // Get device info
    MyPlugin.getDeviceInfo(
        function(deviceInfo) {
            console.log('Device Info:', deviceInfo);
        },
        function(error) {
            console.error('Error:', error);
        }
    );

    // Start monitoring
    MyPlugin.startMonitoring(function(event) {
        console.log('Event received:', event);
    });

    // Stop monitoring when done
    // MyPlugin.stopMonitoring();
});

💻 Déploiement et Sécurité d'Entreprise Cordova text

🔴 complex

Configurer les applications Cordova de niveau entreprise avec les meilleures pratiques de sécurité, certificate pinning, chiffrement et stratégies de déploiement corporatif

// config-security.xml (Security Configuration)
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.enterprise.cordovaapp" version="2.0.0" xmlns="http://www.w3.org/ns/widgets">
    <name>EnterpriseApp</name>
    <description>Enterprise Cordova Application</description>
    <author email="[email protected]" href="https://enterprise.example.com">
        Enterprise Team
    </author>
    <content src="index.html" />

    <!-- Security Settings -->
    <access origin="https://api.enterprise.com" />
    <access origin="https://auth.enterprise.com" />
    <access origin="https://cdn.enterprise.com" />
    <allow-navigation href="https://*.enterprise.com/*" />
    <allow-intent href="https://*.enterprise.com/*" />
    <feature name="Whitelist">
        <param name="allowed-intents" value="https://*.enterprise.com/*" />
    </feature>

    <!-- Security Plugins -->
    <plugin name="cordova-plugin-advanced-http" spec="^3.2.2" />
    <plugin name="cordova-plugin-certificate-observer" spec="^1.0.0" />
    <plugin name="cordova-plugin-secure-storage" spec="^4.0.2" />
    <plugin name="cordova-plugin-screen-orientation" spec="^3.0.4" />
    <plugin name="cordova-plugin-app-version" spec="^0.1.9" />
    <plugin name="cordova-plugin-ios-security" spec="^2.0.0" />
    <plugin name="cordova-android-security" spec="^1.0.0" />

    <!-- Enterprise Platform Preferences -->
    <platform name="android">
        <preference name="android-minSdkVersion" value="23" />
        <preference name="android-targetSdkVersion" value="30" />
        <preference name="android:usesCleartextTraffic" value="false" />
        <preference name="android:networkSecurityConfig" value="res/xml/network_security_config.xml" />
        <preference name="android:allowBackup" value="false" />
        <preference name="android:fullBackupContent" value="false" />
        <preference name="AndroidInsecureFileModeEnabled" value="false" />

        <!-- Certificate Pinning -->
        <config-file target="AndroidManifest.xml" parent="/manifest/application">
            <meta-data android:name="io.intercom.certificate-pin" android:value="sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" />
        </config-file>
    </platform>

    <platform name="ios">
        <preference name="deployment-target" value="12.0" />
        <preference name="BackupWebStorage" value="none" />
        <preference name="DisallowOverscroll" value="true" />

        <!-- iOS Security Configuration -->
        <config-file target="*-Info.plist" parent="ITSAppUsesNonExemptEncryption">
            <true/>
        </config-file>

        <!-- App Transport Security -->
        <config-file target="*-Info.plist" parent="NSAppTransportSecurity">
            <dict>
                <key>NSAllowsArbitraryLoads</key>
                <false/>
                <key>NSExceptionDomains</key>
                <dict>
                    <key>api.enterprise.com</key>
                    <dict>
                        <key>NSExceptionRequiresForwardSecrecy</key>
                        <false/>
                        <key>NSExceptionMinimumTLSVersion</key>
                        <string>TLSv1.2</string>
                        <key>NSIncludesSubdomains</key>
                        <true/>
                    </dict>
                </dict>
            </dict>
        </config-file>
    </platform>
</widget>

// js/services/SecurityService.js
class SecurityService {
    constructor() {
        this.secureStorage = null;
        this.initializeSecurity();
    }

    async initializeSecurity() {
        try {
            // Initialize secure storage
            if (window.cordova && window.cordova.plugins.SecureStorage) {
                this.secureStorage = cordova.plugins.SecureStorage;
                await this.initializeSecureStorage();
            }

            // Setup certificate pinning
            await this.setupCertificatePinning();

            // Initialize encryption
            await this.initializeEncryption();
        } catch (error) {
            console.error('Security initialization failed:', error);
        }
    }

    async initializeSecureStorage() {
        return new Promise((resolve, reject) => {
            this.secureStorage.init(
                () => {
                    console.log('Secure storage initialized');
                    resolve();
                },
                (error) => {
                    console.error('Secure storage initialization failed:', error);
                    reject(error);
                },
                'enterprise_secure_storage'
            );
        });
    }

    async setupCertificatePinning() {
        if (!window.cordova || !window.cordova.plugins.CertificatePinning) {
            console.warn('Certificate pinning plugin not available');
            return;
        }

        return new Promise((resolve, reject) => {
            const certificates = [
                'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', // Primary certificate
                'sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=' // Backup certificate
            ];

            cordova.plugins.CertificatePinning.pinCertificates(
                certificates,
                () => {
                    console.log('Certificate pinning configured');
                    resolve();
                },
                (error) => {
                    console.error('Certificate pinning failed:', error);
                    reject(error);
                }
            );
        });
    }

    async initializeEncryption() {
        // Initialize AES encryption key
        this.encryptionKey = await this.getOrCreateEncryptionKey();
    }

    async getOrCreateEncryptionKey() {
        try {
            const existingKey = await this.getSecureItem('encryption_key');
            if (existingKey) {
                return existingKey;
            }

            // Generate new encryption key
            const key = this.generateEncryptionKey();
            await this.setSecureItem('encryption_key', key);
            return key;
        } catch (error) {
            console.error('Encryption key setup failed:', error);
            throw error;
        }
    }

    generateEncryptionKey() {
        // Generate a random 256-bit key
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
    }

    // Secure Storage Operations
    async setSecureItem(key, value) {
        if (!this.secureStorage) {
            throw new Error('Secure storage not available');
        }

        return new Promise((resolve, reject) => {
            this.secureStorage.set(
                () => resolve(),
                (error) => reject(error),
                key,
                value
            );
        });
    }

    async getSecureItem(key) {
        if (!this.secureStorage) {
            throw new Error('Secure storage not available');
        }

        return new Promise((resolve, reject) => {
            this.secureStorage.get(
                (value) => resolve(value),
                (error) => reject(error),
                key
            );
        });
    }

    async removeSecureItem(key) {
        if (!this.secureStorage) {
            throw new Error('Secure storage not available');
        }

        return new Promise((resolve, reject) => {
            this.secureStorage.remove(
                () => resolve(),
                (error) => reject(error),
                key
            );
        });
    }

    // Encryption/Decryption
    async encryptData(data) {
        try {
            const encoder = new TextEncoder();
            const dataBuffer = encoder.encode(JSON.stringify(data));

            // Import encryption key
            const key = await crypto.subtle.importKey(
                'raw',
                new TextEncoder().encode(this.encryptionKey).slice(0, 32),
                { name: 'AES-CBC' },
                false,
                ['encrypt']
            );

            // Generate random IV
            const iv = crypto.getRandomValues(new Uint8Array(16));

            // Encrypt data
            const encryptedData = await crypto.subtle.encrypt(
                {
                    name: 'AES-CBC',
                    iv: iv
                },
                key,
                dataBuffer
            );

            // Combine IV and encrypted data
            const combined = new Uint8Array(iv.length + encryptedData.byteLength);
            combined.set(iv);
            combined.set(new Uint8Array(encryptedData), iv.length);

            return btoa(String.fromCharCode.apply(null, combined));
        } catch (error) {
            console.error('Encryption failed:', error);
            throw error;
        }
    }

    async decryptData(encryptedData) {
        try {
            const combined = new Uint8Array(
                atob(encryptedData).split('').map(char => char.charCodeAt(0))
            );

            // Extract IV
            const iv = combined.slice(0, 16);
            const data = combined.slice(16);

            // Import encryption key
            const key = await crypto.subtle.importKey(
                'raw',
                new TextEncoder().encode(this.encryptionKey).slice(0, 32),
                { name: 'AES-CBC' },
                false,
                ['decrypt']
            );

            // Decrypt data
            const decryptedData = await crypto.subtle.decrypt(
                {
                    name: 'AES-CBC',
                    iv: iv
                },
                key,
                data
            );

            const decoder = new TextDecoder();
            return JSON.parse(decoder.decode(decryptedData));
        } catch (error) {
            console.error('Decryption failed:', error);
            throw error;
        }
    }

    // API Security
    async makeSecureRequest(url, options = {}) {
        const defaultOptions = {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'X-App-Version': await this.getAppVersion(),
                'X-Device-ID': await this.getDeviceId()
            },
            timeout: 30000
        };

        const requestOptions = { ...defaultOptions, ...options };

        try {
            // Add authentication token if available
            const authToken = await this.getSecureItem('auth_token');
            if (authToken) {
                requestOptions.headers['Authorization'] = `Bearer ${authToken}`;
            }

            // Make request with certificate pinning
            const response = await this.httpRequestWithPinning(url, requestOptions);
            return response;
        } catch (error) {
            console.error('Secure request failed:', error);
            throw error;
        }
    }

    async httpRequestWithPinning(url, options) {
        if (window.cordova && window.cordova.plugin.http) {
            // Use Cordova HTTP plugin with certificate pinning
            return new Promise((resolve, reject) => {
                cordova.plugin.http.setRequestSerializer('json');
                cordova.plugin.http.setResponseSerializer('json');

                const success = (response) => {
                    resolve({
                        status: response.status,
                        data: response.data,
                        headers: response.headers
                    });
                };

                const failure = (error) => {
                    reject(new Error(`HTTP Error: ${error.error}`));
                };

                if (options.method === 'GET') {
                    cordova.plugin.http.get(url, options.params || {}, options.headers, success, failure);
                } else if (options.method === 'POST') {
                    cordova.plugin.http.post(url, options.data || {}, options.headers, success, failure);
                } else if (options.method === 'PUT') {
                    cordova.plugin.http.put(url, options.data || {}, options.headers, success, failure);
                } else if (options.method === 'DELETE') {
                    cordova.plugin.http.delete(url, options.params || {}, options.headers, success, failure);
                }
            });
        } else {
            // Fallback to standard fetch with security considerations
            const response = await fetch(url, options);

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }

            return {
                status: response.status,
                data: await response.json(),
                headers: response.headers
            };
        }
    }

    async getAppVersion() {
        return new Promise((resolve, reject) => {
            if (window.cordova && window.cordova.getVersionCode) {
                window.cordova.getVersionCode(
                    (version) => resolve(version),
                    (error) => reject(error)
                );
            } else {
                resolve('1.0.0'); // Fallback version
            }
        });
    }

    async getDeviceId() {
        try {
            let deviceId = await this.getSecureItem('device_id');
            if (!deviceId) {
                deviceId = this.generateDeviceId();
                await this.setSecureItem('device_id', deviceId);
            }
            return deviceId;
        } catch (error) {
            console.error('Failed to get device ID:', error);
            return 'unknown';
        }
    }

    generateDeviceId() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            const r = Math.random() * 16 | 0;
            const v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    // Session Management
    async createSession(userCredentials) {
        try {
            // Authenticate with backend
            const authResponse = await this.makeSecureRequest(
                'https://api.enterprise.com/auth/login',
                {
                    method: 'POST',
                    data: userCredentials
                }
            );

            if (authResponse.status === 200 && authResponse.data.token) {
                // Securely store authentication tokens
                await this.setSecureItem('auth_token', authResponse.data.token);
                await this.setSecureItem('refresh_token', authResponse.data.refreshToken);

                // Encrypt and store user session data
                const encryptedSession = await this.encryptData({
                    user: authResponse.data.user,
                    permissions: authResponse.data.permissions,
                    expiresAt: Date.now() + (authResponse.data.expiresIn * 1000)
                });

                await this.setSecureItem('user_session', encryptedSession);

                return authResponse.data;
            } else {
                throw new Error('Authentication failed');
            }
        } catch (error) {
            console.error('Session creation failed:', error);
            throw error;
        }
    }

    async validateSession() {
        try {
            const encryptedSession = await this.getSecureItem('user_session');
            if (!encryptedSession) {
                return false;
            }

            const session = await this.decryptData(encryptedSession);

            // Check if session is expired
            if (session.expiresAt < Date.now()) {
                await this.clearSession();
                return false;
            }

            // Validate token with backend
            const validationResponse = await this.makeSecureRequest(
                'https://api.enterprise.com/auth/validate',
                {
                    method: 'POST',
                    data: { token: await this.getSecureItem('auth_token') }
                }
            );

            return validationResponse.status === 200;
        } catch (error) {
            console.error('Session validation failed:', error);
            await this.clearSession();
            return false;
        }
    }

    async clearSession() {
        try {
            await this.removeSecureItem('auth_token');
            await this.removeSecureItem('refresh_token');
            await this.removeSecureItem('user_session');
            await this.removeSecureItem('device_id');
        } catch (error) {
            console.error('Session cleanup failed:', error);
        }
    }

    // Audit Logging
    async logSecurityEvent(eventType, details) {
        try {
            const auditLog = {
                timestamp: new Date().toISOString(),
                eventType: eventType,
                details: details,
                deviceId: await this.getDeviceId(),
                appVersion: await this.getAppVersion()
            };

            // Encrypt audit log
            const encryptedLog = await this.encryptData(auditLog);

            // Send to secure logging endpoint
            await this.makeSecureRequest(
                'https://api.enterprise.com/audit/log',
                {
                    method: 'POST',
                    data: { log: encryptedLog }
                }
            );
        } catch (error) {
            console.error('Security event logging failed:', error);
        }
    }
}

// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
    module.exports = SecurityService;
}

// js/EnterpriseApp.js (Main Application)
class EnterpriseApp {
    constructor() {
        this.securityService = new SecurityService();
        this.isAuthenticated = false;
    }

    async initialize() {
        try {
            // Validate existing session
            this.isAuthenticated = await this.securityService.validateSession();

            if (this.isAuthenticated) {
                await this.loadUserData();
                await this.showMainInterface();
            } else {
                await this.showLoginInterface();
            }

            // Log app initialization
            await this.securityService.logSecurityEvent('APP_INITIALIZED', {
                timestamp: Date.now(),
                existingSession: this.isAuthenticated
            });

        } catch (error) {
            console.error('App initialization failed:', error);
            await this.handleInitializationError(error);
        }
    }

    async login(username, password) {
        try {
            const credentials = {
                username: username,
                password: password,
                deviceId: await this.securityService.getDeviceId(),
                timestamp: Date.now()
            };

            const session = await this.securityService.createSession(credentials);
            this.isAuthenticated = true;

            await this.securityService.logSecurityEvent('USER_LOGIN', {
                username: username,
                success: true
            });

            await this.loadUserData();
            await this.showMainInterface();

            return session;
        } catch (error) {
            await this.securityService.logSecurityEvent('LOGIN_FAILED', {
                username: username,
                error: error.message
            });

            throw error;
        }
    }

    async logout() {
        try {
            await this.securityService.logSecurityEvent('USER_LOGOUT', {
                timestamp: Date.now()
            });

            await this.securityService.clearSession();
            this.isAuthenticated = false;

            await this.showLoginInterface();
        } catch (error) {
            console.error('Logout failed:', error);
        }
    }

    async loadUserData() {
        // Load user-specific data securely
    }

    async showMainInterface() {
        // Show main application interface
    }

    async showLoginInterface() {
        // Show login interface
    }

    async handleInitializationError(error) {
        // Handle initialization errors
        console.error('Initialization error:', error);
        await this.securityService.logSecurityEvent('INIT_ERROR', {
            error: error.message
        });
    }
}

// Initialize the enterprise app
document.addEventListener('deviceready', () => {
    const app = new EnterpriseApp();
    app.initialize();
});