@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.
- package/CapgoCapacitorWatch.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +848 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/app/capgo/capacitor/watch/CapgoWatchPlugin.java +61 -0
- package/dist/docs.json +847 -0
- package/dist/esm/definitions.d.ts +371 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +12 -0
- package/dist/esm/web.js +28 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +42 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +45 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapgoWatchPlugin/CapgoWatchPlugin.swift +280 -0
- package/ios/Tests/CapgoWatchPluginTests/CapgoWatchPluginTests.swift +11 -0
- package/package.json +88 -0
|
@@ -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
|
+
}
|