dsh-realtime-audio-ws 0.2.2 → 0.2.4

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/src/strip.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The strip: the panel this plugin puts in the host's own page.
3
+ *
4
+ * S2 story 3. Until it existed the plugin was driven from a `globalThis` key in a devtools console, and the
5
+ * roadmap's judgement on that was blunt — *a plugin driven from a console global will not be installed by
6
+ * anyone*. What changes is not decoration: the on-switch moves into the page, and `sessionId`, which cost
7
+ * two restarts and a false lead to change, becomes a picker.
8
+ *
9
+ * ## Where the behaviour lives, and why not here
10
+ *
11
+ * This module mints **rows** for `webserver/index-inject` — the door the page already receives its settings
12
+ * through. Two of the six row kinds carry UI: `html` for raw markup, `script` for an inline classic script.
13
+ * So the markup is here and the bootstrap is here, and **the panel's logic is not**: everything that
14
+ * decides what to render and what a button does lives in the client bundle
15
+ * (`src/client/index.ts`), where it is covered by tests. A panel implemented inside an injected string is a
16
+ * feature nothing can execute in CI, which is the same defect class as a build artefact no test reads.
17
+ *
18
+ * The bootstrap is therefore three statements, and it is still *executed* by a test — as a function with a
19
+ * fake scope — so the injected code is not exempt from the gate just because it is shipped as text.
20
+ *
21
+ * ## Two ways in, deliberately
22
+ *
23
+ * The bundle mounts the panel when it applies, and the injected script mounts it again in case it lands
24
+ * first. Both are idempotent, because the order between the module table and the body rows is not something
25
+ * this package controls, and a panel that appears only when two independent orderings agree is a panel that
26
+ * appears only on the author's machine.
27
+ *
28
+ * @module dsh-realtime-audio-ws/strip
29
+ */
30
+
31
+ import type { InjectedRow } from './injection.ts'
32
+
33
+ /** The element the panel mounts on. Duplicated in the client half, which cannot import it. */
34
+ export const STRIP_ELEMENT_ID = 'dsh-realtime-strip'
35
+
36
+ /**
37
+ * The panel's container, as markup.
38
+ *
39
+ * Fixed to the bottom right, inheriting the host's font and colour rather than inventing a second visual
40
+ * language beside it — but **opaque**, and that is a correction rather than a taste. This panel sits over
41
+ * the host's own text, so a translucent wash lets the page show through and the two become unreadable
42
+ * together: two texts superimposed, neither of them legible. That was reported from a screenshot of the
43
+ * running app, which is the only way it was ever going to be found — nothing in a DOM test can see what is
44
+ * *behind* the panel.
45
+ *
46
+ * `Canvas` is the theme's own surface colour, so the panel follows light or dark mode without naming a
47
+ * palette of its own, and the 6% tint of the inherited colour keeps the surface from reading as a hole
48
+ * punched in the page. The soft shadow says it sits above the page — not a border, which would be visual
49
+ * noise around a strip whose job is to be glanced at.
50
+ * @returns one `html` row's payload.
51
+ */
52
+ export function stripMarkup(): string {
53
+ return [
54
+ `<div id="${STRIP_ELEMENT_ID}"`,
55
+ ' style="position:fixed;right:12px;bottom:12px;z-index:40;',
56
+ 'max-width:26rem;max-height:60vh;overflow:auto;padding:10px 12px;',
57
+ 'font:inherit;color:inherit;line-height:1.4;',
58
+ 'background:color-mix(in srgb, Canvas 94%, currentColor);',
59
+ 'box-shadow:0 4px 16px rgb(0 0 0 / 0.18);border-radius:10px">',
60
+ '</div>',
61
+ ].join('')
62
+ }
63
+
64
+ /**
65
+ * The bootstrap the page runs after the markup.
66
+ *
67
+ * It calls the bundle's own `mount`, and does nothing when the bundle has not been materialised yet — in
68
+ * which case the bundle mounts the panel itself when it applies, or this is called again on
69
+ * `DOMContentLoaded`. Nothing here decides anything about the panel; it is a doorbell.
70
+ * @returns one `script` row's payload.
71
+ */
72
+ export function stripBootstrap(): string {
73
+ return [
74
+ ';(function () {',
75
+ " function mount() { var G = globalThis.__dshRealtimeAudio; if (G && typeof G.mount === 'function') G.mount() }",
76
+ ' mount()',
77
+ " if (typeof document !== 'undefined' && document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount)",
78
+ '})()',
79
+ ].join('\n')
80
+ }
81
+
82
+ /**
83
+ * The two rows this plugin contributes for its panel.
84
+ *
85
+ * `body` placement for both: the strip belongs to the page's chrome rather than to its head, and a `body`
86
+ * row is rendered after the module queue's own `head` rows, which is the ordering the bundle's bootstrap
87
+ * expects.
88
+ *
89
+ * Neither payload may contain a closing script or style tag — the table's own contract, because the row is
90
+ * spliced into the document text — and a test asserts that for both, alongside executing the bootstrap.
91
+ * @returns the rows, in the order they must execute.
92
+ */
93
+ export function stripRows(): InjectedRow[] {
94
+ return [
95
+ { kind: 'html', placement: 'body', html: stripMarkup() },
96
+ { kind: 'script', placement: 'body', text: stripBootstrap() },
97
+ ]
98
+ }
package/src/types.ts CHANGED
@@ -67,6 +67,14 @@ export interface RealtimeAudioWsConfig {
67
67
  * more to a stranger than an empty socket.
68
68
  */
69
69
  readonly diagnosticsPath: string
70
+ /**
71
+ * File the journal is appended to, one JSON object per line. Empty means no file.
72
+ *
73
+ * Off by default, and named by the deployment that wants a recording: the journal is held in memory and
74
+ * served over a route that wants a capability token, so a deployment that needs the record to outlive the
75
+ * process — or to be handed to somebody else — says so here.
76
+ */
77
+ readonly journalPath: string
70
78
  }
71
79
 
72
80
  /**
@@ -81,7 +89,7 @@ export interface RealtimeAudioWsConfig {
81
89
  * `Uint8Array`.
82
90
  */
83
91
  export interface AudioSocket {
84
- send(data: Uint8Array): void
92
+ send(data: Uint8Array | string): void
85
93
  close(code?: number, reason?: string): void
86
94
  terminate(): void
87
95
  on(event: 'message', listener: (data: unknown, isBinary: boolean) => void): unknown