cordova-plugin-crosswire-sword 0.2.11 → 0.2.13

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/RELEASENOTES.md CHANGED
@@ -20,6 +20,17 @@
20
20
  -->
21
21
  # Release Notes
22
22
 
23
+ ### 0.2.13 (Oct 6, 2026)
24
+ * BibleSync: every call into the library is made under one lock, on both platforms. Its receive loop runs on a thread of the plugin's while the page's sends and questions arrive on Cordova's, and the library keeps no lock of its own; a verse sent as a packet was being received could be refused as "still receiving", or worse
25
+ * iOS: the engine header the Swift bridge compiles against is refreshed whenever the package is built
26
+
27
+ ### 0.2.12 (Oct 5, 2026)
28
+ * BibleSync on a network that loses packets, as Wi-Fi does: each verse carries a sequence number and is sent three times, which a receiver shows once; a peer is kept through half an hour of silence, not one minute; a newcomer is answered at once, twice; a beacon every 10 seconds (biblesync 2.2.0 with pull requests 19 and 20)
29
+ * startBibleSync takes options { repeatCount, repeatDelayMS, peerLifetimeS }; the defaults are 2, 500 and 1800
30
+ * getBibleSyncStatus says whether a running session is still alive on the same network interface (off, alive, disabled, moved), so an app resumed from sleep or the background restarts BibleSync only when it must
31
+ * The page hears BibleSync's interface, its peers and its errors on both platforms ({ cmd: info | peer | error }); a beacon that cannot be sent while the phone sleeps no longer ends the session
32
+ * iOS: BibleSync is offered (features.bibleSync), the app carrying Apple's multicast entitlement; every received message had been dropped
33
+
23
34
  ### 0.2.11 (Oct 1, 2026)
24
35
  * Study data (bookmarks and, in time, notes) in a folder of the user's choosing, named sword-studydata so several devices and SWORD apps can share it (doc/bookmarks-format.md in Bishop): a picked folder holding bookmarks/, or itself named sword-studydata, is used as it is, otherwise sword-studydata/ is made inside it
25
36
  * iOS: the study-data folder may be in iCloud Drive or any app's folder; where a provider grants files but no folder (Google Drive), the bookmark files are picked in place (SWMgr_chooseDataFiles) or Bishop's own are saved there and opened once (SWMgr_exportDataFiles); a provider's file is rewritten in place so it keeps its identity
