signalk-p2p-plugin 0.3.0 → 0.4.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -14
  3. package/deploy/README.md +24 -11
  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 +29 -2
  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 +36 -8
  58. package/docs/plans/peer-node.md +7 -2
  59. package/docs/plans/security.md +1 -1
  60. package/docs/plans/shared-data.md +4 -3
  61. package/package.json +3 -2
  62. package/policy/default.json +30 -0
  63. package/policy/operator.pub +2 -0
  64. package/public/index.html +299 -83
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>
@@ -120,9 +168,7 @@
120
168
  <div id="groups"></div>
121
169
  <div class="actions">
122
170
  <button onclick="createGroup()">Create group</button>
123
- <button onclick="invite()">Create invite</button>
124
171
  <button onclick="joinGroup()">Join with token</button>
125
- <button onclick="setThreshold()">Set threshold</button>
126
172
  </div>
127
173
  <textarea
128
174
  id="token"
@@ -130,12 +176,12 @@
130
176
  ></textarea>
131
177
  </section>
132
178
 
133
- <section>
179
+ <section id="requests-section">
134
180
  <h2>Join requests</h2>
135
181
  <div id="requests"></div>
136
182
  </section>
137
183
 
138
- <section>
184
+ <section id="proposals-section">
139
185
  <h2>Pending proposals</h2>
140
186
  <div id="proposals"></div>
141
187
  </section>
@@ -212,7 +258,13 @@
212
258
  call("/sharing"),
213
259
  ]);
214
260
  renderIdentity(results[0], results[1]);
215
- renderPublicNetwork(results[0], results[6]);
261
+ renderSharing(
262
+ results[0],
263
+ results[6],
264
+ (results[3].groups || []).filter(function (group) {
265
+ return !group.removed;
266
+ }),
267
+ );
216
268
  renderClaims(results[2].claims || []);
217
269
  renderGroups(results[3].groups || []);
218
270
  renderRequests(results[4].requests || []);
@@ -244,6 +296,27 @@
244
296
  );
245
297
  root.appendChild(text("h3", "Status"));
246
298
  root.appendChild(identityRow("Version", status.version));
299
+ if (status.policy) {
300
+ root.appendChild(
301
+ identityRow(
302
+ "Network policy",
303
+ "v" +
304
+ status.policy.version +
305
+ " · issued " +
306
+ new Date(status.policy.issuedAt * 1000)
307
+ .toISOString()
308
+ .slice(0, 10),
309
+ ),
310
+ );
311
+ if (status.policy.operator) {
312
+ root.appendChild(
313
+ identityRow(
314
+ "Policy operator",
315
+ status.policy.operator.slice(0, 16) + "…",
316
+ ),
317
+ );
318
+ }
319
+ }
247
320
  root.appendChild(identityRow("Started", status.started ? "yes" : "no"));
248
321
  root.appendChild(identityRow("Peers", status.peers));
249
322
  root.appendChild(identityRow("Injected vessels", status.injected));
@@ -278,13 +351,28 @@
278
351
  }
279
352
  }
280
353
 
