@capgo/capacitor-watch 8.0.25 → 8.1.1

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
@@ -509,8 +509,12 @@ struct StatusView: View {
509
509
  <docgen-api>
510
510
  <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
511
511
 
512
- Apple Watch communication plugin for Capacitor.
513
- Provides bidirectional messaging between iPhone and Apple Watch using WatchConnectivity.
512
+ Watch / Wear OS communication plugin for Capacitor.
513
+ Provides bidirectional messaging between the phone and a paired watch.
514
+
515
+ - **iOS**: uses Apple WatchConnectivity framework to communicate with Apple Watch.
516
+ - **Android**: uses Google Wear OS Data Layer API (play-services-wearable) to communicate with a Wear OS watch.
517
+ - **Web**: not supported; all methods throw or return safe defaults.
514
518
 
515
519
  ### sendMessage(...)
516
520
 
@@ -792,15 +796,15 @@ Options for replying to a message from the watch.
792
796
 
793
797
  #### WatchInfo
794
798
 
795
- Information about Watch connectivity status.
799
+ Information about Watch / Wear OS connectivity status.
796
800
 
797
- | Prop | Type | Description |
798
- | ------------------------- | -------------------- | ---------------------------------------------------------------------------------------------- |
799
- | **`isSupported`** | <code>boolean</code> | Whether WatchConnectivity is supported on this device. Always false on iPad, web, and Android. |
800
- | **`isPaired`** | <code>boolean</code> | Whether an Apple Watch is paired with this iPhone. |
801
- | **`isWatchAppInstalled`** | <code>boolean</code> | Whether the paired watch has the companion app installed. |
802
- | **`isReachable`** | <code>boolean</code> | Whether the watch is currently reachable for immediate messaging. |
803
- | **`activationState`** | <code>number</code> | The current activation state of the WCSession. 0 = notActivated, 1 = inactive, 2 = activated |
801
+ | Prop | Type | Description |
802
+ | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
803
+ | **`isSupported`** | <code>boolean</code> | Whether the watch communication API is supported on this device. - iOS: false on iPad; true on iPhone when WatchConnectivity is available. - Android: true when Google Play Services with Wear OS support is available; false otherwise. - Web: always false. |
804
+ | **`isPaired`** | <code>boolean</code> | Whether a watch is currently paired/connected. - iOS: whether an Apple Watch is paired with this iPhone. - Android: whether at least one Wear OS node is currently connected. |
805
+ | **`isWatchAppInstalled`** | <code>boolean</code> | Whether the watch companion app is installed. - iOS: whether the paired Apple Watch has the companion app installed. - Android: whether at least one connected Wear OS node is reachable (used as a proxy). |
806
+ | **`isReachable`** | <code>boolean</code> | Whether the watch is currently reachable for immediate messaging. |
807
+ | **`activationState`** | <code>number</code> | The current session activation state. - iOS: 0 = notActivated, 1 = inactive, 2 = activated (WCSessionActivationState). - Android: 2 when a Wear OS node is connected, 0 otherwise. |
804
808
 
805
809
 
806
810
  #### PluginListenerHandle
@@ -52,6 +52,7 @@ dependencies {
52
52
  implementation fileTree(dir: 'libs', include: ['*.jar'])
53
53
  implementation project(':capacitor-android')
54
54
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ implementation "com.google.android.gms:play-services-wearable:18.1.0"
55
56
  testImplementation "junit:junit:$junitVersion"
56
57
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
57
58
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -1,61 +1,357 @@
1
1
  package app.capgo.capacitor.watch;
2
2
 
3
+ import android.net.Uri;
4
+ import android.util.Log;
3
5
  import com.getcapacitor.JSObject;
4
6
  import com.getcapacitor.Plugin;
5
7
  import com.getcapacitor.PluginCall;
6
8
  import com.getcapacitor.PluginMethod;
7
9
  import com.getcapacitor.annotation.CapacitorPlugin;
10
+ import com.google.android.gms.tasks.Tasks;
11
+ import com.google.android.gms.wearable.CapabilityClient;
12
+ import com.google.android.gms.wearable.CapabilityInfo;
13
+ import com.google.android.gms.wearable.DataClient;
14
+ import com.google.android.gms.wearable.DataEvent;
15
+ import com.google.android.gms.wearable.DataEventBuffer;
16
+ import com.google.android.gms.wearable.DataItem;
17
+ import com.google.android.gms.wearable.DataMap;
18
+ import com.google.android.gms.wearable.DataMapItem;
19
+ import com.google.android.gms.wearable.MessageClient;
20
+ import com.google.android.gms.wearable.MessageEvent;
21
+ import com.google.android.gms.wearable.Node;
22
+ import com.google.android.gms.wearable.PutDataMapRequest;
23
+ import com.google.android.gms.wearable.Wearable;
24
+ import java.nio.charset.StandardCharsets;
25
+ import java.util.Iterator;
26
+ import java.util.List;
27
+ import java.util.Map;
28
+ import java.util.UUID;
29
+ import java.util.concurrent.ConcurrentHashMap;
30
+ import java.util.concurrent.ExecutionException;
31
+ import java.util.concurrent.ExecutorService;
32
+ import java.util.concurrent.Executors;
33
+ import org.json.JSONException;
34
+ import org.json.JSONObject;
8
35
 
9
36
  /**
10
- * Apple Watch communication plugin for Capacitor.
11
- * This plugin only works on iOS - Android implementation provides stub methods
12
- * that return appropriate error messages.
37
+ * Wear OS communication plugin for Capacitor.
38
+ * Provides bidirectional messaging between Android phone and Wear OS watch
39
+ * using the Wear OS Data Layer API (play-services-wearable).
40
+ *
41
+ * <p>Message paths used over the Data Layer:</p>
42
+ * <ul>
43
+ * <li>{@code /capgo/message} — regular one-way messages</li>
44
+ * <li>{@code /capgo/message/withreply} — messages that expect a reply</li>
45
+ * <li>{@code /capgo/reply/{callbackId}} — reply to a watch-initiated message</li>
46
+ * <li>{@code /capgo/context} — application context sync via DataItem</li>
47
+ * <li>{@code /capgo/userinfo/{uuid}} — queued user-info transfer via DataItem</li>
48
+ * </ul>
13
49
  */
14
50
  @CapacitorPlugin(name = "CapgoWatch")
15
51
  public class CapgoWatchPlugin extends Plugin {
16
52
 
17
- private final String pluginVersion = "8.0.25";
18
- private static final String NOT_SUPPORTED_MSG = "Apple Watch is only supported on iOS";
53
+ private static final String TAG = "CapgoWatchPlugin";
54
+ private static final String PLUGIN_VERSION = "8.0.25";
55
+
56
+ /** Capability advertised by the companion Wear OS app. */
57
+ static final String WATCH_APP_CAPABILITY = "capgo_watch";
58
+
59
+ /** Path for regular messages sent/received via MessageClient. */
60
+ static final String PATH_MESSAGE = "/capgo/message";
61
+ /** Path prefix for messages that require a reply. */
62
+ static final String PATH_MESSAGE_WITH_REPLY = "/capgo/message/withreply";
63
+ /** Path prefix for reply messages sent back to the watch. */
64
+ static final String PATH_REPLY = "/capgo/reply/";
65
+ /** DataItem path for application context sync. */
66
+ static final String PATH_CONTEXT = "/capgo/context";
67
+ /** DataItem path prefix for user info transfers. */
68
+ static final String PATH_USER_INFO = "/capgo/userinfo/";
69
+
70
+ /** Pending reply callbacks expire after 5 minutes. */
71
+ private static final long PENDING_REPLY_TTL_MS = 5 * 60 * 1000L;
72
+
73
+ /** Maps callbackId to pending reply metadata. */
74
+ private final Map<String, PendingReply> pendingReplies = new ConcurrentHashMap<>();
75
+
76
+ /** Shared thread pool for all background Wear OS operations. */
77
+ private final ExecutorService executor = Executors.newCachedThreadPool();
78
+
79
+ private MessageClient messageClient;
80
+ private DataClient dataClient;
81
+
82
+ private final MessageClient.OnMessageReceivedListener messageListener = this::handleMessageReceived;
83
+ private final DataClient.OnDataChangedListener dataListener = this::handleDataChanged;
84
+
85
+ private static final class PendingReply {
86
+
87
+ final String nodeId;
88
+ final long createdAt;
89
+
90
+ PendingReply(final String nodeId, final long createdAt) {
91
+ this.nodeId = nodeId;
92
+ this.createdAt = createdAt;
93
+ }
94
+ }
95
+
96
+ @Override
97
+ public void load() {
98
+ messageClient = Wearable.getMessageClient(getContext());
99
+ dataClient = Wearable.getDataClient(getContext());
100
+ messageClient.addListener(messageListener).addOnFailureListener((e) -> Log.e(TAG, "Failed to register message listener", e));
101
+ dataClient.addListener(dataListener).addOnFailureListener((e) -> Log.e(TAG, "Failed to register data listener", e));
102
+ }
103
+
104
+ @Override
105
+ protected void handleOnDestroy() {
106
+ if (messageClient != null) {
107
+ messageClient.removeListener(messageListener);
108
+ }
109
+ if (dataClient != null) {
110
+ dataClient.removeListener(dataListener);
111
+ }
112
+ executor.shutdown();
113
+ }
114
+
115
+ private void expirePendingReplies() {
116
+ final long now = System.currentTimeMillis();
117
+ final Iterator<Map.Entry<String, PendingReply>> iterator = pendingReplies.entrySet().iterator();
118
+ while (iterator.hasNext()) {
119
+ final Map.Entry<String, PendingReply> entry = iterator.next();
120
+ if (now - entry.getValue().createdAt > PENDING_REPLY_TTL_MS) {
121
+ iterator.remove();
122
+ }
123
+ }
124
+ }
125
+
126
+ // ── Incoming message / data handlers ─────────────────────────────────────
127
+
128
+ private void handleMessageReceived(MessageEvent event) {
129
+ final String path = event.getPath();
130
+ if (!PATH_MESSAGE.equals(path) && !PATH_MESSAGE_WITH_REPLY.equals(path)) {
131
+ return;
132
+ }
133
+
134
+ expirePendingReplies();
135
+
136
+ final String nodeId = event.getSourceNodeId();
137
+ final String payload = new String(event.getData(), StandardCharsets.UTF_8);
138
+
139
+ try {
140
+ final JSONObject json = new JSONObject(payload);
141
+ final JSObject messageData = new JSObject(json.toString());
142
+
143
+ if (PATH_MESSAGE_WITH_REPLY.equals(path)) {
144
+ final String callbackId = UUID.randomUUID().toString();
145
+ pendingReplies.put(callbackId, new PendingReply(nodeId, System.currentTimeMillis()));
146
+
147
+ final JSObject evt = new JSObject();
148
+ evt.put("message", messageData);
149
+ evt.put("callbackId", callbackId);
150
+ notifyListeners("messageReceivedWithReply", evt);
151
+ } else {
152
+ final JSObject evt = new JSObject();
153
+ evt.put("message", messageData);
154
+ notifyListeners("messageReceived", evt);
155
+ }
156
+ } catch (JSONException e) {
157
+ Log.e(TAG, "Error parsing received message", e);
158
+ }
159
+ }
160
+
161
+ private void handleDataChanged(DataEventBuffer dataEvents) {
162
+ for (DataEvent event : dataEvents) {
163
+ if (event.getType() != DataEvent.TYPE_CHANGED) {
164
+ continue;
165
+ }
166
+
167
+ final DataItem item = event.getDataItem();
168
+ final String path = item.getUri().getPath();
169
+ if (path == null) {
170
+ continue;
171
+ }
172
+
173
+ final boolean isContext = PATH_CONTEXT.equals(path);
174
+ final boolean isUserInfo = path.startsWith(PATH_USER_INFO);
175
+ if (!isContext && !isUserInfo) {
176
+ continue;
177
+ }
178
+
179
+ try {
180
+ final DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
181
+ final String payload = dataMap.getString("payload", "{}");
182
+ final JSONObject json = new JSONObject(payload);
183
+ final JSObject data = new JSObject(json.toString());
184
+
185
+ if (isContext) {
186
+ final JSObject evt = new JSObject();
187
+ evt.put("context", data);
188
+ notifyListeners("applicationContextReceived", evt);
189
+ } else {
190
+ final JSObject evt = new JSObject();
191
+ evt.put("userInfo", data);
192
+ notifyListeners("userInfoReceived", evt);
193
+ deleteDataItem(item.getUri());
194
+ }
195
+ } catch (Exception e) {
196
+ Log.e(TAG, "Error processing data change", e);
197
+ }
198
+ }
199
+ }
200
+
201
+ private void deleteDataItem(final Uri uri) {
202
+ if (dataClient == null) {
203
+ return;
204
+ }
205
+ dataClient.deleteDataItems(uri).addOnFailureListener((e) -> Log.w(TAG, "Failed to delete delivered user info DataItem", e));
206
+ }
207
+
208
+ // ── Plugin methods ────────────────────────────────────────────────────────
19
209
 
20
210
  @PluginMethod
21
211
  public void sendMessage(final PluginCall call) {
22
- call.reject(NOT_SUPPORTED_MSG);
212
+ final JSObject data = call.getObject("data");
213
+ if (data == null) {
214
+ call.reject("Missing required parameter: 'data'");
215
+ return;
216
+ }
217
+
218
+ final byte[] payload = data.toString().getBytes(StandardCharsets.UTF_8);
219
+ executor.execute(() -> {
220
+ try {
221
+ final List<Node> nodes = Tasks.await(Wearable.getNodeClient(getContext()).getConnectedNodes());
222
+ if (nodes.isEmpty()) {
223
+ call.reject("No connected Wear OS devices found");
224
+ return;
225
+ }
226
+ for (final Node node : nodes) {
227
+ Tasks.await(messageClient.sendMessage(node.getId(), PATH_MESSAGE, payload));
228
+ }
229
+ call.resolve();
230
+ } catch (ExecutionException | InterruptedException e) {
231
+ call.reject("Failed to send message: " + e.getMessage(), e);
232
+ }
233
+ });
23
234
  }
24
235
 
25
236
  @PluginMethod
26
237
  public void updateApplicationContext(final PluginCall call) {
27
- call.reject(NOT_SUPPORTED_MSG);
238
+ final JSObject context = call.getObject("context");
239
+ if (context == null) {
240
+ call.reject("Missing required parameter: 'context'");
241
+ return;
242
+ }
243
+
244
+ executor.execute(() -> {
245
+ try {
246
+ final PutDataMapRequest request = PutDataMapRequest.create(PATH_CONTEXT);
247
+ request.getDataMap().putString("payload", context.toString());
248
+ request.setUrgent();
249
+ Tasks.await(dataClient.putDataItem(request.asPutDataRequest()));
250
+ call.resolve();
251
+ } catch (ExecutionException | InterruptedException e) {
252
+ call.reject("Failed to update application context: " + e.getMessage(), e);
253
+ }
254
+ });
28
255
  }
29
256
 
30
257
  @PluginMethod
31
258
  public void transferUserInfo(final PluginCall call) {
32
- call.reject(NOT_SUPPORTED_MSG);
259
+ final JSObject userInfo = call.getObject("userInfo");
260
+ if (userInfo == null) {
261
+ call.reject("Missing required parameter: 'userInfo'");
262
+ return;
263
+ }
264
+
265
+ final String path = PATH_USER_INFO + UUID.randomUUID();
266
+ executor.execute(() -> {
267
+ try {
268
+ final PutDataMapRequest request = PutDataMapRequest.create(path);
269
+ request.getDataMap().putString("payload", userInfo.toString());
270
+ request.setUrgent();
271
+ Tasks.await(dataClient.putDataItem(request.asPutDataRequest()));
272
+ call.resolve();
273
+ } catch (ExecutionException | InterruptedException e) {
274
+ call.reject("Failed to transfer user info: " + e.getMessage(), e);
275
+ }
276
+ });
33
277
  }
34
278
 
35
279
  @PluginMethod
36
280
  public void replyToMessage(final PluginCall call) {
37
- call.reject(NOT_SUPPORTED_MSG);
281
+ final String callbackId = call.getString("callbackId");
282
+ final JSObject data = call.getObject("data");
283
+
284
+ if (callbackId == null || callbackId.isEmpty()) {
285
+ call.reject("Missing required parameter: 'callbackId'");
286
+ return;
287
+ }
288
+ if (data == null) {
289
+ call.reject("Missing required parameter: 'data'");
290
+ return;
291
+ }
292
+
293
+ expirePendingReplies();
294
+
295
+ final PendingReply pendingReply = pendingReplies.get(callbackId);
296
+ if (pendingReply == null) {
297
+ call.reject("No pending reply found for callbackId: " + callbackId);
298
+ return;
299
+ }
300
+ if (System.currentTimeMillis() - pendingReply.createdAt > PENDING_REPLY_TTL_MS) {
301
+ pendingReplies.remove(callbackId);
302
+ call.reject("Pending reply expired for callbackId: " + callbackId);
303
+ return;
304
+ }
305
+
306
+ final String nodeId = pendingReply.nodeId;
307
+ final byte[] payload = data.toString().getBytes(StandardCharsets.UTF_8);
308
+ final String replyPath = PATH_REPLY + callbackId;
309
+ executor.execute(() -> {
310
+ try {
311
+ Tasks.await(messageClient.sendMessage(nodeId, replyPath, payload));
312
+ pendingReplies.remove(callbackId);
313
+ call.resolve();
314
+ } catch (ExecutionException | InterruptedException e) {
315
+ call.reject("Failed to send reply: " + e.getMessage(), e);
316
+ }
317
+ });
38
318
  }
39
319
 
40
320
  @PluginMethod
41
321
  public void getInfo(final PluginCall call) {
42
- final JSObject ret = new JSObject();
43
- ret.put("isSupported", false);
44
- ret.put("isPaired", false);
45
- ret.put("isWatchAppInstalled", false);
46
- ret.put("isReachable", false);
47
- ret.put("activationState", 0);
48
- call.resolve(ret);
322
+ executor.execute(() -> {
323
+ try {
324
+ final List<Node> nodes = Tasks.await(Wearable.getNodeClient(getContext()).getConnectedNodes());
325
+ final boolean isReachable = !nodes.isEmpty();
326
+ final CapabilityClient capabilityClient = Wearable.getCapabilityClient(getContext());
327
+ final CapabilityInfo capabilityInfo = Tasks.await(
328
+ capabilityClient.getCapability(WATCH_APP_CAPABILITY, CapabilityClient.FILTER_ALL)
329
+ );
330
+ final boolean isWatchAppInstalled = !capabilityInfo.getNodes().isEmpty();
331
+ final JSObject ret = new JSObject();
332
+ ret.put("isSupported", true);
333
+ ret.put("isPaired", isReachable);
334
+ ret.put("isWatchAppInstalled", isWatchAppInstalled);
335
+ ret.put("isReachable", isReachable);
336
+ ret.put("activationState", isReachable ? 2 : 0);
337
+ call.resolve(ret);
338
+ } catch (ExecutionException | InterruptedException e) {
339
+ // Wear OS API unavailable (e.g. Google Play Services missing)
340
+ final JSObject ret = new JSObject();
341
+ ret.put("isSupported", false);
342
+ ret.put("isPaired", false);
343
+ ret.put("isWatchAppInstalled", false);
344
+ ret.put("isReachable", false);
345
+ ret.put("activationState", 0);
346
+ call.resolve(ret);
347
+ }
348
+ });
49
349
  }
50
350
 
51
351
  @PluginMethod
52
352
  public void getPluginVersion(final PluginCall call) {
53
- try {
54
- final JSObject ret = new JSObject();
55
- ret.put("version", this.pluginVersion);
56
- call.resolve(ret);
57
- } catch (final Exception e) {
58
- call.reject("Could not get plugin version", e);
59
- }
353
+ final JSObject ret = new JSObject();
354
+ ret.put("version", PLUGIN_VERSION);
355
+ call.resolve(ret);
60
356
  }
61
357
  }
package/dist/docs.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "api": {
3
3
  "name": "CapgoWatchPlugin",
4
4
  "slug": "capgowatchplugin",
5
- "docs": "Apple Watch communication plugin for Capacitor.\nProvides bidirectional messaging between iPhone and Apple Watch using WatchConnectivity.",
5
+ "docs": "Watch / Wear OS communication plugin for Capacitor.\nProvides bidirectional messaging between the phone and a paired watch.\n\n- **iOS**: uses Apple WatchConnectivity framework to communicate with Apple Watch.\n- **Android**: uses Google Wear OS Data Layer API (play-services-wearable) to communicate with a Wear OS watch.\n- **Web**: not supported; all methods throw or return safe defaults.",
6
6
  "tags": [
7
7
  {
8
8
  "text": "8.0.0",
@@ -609,7 +609,7 @@
609
609
  {
610
610
  "name": "WatchInfo",
611
611
  "slug": "watchinfo",
612
- "docs": "Information about Watch connectivity status.",
612
+ "docs": "Information about Watch / Wear OS connectivity status.",
613
613
  "tags": [
614
614
  {
615
615
  "text": "8.0.0",
@@ -621,21 +621,21 @@
621
621
  {
622
622
  "name": "isSupported",
623
623
  "tags": [],
624
- "docs": "Whether WatchConnectivity is supported on this device.\nAlways false on iPad, web, and Android.",
624
+ "docs": "Whether the watch communication API is supported on this device.\n- iOS: false on iPad; true on iPhone when WatchConnectivity is available.\n- Android: true when Google Play Services with Wear OS support is available; false otherwise.\n- Web: always false.",
625
625
  "complexTypes": [],
626
626
  "type": "boolean"
627
627
  },
628
628
  {
629
629
  "name": "isPaired",
630
630
  "tags": [],
631
- "docs": "Whether an Apple Watch is paired with this iPhone.",
631
+ "docs": "Whether a watch is currently paired/connected.\n- iOS: whether an Apple Watch is paired with this iPhone.\n- Android: whether at least one Wear OS node is currently connected.",
632
632
  "complexTypes": [],
633
633
  "type": "boolean"
634
634
  },
635
635
  {
636
636
  "name": "isWatchAppInstalled",
637
637
  "tags": [],
638
- "docs": "Whether the paired watch has the companion app installed.",
638
+ "docs": "Whether the watch companion app is installed.\n- iOS: whether the paired Apple Watch has the companion app installed.\n- Android: whether at least one connected Wear OS node is reachable (used as a proxy).",
639
639
  "complexTypes": [],
640
640
  "type": "boolean"
641
641
  },
@@ -649,7 +649,7 @@
649
649
  {
650
650
  "name": "activationState",
651
651
  "tags": [],
652
- "docs": "The current activation state of the WCSession.\n0 = notActivated, 1 = inactive, 2 = activated",
652
+ "docs": "The current session activation state.\n- iOS: 0 = notActivated, 1 = inactive, 2 = activated (WCSessionActivationState).\n- Android: 2 when a Wear OS node is connected, 0 otherwise.",
653
653
  "complexTypes": [],
654
654
  "type": "number"
655
655
  }
@@ -58,22 +58,28 @@ export interface ReplyMessageOptions {
58
58
  data: WatchMessageData;
59
59
  }
60
60
  /**
61
- * Information about Watch connectivity status.
61
+ * Information about Watch / Wear OS connectivity status.
62
62
  *
63
63
  * @since 8.0.0
64
64
  */
65
65
  export interface WatchInfo {
66
66
  /**
67
- * Whether WatchConnectivity is supported on this device.
68
- * Always false on iPad, web, and Android.
67
+ * Whether the watch communication API is supported on this device.
68
+ * - iOS: false on iPad; true on iPhone when WatchConnectivity is available.
69
+ * - Android: true when Google Play Services with Wear OS support is available; false otherwise.
70
+ * - Web: always false.
69
71
  */
70
72
  isSupported: boolean;
71
73
  /**
72
- * Whether an Apple Watch is paired with this iPhone.
74
+ * Whether a watch is currently paired/connected.
75
+ * - iOS: whether an Apple Watch is paired with this iPhone.
76
+ * - Android: whether at least one Wear OS node is currently connected.
73
77
  */
74
78
  isPaired: boolean;
75
79
  /**
76
- * Whether the paired watch has the companion app installed.
80
+ * Whether the watch companion app is installed.
81
+ * - iOS: whether the paired Apple Watch has the companion app installed.
82
+ * - Android: whether at least one connected Wear OS node is reachable (used as a proxy).
77
83
  */
78
84
  isWatchAppInstalled: boolean;
79
85
  /**
@@ -81,8 +87,9 @@ export interface WatchInfo {
81
87
  */
82
88
  isReachable: boolean;
83
89
  /**
84
- * The current activation state of the WCSession.
85
- * 0 = notActivated, 1 = inactive, 2 = activated
90
+ * The current session activation state.
91
+ * - iOS: 0 = notActivated, 1 = inactive, 2 = activated (WCSessionActivationState).
92
+ * - Android: 2 when a Wear OS node is connected, 0 otherwise.
86
93
  */
87
94
  activationState: number;
88
95
  }
@@ -158,8 +165,12 @@ export interface ActivationStateChangedEvent {
158
165
  state: number;
159
166
  }
160
167
  /**
161
- * Apple Watch communication plugin for Capacitor.
162
- * Provides bidirectional messaging between iPhone and Apple Watch using WatchConnectivity.
168
+ * Watch / Wear OS communication plugin for Capacitor.
169
+ * Provides bidirectional messaging between the phone and a paired watch.
170
+ *
171
+ * - **iOS**: uses Apple WatchConnectivity framework to communicate with Apple Watch.
172
+ * - **Android**: uses Google Wear OS Data Layer API (play-services-wearable) to communicate with a Wear OS watch.
173
+ * - **Web**: not supported; all methods throw or return safe defaults.
163
174
  *
164
175
  * @since 8.0.0
165
176
  */
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Data that can be sent between iPhone and Apple Watch.\n * Values must be serializable (string, number, boolean, arrays, or nested objects).\n *\n * @since 8.0.0\n */\nexport type WatchMessageData = Record<string, unknown>;\n\n/**\n * Options for sending a message to the watch.\n *\n * @since 8.0.0\n */\nexport interface SendMessageOptions {\n /**\n * The data to send to the watch.\n * Must be serializable (string, number, boolean, arrays, or nested objects).\n */\n data: WatchMessageData;\n}\n\n/**\n * Options for updating the application context.\n *\n * @since 8.0.0\n */\nexport interface UpdateContextOptions {\n /**\n * The context data to sync with the watch.\n * Only the latest context is kept - previous values are overwritten.\n */\n context: WatchMessageData;\n}\n\n/**\n * Options for transferring user info.\n *\n * @since 8.0.0\n */\nexport interface TransferUserInfoOptions {\n /**\n * The user info data to transfer.\n * Transfers are queued and delivered in order.\n */\n userInfo: WatchMessageData;\n}\n\n/**\n * Options for replying to a message from the watch.\n *\n * @since 8.0.0\n */\nexport interface ReplyMessageOptions {\n /**\n * The callback ID received in the messageReceivedWithReply event.\n */\n callbackId: string;\n /**\n * The reply data to send back to the watch.\n */\n data: WatchMessageData;\n}\n\n/**\n * Information about Watch connectivity status.\n *\n * @since 8.0.0\n */\nexport interface WatchInfo {\n /**\n * Whether WatchConnectivity is supported on this device.\n * Always false on iPad, web, and Android.\n */\n isSupported: boolean;\n /**\n * Whether an Apple Watch is paired with this iPhone.\n */\n isPaired: boolean;\n /**\n * Whether the paired watch has the companion app installed.\n */\n isWatchAppInstalled: boolean;\n /**\n * Whether the watch is currently reachable for immediate messaging.\n */\n isReachable: boolean;\n /**\n * The current activation state of the WCSession.\n * 0 = notActivated, 1 = inactive, 2 = activated\n */\n activationState: number;\n}\n\n/**\n * Event data for received messages.\n *\n * @since 8.0.0\n */\nexport interface MessageReceivedEvent {\n /**\n * The message data received from the watch.\n */\n message: WatchMessageData;\n}\n\n/**\n * Event data for messages that require a reply.\n *\n * @since 8.0.0\n */\nexport interface MessageReceivedWithReplyEvent {\n /**\n * The message data received from the watch.\n */\n message: WatchMessageData;\n /**\n * The callback ID to use when replying with replyToMessage().\n */\n callbackId: string;\n}\n\n/**\n * Event data for application context updates.\n *\n * @since 8.0.0\n */\nexport interface ContextReceivedEvent {\n /**\n * The context data received from the watch.\n */\n context: WatchMessageData;\n}\n\n/**\n * Event data for user info transfers.\n *\n * @since 8.0.0\n */\nexport interface UserInfoReceivedEvent {\n /**\n * The user info data received from the watch.\n */\n userInfo: WatchMessageData;\n}\n\n/**\n * Event data for reachability changes.\n *\n * @since 8.0.0\n */\nexport interface ReachabilityChangedEvent {\n /**\n * Whether the watch is now reachable.\n */\n isReachable: boolean;\n}\n\n/**\n * Event data for activation state changes.\n *\n * @since 8.0.0\n */\nexport interface ActivationStateChangedEvent {\n /**\n * The new activation state.\n * 0 = notActivated, 1 = inactive, 2 = activated\n */\n state: number;\n}\n\n/**\n * Apple Watch communication plugin for Capacitor.\n * Provides bidirectional messaging between iPhone and Apple Watch using WatchConnectivity.\n *\n * @since 8.0.0\n */\nexport interface CapgoWatchPlugin {\n /**\n * Send an interactive message to the watch.\n * The watch must be reachable for this to succeed.\n * Use this for time-sensitive, interactive communication.\n *\n * @param options - The message options\n * @returns Promise that resolves when the message is sent\n * @throws Error if the watch is not reachable or session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.sendMessage({\n * data: { action: 'refresh', timestamp: Date.now() }\n * });\n * ```\n */\n sendMessage(options: SendMessageOptions): Promise<void>;\n\n /**\n * Update the application context shared with the watch.\n * Only the latest context is kept - this overwrites any previous context.\n * Use this for syncing app state that the watch needs to display.\n *\n * @param options - The context options\n * @returns Promise that resolves when the context is updated\n * @throws Error if session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.updateApplicationContext({\n * context: { theme: 'dark', lastSync: Date.now() }\n * });\n * ```\n */\n updateApplicationContext(options: UpdateContextOptions): Promise<void>;\n\n /**\n * Transfer user info to the watch.\n * Transfers are queued and delivered in order, even if the watch is not currently reachable.\n * Use this for important data that must be delivered reliably.\n *\n * @param options - The user info options\n * @returns Promise that resolves when the transfer is queued\n * @throws Error if session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.transferUserInfo({\n * userInfo: { recordId: '123', action: 'created' }\n * });\n * ```\n */\n transferUserInfo(options: TransferUserInfoOptions): Promise<void>;\n\n /**\n * Reply to a message from the watch that requested a reply.\n * Use this in response to the messageReceivedWithReply event.\n *\n * @param options - The reply options including the callbackId\n * @returns Promise that resolves when the reply is sent\n * @throws Error if the callback ID is invalid or session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * CapgoWatch.addListener('messageReceivedWithReply', async (event) => {\n * const result = await processRequest(event.message);\n * await CapgoWatch.replyToMessage({\n * callbackId: event.callbackId,\n * data: { result }\n * });\n * });\n * ```\n */\n replyToMessage(options: ReplyMessageOptions): Promise<void>;\n\n /**\n * Get information about the watch connectivity status.\n *\n * @returns Promise that resolves with watch connectivity info\n * @since 8.0.0\n * @example\n * ```typescript\n * const info = await CapgoWatch.getInfo();\n * if (info.isReachable) {\n * await CapgoWatch.sendMessage({ data: { ping: true } });\n * }\n * ```\n */\n getInfo(): Promise<WatchInfo>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @since 8.0.0\n * @example\n * ```typescript\n * const { version } = await CapgoWatch.getPluginVersion();\n * console.log('Plugin version:', version);\n * ```\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Listen for messages received from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * const handle = await CapgoWatch.addListener('messageReceived', (event) => {\n * console.log('Message from watch:', event.message);\n * });\n * // Later: handle.remove();\n * ```\n */\n addListener(\n eventName: 'messageReceived',\n listenerFunc: (event: MessageReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for messages from the watch that require a reply.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('messageReceivedWithReply', async (event) => {\n * const response = await handleRequest(event.message);\n * await CapgoWatch.replyToMessage({\n * callbackId: event.callbackId,\n * data: response\n * });\n * });\n * ```\n */\n addListener(\n eventName: 'messageReceivedWithReply',\n listenerFunc: (event: MessageReceivedWithReplyEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for application context updates from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('applicationContextReceived', (event) => {\n * console.log('Context from watch:', event.context);\n * });\n * ```\n */\n addListener(\n eventName: 'applicationContextReceived',\n listenerFunc: (event: ContextReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for user info transfers from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('userInfoReceived', (event) => {\n * console.log('User info from watch:', event.userInfo);\n * });\n * ```\n */\n addListener(\n eventName: 'userInfoReceived',\n listenerFunc: (event: UserInfoReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for watch reachability changes.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('reachabilityChanged', (event) => {\n * console.log('Watch reachable:', event.isReachable);\n * });\n * ```\n */\n addListener(\n eventName: 'reachabilityChanged',\n listenerFunc: (event: ReachabilityChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for session activation state changes.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('activationStateChanged', (event) => {\n * console.log('Activation state:', event.state);\n * });\n * ```\n */\n addListener(\n eventName: 'activationStateChanged',\n listenerFunc: (event: ActivationStateChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.removeAllListeners();\n * ```\n */\n removeAllListeners(): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Data that can be sent between iPhone and Apple Watch.\n * Values must be serializable (string, number, boolean, arrays, or nested objects).\n *\n * @since 8.0.0\n */\nexport type WatchMessageData = Record<string, unknown>;\n\n/**\n * Options for sending a message to the watch.\n *\n * @since 8.0.0\n */\nexport interface SendMessageOptions {\n /**\n * The data to send to the watch.\n * Must be serializable (string, number, boolean, arrays, or nested objects).\n */\n data: WatchMessageData;\n}\n\n/**\n * Options for updating the application context.\n *\n * @since 8.0.0\n */\nexport interface UpdateContextOptions {\n /**\n * The context data to sync with the watch.\n * Only the latest context is kept - previous values are overwritten.\n */\n context: WatchMessageData;\n}\n\n/**\n * Options for transferring user info.\n *\n * @since 8.0.0\n */\nexport interface TransferUserInfoOptions {\n /**\n * The user info data to transfer.\n * Transfers are queued and delivered in order.\n */\n userInfo: WatchMessageData;\n}\n\n/**\n * Options for replying to a message from the watch.\n *\n * @since 8.0.0\n */\nexport interface ReplyMessageOptions {\n /**\n * The callback ID received in the messageReceivedWithReply event.\n */\n callbackId: string;\n /**\n * The reply data to send back to the watch.\n */\n data: WatchMessageData;\n}\n\n/**\n * Information about Watch / Wear OS connectivity status.\n *\n * @since 8.0.0\n */\nexport interface WatchInfo {\n /**\n * Whether the watch communication API is supported on this device.\n * - iOS: false on iPad; true on iPhone when WatchConnectivity is available.\n * - Android: true when Google Play Services with Wear OS support is available; false otherwise.\n * - Web: always false.\n */\n isSupported: boolean;\n /**\n * Whether a watch is currently paired/connected.\n * - iOS: whether an Apple Watch is paired with this iPhone.\n * - Android: whether at least one Wear OS node is currently connected.\n */\n isPaired: boolean;\n /**\n * Whether the watch companion app is installed.\n * - iOS: whether the paired Apple Watch has the companion app installed.\n * - Android: whether at least one connected Wear OS node is reachable (used as a proxy).\n */\n isWatchAppInstalled: boolean;\n /**\n * Whether the watch is currently reachable for immediate messaging.\n */\n isReachable: boolean;\n /**\n * The current session activation state.\n * - iOS: 0 = notActivated, 1 = inactive, 2 = activated (WCSessionActivationState).\n * - Android: 2 when a Wear OS node is connected, 0 otherwise.\n */\n activationState: number;\n}\n\n/**\n * Event data for received messages.\n *\n * @since 8.0.0\n */\nexport interface MessageReceivedEvent {\n /**\n * The message data received from the watch.\n */\n message: WatchMessageData;\n}\n\n/**\n * Event data for messages that require a reply.\n *\n * @since 8.0.0\n */\nexport interface MessageReceivedWithReplyEvent {\n /**\n * The message data received from the watch.\n */\n message: WatchMessageData;\n /**\n * The callback ID to use when replying with replyToMessage().\n */\n callbackId: string;\n}\n\n/**\n * Event data for application context updates.\n *\n * @since 8.0.0\n */\nexport interface ContextReceivedEvent {\n /**\n * The context data received from the watch.\n */\n context: WatchMessageData;\n}\n\n/**\n * Event data for user info transfers.\n *\n * @since 8.0.0\n */\nexport interface UserInfoReceivedEvent {\n /**\n * The user info data received from the watch.\n */\n userInfo: WatchMessageData;\n}\n\n/**\n * Event data for reachability changes.\n *\n * @since 8.0.0\n */\nexport interface ReachabilityChangedEvent {\n /**\n * Whether the watch is now reachable.\n */\n isReachable: boolean;\n}\n\n/**\n * Event data for activation state changes.\n *\n * @since 8.0.0\n */\nexport interface ActivationStateChangedEvent {\n /**\n * The new activation state.\n * 0 = notActivated, 1 = inactive, 2 = activated\n */\n state: number;\n}\n\n/**\n * Watch / Wear OS communication plugin for Capacitor.\n * Provides bidirectional messaging between the phone and a paired watch.\n *\n * - **iOS**: uses Apple WatchConnectivity framework to communicate with Apple Watch.\n * - **Android**: uses Google Wear OS Data Layer API (play-services-wearable) to communicate with a Wear OS watch.\n * - **Web**: not supported; all methods throw or return safe defaults.\n *\n * @since 8.0.0\n */\nexport interface CapgoWatchPlugin {\n /**\n * Send an interactive message to the watch.\n * The watch must be reachable for this to succeed.\n * Use this for time-sensitive, interactive communication.\n *\n * @param options - The message options\n * @returns Promise that resolves when the message is sent\n * @throws Error if the watch is not reachable or session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.sendMessage({\n * data: { action: 'refresh', timestamp: Date.now() }\n * });\n * ```\n */\n sendMessage(options: SendMessageOptions): Promise<void>;\n\n /**\n * Update the application context shared with the watch.\n * Only the latest context is kept - this overwrites any previous context.\n * Use this for syncing app state that the watch needs to display.\n *\n * @param options - The context options\n * @returns Promise that resolves when the context is updated\n * @throws Error if session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.updateApplicationContext({\n * context: { theme: 'dark', lastSync: Date.now() }\n * });\n * ```\n */\n updateApplicationContext(options: UpdateContextOptions): Promise<void>;\n\n /**\n * Transfer user info to the watch.\n * Transfers are queued and delivered in order, even if the watch is not currently reachable.\n * Use this for important data that must be delivered reliably.\n *\n * @param options - The user info options\n * @returns Promise that resolves when the transfer is queued\n * @throws Error if session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.transferUserInfo({\n * userInfo: { recordId: '123', action: 'created' }\n * });\n * ```\n */\n transferUserInfo(options: TransferUserInfoOptions): Promise<void>;\n\n /**\n * Reply to a message from the watch that requested a reply.\n * Use this in response to the messageReceivedWithReply event.\n *\n * @param options - The reply options including the callbackId\n * @returns Promise that resolves when the reply is sent\n * @throws Error if the callback ID is invalid or session is not active\n * @since 8.0.0\n * @example\n * ```typescript\n * CapgoWatch.addListener('messageReceivedWithReply', async (event) => {\n * const result = await processRequest(event.message);\n * await CapgoWatch.replyToMessage({\n * callbackId: event.callbackId,\n * data: { result }\n * });\n * });\n * ```\n */\n replyToMessage(options: ReplyMessageOptions): Promise<void>;\n\n /**\n * Get information about the watch connectivity status.\n *\n * @returns Promise that resolves with watch connectivity info\n * @since 8.0.0\n * @example\n * ```typescript\n * const info = await CapgoWatch.getInfo();\n * if (info.isReachable) {\n * await CapgoWatch.sendMessage({ data: { ping: true } });\n * }\n * ```\n */\n getInfo(): Promise<WatchInfo>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @since 8.0.0\n * @example\n * ```typescript\n * const { version } = await CapgoWatch.getPluginVersion();\n * console.log('Plugin version:', version);\n * ```\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Listen for messages received from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * const handle = await CapgoWatch.addListener('messageReceived', (event) => {\n * console.log('Message from watch:', event.message);\n * });\n * // Later: handle.remove();\n * ```\n */\n addListener(\n eventName: 'messageReceived',\n listenerFunc: (event: MessageReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for messages from the watch that require a reply.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('messageReceivedWithReply', async (event) => {\n * const response = await handleRequest(event.message);\n * await CapgoWatch.replyToMessage({\n * callbackId: event.callbackId,\n * data: response\n * });\n * });\n * ```\n */\n addListener(\n eventName: 'messageReceivedWithReply',\n listenerFunc: (event: MessageReceivedWithReplyEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for application context updates from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('applicationContextReceived', (event) => {\n * console.log('Context from watch:', event.context);\n * });\n * ```\n */\n addListener(\n eventName: 'applicationContextReceived',\n listenerFunc: (event: ContextReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for user info transfers from the watch.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('userInfoReceived', (event) => {\n * console.log('User info from watch:', event.userInfo);\n * });\n * ```\n */\n addListener(\n eventName: 'userInfoReceived',\n listenerFunc: (event: UserInfoReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for watch reachability changes.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('reachabilityChanged', (event) => {\n * console.log('Watch reachable:', event.isReachable);\n * });\n * ```\n */\n addListener(\n eventName: 'reachabilityChanged',\n listenerFunc: (event: ReachabilityChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for session activation state changes.\n *\n * @param eventName - The event name\n * @param listenerFunc - Callback function\n * @returns Handle to remove the listener\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.addListener('activationStateChanged', (event) => {\n * console.log('Activation state:', event.state);\n * });\n * ```\n */\n addListener(\n eventName: 'activationStateChanged',\n listenerFunc: (event: ActivationStateChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 8.0.0\n * @example\n * ```typescript\n * await CapgoWatch.removeAllListeners();\n * ```\n */\n removeAllListeners(): Promise<void>;\n}\n"]}
@@ -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.25"
9
+ private let pluginVersion: String = "8.1.1"
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.25",
3
+ "version": "8.1.1",
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",
@@ -52,7 +52,8 @@
52
52
  "prepublishOnly": "bun run build",
53
53
  "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
54
54
  "example:install": "cd example-app && bun install --frozen-lockfile",
55
- "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build"
55
+ "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
56
+ "changelog:ai": "node scripts/generate-ai-changelog.mjs"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@capacitor/android": "^8.0.0",