@vincentt-xr/harness 1.4.0 → 1.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/dist/client/channel.d.ts +33 -3
- package/dist/client/channel.js +60 -9
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/shared/channel.d.ts +43 -4
- package/dist/shared/channel.js +28 -3
- package/package.json +1 -1
package/dist/client/channel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnnouncedPreset, type ConsoleSetMediaSourceMessage, type HarnessAnnounceMessage } from "../shared/channel.js";
|
|
1
|
+
import { type AnnouncedPreset, type ConsoleSetMediaSourceMessage, type ConsoleSetRenderHoldMessage, type HarnessAnnounceMessage } from "../shared/channel.js";
|
|
2
2
|
import { type FramingView } from "./previewOrigin.js";
|
|
3
3
|
/** The minimum of `window` this needs, so the whole module is testable without a DOM. */
|
|
4
4
|
export interface ChannelWindow {
|
|
@@ -13,6 +13,16 @@ export interface ChannelOptions {
|
|
|
13
13
|
presets: AnnouncedPreset[];
|
|
14
14
|
/** Applied when the console names a preset. The app owns the swap; the console never does. */
|
|
15
15
|
onSetMediaSource: (presetId: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Called when the console asks the app to freeze or resume its own render.
|
|
18
|
+
*
|
|
19
|
+
* SUPPLYING THIS IS WHAT DECLARES THE CAPABILITY — see `buildAnnounce`. The
|
|
20
|
+
* harness holds no opinion about how the app holds; it hands over `held` and
|
|
21
|
+
* the app decides what stops. Omit it and the app declares nothing and the
|
|
22
|
+
* console renders no control, which is the correct state for an app that
|
|
23
|
+
* cannot hold.
|
|
24
|
+
*/
|
|
25
|
+
onSetRenderHold?: (held: boolean) => void;
|
|
16
26
|
/** The console origins commands may come from. Injected; defaults to the production set. */
|
|
17
27
|
allowedOrigins?: readonly string[];
|
|
18
28
|
/** Injectable for tests. Defaults to `window`. */
|
|
@@ -21,12 +31,23 @@ export interface ChannelOptions {
|
|
|
21
31
|
framing?: FramingView;
|
|
22
32
|
}
|
|
23
33
|
/**
|
|
24
|
-
* The announce, built from a preset list.
|
|
34
|
+
* The announce, built from a preset list and the app's WIRED handlers.
|
|
25
35
|
*
|
|
26
36
|
* Exported so a test can assert the payload's exact shape without a window, and
|
|
27
37
|
* so `url` never being on the wire is checkable at the boundary that produces it.
|
|
38
|
+
*
|
|
39
|
+
* THE CAPABILITY IS DERIVED, NEVER DECLARED. The second parameter is the handler
|
|
40
|
+
* itself, not a capability list, so there is no way to spell a declaration the
|
|
41
|
+
* app cannot honour: `render-hold` appears exactly when `onSetRenderHold` was
|
|
42
|
+
* supplied. An app that declared it without implementing it would render a live
|
|
43
|
+
* button that does nothing, and the console cannot detect that — there is no ack,
|
|
44
|
+
* by design — so this boundary is the only place it is preventable.
|
|
45
|
+
*
|
|
46
|
+
* The key is OMITTED rather than set to `[]` when nothing is wired: an old
|
|
47
|
+
* harness omits it, and emitting an empty array would hand the console a second
|
|
48
|
+
* shape meaning the same thing.
|
|
28
49
|
*/
|
|
29
|
-
export declare function buildAnnounce(presets: AnnouncedPreset[]): HarnessAnnounceMessage;
|
|
50
|
+
export declare function buildAnnounce(presets: AnnouncedPreset[], handlers?: Pick<ChannelOptions, "onSetRenderHold">): HarnessAnnounceMessage;
|
|
30
51
|
/**
|
|
31
52
|
* Is this a command this app acts on?
|
|
32
53
|
*
|
|
@@ -36,6 +57,15 @@ export declare function buildAnnounce(presets: AnnouncedPreset[]): HarnessAnnoun
|
|
|
36
57
|
* by a console that decides to send one.
|
|
37
58
|
*/
|
|
38
59
|
export declare function isSetMediaSourceCommand(data: unknown): data is ConsoleSetMediaSourceMessage;
|
|
60
|
+
/**
|
|
61
|
+
* Is this a hold command this app acts on?
|
|
62
|
+
*
|
|
63
|
+
* Shape check only, mirroring `isSetMediaSourceCommand` — the origin and the
|
|
64
|
+
* source are checked separately and ALL must pass. `held` is required to be a
|
|
65
|
+
* real boolean rather than coerced: a truthy string arriving as `held` would
|
|
66
|
+
* freeze an app that could never be told to resume by the same mistake.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isSetRenderHoldCommand(data: unknown): data is ConsoleSetRenderHoldMessage;
|
|
39
69
|
/**
|
|
40
70
|
* Open the app's side of the channel. Returns a teardown.
|
|
41
71
|
*
|
package/dist/client/channel.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// The app side of the console↔app channel.
|
|
2
2
|
//
|
|
3
|
-
// It does two things and refuses everything else: it announces
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// It does two things and refuses everything else: it announces ONCE on mount,
|
|
4
|
+
// and it listens for the console's commands. It never acknowledges, never
|
|
5
|
+
// reports an outcome, never re-announces, never counts, and never requests
|
|
6
6
|
// (B-F13-3). The transport is bidirectional — postMessage has no one-way mode —
|
|
7
7
|
// so the vocabulary in `shared/channel.ts` and the single-send guard below are
|
|
8
8
|
// the only cap there is.
|
|
9
|
+
//
|
|
10
|
+
// It carries the hold command and does not perform the hold. The harness has
|
|
11
|
+
// ZERO coupling to r3f/three, and that absence is load-bearing rather than
|
|
12
|
+
// incidental: it is what lets the harness be transport with no opinion about the
|
|
13
|
+
// app. The app freezes itself in its own render tree.
|
|
9
14
|
import { ANNOUNCE_MAX_PRESETS, CHANNEL_PROTOCOL_VERSION, CONSOLE_MESSAGE_SOURCE, HARNESS_MESSAGE_SOURCE, PRESET_ID_RE, PRESET_KINDS, PRESET_MAX_ID_LENGTH, PRESET_MAX_LABEL_LENGTH, } from "../shared/channel.js";
|
|
10
15
|
import { isAllowedConsoleOrigin, PRODUCTION_CONSOLE_ORIGINS } from "./consoleOrigin.js";
|
|
11
16
|
import { isFramed } from "./previewOrigin.js";
|
|
@@ -39,14 +44,25 @@ function isValidPreset(preset) {
|
|
|
39
44
|
return true;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
|
-
* The announce, built from a preset list.
|
|
47
|
+
* The announce, built from a preset list and the app's WIRED handlers.
|
|
43
48
|
*
|
|
44
49
|
* Exported so a test can assert the payload's exact shape without a window, and
|
|
45
50
|
* so `url` never being on the wire is checkable at the boundary that produces it.
|
|
51
|
+
*
|
|
52
|
+
* THE CAPABILITY IS DERIVED, NEVER DECLARED. The second parameter is the handler
|
|
53
|
+
* itself, not a capability list, so there is no way to spell a declaration the
|
|
54
|
+
* app cannot honour: `render-hold` appears exactly when `onSetRenderHold` was
|
|
55
|
+
* supplied. An app that declared it without implementing it would render a live
|
|
56
|
+
* button that does nothing, and the console cannot detect that — there is no ack,
|
|
57
|
+
* by design — so this boundary is the only place it is preventable.
|
|
58
|
+
*
|
|
59
|
+
* The key is OMITTED rather than set to `[]` when nothing is wired: an old
|
|
60
|
+
* harness omits it, and emitting an empty array would hand the console a second
|
|
61
|
+
* shape meaning the same thing.
|
|
46
62
|
*/
|
|
47
|
-
export function buildAnnounce(presets) {
|
|
63
|
+
export function buildAnnounce(presets, handlers = {}) {
|
|
48
64
|
const valid = presets.filter(isValidPreset).slice(0, ANNOUNCE_MAX_PRESETS);
|
|
49
|
-
|
|
65
|
+
const announce = {
|
|
50
66
|
source: HARNESS_MESSAGE_SOURCE,
|
|
51
67
|
v: CHANNEL_PROTOCOL_VERSION,
|
|
52
68
|
type: "announce",
|
|
@@ -57,6 +73,9 @@ export function buildAnnounce(presets) {
|
|
|
57
73
|
mirrored: p.mirrored,
|
|
58
74
|
})),
|
|
59
75
|
};
|
|
76
|
+
if (handlers.onSetRenderHold)
|
|
77
|
+
announce.capabilities = ["render-hold"];
|
|
78
|
+
return announce;
|
|
60
79
|
}
|
|
61
80
|
/**
|
|
62
81
|
* Is this a command this app acts on?
|
|
@@ -80,6 +99,26 @@ export function isSetMediaSourceCommand(data) {
|
|
|
80
99
|
return false;
|
|
81
100
|
return PRESET_ID_RE.test(m.presetId);
|
|
82
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Is this a hold command this app acts on?
|
|
104
|
+
*
|
|
105
|
+
* Shape check only, mirroring `isSetMediaSourceCommand` — the origin and the
|
|
106
|
+
* source are checked separately and ALL must pass. `held` is required to be a
|
|
107
|
+
* real boolean rather than coerced: a truthy string arriving as `held` would
|
|
108
|
+
* freeze an app that could never be told to resume by the same mistake.
|
|
109
|
+
*/
|
|
110
|
+
export function isSetRenderHoldCommand(data) {
|
|
111
|
+
if (typeof data !== "object" || data === null)
|
|
112
|
+
return false;
|
|
113
|
+
const m = data;
|
|
114
|
+
if (m.source !== CONSOLE_MESSAGE_SOURCE)
|
|
115
|
+
return false;
|
|
116
|
+
if (m.v !== CHANNEL_PROTOCOL_VERSION)
|
|
117
|
+
return false;
|
|
118
|
+
if (m.type !== "set-render-hold")
|
|
119
|
+
return false;
|
|
120
|
+
return typeof m.held === "boolean";
|
|
121
|
+
}
|
|
83
122
|
/**
|
|
84
123
|
* Open the app's side of the channel. Returns a teardown.
|
|
85
124
|
*
|
|
@@ -107,9 +146,21 @@ export function openConsoleChannel(opts) {
|
|
|
107
146
|
// about — and no test could see it, because none delivered a null source.
|
|
108
147
|
if (event.source !== view.parent)
|
|
109
148
|
return;
|
|
110
|
-
|
|
149
|
+
// Both commands clear the IDENTICAL two checks above before any dispatch.
|
|
150
|
+
// Neither name is privileged and neither has its own gate — a second command
|
|
151
|
+
// is one more branch inside a parser that already validates by exact match
|
|
152
|
+
// against a closed set, which is the whole reason growing the vocabulary is
|
|
153
|
+
// cheap and growing the CHECKS would not be.
|
|
154
|
+
if (isSetMediaSourceCommand(event.data)) {
|
|
155
|
+
opts.onSetMediaSource(event.data.presetId);
|
|
111
156
|
return;
|
|
112
|
-
|
|
157
|
+
}
|
|
158
|
+
if (isSetRenderHoldCommand(event.data)) {
|
|
159
|
+
// An app with no handler ignores it in silence. It never declared the
|
|
160
|
+
// capability, so a console sending this is speaking to the wrong app —
|
|
161
|
+
// and there is no channel to say so on.
|
|
162
|
+
opts.onSetRenderHold?.(event.data.held);
|
|
163
|
+
}
|
|
113
164
|
};
|
|
114
165
|
view.addEventListener("message", onMessage);
|
|
115
166
|
// ONE MESSAGE, ONCE. Posted to each allowed console origin explicitly — never
|
|
@@ -119,7 +170,7 @@ export function openConsoleChannel(opts) {
|
|
|
119
170
|
// There is deliberately no re-announce, no retry and no interval: a console
|
|
120
171
|
// that mounts after the app misses it and stays in its waiting state, which is
|
|
121
172
|
// the honest failure the design chose over a channel that keeps talking.
|
|
122
|
-
const announce = buildAnnounce(opts.presets);
|
|
173
|
+
const announce = buildAnnounce(opts.presets, opts);
|
|
123
174
|
for (const origin of allowed) {
|
|
124
175
|
try {
|
|
125
176
|
view.parent?.postMessage(announce, origin);
|
package/dist/client/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export type { HarnessProviderProps } from "./HarnessProvider.js";
|
|
|
3
3
|
export { sendAnnotation, captureScreenshot, mountFeedbackButton, shouldShowFeedback, type SendAnnotationOptions, type FeedbackButtonOptions, } from "./annotate.js";
|
|
4
4
|
export { mountShareButton, deriveShareUrl, isPhone, isPreviewOrigin, renderQr, PREVIEW_APEXES, type ShareButtonOptions, } from "./share.js";
|
|
5
5
|
export { isFramed, type FramingView } from "./previewOrigin.js";
|
|
6
|
-
export { openConsoleChannel, buildAnnounce, isSetMediaSourceCommand, type ChannelOptions, type ChannelWindow, } from "./channel.js";
|
|
6
|
+
export { openConsoleChannel, buildAnnounce, isSetMediaSourceCommand, isSetRenderHoldCommand, type ChannelOptions, type ChannelWindow, } from "./channel.js";
|
|
7
7
|
export { isAllowedConsoleOrigin, PRODUCTION_CONSOLE_ORIGINS } from "./consoleOrigin.js";
|
|
8
|
-
export { CHANNEL_MESSAGE_TYPES, HARNESS_OUTBOUND_MESSAGE_TYPES, CONSOLE_INBOUND_MESSAGE_TYPES, HARNESS_MESSAGE_SOURCE, CONSOLE_MESSAGE_SOURCE, CHANNEL_PROTOCOL_VERSION, PRESET_KINDS, ANNOUNCE_MAX_PRESETS, type AnnouncedPreset, type PresetKind, type HarnessAnnounceMessage, type ConsoleSetMediaSourceMessage, } from "../shared/channel.js";
|
|
8
|
+
export { CHANNEL_MESSAGE_TYPES, HARNESS_OUTBOUND_MESSAGE_TYPES, CONSOLE_INBOUND_MESSAGE_TYPES, HARNESS_MESSAGE_SOURCE, CONSOLE_MESSAGE_SOURCE, CHANNEL_PROTOCOL_VERSION, PRESET_KINDS, APP_CAPABILITIES, ANNOUNCE_MAX_PRESETS, ANNOUNCE_MAX_CAPABILITIES, type AnnouncedPreset, type PresetKind, type AppCapability, type HarnessAnnounceMessage, type ConsoleSetMediaSourceMessage, type ConsoleSetRenderHoldMessage, } from "../shared/channel.js";
|
|
9
9
|
export { getClusterContainer, releaseClusterContainer } from "./cluster.js";
|
|
10
10
|
export type { DiagEvent, LogEvent, NetworkEvent, TraceEvent } from "../shared/events.js";
|
|
11
11
|
export type { Annotation, AnnotationInput, AnnotationSpec, AnnotationStroke, AnnotationPin, } from "../shared/events.js";
|
package/dist/client/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export { HarnessProvider } from "./HarnessProvider.js";
|
|
|
6
6
|
export { sendAnnotation, captureScreenshot, mountFeedbackButton, shouldShowFeedback, } from "./annotate.js";
|
|
7
7
|
export { mountShareButton, deriveShareUrl, isPhone, isPreviewOrigin, renderQr, PREVIEW_APEXES, } from "./share.js";
|
|
8
8
|
export { isFramed } from "./previewOrigin.js";
|
|
9
|
-
export { openConsoleChannel, buildAnnounce, isSetMediaSourceCommand, } from "./channel.js";
|
|
9
|
+
export { openConsoleChannel, buildAnnounce, isSetMediaSourceCommand, isSetRenderHoldCommand, } from "./channel.js";
|
|
10
10
|
export { isAllowedConsoleOrigin, PRODUCTION_CONSOLE_ORIGINS } from "./consoleOrigin.js";
|
|
11
|
-
export { CHANNEL_MESSAGE_TYPES, HARNESS_OUTBOUND_MESSAGE_TYPES, CONSOLE_INBOUND_MESSAGE_TYPES, HARNESS_MESSAGE_SOURCE, CONSOLE_MESSAGE_SOURCE, CHANNEL_PROTOCOL_VERSION, PRESET_KINDS, ANNOUNCE_MAX_PRESETS, } from "../shared/channel.js";
|
|
11
|
+
export { CHANNEL_MESSAGE_TYPES, HARNESS_OUTBOUND_MESSAGE_TYPES, CONSOLE_INBOUND_MESSAGE_TYPES, HARNESS_MESSAGE_SOURCE, CONSOLE_MESSAGE_SOURCE, CHANNEL_PROTOCOL_VERSION, PRESET_KINDS, APP_CAPABILITIES, ANNOUNCE_MAX_PRESETS, ANNOUNCE_MAX_CAPABILITIES, } from "../shared/channel.js";
|
|
12
12
|
export { getClusterContainer, releaseClusterContainer } from "./cluster.js";
|
package/dist/shared/channel.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** The app→console message names. Exactly one, and adding a second is the failure. */
|
|
2
2
|
export declare const HARNESS_OUTBOUND_MESSAGE_TYPES: readonly ["announce"];
|
|
3
|
-
/** The console→app message names. Exactly
|
|
4
|
-
export declare const CONSOLE_INBOUND_MESSAGE_TYPES: readonly ["set-media-source"];
|
|
3
|
+
/** The console→app message names. Exactly two. */
|
|
4
|
+
export declare const CONSOLE_INBOUND_MESSAGE_TYPES: readonly ["set-media-source", "set-render-hold"];
|
|
5
5
|
/** The whole channel vocabulary, both directions. */
|
|
6
|
-
export declare const CHANNEL_MESSAGE_TYPES: readonly ["announce", "set-media-source"];
|
|
6
|
+
export declare const CHANNEL_MESSAGE_TYPES: readonly ["announce", "set-media-source", "set-render-hold"];
|
|
7
7
|
export type HarnessOutboundMessageType = (typeof HARNESS_OUTBOUND_MESSAGE_TYPES)[number];
|
|
8
8
|
export type ConsoleInboundMessageType = (typeof CONSOLE_INBOUND_MESSAGE_TYPES)[number];
|
|
9
9
|
/** Envelope discriminator for app→console traffic. */
|
|
@@ -15,6 +15,18 @@ export declare const CHANNEL_PROTOCOL_VERSION = 1;
|
|
|
15
15
|
/** The media kinds a preset may declare. Closed — an unknown kind drops the announce. */
|
|
16
16
|
export declare const PRESET_KINDS: readonly ["video", "image", "camera"];
|
|
17
17
|
export type PresetKind = (typeof PRESET_KINDS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* What an app may declare it CAN DO. Closed and validated exactly as
|
|
20
|
+
* `PRESET_KINDS` is — an unknown entry is dropped rather than passed through.
|
|
21
|
+
*
|
|
22
|
+
* A capability is a property of the app's own code, never a report of its
|
|
23
|
+
* current condition: `render-hold` says *this app carries a hold handler*, not
|
|
24
|
+
* *this app is held*. The console renders a control on the strength of it and
|
|
25
|
+
* can never learn whether the app acted, so the declaration has to be derived
|
|
26
|
+
* from the wiring rather than written by hand — see `buildAnnounce`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const APP_CAPABILITIES: readonly ["render-hold"];
|
|
29
|
+
export type AppCapability = (typeof APP_CAPABILITIES)[number];
|
|
18
30
|
/**
|
|
19
31
|
* One announced preset — CAPABILITY, not observation.
|
|
20
32
|
*
|
|
@@ -37,14 +49,34 @@ export interface HarnessAnnounceMessage {
|
|
|
37
49
|
v: typeof CHANNEL_PROTOCOL_VERSION;
|
|
38
50
|
type: "announce";
|
|
39
51
|
presets: AnnouncedPreset[];
|
|
52
|
+
/**
|
|
53
|
+
* OPTIONAL, and the key is OMITTED when there is nothing to declare — never
|
|
54
|
+
* `[]`. An old harness omits it, so a console reading `undefined` cannot tell
|
|
55
|
+
* an old app from a new one that declares nothing, and must not be given two
|
|
56
|
+
* shapes to mean the same thing.
|
|
57
|
+
*/
|
|
58
|
+
capabilities?: readonly AppCapability[];
|
|
40
59
|
}
|
|
41
|
-
/** console → app.
|
|
60
|
+
/** console → app. Applies a named media preset. */
|
|
42
61
|
export interface ConsoleSetMediaSourceMessage {
|
|
43
62
|
source: typeof CONSOLE_MESSAGE_SOURCE;
|
|
44
63
|
v: typeof CHANNEL_PROTOCOL_VERSION;
|
|
45
64
|
type: "set-media-source";
|
|
46
65
|
presetId: string;
|
|
47
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* console → app. Freezes or resumes the app's own render loop.
|
|
69
|
+
*
|
|
70
|
+
* `held` is absolute, never a toggle: the console tracks only what it last sent
|
|
71
|
+
* and cannot read the app, so a toggle would drift the moment one message was
|
|
72
|
+
* dropped and neither side could tell.
|
|
73
|
+
*/
|
|
74
|
+
export interface ConsoleSetRenderHoldMessage {
|
|
75
|
+
source: typeof CONSOLE_MESSAGE_SOURCE;
|
|
76
|
+
v: typeof CHANNEL_PROTOCOL_VERSION;
|
|
77
|
+
type: "set-render-hold";
|
|
78
|
+
held: boolean;
|
|
79
|
+
}
|
|
48
80
|
/**
|
|
49
81
|
* Caps on an inbound announce, mirrored by the console's own validator.
|
|
50
82
|
*
|
|
@@ -56,5 +88,12 @@ export interface ConsoleSetMediaSourceMessage {
|
|
|
56
88
|
export declare const ANNOUNCE_MAX_PRESETS = 32;
|
|
57
89
|
export declare const PRESET_MAX_ID_LENGTH = 64;
|
|
58
90
|
export declare const PRESET_MAX_LABEL_LENGTH = 120;
|
|
91
|
+
/**
|
|
92
|
+
* The same class of cap as `ANNOUNCE_MAX_PRESETS`, and it exists for the same
|
|
93
|
+
* reason: the receiver must be able to refuse an oversized list BEFORE walking
|
|
94
|
+
* it, and both repos must refuse the identical payload. It is comfortably above
|
|
95
|
+
* `APP_CAPABILITIES.length` so growing the closed set does not require moving it.
|
|
96
|
+
*/
|
|
97
|
+
export declare const ANNOUNCE_MAX_CAPABILITIES = 8;
|
|
59
98
|
/** Conservative id grammar. Ids are ours; a label is creator-facing text, an id is not. */
|
|
60
99
|
export declare const PRESET_ID_RE: RegExp;
|
package/dist/shared/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The console↔app channel's wire contract. Pure data: no DOM, no React, no node.
|
|
2
2
|
//
|
|
3
|
-
// The console mirrors these
|
|
3
|
+
// The console mirrors these three names in its own `contract.ts`. They ship on
|
|
4
4
|
// DIFFERENT release cadences — @vincentt-xr/harness is caret-pinned and reaches
|
|
5
5
|
// old projects on a plain reinstall, the console deploys independently — so a
|
|
6
6
|
// rename in one lands in creators' apps weeks apart from the other, and the
|
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
// observation. postMessage has no one-way mode — both directions of the
|
|
14
14
|
// transport are open at all times — so the vocabulary is the only cap there is,
|
|
15
15
|
// and a vocabulary is a convention until a test asserts it.
|
|
16
|
+
//
|
|
17
|
+
// THE ONE-WAY LINE IS UNCHANGED BY THE SECOND COMMAND. Console→app grew to two
|
|
18
|
+
// names; app→console is still exactly one, still posted once on mount. The
|
|
19
|
+
// announce's `capabilities` field is the app declaring WHAT IT CAN DO — the same
|
|
20
|
+
// class of fact as its preset list, which B-F13-3 permits in terms — and never
|
|
21
|
+
// what it is doing. Capability crosses; observation does not. A second app→console
|
|
22
|
+
// name, or an ack for `set-render-hold`, would be the breach.
|
|
16
23
|
/** The app→console message names. Exactly one, and adding a second is the failure. */
|
|
17
24
|
export const HARNESS_OUTBOUND_MESSAGE_TYPES = ["announce"];
|
|
18
|
-
/** The console→app message names. Exactly
|
|
19
|
-
export const CONSOLE_INBOUND_MESSAGE_TYPES = ["set-media-source"];
|
|
25
|
+
/** The console→app message names. Exactly two. */
|
|
26
|
+
export const CONSOLE_INBOUND_MESSAGE_TYPES = ["set-media-source", "set-render-hold"];
|
|
20
27
|
/** The whole channel vocabulary, both directions. */
|
|
21
28
|
export const CHANNEL_MESSAGE_TYPES = [
|
|
22
29
|
...HARNESS_OUTBOUND_MESSAGE_TYPES,
|
|
@@ -30,6 +37,17 @@ export const CONSOLE_MESSAGE_SOURCE = "vincentt-console";
|
|
|
30
37
|
export const CHANNEL_PROTOCOL_VERSION = 1;
|
|
31
38
|
/** The media kinds a preset may declare. Closed — an unknown kind drops the announce. */
|
|
32
39
|
export const PRESET_KINDS = ["video", "image", "camera"];
|
|
40
|
+
/**
|
|
41
|
+
* What an app may declare it CAN DO. Closed and validated exactly as
|
|
42
|
+
* `PRESET_KINDS` is — an unknown entry is dropped rather than passed through.
|
|
43
|
+
*
|
|
44
|
+
* A capability is a property of the app's own code, never a report of its
|
|
45
|
+
* current condition: `render-hold` says *this app carries a hold handler*, not
|
|
46
|
+
* *this app is held*. The console renders a control on the strength of it and
|
|
47
|
+
* can never learn whether the app acted, so the declaration has to be derived
|
|
48
|
+
* from the wiring rather than written by hand — see `buildAnnounce`.
|
|
49
|
+
*/
|
|
50
|
+
export const APP_CAPABILITIES = ["render-hold"];
|
|
33
51
|
/**
|
|
34
52
|
* Caps on an inbound announce, mirrored by the console's own validator.
|
|
35
53
|
*
|
|
@@ -41,5 +59,12 @@ export const PRESET_KINDS = ["video", "image", "camera"];
|
|
|
41
59
|
export const ANNOUNCE_MAX_PRESETS = 32;
|
|
42
60
|
export const PRESET_MAX_ID_LENGTH = 64;
|
|
43
61
|
export const PRESET_MAX_LABEL_LENGTH = 120;
|
|
62
|
+
/**
|
|
63
|
+
* The same class of cap as `ANNOUNCE_MAX_PRESETS`, and it exists for the same
|
|
64
|
+
* reason: the receiver must be able to refuse an oversized list BEFORE walking
|
|
65
|
+
* it, and both repos must refuse the identical payload. It is comfortably above
|
|
66
|
+
* `APP_CAPABILITIES.length` so growing the closed set does not require moving it.
|
|
67
|
+
*/
|
|
68
|
+
export const ANNOUNCE_MAX_CAPABILITIES = 8;
|
|
44
69
|
/** Conservative id grammar. Ids are ours; a label is creator-facing text, an id is not. */
|
|
45
70
|
export const PRESET_ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|