281
- function renderPublicNetwork(status, sharing) {
354
+ function sharingCheckbox(checked, onchange, disabled) {
355
+ var cell = document.createElement("td");
356
+ var input = document.createElement("input");
357
+ input.type = "checkbox";
358
+ input.checked = !!checked;
359
+ input.disabled = Boolean(disabled);
360
+ input.onchange = function () {
361
+ onchange(input.checked);
362
+ };
363
+ cell.appendChild(input);
364
+ return cell;
365
+ }
366
+
367
+ function renderSharing(status, sharing, groups) {
282
368
  clear("public-network");
283
369
  var root = document.getElementById("public-network");
284
370
  root.className = "network-card";
285
371
  status = status || {};
286
372
  sharing = sharing || {};
373
+ groups = groups || [];
287
374
  var network = sharing.network;
375
+ var publicEnabled = Boolean(network && network.enabled);
288
376
  root.appendChild(
289
377
  identityRow(
290
378
  "Public network",
@@ -293,52 +381,121 @@
293
381
  );
294
382
  if (network) {
295
383
  root.appendChild(
296
- identityRow(
297
- "Status",
298
- (network.enabled ? "enabled" : "disabled") +
299
- " · " +
300
- (network.consent || "opt-out"),
301
- ),
384
+ identityRow("Status", network.enabled ? "enabled" : "disabled"),
302
385
  );
386
+ var consentRow = document.createElement("div");
387
+ consentRow.className = "row";
388
+ consentRow.appendChild(text("code", "Sharing"));
389
+ var consent = document.createElement("select");
390
+ [
391
+ ["receive-only", "Receive only (don't share my vessel)"],
392
+ ["underway-gated", "Share only while underway"],
393
+ ["coarse-position", "Share an approximate position"],
394
+ ["opt-out", "Share my vessel"],
395
+ ].forEach(function (entry) {
396
+ var option = document.createElement("option");
397
+ option.value = entry[0];
398
+ option.textContent = entry[1];
399
+ consent.appendChild(option);
400
+ });
401
+ consent.value = network.consent || "receive-only";
402
+ consent.onchange = function () {
403
+ setConsent(consent.value);
404
+ };
405
+ consentRow.appendChild(consent);
406
+ root.appendChild(consentRow);
303
407
  }
304
408
  root.appendChild(identityRow("Peers", status.peers));
305
- if (status.electedPeer) {
409
+
410
+ var wrap = document.createElement("div");
411
+ wrap.className = "sharing-wrap";
412
+ var table = document.createElement("table");
413
+ table.className = "sharing-table";
414
+
415
+ var head = document.createElement("tr");
416
+ head.appendChild(text("th", "Data set"));
417
+ head.appendChild(text("th", "Public"));
418
+ groups.forEach(function (group) {
419
+ var th = text("th", group.name || group.id.slice(0, 8));
420
+ th.title = group.id;
421
+ head.appendChild(th);
422
+ });
423
+ table.appendChild(head);
424
+
425
+ (sharing.dataSets || []).forEach(function (set) {
426
+ var row = document.createElement("tr");
427
+ var name = document.createElement("td");
428
+ name.appendChild(text("code", set.title));
429
+ name.appendChild(text("small", set.description));
430
+ row.appendChild(name);
431
+ row.appendChild(
432
+ sharingCheckbox(
433
+ set.public,
434
+ function (checked) {
435
+ setSharing(set.id, { public: checked });
436
+ },
437
+ !publicEnabled,
438
+ ),
439
+ );
440
+ groups.forEach(function (group) {
441
+ var checked =
442
+ set.groups === "all" ||
443
+ (Array.isArray(set.groups) && set.groups.indexOf(group.id) >= 0);
444
+ row.appendChild(
445
+ sharingCheckbox(checked, function (value) {
446
+ var current =
447
+ set.groups === "all"
448
+ ? groups.map(function (entry) {
449
+ return entry.id;
450
+ })
451
+ : set.groups.slice();
452
+ if (value && current.indexOf(group.id) < 0) {
453
+ current.push(group.id);
454
+ }
455
+ if (!value) {
456
+ current = current.filter(function (id) {
457
+ return id !== group.id;
458
+ });
459
+ }
460
+ setSharing(set.id, { groups: current });
461
+ }),
462
+ );
463
+ });
464
+ table.appendChild(row);
465
+ });
466
+
467
+ wrap.appendChild(table);
468
+ root.appendChild(wrap);
469
+ if (!groups.length) {
306
470
  root.appendChild(
307
- identityRow(
308
- "Elected relay",
309
- status.electedPeer.selfId +
310
- " (capacity " +
311
- status.electedPeer.capacity +
312
- ")",
471
+ text(
472
+ "p",
473
+ "No private groups yet. Create or join one to add group columns.",
313
474
  ),
314
475
  );
315
476
  }
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
477
  root.appendChild(
332
478
  text(
333
479
  "p",
334
- "Open membership. Changes are saved to the plugin configuration and apply immediately.",
480
+ "Check a group column to share that data set with that group. Public membership is open. Changes apply immediately.",
335
481
  ),
336
482
  );
337
483
  }
338
484
 
339
- async function setPublic(dataSet, enabled) {
485
+ async function setConsent(consent) {
486
+ try {
487
+ await call("/network/consent", { consent: consent });
488
+ say("", false);
489
+ return refresh();
490
+ } catch (error) {
491
+ say(String(error), true);
492
+ return refresh();
493
+ }
494
+ }
495
+
496
+ async function setSharing(dataSet, update) {
340
497
  try {
341
- await call("/sharing", { dataSet: dataSet, public: enabled });
498
+ await call("/sharing", Object.assign({ dataSet: dataSet }, update));
342
499
  say("", false);
343
500
  return refresh();
344
501
  } catch (error) {
@@ -404,8 +561,6 @@
404
561
  text(
405
562
  "span",
406
563
  group.role +
407
- " · epoch " +
408
- group.epoch +
409
564
  " · " +
410
565
  group.members +
411
566
  " member(s)" +
@@ -415,22 +570,29 @@
415
570
  : ""),
416
571
  ),
417
572
  );
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);
573
+ if (group.role === "owner" || group.role === "admin") {
574
+ row.appendChild(
575
+ button("Invite", function () {
576
+ createInvite(group.id);
431
577
  }),
432
- ),
433
- );
578
+ );
579
+ row.appendChild(
580
+ button(
581
+ "Remove",
582
+ action(function () {
583
+ return removeMember(group.id);
584
+ }),
585
+ ),
586
+ );
587
+ row.appendChild(
588
+ button(
589
+ "Promote",
590
+ action(function () {
591
+ return promoteMember(group.id);
592
+ }),
593
+ ),
594
+ );
595
+ }
434
596
  row.appendChild(
435
597
  button(
436
598
  "Leave",
@@ -440,10 +602,47 @@
440
602
  ),
441
603
  );
442
604
  root.appendChild(row);
605
+
606
+ if (!group.removed && group.role !== "viewer") {
607
+ var rateRow = document.createElement("div");
608
+ rateRow.className = "row";
609
+ rateRow.appendChild(text("code", "Publish rate"));
610
+ var input = document.createElement("input");
611
+ input.type = "number";
612
+ input.min = "0.01";
613
+ input.max = String(group.rateCapHz);
614
+ input.step = "0.01";
615
+ input.value = String(group.publishRateHz);
616
+ rateRow.appendChild(input);
617
+ rateRow.appendChild(
618
+ text("span", "/ " + group.rateCapHz + " Hz maximum"),
619
+ );
620
+ rateRow.appendChild(
621
+ button(
622
+ "Set",
623
+ action(function () {
624
+ return setGroupRate(group.id, Number(input.value));
625
+ }),
626
+ ),
627
+ );
628
+ if (group.role === "owner" || group.role === "admin") {
629
+ rateRow.appendChild(
630
+ button(
631
+ "Set cap",
632
+ action(function () {
633
+ return setGroupRateCap(group.id);
634
+ }),
635
+ ),
636
+ );
637
+ }
638
+ root.appendChild(rateRow);
639
+ }
443
640
  });
