feat(ios): plugin Capacitor CoachLiveBridge (Phase 3) — relais WCSession -> /live
CAPPlugin + CAPBridgedPlugin (jsName CoachLiveBridge). Active WCSession au load(),
reçoit didReceiveMessage (temps réel) + didReceiveUserInfo (lot bufferisé) et
relaie au JS via notifyListeners("liveSample"). Enregistré dans
MainViewController.capacitorDidLoad(). Méthode getStatus pour debug JS. Build OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
01468471ECA28EA66AFE2D6C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6C76F68679844EC72178F /* Foundation.framework */; };
|
01468471ECA28EA66AFE2D6C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6C76F68679844EC72178F /* Foundation.framework */; };
|
||||||
|
21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */; };
|
||||||
290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06CDFF5B4F7852B6329862 /* ContentView.swift */; };
|
290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06CDFF5B4F7852B6329862 /* ContentView.swift */; };
|
||||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||||
3E6740C521EF14892687BCF7 /* CoachWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 2517374227A4072A4D6AFC15 /* CoachWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3E6740C521EF14892687BCF7 /* CoachWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 2517374227A4072A4D6AFC15 /* CoachWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WorkoutManager.swift; sourceTree = "<group>"; };
|
8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WorkoutManager.swift; sourceTree = "<group>"; };
|
||||||
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
|
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
|
||||||
A420239FC6D065C8A599B61C /* ConnectivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = "<group>"; };
|
A420239FC6D065C8A599B61C /* ConnectivityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityManager.swift; sourceTree = "<group>"; };
|
||||||
|
A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveBridge.swift; sourceTree = "<group>"; };
|
||||||
C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = "<group>"; };
|
C0AC4A07811751FB78AA0002 /* CoachAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAuth.swift; sourceTree = "<group>"; };
|
||||||
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = "<group>"; };
|
D09B589FA572B2C14E8CD835 /* CoachWatch.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = CoachWatch.entitlements; sourceTree = "<group>"; };
|
||||||
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = "<group>"; };
|
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachWorkoutKit.swift; sourceTree = "<group>"; };
|
||||||
@@ -144,6 +146,7 @@
|
|||||||
504EC3131FED79650016851F /* Info.plist */,
|
504EC3131FED79650016851F /* Info.plist */,
|
||||||
2FAD9762203C412B000D30F8 /* config.xml */,
|
2FAD9762203C412B000D30F8 /* config.xml */,
|
||||||
50B271D01FEDC1A000F3C39B /* public */,
|
50B271D01FEDC1A000F3C39B /* public */,
|
||||||
|
A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */,
|
||||||
);
|
);
|
||||||
path = App;
|
path = App;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -284,6 +287,7 @@
|
|||||||
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */,
|
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */,
|
||||||
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */,
|
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */,
|
||||||
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */,
|
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */,
|
||||||
|
21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
80
ios/App/App/CoachLiveBridge.swift
Normal file
80
ios/App/App/CoachLiveBridge.swift
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// CoachLiveBridge.swift
|
||||||
|
// Plugin Capacitor (côté iPhone) : reçoit les samples de séance poussés par
|
||||||
|
// l'app watchOS CoachWatch via WatchConnectivity et les relaie au JS via
|
||||||
|
// l'event "liveSample" (consommé par la page web /live).
|
||||||
|
//
|
||||||
|
// Enregistrement : MainViewController.capacitorDidLoad() ->
|
||||||
|
// bridge?.registerPluginInstance(CoachLiveBridgePlugin())
|
||||||
|
//
|
||||||
|
// Usage JS :
|
||||||
|
// const CoachLiveBridge = registerPlugin('CoachLiveBridge');
|
||||||
|
// CoachLiveBridge.addListener('liveSample', (s) => { ... });
|
||||||
|
// // s.heartRate, s.activeEnergyKcal, s.distanceMeters, s.elapsedSec, s.ts
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Capacitor
|
||||||
|
import WatchConnectivity
|
||||||
|
|
||||||
|
@objc(CoachLiveBridgePlugin)
|
||||||
|
public class CoachLiveBridgePlugin: CAPPlugin, CAPBridgedPlugin {
|
||||||
|
public let identifier = "CoachLiveBridgePlugin"
|
||||||
|
public let jsName = "CoachLiveBridge"
|
||||||
|
public let pluginMethods: [CAPPluginMethod] = [
|
||||||
|
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise)
|
||||||
|
]
|
||||||
|
|
||||||
|
override public func load() {
|
||||||
|
super.load()
|
||||||
|
guard WCSession.isSupported() else {
|
||||||
|
CAPLog.print("⚡️ CoachLiveBridge: WCSession non supporté sur cet appareil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func getStatus(_ call: CAPPluginCall) {
|
||||||
|
let session = WCSession.default
|
||||||
|
call.resolve([
|
||||||
|
"supported": WCSession.isSupported(),
|
||||||
|
"activated": session.activationState == .activated,
|
||||||
|
"paired": session.isPaired,
|
||||||
|
"watchAppInstalled": session.isWatchAppInstalled,
|
||||||
|
"reachable": session.isReachable
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emit(_ sample: [String: Any]) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.notifyListeners("liveSample", data: sample)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CoachLiveBridgePlugin: WCSessionDelegate {
|
||||||
|
public func session(_ session: WCSession,
|
||||||
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
|
error: Error?) {}
|
||||||
|
|
||||||
|
public func sessionDidBecomeInactive(_ session: WCSession) {}
|
||||||
|
|
||||||
|
public func sessionDidDeactivate(_ session: WCSession) {
|
||||||
|
// Réactiver pour les sessions futures (ex. changement d'Apple Watch).
|
||||||
|
WCSession.default.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temps réel : un sample envoyé via sendMessage.
|
||||||
|
public func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
||||||
|
emit(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Livraison différée : lot de samples envoyé via transferUserInfo.
|
||||||
|
public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
|
||||||
|
if let samples = userInfo["samples"] as? [[String: Any]] {
|
||||||
|
for sample in samples { emit(sample) }
|
||||||
|
} else {
|
||||||
|
emit(userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@ class MainViewController: CAPBridgeViewController {
|
|||||||
// Plugin natif observer HealthKit — détecte fin de séance Watch et
|
// Plugin natif observer HealthKit — détecte fin de séance Watch et
|
||||||
// push notif locale "🎉 Séance terminée" (cf. CoachWorkoutObserver.swift).
|
// push notif locale "🎉 Séance terminée" (cf. CoachWorkoutObserver.swift).
|
||||||
bridge?.registerPluginInstance(CoachWorkoutObserverPlugin())
|
bridge?.registerPluginInstance(CoachWorkoutObserverPlugin())
|
||||||
|
// Plugin natif live workout — reçoit les samples temps réel de l'app
|
||||||
|
// watchOS CoachWatch via WatchConnectivity, relaie au JS (event
|
||||||
|
// "liveSample") pour la page /live (cf. CoachLiveBridge.swift).
|
||||||
|
bridge?.registerPluginInstance(CoachLiveBridgePlugin())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|||||||
Reference in New Issue
Block a user