@capgo/capacitor-watch 8.0.2 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/app/capgo/capacitor/watch/CapgoWatchPlugin.java +1 -1
- package/ios/Sources/CapgoWatchPlugin/CapgoWatchPlugin.swift +1 -1
- package/package.json +3 -2
- package/watch-sdk/Package.swift +19 -0
- package/watch-sdk/README.md +96 -0
- package/watch-sdk/Sources/CapgoWatchSDK/WatchConnector.swift +269 -0
|
@@ -14,7 +14,7 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
|
|
14
14
|
@CapacitorPlugin(name = "CapgoWatch")
|
|
15
15
|
public class CapgoWatchPlugin extends Plugin {
|
|
16
16
|
|
|
17
|
-
private final String pluginVersion = "8.0.
|
|
17
|
+
private final String pluginVersion = "8.0.4";
|
|
18
18
|
private static final String NOT_SUPPORTED_MSG = "Apple Watch is only supported on iOS";
|
|
19
19
|
|
|
20
20
|
@PluginMethod
|
|
@@ -6,7 +6,7 @@ import WatchConnectivity
|
|
|
6
6
|
/// here: https://capacitorjs.com/docs/plugins/ios
|
|
7
7
|
@objc(CapgoWatchPlugin)
|
|
8
8
|
public class CapgoWatchPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
9
|
-
private let pluginVersion: String = "8.0.
|
|
9
|
+
private let pluginVersion: String = "8.0.4"
|
|
10
10
|
public let identifier = "CapgoWatchPlugin"
|
|
11
11
|
public let jsName = "CapgoWatch"
|
|
12
12
|
public let pluginMethods: [CAPPluginMethod] = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-watch",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.4",
|
|
4
4
|
"description": "Capacitor plugin for Apple Watch communication with bidirectional messaging support",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"ios/Sources",
|
|
14
14
|
"ios/Tests",
|
|
15
15
|
"Package.swift",
|
|
16
|
-
"CapgoCapacitorWatch.podspec"
|
|
16
|
+
"CapgoCapacitorWatch.podspec",
|
|
17
|
+
"watch-sdk"
|
|
17
18
|
],
|
|
18
19
|
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
20
|
"license": "MPL-2.0",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapgoWatchSDK",
|
|
6
|
+
platforms: [.iOS(.v15), .watchOS(.v9)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapgoWatchSDK",
|
|
10
|
+
targets: ["CapgoWatchSDK"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [],
|
|
13
|
+
targets: [
|
|
14
|
+
.target(
|
|
15
|
+
name: "CapgoWatchSDK",
|
|
16
|
+
dependencies: [],
|
|
17
|
+
path: "Sources/CapgoWatchSDK")
|
|
18
|
+
]
|
|
19
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# CapgoWatchSDK
|
|
2
|
+
|
|
3
|
+
Swift Package for watchOS apps to communicate with the `@capgo/capacitor-watch` plugin.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this package to your watchOS app target in Xcode:
|
|
8
|
+
|
|
9
|
+
1. File > Add Package Dependencies
|
|
10
|
+
2. Enter: `https://github.com/Cap-go/capacitor-watch.git`
|
|
11
|
+
3. Select the `watch-sdk` directory
|
|
12
|
+
4. Add `CapgoWatchSDK` to your watchOS target
|
|
13
|
+
|
|
14
|
+
Or add to your `Package.swift`:
|
|
15
|
+
|
|
16
|
+
```swift
|
|
17
|
+
dependencies: [
|
|
18
|
+
.package(url: "https://github.com/Cap-go/capacitor-watch.git", from: "8.0.0")
|
|
19
|
+
]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### SwiftUI Example
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
import SwiftUI
|
|
28
|
+
import CapgoWatchSDK
|
|
29
|
+
|
|
30
|
+
@main
|
|
31
|
+
struct MyWatchApp: App {
|
|
32
|
+
init() {
|
|
33
|
+
WatchConnector.shared.activate()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var body: some Scene {
|
|
37
|
+
WindowGroup {
|
|
38
|
+
ContentView()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
struct ContentView: View {
|
|
44
|
+
@ObservedObject var connector = WatchConnector.shared
|
|
45
|
+
|
|
46
|
+
var body: some View {
|
|
47
|
+
VStack {
|
|
48
|
+
Text(connector.isReachable ? "Phone Connected" : "Phone Disconnected")
|
|
49
|
+
|
|
50
|
+
Button("Send Message") {
|
|
51
|
+
connector.sendMessage(["action": "hello"]) { reply in
|
|
52
|
+
print("Reply: \(reply)")
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
.disabled(!connector.isReachable)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Using Delegate
|
|
62
|
+
|
|
63
|
+
```swift
|
|
64
|
+
class WatchManager: WatchConnectorDelegate {
|
|
65
|
+
init() {
|
|
66
|
+
WatchConnector.shared.delegate = self
|
|
67
|
+
WatchConnector.shared.activate()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func didReceiveMessage(_ message: [String: Any]) {
|
|
71
|
+
print("Received: \(message)")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func didReceiveMessage(_ message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
|
|
75
|
+
// Process and reply
|
|
76
|
+
replyHandler(["status": "ok"])
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Async/Await
|
|
82
|
+
|
|
83
|
+
```swift
|
|
84
|
+
Task {
|
|
85
|
+
do {
|
|
86
|
+
let reply = try await WatchConnector.shared.sendMessage(["action": "getData"])
|
|
87
|
+
print("Reply: \(reply)")
|
|
88
|
+
} catch {
|
|
89
|
+
print("Error: \(error)")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MPL-2.0
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import WatchConnectivity
|
|
3
|
+
import Combine
|
|
4
|
+
|
|
5
|
+
/// Protocol for handling messages from the phone
|
|
6
|
+
public protocol WatchConnectorDelegate: AnyObject {
|
|
7
|
+
/// Called when a message is received from the phone
|
|
8
|
+
func didReceiveMessage(_ message: [String: Any])
|
|
9
|
+
|
|
10
|
+
/// Called when a message requiring a reply is received from the phone
|
|
11
|
+
func didReceiveMessage(_ message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void)
|
|
12
|
+
|
|
13
|
+
/// Called when the application context is updated from the phone
|
|
14
|
+
func didReceiveApplicationContext(_ context: [String: Any])
|
|
15
|
+
|
|
16
|
+
/// Called when user info is received from the phone
|
|
17
|
+
func didReceiveUserInfo(_ userInfo: [String: Any])
|
|
18
|
+
|
|
19
|
+
/// Called when reachability changes
|
|
20
|
+
func reachabilityDidChange(_ isReachable: Bool)
|
|
21
|
+
|
|
22
|
+
/// Called when activation completes
|
|
23
|
+
func activationDidComplete(with state: WCSessionActivationState, error: Error?)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Default implementations for optional delegate methods
|
|
27
|
+
public extension WatchConnectorDelegate {
|
|
28
|
+
func didReceiveMessage(_ message: [String: Any]) {}
|
|
29
|
+
func didReceiveMessage(_ message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
|
|
30
|
+
replyHandler([:])
|
|
31
|
+
}
|
|
32
|
+
func didReceiveApplicationContext(_ context: [String: Any]) {}
|
|
33
|
+
func didReceiveUserInfo(_ userInfo: [String: Any]) {}
|
|
34
|
+
func reachabilityDidChange(_ isReachable: Bool) {}
|
|
35
|
+
func activationDidComplete(with state: WCSessionActivationState, error: Error?) {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Main class for watch-side communication with the iPhone app.
|
|
39
|
+
/// Use this in your watchOS app to communicate with the Capacitor plugin on the phone.
|
|
40
|
+
@available(watchOS 9.0, iOS 15.0, *)
|
|
41
|
+
public final class WatchConnector: NSObject, ObservableObject {
|
|
42
|
+
|
|
43
|
+
/// Shared singleton instance
|
|
44
|
+
public static let shared = WatchConnector()
|
|
45
|
+
|
|
46
|
+
/// Delegate for receiving messages and events
|
|
47
|
+
public weak var delegate: WatchConnectorDelegate?
|
|
48
|
+
|
|
49
|
+
/// Published property for SwiftUI - whether the phone is reachable
|
|
50
|
+
@Published public private(set) var isReachable: Bool = false
|
|
51
|
+
|
|
52
|
+
/// Published property for SwiftUI - whether the session is activated
|
|
53
|
+
@Published public private(set) var isActivated: Bool = false
|
|
54
|
+
|
|
55
|
+
/// Published property for SwiftUI - the latest received message
|
|
56
|
+
@Published public private(set) var lastMessage: [String: Any] = [:]
|
|
57
|
+
|
|
58
|
+
/// Published property for SwiftUI - the current application context
|
|
59
|
+
@Published public private(set) var applicationContext: [String: Any] = [:]
|
|
60
|
+
|
|
61
|
+
/// The underlying WCSession
|
|
62
|
+
private var session: WCSession?
|
|
63
|
+
|
|
64
|
+
private override init() {
|
|
65
|
+
super.init()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - Public Methods
|
|
69
|
+
|
|
70
|
+
/// Activate the watch connectivity session.
|
|
71
|
+
/// Call this when your watch app starts, typically in your App's init or onAppear.
|
|
72
|
+
public func activate() {
|
|
73
|
+
guard WCSession.isSupported() else {
|
|
74
|
+
print("[CapgoWatchSDK] WCSession is not supported on this device")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
session = WCSession.default
|
|
79
|
+
session?.delegate = self
|
|
80
|
+
session?.activate()
|
|
81
|
+
print("[CapgoWatchSDK] WCSession activation requested")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Send a message to the phone.
|
|
85
|
+
/// The phone must be reachable for this to succeed.
|
|
86
|
+
/// - Parameter message: The data to send
|
|
87
|
+
/// - Parameter replyHandler: Optional handler for the phone's reply
|
|
88
|
+
/// - Parameter errorHandler: Optional handler for errors
|
|
89
|
+
public func sendMessage(
|
|
90
|
+
_ message: [String: Any],
|
|
91
|
+
replyHandler: (([String: Any]) -> Void)? = nil,
|
|
92
|
+
errorHandler: ((Error) -> Void)? = nil
|
|
93
|
+
) {
|
|
94
|
+
guard let session = session, session.isReachable else {
|
|
95
|
+
errorHandler?(WatchConnectorError.phoneNotReachable)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
session.sendMessage(message, replyHandler: replyHandler) { error in
|
|
100
|
+
print("[CapgoWatchSDK] Error sending message: \(error.localizedDescription)")
|
|
101
|
+
errorHandler?(error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Send a message to the phone and wait for a reply.
|
|
106
|
+
/// - Parameter message: The data to send
|
|
107
|
+
/// - Returns: The reply from the phone
|
|
108
|
+
/// - Throws: WatchConnectorError if the phone is not reachable or the send fails
|
|
109
|
+
@available(watchOS 9.0, iOS 15.0, *)
|
|
110
|
+
public func sendMessage(_ message: [String: Any]) async throws -> [String: Any] {
|
|
111
|
+
guard let session = session, session.isReachable else {
|
|
112
|
+
throw WatchConnectorError.phoneNotReachable
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
116
|
+
session.sendMessage(message, replyHandler: { reply in
|
|
117
|
+
continuation.resume(returning: reply)
|
|
118
|
+
}, errorHandler: { error in
|
|
119
|
+
continuation.resume(throwing: error)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Update the application context to share with the phone.
|
|
125
|
+
/// Only the latest context is kept.
|
|
126
|
+
/// - Parameter context: The context data to sync
|
|
127
|
+
public func updateApplicationContext(_ context: [String: Any]) throws {
|
|
128
|
+
guard let session = session else {
|
|
129
|
+
throw WatchConnectorError.sessionNotActivated
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try session.updateApplicationContext(context)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Transfer user info to the phone.
|
|
136
|
+
/// Transfers are queued and delivered in order.
|
|
137
|
+
/// - Parameter userInfo: The user info to transfer
|
|
138
|
+
/// - Returns: The transfer object for tracking
|
|
139
|
+
@discardableResult
|
|
140
|
+
public func transferUserInfo(_ userInfo: [String: Any]) -> WCSessionUserInfoTransfer? {
|
|
141
|
+
return session?.transferUserInfo(userInfo)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Get the current session state
|
|
145
|
+
public var activationState: WCSessionActivationState {
|
|
146
|
+
return session?.activationState ?? .notActivated
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Check if the phone is currently reachable
|
|
150
|
+
public var phoneIsReachable: Bool {
|
|
151
|
+
return session?.isReachable ?? false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Get the received application context from the phone
|
|
155
|
+
public var receivedApplicationContext: [String: Any] {
|
|
156
|
+
return session?.receivedApplicationContext ?? [:]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// MARK: - WCSessionDelegate
|
|
161
|
+
|
|
162
|
+
@available(watchOS 9.0, iOS 15.0, *)
|
|
163
|
+
extension WatchConnector: WCSessionDelegate {
|
|
164
|
+
|
|
165
|
+
public func session(
|
|
166
|
+
_ session: WCSession,
|
|
167
|
+
activationDidCompleteWith activationState: WCSessionActivationState,
|
|
168
|
+
error: Error?
|
|
169
|
+
) {
|
|
170
|
+
DispatchQueue.main.async {
|
|
171
|
+
self.isActivated = activationState == .activated
|
|
172
|
+
self.isReachable = session.isReachable
|
|
173
|
+
self.applicationContext = session.receivedApplicationContext
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if let error = error {
|
|
177
|
+
print("[CapgoWatchSDK] Activation failed: \(error.localizedDescription)")
|
|
178
|
+
} else {
|
|
179
|
+
print("[CapgoWatchSDK] Activation completed with state: \(activationState.rawValue)")
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
delegate?.activationDidComplete(with: activationState, error: error)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public func sessionReachabilityDidChange(_ session: WCSession) {
|
|
186
|
+
DispatchQueue.main.async {
|
|
187
|
+
self.isReachable = session.isReachable
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
print("[CapgoWatchSDK] Reachability changed: \(session.isReachable)")
|
|
191
|
+
delegate?.reachabilityDidChange(session.isReachable)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// MARK: - Receiving Messages
|
|
195
|
+
|
|
196
|
+
public func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
|
197
|
+
DispatchQueue.main.async {
|
|
198
|
+
self.lastMessage = message
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
print("[CapgoWatchSDK] Received message: \(message)")
|
|
202
|
+
delegate?.didReceiveMessage(message)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public func session(
|
|
206
|
+
_ session: WCSession,
|
|
207
|
+
didReceiveMessage message: [String: Any],
|
|
208
|
+
replyHandler: @escaping ([String: Any]) -> Void
|
|
209
|
+
) {
|
|
210
|
+
DispatchQueue.main.async {
|
|
211
|
+
self.lastMessage = message
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
print("[CapgoWatchSDK] Received message with reply: \(message)")
|
|
215
|
+
delegate?.didReceiveMessage(message, replyHandler: replyHandler)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// MARK: - Application Context
|
|
219
|
+
|
|
220
|
+
public func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
|
|
221
|
+
DispatchQueue.main.async {
|
|
222
|
+
self.applicationContext = applicationContext
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
print("[CapgoWatchSDK] Received application context: \(applicationContext)")
|
|
226
|
+
delegate?.didReceiveApplicationContext(applicationContext)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// MARK: - User Info
|
|
230
|
+
|
|
231
|
+
public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
|
|
232
|
+
print("[CapgoWatchSDK] Received user info: \(userInfo)")
|
|
233
|
+
delegate?.didReceiveUserInfo(userInfo)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// MARK: - iOS only delegate methods (required for protocol conformance)
|
|
237
|
+
|
|
238
|
+
#if os(iOS)
|
|
239
|
+
public func sessionDidBecomeInactive(_ session: WCSession) {
|
|
240
|
+
print("[CapgoWatchSDK] Session became inactive")
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public func sessionDidDeactivate(_ session: WCSession) {
|
|
244
|
+
print("[CapgoWatchSDK] Session deactivated")
|
|
245
|
+
// Reactivate for multi-watch support
|
|
246
|
+
session.activate()
|
|
247
|
+
}
|
|
248
|
+
#endif
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// MARK: - Errors
|
|
252
|
+
|
|
253
|
+
/// Errors that can occur during watch communication
|
|
254
|
+
public enum WatchConnectorError: LocalizedError {
|
|
255
|
+
case phoneNotReachable
|
|
256
|
+
case sessionNotActivated
|
|
257
|
+
case sendFailed(Error)
|
|
258
|
+
|
|
259
|
+
public var errorDescription: String? {
|
|
260
|
+
switch self {
|
|
261
|
+
case .phoneNotReachable:
|
|
262
|
+
return "iPhone is not reachable"
|
|
263
|
+
case .sessionNotActivated:
|
|
264
|
+
return "WCSession is not activated"
|
|
265
|
+
case .sendFailed(let error):
|
|
266
|
+
return "Failed to send message: \(error.localizedDescription)"
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|