@capgo/capacitor-watch 8.0.3 → 8.0.5

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/README.md CHANGED
@@ -80,13 +80,14 @@ Your capabilities should look like this when complete:
80
80
 
81
81
  ### Step 3: Configure AppDelegate.swift
82
82
 
83
- Open your `ios/App/App/AppDelegate.swift` file and add the WatchConnectivity setup:
83
+ > [!NOTE]
84
+ > For now this will not compile. This is fine, we will fix in later steps
84
85
 
85
86
  ```swift
86
87
  import UIKit
87
88
  import Capacitor
88
89
  import WatchConnectivity
89
- import CapgoCapacitorWatch
90
+ import CapgoWatchSDK
90
91
 
91
92
  @UIApplicationMain
92
93
  class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -135,22 +136,38 @@ The watch app needs our SDK to communicate with the phone. Add it as a Swift Pac
135
136
 
136
137
  ![Project package dependencies](https://raw.githubusercontent.com/ionic-team/CapacitorWatch/main/img/spm-project-dependancies.png)
137
138
 
138
- 4. In the search field, enter:
139
- ```
140
- https://github.com/Cap-go/capacitor-watch.git
141
- ```
139
+ 4. Click on the plus button to add a package
140
+
141
+ ![Plus button](./docs/plus_button.png)
142
142
 
143
143
  5. Click **Add Package**
144
144
 
145
- ![Add local SPM package](https://raw.githubusercontent.com/ionic-team/CapacitorWatch/main/img/spm-add-local.png)
145
+
146
+ ![Add local SPM package](./docs/add_watch_sdk.png)
146
147
 
147
148
  6. When prompted, select **CapgoWatchSDK** and add it to your **Watch App target** (not the main app)
148
149
 
149
- ![Pick target for package](https://raw.githubusercontent.com/ionic-team/CapacitorWatch/main/img/spm-pick-target.png)
150
+ ![Pick target for package](./docs/target.png)
150
151
 
151
152
  After adding, your package dependencies should show the CapgoWatchSDK:
152
153
 
153
- ![SPM finished](https://raw.githubusercontent.com/ionic-team/CapacitorWatch/main/img/spm-finished.png)
154
+ ![SPM finished](./docs/added.png)
155
+
156
+ ### Step 6: Fix the build for main app
157
+
158
+ Right now, your main app is missing the CapgoWatchSDK. We need to add it to the main app.
159
+
160
+ 1. Select your project in the navigator (top level, blue icon)
161
+ 2. Go to your iOS app target
162
+ 3. Go to `general`
163
+ 4. Scroll to `Frameworks, Libraries, and Embedded Content`
164
+ 5. Click the plus button to add a framework
165
+
166
+ ![Add framework](./docs/add_framework.png)
167
+
168
+ 7. Click on the `CapgoWatchSDK` framework and click `Add`
169
+
170
+ ![Add framework](./docs/add_framework_2.png)
154
171
 
155
172
  ### Step 6: Configure the Watch App
156
173
 
@@ -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.3";
17
+ private final String pluginVersion = "8.0.5";
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.3"
9
+ private let pluginVersion: String = "8.0.5"
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",
3
+ "version": "8.0.5",
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
+ }