@streamorchard/grapevine 0.4.0 → 0.5.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.
package/PROTOCOL.md CHANGED
@@ -197,6 +197,8 @@ A capability is a named feature one app provides and others use, defined by a JS
197
197
 
198
198
  What a drops app is farming, with enough for another app to show it, in a top-bar widget say. Provided by Pear Drops.
199
199
 
200
+ Event `drops.farming.changed`, and `drops.farming.get` answers with the same: the whole state, below. `drops.farming.show` is further down.
201
+
200
202
  | Field | |
201
203
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
202
204
  | `state` | `farming` (its player is watching a stream for drop credit), `waiting`, `paused`, `done`, `signed-out`, or one added later |
@@ -216,6 +218,8 @@ What a drops app is farming, with enough for another app to show it, in a top-ba
216
218
 
217
219
  An app's stream players, by site. Provided by Stream Multiview.
218
220
 
221
+ Event `players.changed`, and `players.get` answers with the same: the whole state, below.
222
+
219
223
  | Field | |
220
224
  | ----------- | --------------------------------------------------------------------------------------------------------------- |
221
225
  | `platforms` | An object keyed by `Platform`. Each site has `{ signedIn, mode, account, open }` |
@@ -259,7 +263,7 @@ After the hello, each app tells its user one of:
259
263
  | **Works, with a feature missing** | A capability one app wants has no major in common. It names the app with the older versions and, if known, the release to update to: "update Pear Drops to 0.1.7 or later" |
260
264
  | **Can't connect** | No protocol version in common. Again, it names the app to update |
261
265
 
262
- The releases come from the known-apps list (`src/known-apps.js`): `firstRelease` maps `"<capability>@<major>.<minor>"` and `"protocol@<n>"` to the first release of that app that had it.
266
+ The releases come from `schemas/released.json`, recorded as each app ships (`npm run released`), through the known-apps list's `firstRelease`: `"<capability>@<major>.<minor>"` and `"protocol@<n>"` mapped to the first release of that app that had them.
263
267
 
264
268
  ## Renaming an app
265
269
 
package/README.md CHANGED
@@ -111,17 +111,18 @@ Editors suggest every event's fields and underline mistakes, in plain JavaScript
111
111
 
112
112
  `link.peers()` lists every known app with what its card needs, and `link.on('change', …)` says when to draw it again.
113
113
 
114
- | Field | |
115
- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
116
- | `name`, `summary`, `icon`, `download` | The app's logo (bundled, so it shows even when it isn't installed), one line about it, and where to get it |
117
- | `installed` | Whether its data folder exists (with `appDataDir`) |
118
- | `enabled` | Your switch for it |
119
- | `status` | `off`, `waiting`, `connected`, `pair-again` or `incompatible` |
120
- | `reason` | Why it's waiting: `switched-off` (its own switch is off), `disconnected-there` |
121
- | `keyHeldBy` | For `pair-again`: `us` means show **Pair again** here, `them` means it's pressed in the other app |
122
- | `version`, `report.text` | While connected: its version, and "Works" or what's missing ("update Pear Drops to 0.1.7 or later") |
123
- | `pairedAt`, `keysEncrypted` | When they paired, and whether keys are stored encrypted |
124
- | `formerIds` | Ids the app had before a rename |
114
+ | Field | |
115
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
116
+ | `name`, `summary`, `icon`, `download` | The app's logo (bundled, so it shows even when it isn't installed), one line about it, and where to get it |
117
+ | `installed` | Whether its data folder exists (with `appDataDir`) |
118
+ | `enabled` | Your switch for it |
119
+ | `status` | `off`, `waiting`, `connected`, `pair-again` or `incompatible` |
120
+ | `reason` | Why it's waiting: `switched-off` (its own switch is off), `disconnected-there` |
121
+ | `keyHeldBy` | For `pair-again`: `us` means show **Pair again** here, `them` means it's pressed in the other app |
122
+ | `version`, `report.text` | While connected: its version, and "Works" or what's missing ("update Pear Drops to 0.1.7 or later") |
123
+ | `pairedAt`, `keysEncrypted` | When they paired, and whether keys are stored encrypted |
124
+ | `formerIds` | Ids the app had before a rename |
125
+ | `shares` | What the two apps share: `{ out, in }`, capabilities this app provides that it uses, and the other way round |
125
126
 
