@capgo/capacitor-watch 8.0.2

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.
@@ -0,0 +1,280 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import WatchConnectivity
4
+
5
+ /// Please read the Capacitor iOS Plugin Development Guide
6
+ /// here: https://capacitorjs.com/docs/plugins/ios
7
+ @objc(CapgoWatchPlugin)
8
+ public class CapgoWatchPlugin: CAPPlugin, CAPBridgedPlugin {
9
+ private let pluginVersion: String = "8.0.2"
10
+ public let identifier = "CapgoWatchPlugin"
11
+ public let jsName = "CapgoWatch"
12
+ public let pluginMethods: [CAPPluginMethod] = [
13
+ CAPPluginMethod(name: "sendMessage", returnType: CAPPluginReturnPromise),
14
+ CAPPluginMethod(name: "updateApplicationContext", returnType: CAPPluginReturnPromise),
15
+ CAPPluginMethod(name: "transferUserInfo", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "replyToMessage", returnType: CAPPluginReturnPromise),
17
+ CAPPluginMethod(name: "getInfo", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
19
+ ]
20
+
21
+ private var sessionDelegate: WatchSessionDelegate?
22
+ private var pendingReplies: [String: ([String: Any]) -> Void] = [:]
23
+ private let replyLock = NSLock()
24
+
25
+ override public func load() {
26
+ guard WCSession.isSupported() else {
27
+ CAPLog.print("[CapgoWatch] WatchConnectivity is not supported on this device")
28
+ return
29
+ }
30
+
31
+ sessionDelegate = WatchSessionDelegate(plugin: self)
32
+ WCSession.default.delegate = sessionDelegate
33
+ WCSession.default.activate()
34
+ CAPLog.print("[CapgoWatch] WatchConnectivity session activated")
35
+ }
36
+
37
+ @objc func sendMessage(_ call: CAPPluginCall) {
38
+ guard WCSession.isSupported() else {
39
+ call.reject("WatchConnectivity is not supported on this device")
40
+ return
41
+ }
42
+
43
+ guard WCSession.default.activationState == .activated else {
44
+ call.reject("WatchConnectivity session is not activated")
45
+ return
46
+ }
47
+
48
+ guard WCSession.default.isReachable else {
49
+ call.reject("Watch is not reachable")
50
+ return
51
+ }
52
+
53
+ guard let data = call.getObject("data") else {
54
+ call.reject("data is required")
55
+ return
56
+ }
57
+
58
+ let message = convertToWatchMessage(data)
59
+
60
+ WCSession.default.sendMessage(message, replyHandler: nil) { error in
61
+ call.reject("Failed to send message: \(error.localizedDescription)")
62
+ }
63
+
64
+ call.resolve()
65
+ }
66
+
67
+ @objc func updateApplicationContext(_ call: CAPPluginCall) {
68
+ guard WCSession.isSupported() else {
69
+ call.reject("WatchConnectivity is not supported on this device")
70
+ return
71
+ }
72
+
73
+ guard WCSession.default.activationState == .activated else {
74
+ call.reject("WatchConnectivity session is not activated")
75
+ return
76
+ }
77
+
78
+ guard let context = call.getObject("context") else {
79
+ call.reject("context is required")
80
+ return
81
+ }
82
+
83
+ let watchContext = convertToWatchMessage(context)
84
+
85
+ do {
86
+ try WCSession.default.updateApplicationContext(watchContext)
87
+ call.resolve()
88
+ } catch {
89
+ call.reject("Failed to update application context: \(error.localizedDescription)")
90
+ }
91
+ }
92
+
93
+ @objc func transferUserInfo(_ call: CAPPluginCall) {
94
+ guard WCSession.isSupported() else {
95
+ call.reject("WatchConnectivity is not supported on this device")
96
+ return
97
+ }
98
+
99
+ guard WCSession.default.activationState == .activated else {
100
+ call.reject("WatchConnectivity session is not activated")
101
+ return
102
+ }
103
+
104
+ guard let userInfo = call.getObject("userInfo") else {
105
+ call.reject("userInfo is required")
106
+ return
107
+ }
108
+
109
+ let watchUserInfo = convertToWatchMessage(userInfo)
110
+ WCSession.default.transferUserInfo(watchUserInfo)
111
+ call.resolve()
112
+ }
113
+
114
+ @objc func replyToMessage(_ call: CAPPluginCall) {
115
+ guard let callbackId = call.getString("callbackId") else {
116
+ call.reject("callbackId is required")
117
+ return
118
+ }
119
+
120
+ guard let data = call.getObject("data") else {
121
+ call.reject("data is required")
122
+ return
123
+ }
124
+
125
+ replyLock.lock()
126
+ let replyHandler = pendingReplies.removeValue(forKey: callbackId)
127
+ replyLock.unlock()
128
+
129
+ guard let handler = replyHandler else {
130
+ call.reject("Invalid or expired callbackId")
131
+ return
132
+ }
133
+
134
+ let replyData = convertToWatchMessage(data)
135
+ handler(replyData)
136
+ call.resolve()
137
+ }
138
+
139
+ @objc func getInfo(_ call: CAPPluginCall) {
140
+ let isSupported = WCSession.isSupported()
141
+
142
+ var isPaired = false
143
+ var isWatchAppInstalled = false
144
+ var isReachable = false
145
+ var activationState = 0
146
+
147
+ if isSupported {
148
+ isPaired = WCSession.default.isPaired
149
+ isWatchAppInstalled = WCSession.default.isWatchAppInstalled
150
+ isReachable = WCSession.default.isReachable
151
+ activationState = WCSession.default.activationState.rawValue
152
+ }
153
+
154
+ call.resolve([
155
+ "isSupported": isSupported,
156
+ "isPaired": isPaired,
157
+ "isWatchAppInstalled": isWatchAppInstalled,
158
+ "isReachable": isReachable,
159
+ "activationState": activationState
160
+ ])
161
+ }
162
+
163
+ @objc func getPluginVersion(_ call: CAPPluginCall) {
164
+ call.resolve(["version": pluginVersion])
165
+ }
166
+
167
+ // MARK: - Internal methods for delegate
168
+
169
+ func storePendingReply(callbackId: String, handler: @escaping ([String: Any]) -> Void) {
170
+ replyLock.lock()
171
+ pendingReplies[callbackId] = handler
172
+ replyLock.unlock()
173
+ }
174
+
175
+ // MARK: - Helper methods
176
+
177
+ private func convertToWatchMessage(_ jsObject: JSObject) -> [String: Any] {
178
+ var result: [String: Any] = [:]
179
+ for (key, value) in jsObject {
180
+ result[key] = convertJSValue(value)
181
+ }
182
+ return result
183
+ }
184
+
185
+ private func convertJSValue(_ value: Any) -> Any {
186
+ if let dict = value as? JSObject {
187
+ return convertToWatchMessage(dict)
188
+ } else if let array = value as? JSArray {
189
+ return array.map { convertJSValue($0) }
190
+ } else {
191
+ return value
192
+ }
193
+ }
194
+ }
195
+
196
+ // MARK: - WatchSessionDelegate
197
+
198
+ class WatchSessionDelegate: NSObject, WCSessionDelegate {
199
+ private weak var plugin: CapgoWatchPlugin?
200
+
201
+ init(plugin: CapgoWatchPlugin) {
202
+ self.plugin = plugin
203
+ super.init()
204
+ }
205
+
206
+ func session(
207
+ _ session: WCSession,
208
+ activationDidCompleteWith activationState: WCSessionActivationState,
209
+ error: Error?
210
+ ) {
211
+ if let error = error {
212
+ CAPLog.print("[CapgoWatch] Activation failed: \(error.localizedDescription)")
213
+ return
214
+ }
215
+
216
+ CAPLog.print("[CapgoWatch] Activation completed with state: \(activationState.rawValue)")
217
+ plugin?.notifyListeners("activationStateChanged", data: [
218
+ "state": activationState.rawValue
219
+ ])
220
+ }
221
+
222
+ func sessionDidBecomeInactive(_ session: WCSession) {
223
+ CAPLog.print("[CapgoWatch] Session became inactive")
224
+ }
225
+
226
+ func sessionDidDeactivate(_ session: WCSession) {
227
+ CAPLog.print("[CapgoWatch] Session deactivated")
228
+ // Reactivate for multi-watch support
229
+ WCSession.default.activate()
230
+ }
231
+
232
+ func sessionReachabilityDidChange(_ session: WCSession) {
233
+ CAPLog.print("[CapgoWatch] Reachability changed: \(session.isReachable)")
234
+ plugin?.notifyListeners("reachabilityChanged", data: [
235
+ "isReachable": session.isReachable
236
+ ])
237
+ }
238
+
239
+ // MARK: - Message receiving
240
+
241
+ func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
242
+ CAPLog.print("[CapgoWatch] Received message: \(message)")
243
+ plugin?.notifyListeners("messageReceived", data: [
244
+ "message": message
245
+ ])
246
+ }
247
+
248
+ func session(
249
+ _ session: WCSession,
250
+ didReceiveMessage message: [String: Any],
251
+ replyHandler: @escaping ([String: Any]) -> Void
252
+ ) {
253
+ CAPLog.print("[CapgoWatch] Received message with reply: \(message)")
254
+ let callbackId = UUID().uuidString
255
+
256
+ plugin?.storePendingReply(callbackId: callbackId, handler: replyHandler)
257
+ plugin?.notifyListeners("messageReceivedWithReply", data: [
258
+ "message": message,
259
+ "callbackId": callbackId
260
+ ])
261
+ }
262
+
263
+ // MARK: - Application context
264
+
265
+ func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
266
+ CAPLog.print("[CapgoWatch] Received application context: \(applicationContext)")
267
+ plugin?.notifyListeners("applicationContextReceived", data: [
268
+ "context": applicationContext
269
+ ])
270
+ }
271
+
272
+ // MARK: - User info
273
+
274
+ func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
275
+ CAPLog.print("[CapgoWatch] Received user info: \(userInfo)")
276
+ plugin?.notifyListeners("userInfoReceived", data: [
277
+ "userInfo": userInfo
278
+ ])
279
+ }
280
+ }
@@ -0,0 +1,11 @@
1
+ import XCTest
2
+ @testable import CapgoWatchPlugin
3
+
4
+ class CapgoWatchPluginTests: XCTestCase {
5
+ func testPluginExists() throws {
6
+ let plugin = CapgoWatchPlugin()
7
+ XCTAssertNotNil(plugin)
8
+ XCTAssertEqual(plugin.jsName, "CapgoWatch")
9
+ XCTAssertEqual(plugin.identifier, "CapgoWatchPlugin")
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@capgo/capacitor-watch",
3
+ "version": "8.0.2",
4
+ "description": "Capacitor plugin for Apple Watch communication with bidirectional messaging support",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapgoCapacitorWatch.podspec"
17
+ ],
18
+ "author": "Martin Donadieu <martin@capgo.app>",
19
+ "license": "MPL-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Cap-go/capacitor-watch.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Cap-go/capacitor-watch/issues"
26
+ },
27
+ "homepage": "https://capgo.app/docs/plugins/watch/",
28
+ "keywords": [
29
+ "capacitor",
30
+ "plugin",
31
+ "native",
32
+ "watch",
33
+ "apple-watch",
34
+ "watchkit",
35
+ "watchconnectivity"
36
+ ],
37
+ "scripts": {
38
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
39
+ "verify:ios": "xcodebuild -scheme CapgoCapacitorWatch -destination generic/platform=iOS",
40
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
41
+ "verify:web": "npm run build",
42
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
43
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
44
+ "eslint": "eslint . --ext ts",
45
+ "prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
46
+ "swiftlint": "node-swiftlint",
47
+ "docgen": "docgen --api CapgoWatchPlugin --output-readme README.md --output-json dist/docs.json",
48
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
49
+ "clean": "rimraf ./dist",
50
+ "watch": "tsc --watch",
51
+ "prepublishOnly": "npm run build"
52
+ },
53
+ "devDependencies": {
54
+ "@capacitor/android": "^8.0.0",
55
+ "@capacitor/core": "^8.0.0",
56
+ "@capacitor/docgen": "^0.3.1",
57
+ "@capacitor/ios": "^8.0.0",
58
+ "@ionic/eslint-config": "^0.4.0",
59
+ "@ionic/prettier-config": "^4.0.0",
60
+ "@ionic/swiftlint-config": "^2.0.0",
61
+ "@types/node": "^24.10.1",
62
+ "eslint": "^8.57.1",
63
+ "eslint-plugin-import": "^2.31.0",
64
+ "prettier": "^3.6.2",
65
+ "prettier-plugin-java": "^2.7.7",
66
+ "rimraf": "^6.1.0",
67
+ "rollup": "^4.53.2",
68
+ "swiftlint": "^2.0.0",
69
+ "typescript": "^5.9.3",
70
+ "prettier-pretty-check": "^0.2.0"
71
+ },
72
+ "peerDependencies": {
73
+ "@capacitor/core": ">=8.0.0"
74
+ },
75
+ "prettier": "@ionic/prettier-config",
76
+ "swiftlint": "@ionic/swiftlint-config",
77
+ "eslintConfig": {
78
+ "extends": "@ionic/eslint-config/recommended"
79
+ },
80
+ "capacitor": {
81
+ "ios": {
82
+ "src": "ios"
83
+ },
84
+ "android": {
85
+ "src": "android"
86
+ }
87
+ }
88
+ }