Binary file
Binary file
Binary file
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/libs/ios/SWORD.xcframework/ios-arm64/libSWORD.a
3
- size: 37229112 bytes
4
- sha256: 3e031f0b4dbd6b3c5096cadf56adca793363d9d4bb40ad0810e79e2d2d4e7260
3
+ size: 37343432 bytes
4
+ sha256: b0a320876d2a063731888664d4a30338e9fa1044e90c3474c343da2e94d7af50
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/libs/ios/SWORD.xcframework/ios-arm64-simulator/libSWORD.a
3
- size: 37272016 bytes
4
- sha256: d4a361c089cb1384d15fce6755470d9d3bd012d8acf7af69ba27315ea4f71ae8
3
+ size: 37386424 bytes
4
+ sha256: da32b199627dac42856912d00b71a959e402f45eaf4b72777a30507765fcd26d
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "cordova-plugin-crosswire-sword",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "The SWORD Project Plugin",
5
+ "scripts": {
6
+ "prepack": "cp ../../../include/defs.h ../../../include/flatapi.h src/ios/"
7
+ },
5
8
  "files": [
6
9
  "plugin.xml",
7
10
  "libs",
package/plugin.xml CHANGED
@@ -4,7 +4,7 @@
4
4
  xmlns:rim="http://www.blackberry.com/ns/widgets"
5
5
  xmlns:android="http://schemas.android.com/apk/res/android"
6
6
  id="cordova-plugin-crosswire-sword"
7
- version="0.2.11">
7
+ version="0.2.13">
8
8
  <name>cordova-plugin-crosswire-sword</name>
9
9
  <description>The SWORD Project Plugin</description>
10
10
  <license>GNU 2.0</license>
@@ -164,9 +164,32 @@ public class SWMgr {
164
164
  public static interface BibleSyncListener {
165
165
  public void navReceived(String osisRef);
166
166
  public void chatReceived(String user, String message);
167
+ /** the interface address BibleSync is on */
168
+ public void infoReceived(String iface);
169
+ /**
170
+ * @param state 'A' a peer announced itself, 'S' it is a speaker we now listen to, 'D' a speaker went quiet
171
+ * @param who user @ address ('D': the uuid)
172
+ * @param what app version; device; uuid ('D': empty)
173
+ */
174
+ public void peerReceived(char state, String who, String what);
175
+ /** the library could not send, or stopped itself: what it said, and its detail */
176
+ public void errorReceived(String message, String detail);
167
177
  }
168
178
  public native void startBibleSync(String appName, String userName, String passphrase, BibleSyncListener listener);
179
+ /**
180
+ * For the next startBibleSync; a negative value leaves that one as it is.
181
+ * @param repeatCount how many more times each verse is sent (nobody resends multicast); default 2
182
+ * @param repeatDelayMS at least this long between those; default 500
183
+ * @param peerLifetimeS a peer not heard from for this many seconds is given up; default 1800
184
+ */
185
+ public native void setBibleSyncOptions(int repeatCount, int repeatDelayMS, int peerLifetimeS);
169
186
  public native void stopBibleSync();
187
+ /**
188
+ * @return "off" (not running), "alive <address>",
189
+ * "disabled <address>" (the library shut itself down) or
190
+ * "moved <address> <address now>" (the network changed under it)
191
+ */
192
+ public native String getBibleSyncStatus();
170
193
  public native void sendBibleSyncMessage(String osisRef);
171
194
  }
172
195
 
@@ -552,6 +552,8 @@ Log.d(TAG, "SWModule_getRenderChapter");
552
552
  final String appName = args.getString(0);
553
553
  final String userName = args.getString(1);
554
554
  final String passphrase = args.getString(2);
555
+ // repeatCount, repeatDelayMS, peerLifetimeS: -1 (or absent) keeps the default
556
+ mgr.setBibleSyncOptions(args.optInt(3, -1), args.optInt(4, -1), args.optInt(5, -1));
555
557
 
556
558
  if (multicastLock == null) {
557
559
  WifiManager wifi = (WifiManager) cordova.getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
@@ -586,6 +588,42 @@ Log.d(TAG, "SWModule_getRenderChapter");
586
588
  }
587
589
  catch (Exception e) { e.printStackTrace(); }
588
590
  }
591
+ public void infoReceived(String iface) {
592
+ try {
593
+ JSONObject m = new JSONObject();
594
+ m.put("cmd", "info");
595
+ m.put("interface", iface);
596
+ PluginResult result = new PluginResult(PluginResult.Status.OK, m);
597
+ result.setKeepCallback(true);
598
+ bibleSyncListener.sendPluginResult(result);
599
+ }
600
+ catch (Exception e) { e.printStackTrace(); }
601
+ }
602
+ public void peerReceived(char state, String who, String what) {
603
+ try {
604
+ JSONObject m = new JSONObject();
605
+ m.put("cmd", "peer");
606
+ m.put("state", state == 'A' ? "announce" : (state == 'S' ? "speaker" : "gone"));
607
+ m.put("who", who);
608
+ m.put("what", what);
609
+ PluginResult result = new PluginResult(PluginResult.Status.OK, m);
610
+ result.setKeepCallback(true);
611
+ bibleSyncListener.sendPluginResult(result);
612
+ }
613
+ catch (Exception e) { e.printStackTrace(); }
614
+ }
615
+ public void errorReceived(String message, String detail) {
616
+ try {
617
+ JSONObject m = new JSONObject();
618
+ m.put("cmd", "error");
619
+ m.put("message", message);
620
+ m.put("detail", detail);
621
+ PluginResult result = new PluginResult(PluginResult.Status.OK, m);
622
+ result.setKeepCallback(true);
623
+ bibleSyncListener.sendPluginResult(result);
624
+ }
625
+ catch (Exception e) { e.printStackTrace(); }
626
+ }
589
627
  });