126
127
  Two calls go with buttons on the card:
127
128
 
@@ -166,7 +167,17 @@ To change one, follow [the version rules](PROTOCOL.md#versions): additions are a
166
167
 
167
168
  ## Adding an app
168
169
 
169
- Add it to `src/known-apps.js`, with its id, slug, summary, a 256×256 logo in `assets/apps/`, its download page and its data folder's name. Every app that updates Grapevine then shows it on its Connected apps page.
170
+ Add it to `src/known-apps.js` with:
171
+
172
+ - its id and slug
173
+ - a one-line summary
174
+ - a 256×256 logo in `assets/apps/`
175
+ - its download page and its data folder's name
176
+ - the capabilities it `provides` and `uses`
177
+
178
+ Every app that updates Grapevine then shows it on its Connected apps page, with what the two would share. `link.peers()` gives that as `shares`, from this list until they meet and from the hello after.
179
+
180
+ When it ships a release that uses Grapevine, record it: `npm run released -- <its id> <its version>`. That fixes the capability versions it uses (they can't change in place after that) and tells other apps which release to ask for.
170
181
 
171
182
  ## Renaming an app
172
183
 
@@ -183,6 +194,7 @@ npm run lint # ESLint and Prettier
183
194
  npm run format # fix what lint can
184
195
  npm run types # types/capabilities.d.ts from the schemas
185
196
  npm run check # the types are up to date, and tsc finds no type errors
197
+ npm run released -- <app id> <version> # an app shipped a release using Grapevine (see Adding an app)
186
198
  ```
187
199
 
188
200
  CI runs the tests on Windows, macOS and Linux, and lint, types and coverage on Linux.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamorchard/grapevine",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Lets desktop apps on the same computer find each other and talk, privately: local sockets, JSON-RPC, pairing and versioned capabilities.",
5
5
  "license": "GPL-3.0-only",
6
6
  "author": "StreamOrchard",
@@ -52,7 +52,8 @@
52
52
  "lint": "eslint . && prettier --check .",
53
53
  "format": "prettier --write . && eslint --fix .",
54
54
  "types": "node scripts/types.js",
55
- "check": "node scripts/types.js --check && tsc -p jsconfig.json"
55
+ "check": "node scripts/types.js --check && tsc -p jsconfig.json",
56
+ "released": "node scripts/released.js"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@eslint/js": "^10.0.1",
@@ -0,0 +1,5 @@
1
+ {
2
+ "description": "Capability versions that have shipped in an app's release, and which release of each app first had them. Written by `npm run released -- <app id> <version>`; never by hand. A version recorded here can't change in place: test/released.test.js fails until the change goes in a new minor.",
3
+ "schemas": {},
4
+ "firstRelease": {}
5
+ }
package/src/index.js CHANGED
@@ -415,6 +415,20 @@ class Link extends EventEmitter {
415
415
  return this.conns.has(this.currentId(appId));
416
416
  }
417
417
 
418
+ /**
419
+ * What this app and another share: `out`, capabilities this app provides that it uses; `in`,
420
+ * ones it provides that this app uses. Agreed in the hello while connected; from the known-apps
421
+ * list otherwise, so a card can say what linking would do before the two have met.
422
+ */
423
+ sharesWith(app, conn) {
424
+ if (conn) return { out: [...conn.agreed.outgoing.keys()].sort(), in: [...conn.agreed.incoming.keys()].sort() };
425
+ const mine = (list) => (cap) => cap in list;
426
+ return {
427
+ out: (app.uses ?? []).filter(mine(this.mine.provides)).sort(),
428
+ in: (app.provides ?? []).filter(mine(this.mine.uses)).sort(),
429
+ };
430
+ }
431
+
418
432
  /**
419
433
  * Every known app, for the Connected apps page:
420
434
  * status 'off' (its switch is off) | 'waiting' | 'connected' | 'pair-again' | 'incompatible'.
@@ -445,6 +459,7 @@ class Link extends EventEmitter {
445
459
  keyHeldBy: status === 'pair-again' ? (why?.keyHeldBy ?? null) : null,
446
460
  version: conn?.peer.version ?? null,
447
461
  report,
462
+ shares: this.sharesWith(app, conn),
448
463
  pairedAt: paired?.pairedAt ?? null,
449
464
  keysEncrypted: !!this.keys.encrypted,
450
465
  };
package/src/known-apps.js CHANGED
@@ -7,6 +7,8 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
9
  const ICONS = path.join(__dirname, '..', 'assets', 'apps');
10
+ // Which release of each app first had each capability version: written by `npm run released`.
11
+ const RELEASED = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'schemas', 'released.json'), 'utf8')).firstRelease;
10
12
 
11
13
  /**
12
14
  * @typedef {object} KnownApp
@@ -19,6 +21,8 @@ const ICONS = path.join(__dirname, '..', 'assets', 'apps');
19
21
  * @property {string} homepage
20
22
  * @property {string} download its latest release
21
23
  * @property {string} dataFolder its folder in the OS's app data folder: no folder means not installed
24
+ * @property {string[]} provides the capabilities it provides (their names), so another app can say what the two share before they've met
25
+ * @property {string[]} uses the capabilities it uses
22
26
  * @property {Record<string, string>} firstRelease "capability@major.minor" (or "protocol@N") -> the app version that first had it
23
27
  */
24
28
 
@@ -34,7 +38,9 @@ const KNOWN_APPS = [
34
38
  homepage: 'https://github.com/Stream-Orchard/pear-drops',
35
39
  download: 'https://github.com/Stream-Orchard/pear-drops/releases/latest',
36
40
  dataFolder: 'Pear Drops',
37
- firstRelease: {},
41
+ provides: ['drops.farming'],
42
+ uses: ['players'],
43
+ firstRelease: { ...RELEASED['com.streamorchard.peardrops'] },
38
44
  },
39
45
  {
40
46
  id: 'com.glowingpear.streammultiview',
@@ -46,7 +52,9 @@ const KNOWN_APPS = [
46
52
  homepage: 'https://github.com/GlowingPear/stream-window-multiview',
47
53
  download: 'https://github.com/GlowingPear/stream-window-multiview/releases/latest',
48
54
  dataFolder: 'Stream Multiview',
49
- firstRelease: {},
55
+ provides: ['players'],
56
+ uses: ['drops.farming'],
57
+ firstRelease: { ...RELEASED['com.glowingpear.streammultiview'] },
50
58
  },
51
59
  ];
52
60
 
package/types/index.d.ts CHANGED
@@ -64,6 +64,9 @@ export interface KnownApp {
64
64
  download: string;
65
65
  /** Its folder in the OS's app data folder */
66
66
  dataFolder: string;
67
+ /** The capabilities it provides and uses (names), so a card can say what linking would do */
68
+ provides: string[];
69
+ uses: string[];
67
70
  /** "capability@major.minor" (or "protocol@N") -> the app version that first had it */
68
71
  firstRelease: Record<string, string>;
69
72
  }
@@ -119,6 +122,8 @@ export interface Peer {
119
122
  /** Its version, while connected */
120
123
  version: string | null;
121
124
  report: Report | null;
125
+ /** What the two apps share: `out`, capabilities this app provides that it uses; `in`, ones it provides that this app uses */
126
+ shares: { out: string[]; in: string[] };
122
127
  pairedAt: string | null;
123
128
  keysEncrypted: boolean;
124
129
  }