signalk-p2p-plugin 0.3.0 → 0.4.0

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +41 -14
  3. package/deploy/README.md +20 -2
  4. package/deploy/policy.json +30 -0
  5. package/dist/advanced-config.d.ts +11 -0
  6. package/dist/advanced-config.d.ts.map +1 -0
  7. package/dist/advanced-config.js +40 -0
  8. package/dist/advanced-config.js.map +1 -0
  9. package/dist/api.d.ts +14 -1
  10. package/dist/api.d.ts.map +1 -1
  11. package/dist/api.js +41 -3
  12. package/dist/api.js.map +1 -1
  13. package/dist/config.d.ts +5 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +12 -273
  16. package/dist/config.js.map +1 -1
  17. package/dist/data/envelope.d.ts.map +1 -1
  18. package/dist/data/envelope.js +2 -0
  19. package/dist/data/envelope.js.map +1 -1
  20. package/dist/data/types.d.ts +1 -1
  21. package/dist/data/types.d.ts.map +1 -1
  22. package/dist/groups/control.d.ts +8 -0
  23. package/dist/groups/control.d.ts.map +1 -1
  24. package/dist/groups/control.js +30 -0
  25. package/dist/groups/control.js.map +1 -1
  26. package/dist/groups/manager.d.ts +2 -0
  27. package/dist/groups/manager.d.ts.map +1 -1
  28. package/dist/groups/manager.js +28 -1
  29. package/dist/groups/manager.js.map +1 -1
  30. package/dist/groups/types.d.ts +12 -0
  31. package/dist/groups/types.d.ts.map +1 -1
  32. package/dist/groups/types.js +23 -0
  33. package/dist/groups/types.js.map +1 -1
  34. package/dist/live/relay.d.ts +3 -0
  35. package/dist/live/relay.d.ts.map +1 -1
  36. package/dist/live/relay.js +11 -0
  37. package/dist/live/relay.js.map +1 -1
  38. package/dist/peer/cli.js +16 -0
  39. package/dist/peer/cli.js.map +1 -1
  40. package/dist/peer/config.d.ts +3 -0
  41. package/dist/peer/config.d.ts.map +1 -1
  42. package/dist/peer/config.js +5 -0
  43. package/dist/peer/config.js.map +1 -1
  44. package/dist/plugin.d.ts.map +1 -1
  45. package/dist/plugin.js +136 -46
  46. package/dist/plugin.js.map +1 -1
  47. package/dist/policy/operator.d.ts +13 -0
  48. package/dist/policy/operator.d.ts.map +1 -0
  49. package/dist/policy/operator.js +53 -0
  50. package/dist/policy/operator.js.map +1 -0
  51. package/dist/policy/policy.d.ts +49 -0
  52. package/dist/policy/policy.d.ts.map +1 -0
  53. package/dist/policy/policy.js +147 -0
  54. package/dist/policy/policy.js.map +1 -0
  55. package/docs/plans/README.md +2 -2
  56. package/docs/plans/architecture.md +2 -2
  57. package/docs/plans/networks.md +35 -7
  58. package/docs/plans/peer-node.md +7 -2
  59. package/docs/plans/shared-data.md +4 -3
  60. package/package.json +3 -2
  61. package/policy/default.json +30 -0
  62. package/policy/operator.pub +2 -0
  63. package/public/index.html +268 -68
package/public/index.html CHANGED
@@ -40,6 +40,32 @@
40
40
  .network-card input[type="checkbox"] {
41
41
  accent-color: #0fb9e9;
42
42
  }
43
+ .sharing-wrap {
44
+ overflow-x: auto;
45
+ margin-top: 0.5rem;
46
+ }
47
+ .sharing-table {
48
+ width: 100%;
49
+ border-collapse: collapse;
50
+ }
51
+ .sharing-table th,
52
+ .sharing-table td {
53
+ padding: 0.4rem 0.5rem;
54
+ border-bottom: 1px solid #172a45;
55
+ text-align: left;
56
+ vertical-align: top;
57
+ }
58
+ .sharing-table th {
59
+ color: #9fb3c8;
60
+ font-size: 0.8rem;
61
+ text-transform: uppercase;
62
+ letter-spacing: 0.04em;
63
+ white-space: nowrap;
64
+ }
65
+ .sharing-table td small {
66
+ color: #9fb3c8;
67
+ display: block;
68
+ }
43
69
  section {
44
70
  margin: 1.25rem 0;
45
71
  }
@@ -75,6 +101,25 @@
75
101
  button:hover {
76
102
  background: #7dd3fc;
77
103
  }