590
628
  }
591
629
  });
@@ -599,6 +637,16 @@ Log.d(TAG, "SWModule_getRenderChapter");
599
637
  if (multicastLock != null && multicastLock.isHeld()) multicastLock.release();
600
638
  callbackContext.success();
601
639
  }
640
+ else if (action.equals("SWMgr_getBibleSyncStatus")) {
641
+ // "alive 10.0.0.5" -> { state, alive, interface, current }
642
+ String[] s = mgr.getBibleSyncStatus().split(" ");
643
+ JSONObject r = new JSONObject();
644
+ r.put("state", s[0]);
645
+ r.put("alive", s[0].equals("alive"));
646
+ r.put("interface", s.length > 1 ? s[1] : "");
647
+ r.put("current", s.length > 2 ? s[2] : (s.length > 1 ? s[1] : ""));
648
+ callbackContext.success(r);
649
+ }
602
650
  else if (action.equals("SWMgr_setJavascript")) {
603
651
  boolean val = args.getBoolean(0);
604
652
  mgr.setJavascript(val);
@@ -98,8 +98,8 @@ var mySWORDPlugin:SWORD? = nil
98
98
  "version": libswordVersion,
99
99
  // what the page may offer on iOS (SWORD.features)
100
100
  // bibleSync: multicast needs com.apple.developer.networking.multicast,
101
- // requested from Apple; true once the app is signed with it
102
- "features": [ "dataStorage": true, "dataFiles": true, "sharedStorage": false, "bibleSync": false ]
101
+ // which Apple granted the account on 2026-10-01 and Bishop's config.xml carries
102
+ "features": [ "dataStorage": true, "dataFiles": true, "sharedStorage": false, "bibleSync": true ]
103
103
  ] as [AnyHashable : Any]
104
104
  self.commandDelegate!.send(CDVPluginResult(status: CDVCommandStatus_OK, messageAs: info), callbackId: command.callbackId)
105
105
  }
@@ -1210,6 +1210,12 @@ debugPrint("initMgr, mgr: " + String(describing: mgr))
1210
1210
  let appName = command.arguments[0] as? String ?? ""
1211
1211
  let userName = command.arguments[1] as? String ?? ""
1212
1212
  let passphrase = command.arguments[2] as? String ?? ""
1213
+ // optional: how often a verse is repeated and how far apart, and how long
1214
+ // a silent peer is kept (-1 or absent = the library's default)
1215
+ let opt = { (i: Int) -> Int32 in
1216
+ command.arguments.count > i ? Int32(truncatingIfNeeded: (command.arguments[i] as? Int) ?? -1) : -1
1217
+ }
1218
+ org_crosswire_sword_SWMgr_setBibleSyncOptions(mgr, opt(3), opt(4), opt(5))
1213
1219
  bibleSyncCallbackID = command.callbackId
