🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Exemples de Développement d'Application Hybride Capacitor
Exemples de développement d'application hybride Capacitor incluant les APIs d'appareil natif, plugins, déploiement multiplateforme et capacités d'Application Web Progressive
💻 APIs d'Appareil Natif Capacitor typescript
🟢 simple
Implémenter des fonctionnalités d'appareil natif en utilisant les plugins Capacitor incluant caméra, géolocalisation, notifications et accès au système de fichiers
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Device API Demo',
webDir: 'dist',
bundledWebRuntime: false,
plugins: {
Camera: {
permissions: ['camera', 'photos']
},
Geolocation: {
permissions: ['location']
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert']
},
LocalNotifications: {
smallIcon: 'ic_stat_icon_config_sample',
iconColor: '#488AFF',
sound: 'beep.wav'
},
SplashScreen: {
launchShowDuration: 3000,
launchAutoHide: true,
backgroundColor: '#3880ff',
androidSplashResourceName: 'splash',
androidScaleType: 'CENTER_CROP',
showSpinner: true,
androidSpinnerStyle: 'large',
iosSpinnerStyle: 'small',
spinnerColor: '#999999',
splashFullScreen: true,
splashImmersive: true,
layoutName: 'launch_screen',
useDialog: true
}
}
};
export default config;
// src/services/DeviceService.ts
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Geolocation, Position } from '@capacitor/geolocation';
import { LocalNotifications, LocalNotificationSchema } from '@capacitor/local-notifications';
import { PushNotifications, PushNotificationSchema, PermissionStatus } from '@capacitor/push-notifications';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Share } from 'capacitor-share';
export interface DevicePosition {
latitude: number;
longitude: number;
accuracy: number;
timestamp: number;
}
export interface CameraPhoto {
filepath: string;
webviewPath?: string;
base64?: string;
}
export class DeviceService {
// Camera Operations
static async takePhoto(): Promise<CameraPhoto> {
try {
const photo: Photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 90,
allowEditing: true,
promptLabelHeader: 'Take a Photo',
promptLabelCancel: 'Cancel',
promptLabelPhoto: 'From Gallery',
promptLabelPicture: 'Take Photo'
});
return {
filepath: photo.path || '',
webviewPath: photo.webPath
};
} catch (error) {
console.error('Camera error:', error);
throw new Error('Failed to take photo');
}
}
static async selectFromGallery(): Promise<CameraPhoto> {
try {
const photo: Photo = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Photos,
quality: 90
});
return {
filepath: photo.path || '',
webviewPath: photo.webPath
};
} catch (error) {
console.error('Gallery error:', error);
throw new Error('Failed to select photo from gallery');
}
}
// Geolocation Operations
static async getCurrentPosition(): Promise<DevicePosition> {
try {
const coordinates: Position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
});
return {
latitude: coordinates.coords.latitude,
longitude: coordinates.coords.longitude,
accuracy: coordinates.coords.accuracy,
timestamp: coordinates.timestamp
};
} catch (error) {
console.error('Geolocation error:', error);
throw new Error('Failed to get current position');
}
}
static watchPosition(
callback: (position: DevicePosition) => void
): Promise<string> {
return Geolocation.watchPosition(
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
},
(position, err) => {
if (err) {
console.error('Position watch error:', err);
return;
}
if (position) {
callback({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp
});
}
}
);
}
// Notification Operations
static async requestNotificationPermission(): Promise<boolean> {
try {
const permStatus: PermissionStatus = await PushNotifications.requestPermissions();
return permStatus.receive === 'granted';
} catch (error) {
console.error('Notification permission error:', error);
return false;
}
}
static async scheduleLocalNotification(
title: string,
body: string,
schedule?: { at: Date },
id: number = Date.now()
): Promise<void> {
try {
const notification: LocalNotificationSchema = {
id,
title,
body,
schedule,
sound: 'beep.wav',
smallIcon: 'ic_stat_icon_config_sample',
iconColor: '#488AFF',
actionTypeId: ''
};
await LocalNotifications.schedule({
notifications: [notification]
});
} catch (error) {
console.error('Schedule notification error:', error);
throw new Error('Failed to schedule notification');
}
}
static async sendPushNotification(
to: string,
title: string,
body: string
): Promise<void> {
// This would typically be handled by your backend service
// Here's an example of how to register for push notifications
try {
await PushNotifications.register();
PushNotifications.addListener('registration', (token) => {
console.log('Push registration success, token: ' + token.value);
// Send this token to your backend
});
PushNotifications.addListener('registrationError', (error) => {
console.error('Error on registration: ' + JSON.stringify(error.error));
});
PushNotifications.addListener(
'pushNotificationReceived',
(notification: PushNotificationSchema) => {
console.log('Push notification received: ', notification);
}
);
} catch (error) {
console.error('Push notification error:', error);
}
}
// File System Operations
static async savePhotoToFile(photo: CameraPhoto): Promise<string> {
try {
const base64Data = await this.readAsBase64(photo);
const fileName = new Date().getTime() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
return savedFile.uri;
} catch (error) {
console.error('Save photo error:', error);
throw new Error('Failed to save photo');
}
}
static async readFileAsBase64(filePath: string): Promise<string> {
try {
const file = await Filesystem.readFile({
path: filePath
});
return file.data as string;
} catch (error) {
console.error('Read file error:', error);
throw new Error('Failed to read file');
}
}
private static async readAsBase64(photo: CameraPhoto): Promise<string> {
if (photo.webviewPath) {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webviewPath);
const blob = await response.blob();
return await this.convertBlobToBase64(blob) as string;
} else {
// For Capacitor on Android/iOS, we can read directly from file system
return await this.readFileAsBase64(photo.filepath);
}
}
private static convertBlobToBase64(blob: Blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
}
// Share Operations
static async shareContent(
title: string,
text?: string,
url?: string,
dialogTitle?: string
): Promise<void> {
try {
await Share.share({
title,
text,
url,
dialogTitle: dialogTitle || 'Share with buddies'
});
} catch (error) {
console.error('Share error:', error);
throw new Error('Failed to share content');
}
}
}
// src/components/DeviceFeatures.tsx (React Component)
import React, { useState, useEffect } from 'react';
import { DeviceService, DevicePosition, CameraPhoto } from '../services/DeviceService';
const DeviceFeatures: React.FC = () => {
const [position, setPosition] = useState<DevicePosition | null>(null);
const [photo, setPhoto] = useState<CameraPhoto | null>(null);
const [watchId, setWatchId] = useState<string | null>(null);
useEffect(() => {
requestPermissions();
return () => {
if (watchId) {
Geolocation.clearWatch({ id: watchId });
}
};
}, []);
const requestPermissions = async () => {
await DeviceService.requestNotificationPermission();
};
const handleTakePhoto = async () => {
try {
const newPhoto = await DeviceService.takePhoto();
setPhoto(newPhoto);
await DeviceService.savePhotoToFile(newPhoto);
} catch (error) {
console.error('Photo error:', error);
}
};
const handleGetCurrentPosition = async () => {
try {
const currentPosition = await DeviceService.getCurrentPosition();
setPosition(currentPosition);
} catch (error) {
console.error('Position error:', error);
}
};
const handleWatchPosition = async () => {
try {
const id = await DeviceService.watchPosition((pos) => {
setPosition(pos);
});
setWatchId(id);
} catch (error) {
console.error('Watch position error:', error);
}
};
const handleScheduleNotification = async () => {
try {
await DeviceService.scheduleLocalNotification(
'Hello from Capacitor!',
'This is a local notification.',
{ at: new Date(Date.now() + 5000) } // 5 seconds from now
);
} catch (error) {
console.error('Notification error:', error);
}
};
const handleShare = async () => {
try {
await DeviceService.shareContent(
'Check out my location!',
`I'm at lat: ${position?.latitude}, lng: ${position?.longitude}`,
undefined,
'Share Location'
);
} catch (error) {
console.error('Share error:', error);
}
};
return (
<div className="device-features">
<h2>Capacitor Device APIs Demo</h2>
<section>
<h3>Camera</h3>
<button onClick={handleTakePhoto}>Take Photo</button>
{photo && (
<div>
<img src={photo.webviewPath} alt="Taken photo" style={{ maxWidth: '200px' }} />
</div>
)}
</section>
<section>
<h3>Geolocation</h3>
<button onClick={handleGetCurrentPosition}>Get Current Position</button>
<button onClick={handleWatchPosition}>Watch Position</button>
{position && (
<div>
<p>Latitude: {position.latitude}</p>
<p>Longitude: {position.longitude}</p>
<p>Accuracy: {position.accuracy}m</p>
<p>Timestamp: {new Date(position.timestamp).toLocaleString()}</p>
<button onClick={handleShare}>Share Location</button>
</div>
)}
</section>
<section>
<h3>Notifications</h3>
<button onClick={handleScheduleNotification}>
Schedule Notification (5s)
</button>
</section>
</div>
);
};
export default DeviceFeatures;
💻 Développement de Plugins Personnalisés Capacitor typescript
🟡 intermediate
Créer des plugins personnalisés Capacitor pour fonctionnalités spécifiques à la plateforme avec implémentations Swift (iOS) et Java (Android)
// src/definitions.ts
import { PluginListenerHandle } from '@capacitor/core';
export interface MyCustomPlugin {
/**
* Perform a custom operation
*/
customOperation(options: CustomOperationOptions): Promise<CustomOperationResult>;
/**
* Get device information
*/
getDeviceInfo(): Promise<DeviceInfo>;
/**
* Listen for custom events
*/
addListener(
eventName: 'customEvent',
listenerFunc: (event: CustomEvent) => void
): Promise<PluginListenerHandle> & PluginListenerHandle;
/**
* Remove all listeners for this plugin
*/
removeAllListeners(): Promise<void>;
}
export interface CustomOperationOptions {
value: string;
mode?: 'normal' | 'advanced';
}
export interface CustomOperationResult {
success: boolean;
processedValue: string;
timestamp: number;
}
export interface DeviceInfo {
platform: string;
version: string;
customProperty: string;
}
export interface CustomEvent {
data: any;
timestamp: number;
}
// src/web.ts
import { WebPlugin } from '@capacitor/core';
import { MyCustomPlugin, CustomOperationOptions, CustomOperationResult, DeviceInfo, CustomEvent } from './definitions';
export class MyCustomPluginWeb extends WebPlugin implements MyCustomPlugin {
constructor() {
super();
}
async customOperation(options: CustomOperationOptions): Promise<CustomOperationResult> {
// Web implementation
const processedValue = options.mode === 'advanced'
? `ADVANCED: ${options.value}`
: `NORMAL: ${options.value}`;
return {
success: true,
processedValue,
timestamp: Date.now()
};
}
async getDeviceInfo(): Promise<DeviceInfo> {
return {
platform: 'web',
version: navigator.userAgent,
customProperty: 'web-specific-value'
};
}
async addListener(eventName: 'customEvent', listenerFunc: (event: CustomEvent) => void): Promise<any> {
// Web event listener implementation
window.addEventListener(eventName, (event: any) => {
listenerFunc(event.detail);
});
}
async removeAllListeners(): Promise<void> {
// Web cleanup
window.removeEventListener('customEvent', () => {});
}
}
// src/index.ts
import { registerPlugin } from '@capacitor/core';
import type { MyCustomPlugin } from './definitions';
const MyCustomPlugin = registerPlugin<MyCustomPlugin>('MyCustomPlugin', {
web: () => import('./web').then(m => new m.MyCustomPluginWeb()),
});
export * from './definitions';
export { MyCustomPlugin };
// iOS Plugin (Swift) - ios/Plugin/MyCustomPlugin.swift
import Foundation
import Capacitor
@objc(MyCustomPlugin)
public class MyCustomPlugin: CAPPlugin, CAPPluginMethod {
@objc func customOperation(_ call: CAPPluginCall) {
guard let value = call.getString("value") else {
call.reject("Missing required option: value")
return
}
let mode = call.getString("mode") ?? "normal"
let processedValue = mode == "advanced" ? "ADVANCED: \(value)" : "NORMAL: \(value)"
let result = [
"success": true,
"processedValue": processedValue,
"timestamp": Date().timeIntervalSince1970 * 1000
] as [String : Any]
call.resolve(result)
}
@objc func getDeviceInfo(_ call: CAPPluginCall) {
let device = UIDevice.current
let result = [
"platform": "ios",
"version": device.systemVersion,
"customProperty": "ios-specific-value"
]
call.resolve(result)
}
@objc override public func load() {
// Plugin initialization
NotificationCenter.default.addObserver(
self,
selector: #selector(handleCustomNotification),
name: NSNotification.Name("CustomEvent"),
object: nil
)
}
@objc func handleCustomNotification(_ notification: Notification) {
guard let data = notification.userInfo else { return }
let eventData = [
"data": data,
"timestamp": Date().timeIntervalSince1970 * 1000
] as [String : Any]
notifyListeners("customEvent", data: eventData)
}
}
// iOS Plugin (Objective-C) - ios/Plugin/MyCustomPluginPlugin.m
#import <Foundation/Foundation.h>
#import <Capacitor/Capacitor.h>
// Define the plugin using CAP_PLUGIN Macro
CAP_PLUGIN(MyCustomPlugin, "MyCustomPlugin",
CAP_PLUGIN_METHOD(customOperation, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getDeviceInfo, CAPPluginReturnPromise);
)
// Android Plugin (Java) - com.example.myapp/MyCustomPlugin.java
package com.example.myapp;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "MyCustomPlugin")
public class MyCustomPlugin extends Plugin {
@PluginMethod
public void customOperation(PluginCall call) {
String value = call.getString("value");
if (value == null) {
call.reject("Missing required option: value");
return;
}
String mode = call.getString("mode", "normal");
String processedValue = "advanced".equals(mode)
? "ADVANCED: " + value
: "NORMAL: " + value;
JSObject result = new JSObject();
result.put("success", true);
result.put("processedValue", processedValue);
result.put("timestamp", System.currentTimeMillis());
call.resolve(result);
}
@PluginMethod
public void getDeviceInfo(PluginCall call) {
JSObject result = new JSObject();
result.put("platform", "android");
result.put("version", android.os.Build.VERSION.RELEASE);
result.put("customProperty", "android-specific-value");
call.resolve(result);
}
@Override
public void load() {
// Plugin initialization
}
}
// Usage in your app
import { MyCustomPlugin } from 'capacitor-my-custom-plugin';
class CustomPluginService {
static async performCustomOperation(value: string, mode?: 'normal' | 'advanced') {
try {
const result = await MyCustomPlugin.customOperation({ value, mode });
console.log('Custom operation result:', result);
return result;
} catch (error) {
console.error('Custom operation error:', error);
throw error;
}
}
static async getDeviceInformation() {
try {
const deviceInfo = await MyCustomPlugin.getDeviceInfo();
console.log('Device info:', deviceInfo);
return deviceInfo;
} catch (error) {
console.error('Get device info error:', error);
throw error;
}
}
static async listenForCustomEvents() {
await MyCustomPlugin.addListener('customEvent', (event) => {
console.log('Custom event received:', event);
// Handle the event
});
}
}
💻 Déploiement et Optimisation PWA Capacitor typescript
🔴 complex
Configurer l'application Capacitor comme Application Web Progressive avec capacités hors ligne, service workers et optimisation du manifeste d'application
// public/manifest.json
{
"name": "Capacitor PWA Demo",
"short_name": "CapacitorPWA",
"description": "A progressive web app built with Capacitor",
"start_url": "/",
"display": "standalone",
"background_color": "#3880ff",
"theme_color": "#3880ff",
"orientation": "portrait",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"splash_pages": null
}
// public/sw.js (Service Worker)
const CACHE_NAME = 'capacitor-pwa-v1';
const STATIC_CACHE = 'static-cache-v1';
const DYNAMIC_CACHE = 'dynamic-cache-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/assets/icons/icon-192x192.png',
'/assets/icons/icon-512x512.png',
'/manifest.json'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => {
console.log('Service Worker: Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== STATIC_CACHE && cache !== DYNAMIC_CACHE) {
console.log('Service Worker: Clearing old cache');
return caches.delete(cache);
}
})
);
})
.then(() => self.clients.claim())
);
});
// Fetch event - implement caching strategies
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache first strategy for static assets
if (event.request.destination === 'script' ||
event.request.destination === 'style' ||
event.request.destination === 'image') {
return response || fetch(event.request);
}
// Network first strategy for API calls
if (event.request.url.includes('/api/')) {
return fetch(event.request)
.then((fetchResponse) => {
// Cache successful API responses
if (fetchResponse.status === 200) {
caches.open(DYNAMIC_CACHE)
.then((cache) => cache.put(event.request, fetchResponse.clone()));
}
return fetchResponse;
})
.catch(() => {
// Fallback to cached API response if network fails
return caches.match(event.request);
});
}
// Network first for HTML pages
if (event.request.destination === 'document') {
return fetch(event.request)
.then((fetchResponse) => {
// Cache successful page responses
if (fetchResponse.status === 200) {
caches.open(DYNAMIC_CACHE)
.then((cache) => cache.put(event.request, fetchResponse.clone()));
}
return fetchResponse;
})
.catch(() => {
// Fallback to cached page or offline page
return caches.match(event.request) ||
caches.match('/offline.html') ||
new Response('Offline', { status: 503 });
});
}
return fetch(event.request);
})
);
});
// Background sync for offline actions
self.addEventListener('sync', (event) => {
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync());
}
});
async function doBackgroundSync() {
// Handle offline data synchronization
const offlineActions = await getOfflineActions();
for (const action of offlineActions) {
try {
await processOfflineAction(action);
await removeOfflineAction(action.id);
} catch (error) {
console.error('Background sync failed:', error);
}
}
}
// Push notifications
self.addEventListener('push', (event) => {
const options = {
body: event.data.text(),
icon: '/assets/icons/icon-192x192.png',
badge: '/assets/icons/icon-72x72.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'Explore',
icon: '/assets/icons/checkmark.png'
},
{
action: 'close',
title: 'Close',
icon: '/assets/icons/xmark.png'
}
]
};
event.waitUntil(
self.registration.showNotification('Push Notification', options)
);
});
// src/services/PWAService.ts
import { Capacitor } from '@capacitor/core';
export interface OfflineAction {
id: string;
type: string;
data: any;
timestamp: number;
}
export class PWAService {
private static isPWA(): boolean {
return Capacitor.getPlatform() === 'web' &&
(window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone);
}
private static isInStandaloneMode(): boolean {
return ('standalone' in window.navigator && (window.navigator as any).standalone) ||
window.matchMedia('(display-mode: standalone)').matches;
}
static async installPWA(): Promise<void> {
if (!this.isPWA() && 'serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registered:', registration);
// Show install prompt
this.showInstallPrompt();
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
}
private static deferredPrompt: any;
static showInstallPrompt(): void {
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
this.deferredPrompt = e;
// Show custom install UI
this.showInstallUI();
});
}
private static showInstallUI(): void {
const installButton = document.createElement('button');
installButton.textContent = 'Install App';
installButton.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 24px;
background: #3880ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
z-index: 1000;
font-size: 16px;
`;
installButton.addEventListener('click', async () => {
if (this.deferredPrompt) {
this.deferredPrompt.prompt();
const { outcome } = await this.deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('PWA installation accepted');
} else {
console.log('PWA installation dismissed');
}
this.deferredPrompt = null;
installButton.remove();
}
});
document.body.appendChild(installButton);
}
static async saveOfflineAction(action: Omit<OfflineAction, 'id' | 'timestamp'>): Promise<void> {
const offlineAction: OfflineAction = {
...action,
id: `${Date.now()}-${Math.random()}`,
timestamp: Date.now()
};
const existingActions = await this.getOfflineActions();
existingActions.push(offlineAction);
localStorage.setItem('offlineActions', JSON.stringify(existingActions));
// Register background sync if available
if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('background-sync');
}
}
static async getOfflineActions(): Promise<OfflineAction[]> {
const stored = localStorage.getItem('offlineActions');
return stored ? JSON.parse(stored) : [];
}
static async removeOfflineAction(id: string): Promise<void> {
const actions = await this.getOfflineActions();
const filtered = actions.filter(action => action.id !== id);
localStorage.setItem('offlineActions', JSON.stringify(filtered));
}
static isOnline(): boolean {
return navigator.onLine;
}
static async checkConnectivity(): Promise<boolean> {
if (!this.isOnline()) {
return false;
}
try {
const response = await fetch('/api/health', {
method: 'HEAD',
cache: 'no-cache'
});
return response.ok;
} catch {
return false;
}
}
static setupConnectivityListeners(callback: (isOnline: boolean) => void): void {
window.addEventListener('online', () => callback(true));
window.addEventListener('offline', () => callback(false));
}
static async getAppVersion(): Promise<string> {
try {
const response = await fetch('/manifest.json');
const manifest = await response.json();
return manifest.version || '1.0.0';
} catch {
return '1.0.0';
}
}
static getDeviceInfo(): { isPWA: boolean; isStandalone: boolean; platform: string } {
return {
isPWA: this.isPWA(),
isStandalone: this.isInStandaloneMode(),
platform: Capacitor.getPlatform()
};
}
}
// src/App.tsx (React Integration)
import React, { useState, useEffect } from 'react';
import { PWAService } from './services/PWAService';
const App: React.FC = () => {
const [isOnline, setIsOnline] = useState(PWAService.isOnline());
const [appInfo, setAppInfo] = useState(PWAService.getDeviceInfo());
useEffect(() => {
// Initialize PWA
PWAService.installPWA();
// Setup connectivity listeners
PWAService.setupConnectivityListeners(setIsOnline);
// Check app version
PWAService.getAppVersion().then(version => {
console.log('App version:', version);
});
// Handle offline actions when coming back online
if (isOnline) {
PWAService.getOfflineActions().then(actions => {
if (actions.length > 0) {
console.log('Processing offline actions:', actions.length);
}
});
}
}, [isOnline]);
return (
<div className="app">
<header>
<h1>Capacitor PWA Demo</h1>
<div className="status">
<span className={isOnline ? 'online' : 'offline'}>
{isOnline ? '🟢 Online' : '🔴 Offline'}
</span>
<span>Platform: {appInfo.platform}</span>
{appInfo.isPWA && <span>PWA Mode</span>}
</div>
</header>
<main>
{/* Your app content */}
</main>
</div>
);
};
export default App;