104
+ details {
105
+ margin: 1rem 0;
106
+ }
107
+ summary {
108
+ cursor: pointer;
109
+ color: #9fb3c8;
110
+ font-weight: 600;
111
+ }
112
+ input[type="number"],
113
+ select {
114
+ background: #101f38;
115
+ color: #e8eef8;
116
+ border: 1px solid #27405f;
117
+ border-radius: 0.35rem;
118
+ padding: 0.2rem 0.35rem;
119
+ }
120
+ input[type="number"] {
121
+ width: 5.5rem;
122
+ }
78
123
  textarea {
79
124
  width: 100%;
80
125
  min-height: 4rem;
@@ -103,15 +148,18 @@
103
148
  <h1>Signal K P2P</h1>
104
149
  <div id="message"></div>
105
150
 
106
- <section>
107
- <h2>Identity</h2>
108
- <div id="identity">loading…</div>
109
- </section>
151
+ <details>
152
+ <summary>Advanced</summary>
153
+ <section>
154
+ <h2>Identity</h2>
155
+ <div id="identity">loading…</div>
156
+ </section>
110
157
 
111
- <section>
112
- <h2>Claims</h2>
113
- <div id="claims"></div>
114
- </section>
158
+ <section>
159
+ <h2>Claims</h2>
160
+ <div id="claims"></div>
161
+ </section>
162
+ </details>
115
163
 
116
164
  <section>
117
165
  <h2>Groups &amp; networks</h2>
@@ -122,7 +170,6 @@
122
170
  <button onclick="createGroup()">Create group</button>
123
171
  <button onclick="invite()">Create invite</button>
124
172
  <button onclick="joinGroup()">Join with token</button>
125
- <button onclick="setThreshold()">Set threshold</button>
126
173
  </div>
127
174
  <textarea
128
175
  id="token"
@@ -130,12 +177,12 @@
130
177
  ></textarea>
131
178
  </section>
132
179
 
133
- <section>
180
+ <section id="requests-section">
134
181
  <h2>Join requests</h2>
135
182
  <div id="requests"></div>
136
183
  </section>
137
184
 
138
- <section>
185
+ <section id="proposals-section">
139
186
  <h2>Pending proposals</h2>
140
187
  <div id="proposals"></div>
141
188
  </section>
@@ -212,7 +259,13 @@
212
259
  call("/sharing"),
213
260
  ]);
214
261
  renderIdentity(results[0], results[1]);
215
- renderPublicNetwork(results[0], results[6]);
262
+ renderSharing(
263
+ results[0],
264
+ results[6],
265
+ (results[3].groups || []).filter(function (group) {
266
+ return !group.removed;
267
+ }),
268
+ );
216
269
  renderClaims(results[2].claims || []);
217
270
  renderGroups(results[3].groups || []);
218
271
  renderRequests(results[4].requests || []);
@@ -244,6 +297,27 @@
244
297
  );
245
298
  root.appendChild(text("h3", "Status"));
246
299
  root.appendChild(identityRow("Version", status.version));
300
+ if (status.policy) {
301
+ root.appendChild(
302
+ identityRow(
303
+ "Network policy",
304
+ "v" +
305
+ status.policy.version +
306
+ " · issued " +
307
+ new Date(status.policy.issuedAt * 1000)
308
+ .toISOString()
309
+ .slice(0, 10),
310
+ ),
311
+ );
312
+ if (status.policy.operator) {
313
+ root.appendChild(
314
+ identityRow(
315
+ "Policy operator",
316
+ status.policy.operator.slice(0, 16) + "…",
317
+ ),
318
+ );
319
+ }
320
+ }
247
321
  root.appendChild(identityRow("Started", status.started ? "yes" : "no"));
248
322
  root.appendChild(identityRow("Peers", status.peers));
249
323
  root.appendChild(identityRow("Injected vessels", status.injected));
@@ -278,13 +352,28 @@
278
352
  }
279
353
  }
280
354
 