1214
1220
  DispatchQueue.global().async {
1215
1221
  mySWORDPlugin = self
@@ -1217,8 +1223,10 @@ debugPrint("initMgr, mgr: " + String(describing: mgr))
1217
1223
  // a nav message carries no second string (flatapi passes 0)
1218
1224
  let response1 = str1 == nil ? "" : String(cString: str1!)
1219
1225
  let response2 = str2 == nil ? "" : String(cString: str2!)
1226
+ // (CChar("N") is Int8's failable parse of the string "N", i.e. nil,
1227
+ // which equals no command: every message was dropped that way)
1220
1228
  if (mySWORDPlugin != nil && mySWORDPlugin!.bibleSyncCallbackID != "") {
1221
- if (cmd == CChar("N")) {
1229
+ if (cmd == CChar(UInt8(ascii: "N"))) {
1222
1230
  var retVal = [String:Any]()
1223
1231
  retVal["cmd"] = "nav";
1224
1232
  retVal["osisRef"] = response1;
@@ -1226,7 +1234,7 @@ debugPrint("initMgr, mgr: " + String(describing: mgr))
1226
1234
  result.setKeepCallbackAs(true)
1227
1235
  mySWORDPlugin!.commandDelegate!.send(result, callbackId: mySWORDPlugin!.bibleSyncCallbackID)
1228
1236
  }
1229
- else if (cmd == CChar("C") ) {
1237
+ else if (cmd == CChar(UInt8(ascii: "C")) ) {
1230
1238
  var retVal = [String:Any]()
1231
1239
  retVal["cmd"] = "chat";
1232
1240
  retVal["user"] = response1;
@@ -1235,6 +1243,36 @@ debugPrint("initMgr, mgr: " + String(describing: mgr))
1235
1243
  result.setKeepCallbackAs(true)
1236
1244
  mySWORDPlugin!.commandDelegate!.send(result, callbackId: mySWORDPlugin!.bibleSyncCallbackID)
1237
1245
  }
1246
+ else if (cmd == CChar(UInt8(ascii: "I"))) {
1247
+ // the interface address BibleSync is on
1248
+ var retVal = [String:Any]()
1249
+ retVal["cmd"] = "info";
1250
+ retVal["interface"] = response1;
1251
+ let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: retVal)
1252
+ result.setKeepCallbackAs(true)
1253
+ mySWORDPlugin!.commandDelegate!.send(result, callbackId: mySWORDPlugin!.bibleSyncCallbackID)
1254
+ }
1255
+ else if (cmd == CChar(UInt8(ascii: "A")) || cmd == CChar(UInt8(ascii: "S")) || cmd == CChar(UInt8(ascii: "D"))) {
1256
+ // a peer announced itself, became a speaker we listen to, or went quiet
1257
+ var retVal = [String:Any]()
1258
+ retVal["cmd"] = "peer";
1259
+ retVal["state"] = cmd == CChar(UInt8(ascii: "A")) ? "announce" : (cmd == CChar(UInt8(ascii: "S")) ? "speaker" : "gone");
1260
+ retVal["who"] = response1; // user @ address ('gone': the uuid)
1261
+ retVal["what"] = response2; // app version; device; uuid
1262
+ let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: retVal)
1263
+ result.setKeepCallbackAs(true)
1264
+ mySWORDPlugin!.commandDelegate!.send(result, callbackId: mySWORDPlugin!.bibleSyncCallbackID)
1265
+ }
1266
+ else if (cmd == CChar(UInt8(ascii: "E"))) {
1267
+ // the library stopped itself: what it said, and its detail
1268
+ var retVal = [String:Any]()
1269
+ retVal["cmd"] = "error";
1270
+ retVal["message"] = response1;
1271
+ retVal["detail"] = response2;
1272
+ let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: retVal)
1273
+ result.setKeepCallbackAs(true)
1274
+ mySWORDPlugin!.commandDelegate!.send(result, callbackId: mySWORDPlugin!.bibleSyncCallbackID)
1275
+ }
1238
1276
  }
1239
1277
  });
1240
1278
 
@@ -1250,6 +1288,21 @@ debugPrint("initMgr, mgr: " + String(describing: mgr))
1250
1288
  self.commandDelegate!.send(result, callbackId: command.callbackId)
1251
1289
  }
1252
1290
 
1291
+ // "off" | "alive <addr>" | "disabled <addr>" | "moved <addr> <addr now>", as
1292
+ // { state, alive, interface, current }: the page asks before restarting on resume
1293
+ @objc(SWMgr_getBibleSyncStatus:)
1294
+ func SWMgr_getBibleSyncStatus(command: CDVInvokedUrlCommand) {
1295
+ initMgr()
1296
+ let status = String(cString: org_crosswire_sword_SWMgr_getBibleSyncStatus(mgr))
1297
+ let parts = status.split(separator: " ").map { String($0) }
1298
+ var retVal = [String:Any]()
1299
+ retVal["state"] = parts.count > 0 ? parts[0] : "off"
1300
+ retVal["alive"] = parts.count > 0 && parts[0] == "alive"
1301
+ retVal["interface"] = parts.count > 1 ? parts[1] : ""
1302
+ retVal["current"] = parts.count > 2 ? parts[2] : (parts.count > 1 ? parts[1] : "")
1303
+ self.commandDelegate!.send(CDVPluginResult(status: CDVCommandStatus_OK, messageAs: retVal), callbackId: command.callbackId)
1304
+ }
1305
+
1253
1306
  @objc(SWMgr_stopBibleSync:)