444
641
  }
445
642
 
446
643
  function renderRequests(requests) {
644
+ document.getElementById("requests-section").style.display =
645
+ requests.length ? "" : "none";
447
646
  clear("requests");
448
647
  var root = document.getElementById("requests");
449
648
  if (!requests.length) {
@@ -477,6 +676,8 @@
477
676
  }
478
677
 
479
678
  function renderProposals(proposals) {
679
+ document.getElementById("proposals-section").style.display =
680
+ proposals.length ? "" : "none";
480
681
  clear("proposals");
481
682
  var root = document.getElementById("proposals");
482
683
  if (!proposals.length) {
@@ -538,26 +739,38 @@
538
739
  await call("/groups", { name: name });
539
740
  }
540
741
 
541
- async function invite() {
542
- var groupId = prompt("Group id");
543
- if (!groupId) return;
544
- var role =
545
- prompt("Role (owner/admin/member/viewer)", "member") || "member";
546
- var days = Number(prompt("Expires in days", "7") || "7");
547
- var result = await call("/groups/invite", {
548
- groupId: groupId,
549
- role: role,
550
- expiresInSeconds: Math.max(60, days * 86400),
551
- });
552
- document.getElementById("token").value = result.token || "";
742
+ async function createInvite(groupId) {
743
+ try {
744
+ var result = await call("/groups/invite", { groupId: groupId });
745
+ var token = result.token || "";
746
+ var field = document.getElementById("token");
747
+ field.value = token;
748
+ field.focus();
749
+ field.select();
750
+ if (navigator.clipboard && navigator.clipboard.writeText) {
751
+ try {
752
+ await navigator.clipboard.writeText(token);
753
+ say("Invite token copied. Send it to the person joining.", false);
754
+ return;
755
+ } catch (error) {}
756
+ }
757
+ say("Invite token created. Copy it from the box below.", false);
758
+ } catch (error) {
759
+ say(String(error), true);
760
+ }
553
761
  }
554
762
 
555
763
  async function joinGroup() {
556
764
  var token =
557
- prompt("Invite token") || document.getElementById("token").value;
765
+ prompt("Paste the group invite token") ||
766
+ document.getElementById("token").value;
558
767
  if (!token) return;
559
- var mmsi = prompt("Your MMSI (optional)") || undefined;
560
- await call("/groups/join", { token: token, mmsi: mmsi });
768
+ try {
769
+ await call("/groups/join", { token: token });
770
+ say("Join request sent. A group admin must approve it.", false);
771
+ } catch (error) {
772
+ say(String(error), true);
773
+ }
561
774
  }
562
775
 
563
776
  async function approveRequest(entry) {
@@ -600,13 +813,16 @@
600
813
  await call("/groups/leave", { groupId: groupId });
601
814
  }
602
815
 
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", {
816
+ async function setGroupRate(groupId, rateHz) {
817
+ await call("/groups/rate", { groupId: groupId, rateHz: rateHz });
818
+ }
819
+
820
+ async function setGroupRateCap(groupId) {
821
+ var value = prompt("Group publish rate cap (Hz)", "1");
822
+ if (value === null) return;
823
+ await call("/groups/rate-cap", {
608
824
  groupId: groupId,
609
- threshold: threshold,
825
+ rateHz: Number(value),
610
826
  });
611
827
  }
612
828