281
- function renderPublicNetwork(status, sharing) {
355
+ function sharingCheckbox(checked, onchange, disabled) {
356
+ var cell = document.createElement("td");
357
+ var input = document.createElement("input");
358
+ input.type = "checkbox";
359
+ input.checked = !!checked;
360
+ input.disabled = Boolean(disabled);
361
+ input.onchange = function () {
362
+ onchange(input.checked);
363
+ };
364
+ cell.appendChild(input);
365
+ return cell;
366
+ }
367
+
368
+ function renderSharing(status, sharing, groups) {
282
369
  clear("public-network");
283
370
  var root = document.getElementById("public-network");
284
371
  root.className = "network-card";
285
372
  status = status || {};
286
373
  sharing = sharing || {};
374
+ groups = groups || [];
287
375
  var network = sharing.network;
376
+ var publicEnabled = Boolean(network && network.enabled);
288
377
  root.appendChild(
289
378
  identityRow(
290
379
  "Public network",
@@ -293,52 +382,121 @@
293
382
  );
294
383
  if (network) {
295
384
  root.appendChild(
296
- identityRow(
297
- "Status",
298
- (network.enabled ? "enabled" : "disabled") +
299
- " · " +
300
- (network.consent || "opt-out"),
301
- ),
385
+ identityRow("Status", network.enabled ? "enabled" : "disabled"),
302
386
  );
387
+ var consentRow = document.createElement("div");
388
+ consentRow.className = "row";
389
+ consentRow.appendChild(text("code", "Sharing"));
390
+ var consent = document.createElement("select");
391
+ [
392
+ ["receive-only", "Receive only (don't share my vessel)"],
393
+ ["underway-gated", "Share only while underway"],
394
+ ["coarse-position", "Share an approximate position"],
395
+ ["opt-out", "Share my vessel"],
396
+ ].forEach(function (entry) {
397
+ var option = document.createElement("option");
398
+ option.value = entry[0];
399
+ option.textContent = entry[1];
400
+ consent.appendChild(option);
401
+ });
402
+ consent.value = network.consent || "receive-only";
403
+ consent.onchange = function () {
404
+ setConsent(consent.value);
405
+ };
406
+ consentRow.appendChild(consent);
407
+ root.appendChild(consentRow);
303
408
  }
304
409
  root.appendChild(identityRow("Peers", status.peers));
305
- if (status.electedPeer) {
410
+
411
+ var wrap = document.createElement("div");
412
+ wrap.className = "sharing-wrap";
413
+ var table = document.createElement("table");
414
+ table.className = "sharing-table";
415
+
416
+ var head = document.createElement("tr");
417
+ head.appendChild(text("th", "Data set"));
418
+ head.appendChild(text("th", "Public"));
419
+ groups.forEach(function (group) {
420
+ var th = text("th", group.name || group.id.slice(0, 8));
421
+ th.title = group.id;
422
+ head.appendChild(th);
423
+ });
424
+ table.appendChild(head);
425
+
426
+ (sharing.dataSets || []).forEach(function (set) {
427
+ var row = document.createElement("tr");
428
+ var name = document.createElement("td");
429
+ name.appendChild(text("code", set.title));
430
+ name.appendChild(text("small", set.description));
431
+ row.appendChild(name);
432
+ row.appendChild(
433
+ sharingCheckbox(
434
+ set.public,
435
+ function (checked) {
436
+ setSharing(set.id, { public: checked });
437
+ },
438
+ !publicEnabled,
439
+ ),
440
+ );
441
+ groups.forEach(function (group) {
442
+ var checked =
443
+ set.groups === "all" ||
444
+ (Array.isArray(set.groups) && set.groups.indexOf(group.id) >= 0);
445
+ row.appendChild(
446
+ sharingCheckbox(checked, function (value) {
447
+ var current =
448
+ set.groups === "all"
449
+ ? groups.map(function (entry) {
450
+ return entry.id;
451
+ })
452
+ : set.groups.slice();
453
+ if (value && current.indexOf(group.id) < 0) {
454
+ current.push(group.id);
455
+ }
456
+ if (!value) {
457
+ current = current.filter(function (id) {
458
+ return id !== group.id;
459
+ });
460
+ }
461
+ setSharing(set.id, { groups: current });
462
+ }),
463
+ );
464
+ });
465
+ table.appendChild(row);
466
+ });
467
+
468
+ wrap.appendChild(table);
469
+ root.appendChild(wrap);
470
+ if (!groups.length) {
306
471
  root.appendChild(
307
- identityRow(
308
- "Elected relay",
309
- status.electedPeer.selfId +
310
- " (capacity " +
311
- status.electedPeer.capacity +
312
- ")",
472
+ text(
473
+ "p",
474
+ "No private groups yet. Create or join one to add group columns.",
313
475
  ),
314
476
  );
315
477
  }
316
- (sharing.dataSets || []).forEach(function (set) {
317
- var label = document.createElement("label");
318
- label.className = "row";
319
- var input = document.createElement("input");
320
- input.type = "checkbox";
321
- input.checked = !!set.public;
322
- input.disabled = !(network && network.enabled);
323
- input.onchange = function () {
324
- setPublic(set.id, input.checked);
325
- };
326
- label.appendChild(input);
327
- label.appendChild(text("code", set.title));
328
- label.appendChild(text("span", set.description));
329
- root.appendChild(label);
330
- });
331
478
  root.appendChild(
332
479
  text(
333
480
  "p",
334
- "Open membership. Changes are saved to the plugin configuration and apply immediately.",
481
+ "Check a group column to share that data set with that group. Public membership is open. Changes apply immediately.",
335
482
  ),
336
483
  );
337
484
  }
338
485
 
339
- async function setPublic(dataSet, enabled) {
486
+ async function setConsent(consent) {
340
487
  try {
341
- await call("/sharing", { dataSet: dataSet, public: enabled });
488
+ await call("/network/consent", { consent: consent });
489
+ say("", false);
490
+ return refresh();
491
+ } catch (error) {
492
+ say(String(error), true);
493
+ return refresh();
494
+ }
495
+ }
496
+
497
+ async function setSharing(dataSet, update) {
498
+ try {
499
+ await call("/sharing", Object.assign({ dataSet: dataSet }, update));
342
500
  say("", false);
343
501
  return refresh();
344
502
  } catch (error) {
@@ -404,8 +562,6 @@
404
562
  text(
405
563
  "span",
406
564
  group.role +
407
- " · epoch " +
408
- group.epoch +
409
565
  " · " +
410
566
  group.members +
411
567
  " member(s)" +
@@ -415,22 +571,24 @@
415
571
  : ""),
416
572
  ),
417
573
  );
418
- row.appendChild(
419
- button(
420
- "Remove",
421
- action(function () {
422
- return removeMember(group.id);
423
- }),
424
- ),
425
- );
426
- row.appendChild(
427
- button(
428
- "Promote",
429
- action(function () {
430
- return promoteMember(group.id);
431
- }),
432
- ),
433
- );
574
+ if (group.role === "owner" || group.role === "admin") {
575
+ row.appendChild(
576
+ button(
577
+ "Remove",
578
+ action(function () {
579
+ return removeMember(group.id);
580
+ }),
581
+ ),
582
+ );
583
+ row.appendChild(
584
+ button(
585
+ "Promote",
586
+ action(function () {
587
+ return promoteMember(group.id);
588
+ }),
589
+ ),
590
+ );
591
+ }
434
592
  row.appendChild(
435
593
  button(
436
594
  "Leave",
@@ -440,10 +598,47 @@
440
598
  ),
441
599
  );
442
600
  root.appendChild(row);
601
+
602
+ if (!group.removed && group.role !== "viewer") {
603
+ var rateRow = document.createElement("div");
604
+ rateRow.className = "row";
605
+ rateRow.appendChild(text("code", "Publish rate"));
606
+ var input = document.createElement("input");
607
+ input.type = "number";
608
+ input.min = "0.01";
609
+ input.max = String(group.rateCapHz);
610
+ input.step = "0.01";
611
+ input.value = String(group.publishRateHz);
612
+ rateRow.appendChild(input);
613
+ rateRow.appendChild(
614
+ text("span", "/ " + group.rateCapHz + " Hz maximum"),
615
+ );
616
+ rateRow.appendChild(
617
+ button(
618
+ "Set",
619
+ action(function () {
620
+ return setGroupRate(group.id, Number(input.value));
621
+ }),
622
+ ),
623
+ );
624
+ if (group.role === "owner" || group.role === "admin") {
625
+ rateRow.appendChild(
626
+ button(
627
+ "Set cap",
628
+ action(function () {
629
+ return setGroupRateCap(group.id);
630
+ }),
631
+ ),
632
+ );
633
+ }
634
+ root.appendChild(rateRow);
635
+ }
443
636
  });
444
637
  }
445
638
 
446
639
  function renderRequests(requests) {
640
+ document.getElementById("requests-section").style.display =
641
+ requests.length ? "" : "none";
447
642
  clear("requests");
448
643
  var root = document.getElementById("requests");
449
644
  if (!requests.length) {
@@ -477,6 +672,8 @@
477
672
  }
478
673
 
479
674
  function renderProposals(proposals) {
675
+ document.getElementById("proposals-section").style.display =
676
+ proposals.length ? "" : "none";
480
677
  clear("proposals");
481
678
  var root = document.getElementById("proposals");
482
679
  if (!proposals.length) {
@@ -600,13 +797,16 @@
600
797
  await call("/groups/leave", { groupId: groupId });
601
798
  }
602
799
 
603
- async function setThreshold() {
604
- var groupId = prompt("Group id");
605
- if (!groupId) return;
606
- var threshold = Number(prompt("Required admin approvals", "1") || "1");
607
- await call("/groups/threshold", {
800
+ async function setGroupRate(groupId, rateHz) {
801
+ await call("/groups/rate", { groupId: groupId, rateHz: rateHz });
802
+ }
803
+
804
+ async function setGroupRateCap(groupId) {
805
+ var value = prompt("Group publish rate cap (Hz)", "1");
806
+ if (value === null) return;
807
+ await call("/groups/rate-cap", {
608
808
  groupId: groupId,
609
- threshold: threshold,
809
+ rateHz: Number(value),
610
810
  });
611
811
  }
612
812