1254
1307
  func SWMgr_stopBibleSync(command: CDVInvokedUrlCommand) {
1255
1308
  initMgr()
package/src/ios/flatapi.h CHANGED
@@ -611,6 +611,22 @@ void SWDLLEXPORT org_crosswire_sword_SWMgr_startBibleSync
611
611
  void SWDLLEXPORT org_crosswire_sword_SWMgr_stopBibleSync
612
612
  (SWHANDLE hMgr);
613
613
 
614
+ /*
615
+ * Class: org_crosswire_android_sword_SWMgr
616
+ * Method: setBibleSyncOptions
617
+ * Signature: (III)V
618
+ */
619
+ void SWDLLEXPORT org_crosswire_sword_SWMgr_setBibleSyncOptions
620
+ (SWHANDLE hMgr, int repeatCount, int repeatDelayMS, int peerLifetimeS);
621
+
622
+ /*
623
+ * Class: org_crosswire_android_sword_SWMgr
624
+ * Method: getBibleSyncStatus
625
+ * Signature: ()Ljava/lang/String;
626
+ */
627
+ const char * SWDLLEXPORT org_crosswire_sword_SWMgr_getBibleSyncStatus
628
+ (SWHANDLE hMgr);
629
+
614
630
  /*
615
631
  * Class: org_crosswire_android_sword_SWMgr
616
632
  * Method: sendBibleSyncMessage
package/www/SWORD.js CHANGED
@@ -381,10 +381,16 @@ SWMgr.prototype.addExtraConfig = function(confBlob, callback) {
381
381
  return retVal;
382
382
  }
383
383
 
384
- SWMgr.prototype.startBibleSync = function(appName, userName, passphrase, callback) {
384
+ // options (all optional; the plugin's defaults otherwise):
385
+ // repeatCount how many more times each verse is sent, since nobody resends multicast (2)
386
+ // repeatDelayMS at least this long between those (500)
387
+ // peerLifetimeS a peer not heard from for this many seconds is given up (1800)
388
+ SWMgr.prototype.startBibleSync = function(appName, userName, passphrase, callback, options) {
389
+ var o = options || {};
390
+ var opt = function(v) { return (typeof v === 'number' && v >= 0) ? Math.floor(v) : -1; };
385
391
  exec(callback,
386
392
  function(err) { utils.alert('[ERROR] problem: ' + err); },
387
- "SWORD", "SWMgr_startBibleSync", [appName, userName, passphrase]
393
+ "SWORD", "SWMgr_startBibleSync", [appName, userName, passphrase, opt(o.repeatCount), opt(o.repeatDelayMS), opt(o.peerLifetimeS)]
388
394
  );
389
395
  }
390
396
 
@@ -395,6 +401,16 @@ SWMgr.prototype.stopBibleSync = function() {
395
401
  );
396
402
  }
397
403
 
404
+ // whether a running BibleSync session is still worth keeping (after sleep or
405
+ // the background): { state: 'off'|'alive'|'disabled'|'moved', alive, interface,
406
+ // current }; null where the platform cannot say
407
+ SWMgr.prototype.getBibleSyncStatus = function(callback) {
408
+ exec(callback,
409
+ function(err) { callback(null); },
410
+ "SWORD", "SWMgr_getBibleSyncStatus", []
411
+ );
412
+ }
413
+
398
414
  SWMgr.prototype.sendBibleSyncMessage = function(osisRef, callback) {
399
415
  exec(callback?callback:function() {},
400
416
  function(err) { utils.alert('[ERROR] problem: ' + err); },