nixamp 0.7.17 → 0.7.19

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/dist/publish.d.ts CHANGED
@@ -12,7 +12,14 @@ export interface PublishTarget {
12
12
  * gets a 401 in JSON, which is a caller hearing nothing.
13
13
  */
14
14
  audio?: string;
15
- tracks: number;
15
+ /**
16
+ * How many tracks there are, asked at every heartbeat rather than once.
17
+ *
18
+ * Taken as a number, it was read before the library had finished loading --
19
+ * the port opens first now -- so a server with five thousand tracks
20
+ * advertised nought of them for as long as it stayed up.
21
+ */
22
+ tracks: () => number;
16
23
  nowPlaying: () => string;
17
24
  /**
18
25
  * The account this stream belongs to, from `nixamp login`.
package/dist/publish.js CHANGED
@@ -58,7 +58,7 @@ export class Publisher {
58
58
  name: this.target.name,
59
59
  url: this.target.url,
60
60
  ...(this.target.audio ? { audio: this.target.audio } : {}),
61
- tracks: this.target.tracks,
61
+ tracks: this.target.tracks(),
62
62
  nowPlaying: this.target.nowPlaying(),
63
63
  }),
64
64
  });
package/dist/server.js CHANGED
@@ -775,10 +775,17 @@ export function createHandler(engine, options) {
775
775
  // cookie, so every later fetch, EventSource and <audio src> carries it
776
776
  // without the page knowing anything about keys. Either key works here, and
777
777
  // which one was used decides what the browser can then do.
778
- const offeredInPath = key !== null ? keyInPath(path) : null;
779
- if (offeredInPath !== null) {
780
- const offered = offeredInPath;
781
- if (scopeOf(offered, key, listenKey) === null) {
778
+ const inPath = key !== null ? keyInPath(path) : null;
779
+ if (inPath !== null) {
780
+ const offered = inPath.key;
781
+ // The path has to be telling the truth about the key it carries. A `/v/`
782
+ // link holding the control key would read as view-only to whoever you
783
+ // sent it to and hand them the controls, which is the whole reason for
784
+ // naming the two shapes in the first place.
785
+ //
786
+ // A mismatch answers exactly as a wrong key does, so nothing is learned
787
+ // from the difference between the two.
788
+ if (scopeOf(offered, key, listenKey) !== inPath.wants) {
782
789
  json(response, 404, { error: "not found" });
783
790
  return;
784
791
  }
@@ -2937,9 +2944,9 @@ export async function serve(argv, version = "0.1.0") {
2937
2944
  name: options.name || hostname(),
2938
2945
  url: listen,
2939
2946
  audio,
2940
- // Asked of the engine rather than a variable, because the library is
2941
- // now read after the port opens and may still be arriving.
2942
- tracks: engine.snapshot(false).trackCount,
2947
+ // Asked at every heartbeat rather than once, because the library is
2948
+ // read after the port opens and is still arriving when this is made.
2949
+ tracks: () => engine.snapshot(false).trackCount,
2943
2950
  // From `nixamp login`. The directory will not list a stream it cannot
2944
2951
  // attribute to somebody, because a listing is now a phone code that
2945
2952
  // costs money to answer.
package/dist/share.d.ts CHANGED
@@ -94,8 +94,19 @@ export declare function reachableAddresses(host: string, port: number, publicUrl
94
94
  * A link should say what it is before anybody clicks it.
95
95
  */
96
96
  export declare const KEY_PATHS: readonly ["/a/", "/v/"];
97
- /** The key in a share link, whichever of the three shapes it came in. */
98
- export declare function keyInPath(path: string): string | null;
97
+ /**
98
+ * The key in a share link, and what the link claimed to be.
99
+ *
100
+ * Both halves, because a label nobody checks is a label that can lie. `/a/`
101
+ * means this administers and `/v/` means this only views; handed the other
102
+ * key, a path would otherwise have said one thing and done the other -- and
103
+ * the dangerous direction is real: a `/v/` link built around the control key
104
+ * reads as view-only to the person you send it to and hands them the controls.
105
+ */
106
+ export declare function keyInPath(path: string): {
107
+ key: string;
108
+ wants: Scope;
109
+ } | null;
99
110
  /** The full link, key and all. `admin` picks the shape that says which it is. */
100
111
  export declare function shareLink(base: string, key: string | null, admin?: boolean): string;
101
112
  /**
package/dist/share.js CHANGED
@@ -251,7 +251,15 @@ export function reachableAddresses(host, port, publicUrl = "", scheme = "http")
251
251
  * A link should say what it is before anybody clicks it.
252
252
  */
253
253
  export const KEY_PATHS = ["/a/", "/v/"];
254
- /** The key in a share link, whichever of the three shapes it came in. */
254
+ /**
255
+ * The key in a share link, and what the link claimed to be.
256
+ *
257
+ * Both halves, because a label nobody checks is a label that can lie. `/a/`
258
+ * means this administers and `/v/` means this only views; handed the other
259
+ * key, a path would otherwise have said one thing and done the other -- and
260
+ * the dangerous direction is real: a `/v/` link built around the control key
261
+ * reads as view-only to the person you send it to and hands them the controls.
262
+ */
255
263
  export function keyInPath(path) {
256
264
  for (const prefix of KEY_PATHS) {
257
265
  if (!path.startsWith(prefix))
@@ -259,12 +267,15 @@ export function keyInPath(path) {
259
267
  const rest = path.slice(prefix.length).replace(/\/+$/, "");
260
268
  if (rest === "" || rest.includes("/"))
261
269
  return null;
270
+ let key = rest;
262
271
  try {
263
- return decodeURIComponent(rest);
272
+ key = decodeURIComponent(rest);
264
273
  }
265
274
  catch {
266
- return rest;
275
+ // A key that is not valid percent-encoding is taken as written; it will
276
+ // fail to match anything, which is the right answer either way.
267
277
  }
278
+ return { key, wants: prefix === "/a/" ? "control" : "listen" };
268
279
  }
269
280
  return null;
270
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nixamp",
3
- "version": "0.7.17",
3
+ "version": "0.7.19",
4
4
  "description": "It really whips the terminal's ass. A Winamp-shaped audio player for your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/publish.ts CHANGED
@@ -21,7 +21,14 @@ export interface PublishTarget {
21
21
  * gets a 401 in JSON, which is a caller hearing nothing.
22
22
  */
23
23
  audio?: string;
24
- tracks: number;
24
+ /**
25
+ * How many tracks there are, asked at every heartbeat rather than once.
26
+ *
27
+ * Taken as a number, it was read before the library had finished loading --
28
+ * the port opens first now -- so a server with five thousand tracks
29
+ * advertised nought of them for as long as it stayed up.
30
+ */
31
+ tracks: () => number;
25
32
  nowPlaying: () => string;
26
33
  /**
27
34
  * The account this stream belongs to, from `nixamp login`.
@@ -92,7 +99,7 @@ export class Publisher {
92
99
  name: this.target.name,
93
100
  url: this.target.url,
94
101
  ...(this.target.audio ? { audio: this.target.audio } : {}),
95
- tracks: this.target.tracks,
102
+ tracks: this.target.tracks(),
96
103
  nowPlaying: this.target.nowPlaying(),
97
104
  }),
98
105
  });
package/src/server.ts CHANGED
@@ -1074,10 +1074,17 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
1074
1074
  // cookie, so every later fetch, EventSource and <audio src> carries it
1075
1075
  // without the page knowing anything about keys. Either key works here, and
1076
1076
  // which one was used decides what the browser can then do.
1077
- const offeredInPath = key !== null ? keyInPath(path) : null;
1078
- if (offeredInPath !== null) {
1079
- const offered = offeredInPath;
1080
- if (scopeOf(offered, key, listenKey) === null) {
1077
+ const inPath = key !== null ? keyInPath(path) : null;
1078
+ if (inPath !== null) {
1079
+ const offered = inPath.key;
1080
+ // The path has to be telling the truth about the key it carries. A `/v/`
1081
+ // link holding the control key would read as view-only to whoever you
1082
+ // sent it to and hand them the controls, which is the whole reason for
1083
+ // naming the two shapes in the first place.
1084
+ //
1085
+ // A mismatch answers exactly as a wrong key does, so nothing is learned
1086
+ // from the difference between the two.
1087
+ if (scopeOf(offered, key, listenKey) !== inPath.wants) {
1081
1088
  json(response, 404, { error: "not found" });
1082
1089
  return;
1083
1090
  }
@@ -3394,9 +3401,9 @@ export async function serve(argv: string[], version = "0.1.0"): Promise<void> {
3394
3401
  name: options.name || hostname(),
3395
3402
  url: listen,
3396
3403
  audio,
3397
- // Asked of the engine rather than a variable, because the library is
3398
- // now read after the port opens and may still be arriving.
3399
- tracks: engine.snapshot(false).trackCount,
3404
+ // Asked at every heartbeat rather than once, because the library is
3405
+ // read after the port opens and is still arriving when this is made.
3406
+ tracks: () => engine.snapshot(false).trackCount,
3400
3407
  // From `nixamp login`. The directory will not list a stream it cannot
3401
3408
  // attribute to somebody, because a listing is now a phone code that
3402
3409
  // costs money to answer.
package/src/share.ts CHANGED
@@ -282,17 +282,28 @@ export function reachableAddresses(
282
282
  */
283
283
  export const KEY_PATHS = ["/a/", "/v/"] as const;
284
284
 
285
- /** The key in a share link, whichever of the three shapes it came in. */
286
- export function keyInPath(path: string): string | null {
285
+ /**
286
+ * The key in a share link, and what the link claimed to be.
287
+ *
288
+ * Both halves, because a label nobody checks is a label that can lie. `/a/`
289
+ * means this administers and `/v/` means this only views; handed the other
290
+ * key, a path would otherwise have said one thing and done the other -- and
291
+ * the dangerous direction is real: a `/v/` link built around the control key
292
+ * reads as view-only to the person you send it to and hands them the controls.
293
+ */
294
+ export function keyInPath(path: string): { key: string; wants: Scope } | null {
287
295
  for (const prefix of KEY_PATHS) {
288
296
  if (!path.startsWith(prefix)) continue;
289
297
  const rest = path.slice(prefix.length).replace(/\/+$/, "");
290
298
  if (rest === "" || rest.includes("/")) return null;
299
+ let key = rest;
291
300
  try {
292
- return decodeURIComponent(rest);
301
+ key = decodeURIComponent(rest);
293
302
  } catch {
294
- return rest;
303
+ // A key that is not valid percent-encoding is taken as written; it will
304
+ // fail to match anything, which is the right answer either way.
295
305
  }
306
+ return { key, wants: prefix === "/a/" ? "control" : "listen" };
296
307
  }
297
308
  return null;
298
309
  }
package/web/dist/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* nixamp service worker — generated, do not edit */
2
- const CACHE = "nixamp-1788962802424";
2
+ const CACHE = "nixamp-1788964111536";
3
3
  const PRECACHE = [
4
4
  "/",
5
5
  "/apple-touch-icon.png",