@memberjunction/ng-realtime-channels 0.0.0 → 6.2.0-edge.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.
@@ -0,0 +1,412 @@
1
+ /**
2
+ * @fileoverview {@link AngularComponentChannel}: turns an EXISTING Angular component into a realtime channel surface
3
+ * without rewriting it.
4
+ *
5
+ * A channel is what an agent and a user both operate during a call: the agent acts through verbs, perceives through
6
+ * state notes (and, if allowed, pictures), and the user acts directly on the surface. Writing one from scratch means
7
+ * writing the whole contract (descriptor, perception, verbs, binding the surface, completion). This base class owns the
8
+ * mechanics so a channel over a component you already have is a descriptor plus a few binding functions:
9
+ *
10
+ * ```ts
11
+ * @RegisterClass(BaseRealtimeChannelClient, 'SudokuChannel')
12
+ * export class SudokuChannel extends AngularComponentChannel<SudokuComponent> {
13
+ * protected readonly ComponentClass = SudokuComponent;
14
+ * protected readonly Descriptor: RealtimeChannelDescriptor = { Key: 'Sudoku', ... };
15
+ * protected ReadSurfaceState(c: SudokuComponent): JSONObject { return { board: c.Board, moves: c.MoveCount }; }
16
+ * protected ApplySurfaceVerb(c: SudokuComponent, verb: string, args: JSONObject): RealtimeChannelVerbResult { ... }
17
+ * protected SurfaceEvents(c: SudokuComponent): Observable<ChannelSurfaceEvent> { return c.CellChanged.pipe(map(...)); }
18
+ * }
19
+ * export function LoadSudokuChannel(): void {}
20
+ * ```
21
+ *
22
+ * ## What the adapter does for you
23
+ *
24
+ * - **Binding.** The overlay creates the component and hands it to {@link BindSurface}; the adapter subscribes to
25
+ * {@link SurfaceEvents}, applies anything that arrived early, and lets go cleanly on {@link UnbindSurface} (a host may
26
+ * create a NEW component instance for the same channel, e.g. when the overlay is recreated, so durable state belongs
27
+ * to the channel, see below).
28
+ * - **Perception.** Every event the component emits, and every verb the agent runs, is recorded as a change: observers
29
+ * see a typed event, and the model gets ONE coalesced structured note carrying a delta of {@link ReadSurfaceState}
30
+ * (see `RecordChange` in the base class). Exposure policy gates it like any channel.
31
+ * - **Verbs.** Parameters are validated against the descriptor's schema before your code runs; a verb the agent may not
32
+ * have (exposure) is refused by the dispatcher; the outcome is a structured result the model can recover from.
33
+ * - **Completion.** An optional {@link SurfaceCompletion} stream becomes `Complete(output)`.
34
+ * - **Pictures.** If you can name the component's root element ({@link SurfaceElement}) and the host registered a
35
+ * rasterizer (`EnableChannelFrameCapture`), the channel shows the model what the component looks like.
36
+ * - **Persistence.** The last state is saved as the channel's state of record and handed back to
37
+ * {@link OnSurfaceBound} when the surface is created again (collapse/expand, or a resumed session).
38
+ *
39
+ * ## When there is no surface
40
+ *
41
+ * A verb can arrive while no component is bound (the panel was just opened and the overlay is still creating the
42
+ * component). The adapter does NOT run the verb against a hidden second instance, and it does NOT acknowledge it and apply
43
+ * it later: the agent needs the real outcome (a move can be refused), and a second instance would hold state the user is
44
+ * not looking at. Instead the call WAITS, bounded ({@link SurfaceBindTimeoutMs}, 5 s), for the surface, runs once it
45
+ * binds (calls are serialized, so order is preserved), and fails with `'surface_unavailable'` and a message the agent can
46
+ * act on ("ask the user to open it") if it never binds. Seed inputs of `open` are different: there is nothing to report back
47
+ * but success, so they are applied when the surface binds. (A channel with no surface at all, a pure wire, does not need this
48
+ * class.)
49
+ *
50
+ * ## A verb that never answers
51
+ *
52
+ * Verbs run one at a time, so one that never settles would stall every later call. Each verb therefore has a bounded execution
53
+ * time ({@link VerbTimeoutMs}, 15 s). On timeout the call fails with `'verb_timeout'` and a message the agent can act on ("try
54
+ * again or ask the user"), the queue is released, and the timeout is logged once with the channel and verb. The adapter cannot
55
+ * cancel the promise your {@link ApplySurfaceVerb} returned, so what happens next is deliberately defined:
56
+ *
57
+ * - A **late result is never delivered**: the agent was already told the verb failed, and it is not told otherwise.
58
+ * - A late **failure** is logged and dropped.
59
+ * - A late **success** means the component DID change after the agent was told it had not. Nothing is rolled back (the
60
+ * component's own rules decide whether a retry is accepted, which is why they must live in the component), but the change is
61
+ * recorded like any other so the next perception note shows the model the real state.
62
+ * - The next queued verb may run while the late one is still pending, so a verb should not assume exclusive access to the
63
+ * component for longer than it takes to settle.
64
+ *
65
+ * @module @memberjunction/ng-realtime-channels
66
+ */
67
+ import { ValidateJsonAgainstSchemaSubset } from '@memberjunction/ai-core-plus';
68
+ import { LogError } from '@memberjunction/core';
69
+ import { IsPlainObject } from '@memberjunction/global';
70
+ import { BaseRealtimeChannelClient } from '@memberjunction/realtime-runtime';
71
+ import { ChannelFrameCapture } from './channel-frame-capture';
72
+ /** How long a verb waits for the surface to bind before failing, in ms. */
73
+ const SURFACE_BIND_TIMEOUT_MS = 5000;
74
+ /** How long a verb may run before the call fails and the queue is released, in ms. */
75
+ const VERB_TIMEOUT_MS = 15000;
76
+ /** A failure result the dispatcher turns into a message the model can act on. */
77
+ function failure(code, error, details) {
78
+ return details ? { Success: false, ErrorCode: code, Error: error, Details: details } : { Success: false, ErrorCode: code, Error: error };
79
+ }
80
+ /**
81
+ * Base class for a realtime channel whose surface is an existing Angular component. See the file comment for the model.
82
+ *
83
+ * @typeParam TComponent The component class the channel shows and operates.
84
+ */
85
+ export class AngularComponentChannel extends BaseRealtimeChannelClient {
86
+ /** How long a verb waits for the surface to bind before failing with `'surface_unavailable'`, in ms. Override to change it. */
87
+ SurfaceBindTimeoutMs = SURFACE_BIND_TIMEOUT_MS;
88
+ /**
89
+ * How long {@link ApplySurfaceVerb} may run before the call fails with `'verb_timeout'` and the queue is released, in ms
90
+ * (15 s by default; the surface bind wait is separate, see {@link SurfaceBindTimeoutMs}). Override it for a component whose
91
+ * verbs are legitimately slow; a value that is not a positive number turns the limit off, which is only safe for a verb that
92
+ * is guaranteed to settle. See the file comment for what happens to a verb that settles after the timeout.
93
+ */
94
+ VerbTimeoutMs = VERB_TIMEOUT_MS;
95
+ // ── Identity, derived from the descriptor ──────────────────────────────────
96
+ get ChannelName() {
97
+ return this.Descriptor.Key;
98
+ }
99
+ get TabTitle() {
100
+ return this.Descriptor.DisplayName;
101
+ }
102
+ GetDescriptor() {
103
+ return this.Descriptor;
104
+ }
105
+ /** The channel has no native tools: its verbs are reached through the `ContextTool` proxy. */
106
+ GetToolDefinitions() {
107
+ return [];
108
+ }
109
+ GetSurfaceComponent() {
110
+ return this.ComponentClass;
111
+ }
112
+ // ── Surface binding ────────────────────────────────────────────────────────
113
+ surface = null;
114
+ surfaceSubscriptions = [];
115
+ bindWaiters = [];
116
+ lastState = null;
117
+ pendingOpenInputs = null;
118
+ verbChain = Promise.resolve();
119
+ applyingActor = null;
120
+ /** The bound component, or `null` while no surface is up. */
121
+ get Surface() {
122
+ return this.surface;
123
+ }
124
+ /**
125
+ * Opts into pictures when the subclass can name the component's element and the host registered a rasterizer. Subclasses
126
+ * that override this MUST call `super.OnInitialize()`.
127
+ */
128
+ OnInitialize() {
129
+ if (this.SurfaceElement && ChannelFrameCapture.Instance.Capturer) {
130
+ this.EnableVisualPerception({ GetLatestFrame: () => this.captureFrame() });
131
+ }
132
+ }
133
+ /**
134
+ * The overlay created the component: bind to it. Subscribes to its events and completion, restores the last known
135
+ * state, applies any seed inputs that arrived before it existed, and releases verbs that were waiting for it.
136
+ */
137
+ BindSurface(component) {
138
+ this.unbindSubscriptions();
139
+ this.surface = component;
140
+ this.OnSurfaceBound?.(component, this.lastState);
141
+ this.surfaceSubscriptions.push(this.SurfaceEvents(component).subscribe({
142
+ next: (event) => this.onSurfaceEvent(event),
143
+ error: (error) => LogError(`[RealtimeChannel:${this.ChannelName}] The surface's event stream failed: ${error instanceof Error ? error.message : String(error)}`),
144
+ }));
145
+ const completion = this.SurfaceCompletion?.(component);
146
+ if (completion) {
147
+ this.surfaceSubscriptions.push(completion.subscribe({
148
+ next: (output) => this.Complete(output),
149
+ error: (error) => LogError(`[RealtimeChannel:${this.ChannelName}] The surface's completion stream failed: ${error instanceof Error ? error.message : String(error)}`),
150
+ }));
151
+ }
152
+ void this.applyPendingOpen(component);
153
+ const waiters = this.bindWaiters;
154
+ this.bindWaiters = [];
155
+ for (const release of waiters) {
156
+ release(component);
157
+ }
158
+ }
159
+ /** The component is going away (the panel collapsed, or the session ended): keep its last state, let go of it. */
160
+ UnbindSurface() {
161
+ if (this.surface) {
162
+ this.lastState = this.safeReadState(this.surface) ?? this.lastState;
163
+ }
164
+ this.unbindSubscriptions();
165
+ this.surface = null;
166
+ }
167
+ unbindSubscriptions() {
168
+ for (const subscription of this.surfaceSubscriptions) {
169
+ subscription.unsubscribe();
170
+ }
171
+ this.surfaceSubscriptions = [];
172
+ }
173
+ /** Resolves with the bound component, waiting (bounded) for it to bind; `null` when it never does. */
174
+ whenSurfaceBound() {
175
+ if (this.surface) {
176
+ return Promise.resolve(this.surface);
177
+ }
178
+ return new Promise((resolve) => {
179
+ const release = (component) => {
180
+ clearTimeout(timer);
181
+ resolve(component);
182
+ };
183
+ const timer = setTimeout(() => {
184
+ this.bindWaiters = this.bindWaiters.filter((w) => w !== release);
185
+ resolve(null);
186
+ }, this.SurfaceBindTimeoutMs);
187
+ this.bindWaiters.push(release);
188
+ });
189
+ }
190
+ // ── State ──────────────────────────────────────────────────────────────────
191
+ /**
192
+ * The channel's state: the live component's, or the last known when no component is bound (so a note built while the
193
+ * panel is collapsed still describes the channel truthfully).
194
+ */
195
+ GetState() {
196
+ if (this.surface) {
197
+ const live = this.safeReadState(this.surface);
198
+ if (live) {
199
+ this.lastState = live;
200
+ return live;
201
+ }
202
+ }
203
+ return this.lastState ?? {};
204
+ }
205
+ /** Reads the component's state, logging and returning `null` if the subclass's reader throws (a perception note must never break a call). */
206
+ safeReadState(component) {
207
+ try {
208
+ return this.ReadSurfaceState(component);
209
+ }
210
+ catch (error) {
211
+ LogError(`[RealtimeChannel:${this.ChannelName}] ReadSurfaceState failed: ${error instanceof Error ? error.message : String(error)}`);
212
+ return null;
213
+ }
214
+ }
215
+ /** The state of record: the last known state as JSON, or `null` before there is any. */
216
+ SerializeState() {
217
+ const state = this.GetState();
218
+ return Object.keys(state).length > 0 ? JSON.stringify(state) : null;
219
+ }
220
+ /**
221
+ * A saved state came back (a resumed session): hold it, and hand it to {@link OnSurfaceBound} when the component is
222
+ * created. Returns `false` for anything that is not a JSON object.
223
+ */
224
+ RestoreState(stateJson) {
225
+ try {
226
+ const parsed = JSON.parse(stateJson);
227
+ if (!IsPlainObject(parsed)) {
228
+ return false;
229
+ }
230
+ this.lastState = parsed;
231
+ return true;
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ }
237
+ // ── Open ───────────────────────────────────────────────────────────────────
238
+ /**
239
+ * Seeds the surface with the open inputs: now if the component is bound, otherwise when it binds (there is nothing to
240
+ * report back but success, so there is nothing to wait for).
241
+ */
242
+ async OnOpen(inputs) {
243
+ if (!this.OnSurfaceOpen) {
244
+ return;
245
+ }
246
+ if (this.surface) {
247
+ return this.OnSurfaceOpen(this.surface, inputs);
248
+ }
249
+ this.pendingOpenInputs = inputs;
250
+ return;
251
+ }
252
+ async applyPendingOpen(component) {
253
+ const inputs = this.pendingOpenInputs;
254
+ this.pendingOpenInputs = null;
255
+ if (inputs === null || !this.OnSurfaceOpen) {
256
+ return;
257
+ }
258
+ try {
259
+ const result = await this.OnSurfaceOpen(component, inputs);
260
+ if (result && !result.Success) {
261
+ LogError(`[RealtimeChannel:${this.ChannelName}] Seeding the surface was refused: ${result.Error ?? result.ErrorCode ?? 'no reason given'}`);
262
+ }
263
+ }
264
+ catch (error) {
265
+ LogError(`[RealtimeChannel:${this.ChannelName}] Seeding the surface failed: ${error instanceof Error ? error.message : String(error)}`);
266
+ }
267
+ }
268
+ // ── Verbs ──────────────────────────────────────────────────────────────────
269
+ /**
270
+ * Runs one verb. Verbs are serialized (a second call waits for the first), wait (bounded) for the surface, and run
271
+ * against the real component, so the outcome the agent hears is the outcome the user sees.
272
+ */
273
+ ApplyVerb(verb, args, actor) {
274
+ const run = this.verbChain.then(() => this.runVerb(verb, args, actor));
275
+ this.verbChain = run.catch(() => undefined);
276
+ return run;
277
+ }
278
+ async runVerb(verb, args, actor) {
279
+ const declared = this.Descriptor.Verbs.find((v) => v.Name.toLowerCase() === verb.trim().toLowerCase());
280
+ if (!declared) {
281
+ return failure('unknown_verb', `The ${this.Descriptor.DisplayName} channel has no action "${verb}".`, [`available: ${this.Descriptor.Verbs.map((v) => v.Name).join(', ')}`]);
282
+ }
283
+ const refusal = actor === 'agent' ? this.RefuseVerbForExposure(declared) : null;
284
+ if (refusal) {
285
+ return failure('exposure_restricted', refusal, [...this.ExposureReasons]);
286
+ }
287
+ const issues = ValidateJsonAgainstSchemaSubset(args, declared.ParametersSchema);
288
+ if (issues.length > 0) {
289
+ return failure('invalid_params', `The parameters for ${declared.Name} are invalid.`, issues);
290
+ }
291
+ const component = await this.whenSurfaceBound();
292
+ if (!component) {
293
+ return failure('surface_unavailable', `The ${this.Descriptor.DisplayName} is not on screen right now, so "${declared.Name}" could not be done. Ask the user to open it.`);
294
+ }
295
+ return this.applyOnSurface(component, declared.Name, args, actor);
296
+ }
297
+ async applyOnSurface(component, verb, args, actor) {
298
+ this.applyingActor = actor;
299
+ try {
300
+ const outcome = await this.runWithTimeout(component, verb, args, actor);
301
+ if (outcome.TimedOut) {
302
+ return failure('verb_timeout', `The ${this.Descriptor.DisplayName} didn't respond to "${verb}" within ${Math.max(1, Math.round(this.VerbTimeoutMs / 1000))}s; try again or ask the user.`);
303
+ }
304
+ const result = outcome.Result;
305
+ if (result.Success) {
306
+ this.RecordChange({ Author: actor });
307
+ if (actor === 'agent') {
308
+ void this.ConfirmVisualChange();
309
+ }
310
+ }
311
+ return result;
312
+ }
313
+ catch (error) {
314
+ LogError(`[RealtimeChannel:${this.ChannelName}] ${verb} threw: ${error instanceof Error ? error.message : String(error)}`);
315
+ return failure('verb_failed', `${verb} failed: ${error instanceof Error ? error.message : String(error)}`);
316
+ }
317
+ finally {
318
+ this.applyingActor = null;
319
+ }
320
+ }
321
+ /**
322
+ * Runs {@link ApplySurfaceVerb} with a bounded execution time. Settles with the verb's result, with `TimedOut` when it did
323
+ * not answer in time, or rejects with what it threw. Once it has timed out, whatever it later does is handled by
324
+ * {@link onLateVerbSettle} and never reaches the caller: the promise is already settled, so there is no second result.
325
+ */
326
+ runWithTimeout(component, verb, args, actor) {
327
+ const call = Promise.resolve().then(() => this.ApplySurfaceVerb(component, verb, args, actor));
328
+ return new Promise((resolve, reject) => {
329
+ let timedOut = false;
330
+ const limited = Number.isFinite(this.VerbTimeoutMs) && this.VerbTimeoutMs > 0;
331
+ const timer = limited
332
+ ? setTimeout(() => {
333
+ timedOut = true;
334
+ LogError(`[RealtimeChannel:${this.ChannelName}] "${verb}" did not respond within ${this.VerbTimeoutMs} ms. The agent was told it failed and the queue was released; ` +
335
+ `if it completes later its effects are not rolled back.`);
336
+ resolve({ TimedOut: true });
337
+ }, this.VerbTimeoutMs)
338
+ : null;
339
+ call.then((result) => {
340
+ clearTimeout(timer ?? undefined);
341
+ if (timedOut) {
342
+ this.onLateVerbSettle(component, verb, actor, result, null);
343
+ }
344
+ else {
345
+ resolve({ TimedOut: false, Result: result });
346
+ }
347
+ }, (error) => {
348
+ clearTimeout(timer ?? undefined);
349
+ if (timedOut) {
350
+ this.onLateVerbSettle(component, verb, actor, null, error);
351
+ }
352
+ else {
353
+ reject(error);
354
+ }
355
+ });
356
+ });
357
+ }
358
+ /**
359
+ * A verb settled AFTER it timed out. The agent already has its answer (failure), so the late result is never delivered. A
360
+ * late failure is logged. A late success means the component changed after the agent was told it had not: it is recorded as
361
+ * a change (when the same component is still on screen) so the next perception note shows the model the real state.
362
+ */
363
+ onLateVerbSettle(component, verb, actor, result, error) {
364
+ if (error !== null && error !== undefined) {
365
+ LogError(`[RealtimeChannel:${this.ChannelName}] "${verb}" failed after it had timed out: ${error instanceof Error ? error.message : String(error)}`);
366
+ return;
367
+ }
368
+ if (result?.Success && this.surface === component) {
369
+ this.RecordChange({ Author: actor });
370
+ }
371
+ }
372
+ // ── Events ─────────────────────────────────────────────────────────────────
373
+ /** The component emitted something: tell observers, and (unless told not to) record it as a change the model perceives. */
374
+ onSurfaceEvent(event) {
375
+ const author = event.Author ?? this.applyingActor ?? 'user';
376
+ const changeId = this.RecordChange({ Author: author, Perceive: event.Perceive !== false });
377
+ this.EmitChannelEvent(event.Name, event.Payload ?? {}, changeId);
378
+ if (author === 'user') {
379
+ void this.NotifyVisualChange();
380
+ }
381
+ }
382
+ // ── Pictures ───────────────────────────────────────────────────────────────
383
+ /** A picture of the component, or `null` when there is none to send (see the rules in the Interactive Component channel). */
384
+ async captureFrame() {
385
+ const capturer = ChannelFrameCapture.Instance.Capturer;
386
+ if (!capturer || !this.surface || this.Exposure !== 'pixels' || !this.Context?.Client?.IsTrackEstablished('video', 'inbound')) {
387
+ return null;
388
+ }
389
+ const element = this.SurfaceElement?.(this.surface) ?? null;
390
+ if (!element) {
391
+ return null;
392
+ }
393
+ try {
394
+ return await capturer(element);
395
+ }
396
+ catch (error) {
397
+ LogError(`[RealtimeChannel:${this.ChannelName}] Capturing a frame failed: ${error instanceof Error ? error.message : String(error)}`);
398
+ return null;
399
+ }
400
+ }
401
+ // ── Teardown ───────────────────────────────────────────────────────────────
402
+ Dispose() {
403
+ this.unbindSubscriptions();
404
+ this.surface = null;
405
+ for (const release of this.bindWaiters) {
406
+ release(null);
407
+ }
408
+ this.bindWaiters = [];
409
+ super.Dispose();
410
+ }
411
+ }
412
+ //# sourceMappingURL=angular-component-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angular-component-channel.js","sourceRoot":"","sources":["../../src/lib/angular-component-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAKH,OAAO,EAAE,+BAA+B,EAA6D,MAAM,8BAA8B,CAAC;AAC1I,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,yBAAyB,EAAkC,MAAM,kCAAkC,CAAC;AAC7G,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAoB9D,2EAA2E;AAC3E,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAErC,sFAAsF;AACtF,MAAM,eAAe,GAAG,KAAK,CAAC;AAK9B,iFAAiF;AACjF,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa,EAAE,OAAkB;IAC5D,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7I,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAgB,uBAAmD,SAAQ,yBAAqC;IAkFlH,+HAA+H;IACrH,oBAAoB,GAAW,uBAAuB,CAAC;IAEjE;;;;;OAKG;IACO,aAAa,GAAW,eAAe,CAAC;IAElD,8EAA8E;IAE9E,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;IAC/B,CAAC;IAED,IAAoB,QAAQ;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;IACvC,CAAC;IAEe,aAAa;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED,8FAA8F;IAC9E,kBAAkB;QAC9B,OAAO,EAAE,CAAC;IACd,CAAC;IAEe,mBAAmB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC;IAC/B,CAAC;IAED,8EAA8E;IAEtE,OAAO,GAAsB,IAAI,CAAC;IAClC,oBAAoB,GAAmB,EAAE,CAAC;IAC1C,WAAW,GAAkD,EAAE,CAAC;IAChE,SAAS,GAAsB,IAAI,CAAC;IACpC,iBAAiB,GAAsB,IAAI,CAAC;IAC5C,SAAS,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAChD,aAAa,GAAgC,IAAI,CAAC;IAE1D,6DAA6D;IAC7D,IAAc,OAAO;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IAED;;;OAGG;IACgB,YAAY;QAC3B,IAAI,IAAI,CAAC,cAAc,IAAI,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC/D,IAAI,CAAC,sBAAsB,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC/E,CAAC;IACL,CAAC;IAED;;;OAGG;IACa,WAAW,CAAC,SAAqB;QAC7C,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,cAAc,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAC1B,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC;YACpC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;YAC3C,KAAK,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,wCAAwC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;SAC5K,CAAC,CACL,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAC1B,UAAU,CAAC,SAAS,CAAC;gBACjB,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACvC,KAAK,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,6CAA6C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;aACjL,CAAC,CACL,CAAC;QACN,CAAC;QACD,KAAK,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QACtB,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,SAAS,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAED,kHAAkH;IAClG,aAAa;QACzB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACxB,CAAC;IAEO,mBAAmB;QACvB,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnD,YAAY,CAAC,WAAW,EAAE,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,oBAAoB,GAAG,EAAE,CAAC;IACnC,CAAC;IAED,sGAAsG;IAC9F,gBAAgB;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,EAAE;YAC9C,MAAM,OAAO,GAAG,CAAC,SAA4B,EAAQ,EAAE;gBACnD,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,SAAS,CAAC,CAAC;YACvB,CAAC,CAAC;YACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC1B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC;gBACjE,OAAO,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC9B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC;IAED,8EAA8E;IAE9E;;;OAGG;IACa,QAAQ;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;gBACtB,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,6IAA6I;IACrI,aAAa,CAAC,SAAqB;QACvC,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,8BAA8B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACrI,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,wFAAwF;IACxE,cAAc;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,CAAC;IAED;;;OAGG;IACa,YAAY,CAAC,SAAiB;QAC1C,IAAI,CAAC;YACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC9C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,OAAO,KAAK,CAAC;YACjB,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,MAAoB,CAAC;YACtC,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E;;;OAGG;IACgB,KAAK,CAAC,MAAM,CAAC,MAAkB;QAC9C,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC;QAChC,OAAO;IACX,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,SAAqB;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC;QACtC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACzC,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC3D,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC5B,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,sCAAsC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,IAAI,iBAAiB,EAAE,CAAC,CAAC;YAChJ,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,iCAAiC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5I,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E;;;OAGG;IACa,SAAS,CAAC,IAAY,EAAE,IAAgB,EAAE,KAA2B;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5C,OAAO,GAAG,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAgB,EAAE,KAA2B;QAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QACvG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,OAAO,OAAO,CAAC,cAAc,EAAE,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,2BAA2B,IAAI,IAAI,EAAE,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACjL,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,OAAO,CAAC,qBAAqB,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,MAAM,GAAG,+BAA+B,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QAChF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,OAAO,OAAO,CAAC,gBAAgB,EAAE,sBAAsB,QAAQ,CAAC,IAAI,eAAe,EAAE,MAAM,CAAC,CAAC;QACjG,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAChD,IAAI,CAAC,SAAS,EAAE,CAAC;YACb,OAAO,OAAO,CACV,qBAAqB,EACrB,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,oCAAoC,QAAQ,CAAC,IAAI,+CAA+C,CACrI,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACtE,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,SAAqB,EAAE,IAAY,EAAE,IAAgB,EAAE,KAA2B;QAC3G,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACxE,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACnB,OAAO,OAAO,CACV,cAAc,EACd,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,uBAAuB,IAAI,YAAY,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,+BAA+B,CAC7J,CAAC;YACN,CAAC;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC9B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBACrC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;oBACpB,KAAK,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACpC,CAAC;YACL,CAAC;YACD,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,KAAK,IAAI,WAAW,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3H,OAAO,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/G,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC9B,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,SAAqB,EAAE,IAAY,EAAE,IAAgB,EAAE,KAA2B;QACrG,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/F,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAChD,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;YAC9E,MAAM,KAAK,GAAG,OAAO;gBACjB,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;oBACZ,QAAQ,GAAG,IAAI,CAAC;oBAChB,QAAQ,CACJ,oBAAoB,IAAI,CAAC,WAAW,MAAM,IAAI,4BAA4B,IAAI,CAAC,aAAa,gEAAgE;wBACxJ,wDAAwD,CAC/D,CAAC;oBACF,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChC,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC;gBACxB,CAAC,CAAC,IAAI,CAAC;YACX,IAAI,CAAC,IAAI,CACL,CAAC,MAAM,EAAE,EAAE;gBACP,YAAY,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;gBACjC,IAAI,QAAQ,EAAE,CAAC;oBACX,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;gBAChE,CAAC;qBAAM,CAAC;oBACJ,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;gBACjD,CAAC;YACL,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;gBACf,YAAY,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;gBACjC,IAAI,QAAQ,EAAE,CAAC;oBACX,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBAC/D,CAAC;qBAAM,CAAC;oBACJ,MAAM,CAAC,KAAK,CAAC,CAAC;gBAClB,CAAC;YACL,CAAC,CACJ,CAAC;QACN,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACK,gBAAgB,CAAC,SAAqB,EAAE,IAAY,EAAE,KAA2B,EAAE,MAAwC,EAAE,KAAc;QAC/I,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxC,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,MAAM,IAAI,oCAAoC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACrJ,OAAO;QACX,CAAC;QACD,IAAI,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChD,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E,2HAA2H;IACnH,cAAc,CAAC,KAA0B;QAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa,IAAI,MAAM,CAAC;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;QAC3F,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACpB,KAAK,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACnC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E,6HAA6H;IACrH,KAAK,CAAC,YAAY;QACtB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACvD,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;YAC5H,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,CAAC;YACD,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,oBAAoB,IAAI,CAAC,WAAW,+BAA+B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACtI,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9D,OAAO;QACnB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QACtB,KAAK,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC;CACJ","sourcesContent":["/**\n * @fileoverview {@link AngularComponentChannel}: turns an EXISTING Angular component into a realtime channel surface\n * without rewriting it.\n *\n * A channel is what an agent and a user both operate during a call: the agent acts through verbs, perceives through\n * state notes (and, if allowed, pictures), and the user acts directly on the surface. Writing one from scratch means\n * writing the whole contract (descriptor, perception, verbs, binding the surface, completion). This base class owns the\n * mechanics so a channel over a component you already have is a descriptor plus a few binding functions:\n *\n * ```ts\n * @RegisterClass(BaseRealtimeChannelClient, 'SudokuChannel')\n * export class SudokuChannel extends AngularComponentChannel<SudokuComponent> {\n * protected readonly ComponentClass = SudokuComponent;\n * protected readonly Descriptor: RealtimeChannelDescriptor = { Key: 'Sudoku', ... };\n * protected ReadSurfaceState(c: SudokuComponent): JSONObject { return { board: c.Board, moves: c.MoveCount }; }\n * protected ApplySurfaceVerb(c: SudokuComponent, verb: string, args: JSONObject): RealtimeChannelVerbResult { ... }\n * protected SurfaceEvents(c: SudokuComponent): Observable<ChannelSurfaceEvent> { return c.CellChanged.pipe(map(...)); }\n * }\n * export function LoadSudokuChannel(): void {}\n * ```\n *\n * ## What the adapter does for you\n *\n * - **Binding.** The overlay creates the component and hands it to {@link BindSurface}; the adapter subscribes to\n * {@link SurfaceEvents}, applies anything that arrived early, and lets go cleanly on {@link UnbindSurface} (a host may\n * create a NEW component instance for the same channel, e.g. when the overlay is recreated, so durable state belongs\n * to the channel, see below).\n * - **Perception.** Every event the component emits, and every verb the agent runs, is recorded as a change: observers\n * see a typed event, and the model gets ONE coalesced structured note carrying a delta of {@link ReadSurfaceState}\n * (see `RecordChange` in the base class). Exposure policy gates it like any channel.\n * - **Verbs.** Parameters are validated against the descriptor's schema before your code runs; a verb the agent may not\n * have (exposure) is refused by the dispatcher; the outcome is a structured result the model can recover from.\n * - **Completion.** An optional {@link SurfaceCompletion} stream becomes `Complete(output)`.\n * - **Pictures.** If you can name the component's root element ({@link SurfaceElement}) and the host registered a\n * rasterizer (`EnableChannelFrameCapture`), the channel shows the model what the component looks like.\n * - **Persistence.** The last state is saved as the channel's state of record and handed back to\n * {@link OnSurfaceBound} when the surface is created again (collapse/expand, or a resumed session).\n *\n * ## When there is no surface\n *\n * A verb can arrive while no component is bound (the panel was just opened and the overlay is still creating the\n * component). The adapter does NOT run the verb against a hidden second instance, and it does NOT acknowledge it and apply\n * it later: the agent needs the real outcome (a move can be refused), and a second instance would hold state the user is\n * not looking at. Instead the call WAITS, bounded ({@link SurfaceBindTimeoutMs}, 5 s), for the surface, runs once it\n * binds (calls are serialized, so order is preserved), and fails with `'surface_unavailable'` and a message the agent can\n * act on (\"ask the user to open it\") if it never binds. Seed inputs of `open` are different: there is nothing to report back\n * but success, so they are applied when the surface binds. (A channel with no surface at all, a pure wire, does not need this\n * class.)\n *\n * ## A verb that never answers\n *\n * Verbs run one at a time, so one that never settles would stall every later call. Each verb therefore has a bounded execution\n * time ({@link VerbTimeoutMs}, 15 s). On timeout the call fails with `'verb_timeout'` and a message the agent can act on (\"try\n * again or ask the user\"), the queue is released, and the timeout is logged once with the channel and verb. The adapter cannot\n * cancel the promise your {@link ApplySurfaceVerb} returned, so what happens next is deliberately defined:\n *\n * - A **late result is never delivered**: the agent was already told the verb failed, and it is not told otherwise.\n * - A late **failure** is logged and dropped.\n * - A late **success** means the component DID change after the agent was told it had not. Nothing is rolled back (the\n * component's own rules decide whether a retry is accepted, which is why they must live in the component), but the change is\n * recorded like any other so the next perception note shows the model the real state.\n * - The next queued verb may run while the late one is still pending, so a verb should not assume exclusive access to the\n * component for longer than it takes to settle.\n *\n * @module @memberjunction/ng-realtime-channels\n */\n\nimport type { Type } from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport type { JSONObject } from '@memberjunction/ai';\nimport { ValidateJsonAgainstSchemaSubset, type RealtimeChannelActor, type RealtimeChannelDescriptor } from '@memberjunction/ai-core-plus';\nimport { LogError } from '@memberjunction/core';\nimport { IsPlainObject } from '@memberjunction/global';\nimport { BaseRealtimeChannelClient, type RealtimeChannelVerbResult } from '@memberjunction/realtime-runtime';\nimport { ChannelFrameCapture } from './channel-frame-capture';\n\n/**\n * Something the wrapped component did that the channel should know about: a user's move, a selection, a state\n * change worth telling the model about. Return these from {@link AngularComponentChannel.SurfaceEvents}.\n */\nexport interface ChannelSurfaceEvent {\n /** The event's name; declare it in the descriptor's `Events` so the agent knows it exists. */\n Name: string;\n /** The event's payload (JSON). */\n Payload?: JSONObject;\n /**\n * Who caused it. Default `'user'`, except that an event emitted while the adapter is running an agent's verb is\n * attributed to the agent (the component echoes the agent's own move through the same outputs the user's use).\n */\n Author?: RealtimeChannelActor | 'system';\n /** Set `false` for an event observers should see but the model should not be told about. Default `true`. */\n Perceive?: boolean;\n}\n\n/** How long a verb waits for the surface to bind before failing, in ms. */\nconst SURFACE_BIND_TIMEOUT_MS = 5000;\n\n/** How long a verb may run before the call fails and the queue is released, in ms. */\nconst VERB_TIMEOUT_MS = 15000;\n\n/** What a verb run produced: its result, or that it did not answer in time. */\ntype VerbOutcome = { TimedOut: false; Result: RealtimeChannelVerbResult } | { TimedOut: true };\n\n/** A failure result the dispatcher turns into a message the model can act on. */\nfunction failure(code: string, error: string, details?: string[]): RealtimeChannelVerbResult {\n return details ? { Success: false, ErrorCode: code, Error: error, Details: details } : { Success: false, ErrorCode: code, Error: error };\n}\n\n/**\n * Base class for a realtime channel whose surface is an existing Angular component. See the file comment for the model.\n *\n * @typeParam TComponent The component class the channel shows and operates.\n */\nexport abstract class AngularComponentChannel<TComponent extends object> extends BaseRealtimeChannelClient<TComponent> {\n // ── What a subclass declares ───────────────────────────────────────────────\n\n /** The existing component to show as the channel's surface. The overlay creates it; the adapter binds to it. */\n protected abstract readonly ComponentClass: Type<TComponent>;\n\n /**\n * The channel's self-description: what it is, what it holds (nouns), what it accepts (verbs with parameter schemas and\n * `InvokableBy`), what it emits (events), how it opens (inputs) and what it hands back (output). Pure data, and the only\n * thing an agent has to go on, so write `Instructions` and every description for the MODEL.\n */\n protected abstract readonly Descriptor: RealtimeChannelDescriptor;\n\n /**\n * The channel's state as the model should perceive it: one entry per noun in the descriptor, JSON only. Called whenever\n * a perception note is built, so it must be a pure, cheap read of the component.\n *\n * @param component The bound component.\n */\n protected abstract ReadSurfaceState(component: TComponent): JSONObject;\n\n /**\n * Runs one verb on the component. The adapter has already resolved the verb against the descriptor and validated\n * `args` against its schema, so trust the shape; check the GAME or DOCUMENT rules (is it the agent's turn? is the cell\n * free?) and return a failure result with a message the model can act on rather than throwing.\n *\n * @param component The bound component.\n * @param verb The verb's name, as declared in the descriptor.\n * @param args The validated parameters.\n * @param actor Who is acting: the agent, or the user acting through the same verb.\n */\n protected abstract ApplySurfaceVerb(\n component: TComponent,\n verb: string,\n args: JSONObject,\n actor: RealtimeChannelActor\n ): RealtimeChannelVerbResult | Promise<RealtimeChannelVerbResult>;\n\n /**\n * What the component emits that the channel should react to, as a stream. Subscribed when the surface binds and\n * unsubscribed when it unbinds. Merge the component's outputs here; map each to a {@link ChannelSurfaceEvent}.\n *\n * @param component The bound component.\n */\n protected abstract SurfaceEvents(component: TComponent): Observable<ChannelSurfaceEvent>;\n\n /**\n * Applies the seed inputs of `open` to the component (optional). Return a failure to refuse the open. Called when the\n * surface is bound, immediately if it already is.\n *\n * @param component The bound component.\n * @param inputs The validated open inputs.\n */\n protected OnSurfaceOpen?(component: TComponent, inputs: JSONObject): void | RealtimeChannelVerbResult | Promise<void | RealtimeChannelVerbResult>;\n\n /**\n * Emits when the component's job is done and it has something to hand back (a finished game, a submitted form):\n * becomes `Complete(output)`, validated against the descriptor's `Output`. Optional.\n *\n * @param component The bound component.\n */\n protected SurfaceCompletion?(component: TComponent): Observable<JSONObject>;\n\n /**\n * Called each time a component is bound, with the last state the channel knew (this session's, or the saved one on a\n * resumed session), so a component created fresh (a panel expanded again, a session resumed) can be restored to it.\n * Optional; a component that keeps its own state elsewhere does not need it.\n *\n * @param component The newly bound component.\n * @param lastState The last known state, or `null` when there is none.\n */\n protected OnSurfaceBound?(component: TComponent, lastState: JSONObject | null): void;\n\n /**\n * The component's root element, so the channel can show the model a picture of it. Optional: without it (or without a\n * rasterizer registered by the host) the channel offers state only. A component can expose its host element by\n * injecting `ElementRef`.\n *\n * @param component The bound component.\n */\n protected SurfaceElement?(component: TComponent): HTMLElement | null;\n\n /** How long a verb waits for the surface to bind before failing with `'surface_unavailable'`, in ms. Override to change it. */\n protected SurfaceBindTimeoutMs: number = SURFACE_BIND_TIMEOUT_MS;\n\n /**\n * How long {@link ApplySurfaceVerb} may run before the call fails with `'verb_timeout'` and the queue is released, in ms\n * (15 s by default; the surface bind wait is separate, see {@link SurfaceBindTimeoutMs}). Override it for a component whose\n * verbs are legitimately slow; a value that is not a positive number turns the limit off, which is only safe for a verb that\n * is guaranteed to settle. See the file comment for what happens to a verb that settles after the timeout.\n */\n protected VerbTimeoutMs: number = VERB_TIMEOUT_MS;\n\n // ── Identity, derived from the descriptor ──────────────────────────────────\n\n public get ChannelName(): string {\n return this.Descriptor.Key;\n }\n\n public override get TabTitle(): string {\n return this.Descriptor.DisplayName;\n }\n\n public override GetDescriptor(): RealtimeChannelDescriptor {\n return this.Descriptor;\n }\n\n /** The channel has no native tools: its verbs are reached through the `ContextTool` proxy. */\n public override GetToolDefinitions(): never[] {\n return [];\n }\n\n public override GetSurfaceComponent(): Type<TComponent> {\n return this.ComponentClass;\n }\n\n // ── Surface binding ────────────────────────────────────────────────────────\n\n private surface: TComponent | null = null;\n private surfaceSubscriptions: Subscription[] = [];\n private bindWaiters: Array<(component: TComponent | null) => void> = [];\n private lastState: JSONObject | null = null;\n private pendingOpenInputs: JSONObject | null = null;\n private verbChain: Promise<unknown> = Promise.resolve();\n private applyingActor: RealtimeChannelActor | null = null;\n\n /** The bound component, or `null` while no surface is up. */\n protected get Surface(): TComponent | null {\n return this.surface;\n }\n\n /**\n * Opts into pictures when the subclass can name the component's element and the host registered a rasterizer. Subclasses\n * that override this MUST call `super.OnInitialize()`.\n */\n protected override OnInitialize(): void {\n if (this.SurfaceElement && ChannelFrameCapture.Instance.Capturer) {\n this.EnableVisualPerception({ GetLatestFrame: () => this.captureFrame() });\n }\n }\n\n /**\n * The overlay created the component: bind to it. Subscribes to its events and completion, restores the last known\n * state, applies any seed inputs that arrived before it existed, and releases verbs that were waiting for it.\n */\n public override BindSurface(component: TComponent): void {\n this.unbindSubscriptions();\n this.surface = component;\n this.OnSurfaceBound?.(component, this.lastState);\n this.surfaceSubscriptions.push(\n this.SurfaceEvents(component).subscribe({\n next: (event) => this.onSurfaceEvent(event),\n error: (error: unknown) => LogError(`[RealtimeChannel:${this.ChannelName}] The surface's event stream failed: ${error instanceof Error ? error.message : String(error)}`),\n })\n );\n const completion = this.SurfaceCompletion?.(component);\n if (completion) {\n this.surfaceSubscriptions.push(\n completion.subscribe({\n next: (output) => this.Complete(output),\n error: (error: unknown) => LogError(`[RealtimeChannel:${this.ChannelName}] The surface's completion stream failed: ${error instanceof Error ? error.message : String(error)}`),\n })\n );\n }\n void this.applyPendingOpen(component);\n const waiters = this.bindWaiters;\n this.bindWaiters = [];\n for (const release of waiters) {\n release(component);\n }\n }\n\n /** The component is going away (the panel collapsed, or the session ended): keep its last state, let go of it. */\n public override UnbindSurface(): void {\n if (this.surface) {\n this.lastState = this.safeReadState(this.surface) ?? this.lastState;\n }\n this.unbindSubscriptions();\n this.surface = null;\n }\n\n private unbindSubscriptions(): void {\n for (const subscription of this.surfaceSubscriptions) {\n subscription.unsubscribe();\n }\n this.surfaceSubscriptions = [];\n }\n\n /** Resolves with the bound component, waiting (bounded) for it to bind; `null` when it never does. */\n private whenSurfaceBound(): Promise<TComponent | null> {\n if (this.surface) {\n return Promise.resolve(this.surface);\n }\n return new Promise<TComponent | null>((resolve) => {\n const release = (component: TComponent | null): void => {\n clearTimeout(timer);\n resolve(component);\n };\n const timer = setTimeout(() => {\n this.bindWaiters = this.bindWaiters.filter((w) => w !== release);\n resolve(null);\n }, this.SurfaceBindTimeoutMs);\n this.bindWaiters.push(release);\n });\n }\n\n // ── State ──────────────────────────────────────────────────────────────────\n\n /**\n * The channel's state: the live component's, or the last known when no component is bound (so a note built while the\n * panel is collapsed still describes the channel truthfully).\n */\n public override GetState(): JSONObject {\n if (this.surface) {\n const live = this.safeReadState(this.surface);\n if (live) {\n this.lastState = live;\n return live;\n }\n }\n return this.lastState ?? {};\n }\n\n /** Reads the component's state, logging and returning `null` if the subclass's reader throws (a perception note must never break a call). */\n private safeReadState(component: TComponent): JSONObject | null {\n try {\n return this.ReadSurfaceState(component);\n } catch (error) {\n LogError(`[RealtimeChannel:${this.ChannelName}] ReadSurfaceState failed: ${error instanceof Error ? error.message : String(error)}`);\n return null;\n }\n }\n\n /** The state of record: the last known state as JSON, or `null` before there is any. */\n public override SerializeState(): string | null {\n const state = this.GetState();\n return Object.keys(state).length > 0 ? JSON.stringify(state) : null;\n }\n\n /**\n * A saved state came back (a resumed session): hold it, and hand it to {@link OnSurfaceBound} when the component is\n * created. Returns `false` for anything that is not a JSON object.\n */\n public override RestoreState(stateJson: string): boolean {\n try {\n const parsed: unknown = JSON.parse(stateJson);\n if (!IsPlainObject(parsed)) {\n return false;\n }\n this.lastState = parsed as JSONObject;\n return true;\n } catch {\n return false;\n }\n }\n\n // ── Open ───────────────────────────────────────────────────────────────────\n\n /**\n * Seeds the surface with the open inputs: now if the component is bound, otherwise when it binds (there is nothing to\n * report back but success, so there is nothing to wait for).\n */\n protected override async OnOpen(inputs: JSONObject): Promise<void | RealtimeChannelVerbResult> {\n if (!this.OnSurfaceOpen) {\n return;\n }\n if (this.surface) {\n return this.OnSurfaceOpen(this.surface, inputs);\n }\n this.pendingOpenInputs = inputs;\n return;\n }\n\n private async applyPendingOpen(component: TComponent): Promise<void> {\n const inputs = this.pendingOpenInputs;\n this.pendingOpenInputs = null;\n if (inputs === null || !this.OnSurfaceOpen) {\n return;\n }\n try {\n const result = await this.OnSurfaceOpen(component, inputs);\n if (result && !result.Success) {\n LogError(`[RealtimeChannel:${this.ChannelName}] Seeding the surface was refused: ${result.Error ?? result.ErrorCode ?? 'no reason given'}`);\n }\n } catch (error) {\n LogError(`[RealtimeChannel:${this.ChannelName}] Seeding the surface failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // ── Verbs ──────────────────────────────────────────────────────────────────\n\n /**\n * Runs one verb. Verbs are serialized (a second call waits for the first), wait (bounded) for the surface, and run\n * against the real component, so the outcome the agent hears is the outcome the user sees.\n */\n public override ApplyVerb(verb: string, args: JSONObject, actor: RealtimeChannelActor): Promise<RealtimeChannelVerbResult> {\n const run = this.verbChain.then(() => this.runVerb(verb, args, actor));\n this.verbChain = run.catch(() => undefined);\n return run;\n }\n\n private async runVerb(verb: string, args: JSONObject, actor: RealtimeChannelActor): Promise<RealtimeChannelVerbResult> {\n const declared = this.Descriptor.Verbs.find((v) => v.Name.toLowerCase() === verb.trim().toLowerCase());\n if (!declared) {\n return failure('unknown_verb', `The ${this.Descriptor.DisplayName} channel has no action \"${verb}\".`, [`available: ${this.Descriptor.Verbs.map((v) => v.Name).join(', ')}`]);\n }\n const refusal = actor === 'agent' ? this.RefuseVerbForExposure(declared) : null;\n if (refusal) {\n return failure('exposure_restricted', refusal, [...this.ExposureReasons]);\n }\n const issues = ValidateJsonAgainstSchemaSubset(args, declared.ParametersSchema);\n if (issues.length > 0) {\n return failure('invalid_params', `The parameters for ${declared.Name} are invalid.`, issues);\n }\n const component = await this.whenSurfaceBound();\n if (!component) {\n return failure(\n 'surface_unavailable',\n `The ${this.Descriptor.DisplayName} is not on screen right now, so \"${declared.Name}\" could not be done. Ask the user to open it.`\n );\n }\n return this.applyOnSurface(component, declared.Name, args, actor);\n }\n\n private async applyOnSurface(component: TComponent, verb: string, args: JSONObject, actor: RealtimeChannelActor): Promise<RealtimeChannelVerbResult> {\n this.applyingActor = actor;\n try {\n const outcome = await this.runWithTimeout(component, verb, args, actor);\n if (outcome.TimedOut) {\n return failure(\n 'verb_timeout',\n `The ${this.Descriptor.DisplayName} didn't respond to \"${verb}\" within ${Math.max(1, Math.round(this.VerbTimeoutMs / 1000))}s; try again or ask the user.`\n );\n }\n const result = outcome.Result;\n if (result.Success) {\n this.RecordChange({ Author: actor });\n if (actor === 'agent') {\n void this.ConfirmVisualChange();\n }\n }\n return result;\n } catch (error) {\n LogError(`[RealtimeChannel:${this.ChannelName}] ${verb} threw: ${error instanceof Error ? error.message : String(error)}`);\n return failure('verb_failed', `${verb} failed: ${error instanceof Error ? error.message : String(error)}`);\n } finally {\n this.applyingActor = null;\n }\n }\n\n /**\n * Runs {@link ApplySurfaceVerb} with a bounded execution time. Settles with the verb's result, with `TimedOut` when it did\n * not answer in time, or rejects with what it threw. Once it has timed out, whatever it later does is handled by\n * {@link onLateVerbSettle} and never reaches the caller: the promise is already settled, so there is no second result.\n */\n private runWithTimeout(component: TComponent, verb: string, args: JSONObject, actor: RealtimeChannelActor): Promise<VerbOutcome> {\n const call = Promise.resolve().then(() => this.ApplySurfaceVerb(component, verb, args, actor));\n return new Promise<VerbOutcome>((resolve, reject) => {\n let timedOut = false;\n const limited = Number.isFinite(this.VerbTimeoutMs) && this.VerbTimeoutMs > 0;\n const timer = limited\n ? setTimeout(() => {\n timedOut = true;\n LogError(\n `[RealtimeChannel:${this.ChannelName}] \"${verb}\" did not respond within ${this.VerbTimeoutMs} ms. The agent was told it failed and the queue was released; ` +\n `if it completes later its effects are not rolled back.`\n );\n resolve({ TimedOut: true });\n }, this.VerbTimeoutMs)\n : null;\n call.then(\n (result) => {\n clearTimeout(timer ?? undefined);\n if (timedOut) {\n this.onLateVerbSettle(component, verb, actor, result, null);\n } else {\n resolve({ TimedOut: false, Result: result });\n }\n },\n (error: unknown) => {\n clearTimeout(timer ?? undefined);\n if (timedOut) {\n this.onLateVerbSettle(component, verb, actor, null, error);\n } else {\n reject(error);\n }\n }\n );\n });\n }\n\n /**\n * A verb settled AFTER it timed out. The agent already has its answer (failure), so the late result is never delivered. A\n * late failure is logged. A late success means the component changed after the agent was told it had not: it is recorded as\n * a change (when the same component is still on screen) so the next perception note shows the model the real state.\n */\n private onLateVerbSettle(component: TComponent, verb: string, actor: RealtimeChannelActor, result: RealtimeChannelVerbResult | null, error: unknown): void {\n if (error !== null && error !== undefined) {\n LogError(`[RealtimeChannel:${this.ChannelName}] \"${verb}\" failed after it had timed out: ${error instanceof Error ? error.message : String(error)}`);\n return;\n }\n if (result?.Success && this.surface === component) {\n this.RecordChange({ Author: actor });\n }\n }\n\n // ── Events ─────────────────────────────────────────────────────────────────\n\n /** The component emitted something: tell observers, and (unless told not to) record it as a change the model perceives. */\n private onSurfaceEvent(event: ChannelSurfaceEvent): void {\n const author = event.Author ?? this.applyingActor ?? 'user';\n const changeId = this.RecordChange({ Author: author, Perceive: event.Perceive !== false });\n this.EmitChannelEvent(event.Name, event.Payload ?? {}, changeId);\n if (author === 'user') {\n void this.NotifyVisualChange();\n }\n }\n\n // ── Pictures ───────────────────────────────────────────────────────────────\n\n /** A picture of the component, or `null` when there is none to send (see the rules in the Interactive Component channel). */\n private async captureFrame(): Promise<string | null> {\n const capturer = ChannelFrameCapture.Instance.Capturer;\n if (!capturer || !this.surface || this.Exposure !== 'pixels' || !this.Context?.Client?.IsTrackEstablished('video', 'inbound')) {\n return null;\n }\n const element = this.SurfaceElement?.(this.surface) ?? null;\n if (!element) {\n return null;\n }\n try {\n return await capturer(element);\n } catch (error) {\n LogError(`[RealtimeChannel:${this.ChannelName}] Capturing a frame failed: ${error instanceof Error ? error.message : String(error)}`);\n return null;\n }\n }\n\n // ── Teardown ───────────────────────────────────────────────────────────────\n\n public override Dispose(): void {\n this.unbindSubscriptions();\n this.surface = null;\n for (const release of this.bindWaiters) {\n release(null);\n }\n this.bindWaiters = [];\n super.Dispose();\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview The one optional collaborator that lets a realtime channel show the model PIXELS of a DOM surface:
3
+ * something that can turn a rendered element into a JPEG.
4
+ *
5
+ * MemberJunction core carries no DOM rasterizer (it would add a dependency every embed pays for), so a channel's pixel
6
+ * exposure stays at `state` until the HOST that wants the model to SEE surfaces registers one. A host registers it once at
7
+ * startup, before a session is minted, because whether a channel can source an inbound video track is decided at mint.
8
+ * `EnableChannelFrameCapture` (in `dom-frame-capture.ts`) registers the default, `html-to-image` based, rasterizer.
9
+ *
10
+ * @module @memberjunction/ng-realtime-channels
11
+ */
12
+ import { BaseSingleton } from '@memberjunction/global';
13
+ /**
14
+ * Renders an element as a base64-encoded JPEG (no data-URL prefix), or `null` when it cannot (not rendered, tainted canvas,
15
+ * and so on). Must not throw; a channel treats a rejection as "no frame".
16
+ */
17
+ export type ElementFrameCapturer = (element: HTMLElement) => Promise<string | null>;
18
+ /** Holds the host's {@link ElementFrameCapturer}, if it registered one. */
19
+ export declare class ChannelFrameCapture extends BaseSingleton<ChannelFrameCapture> {
20
+ private capturer;
21
+ protected constructor();
22
+ /** The process-wide holder. */
23
+ static get Instance(): ChannelFrameCapture;
24
+ /** The registered capturer, or `null` when the host registered none (channels then offer state only). */
25
+ get Capturer(): ElementFrameCapturer | null;
26
+ /**
27
+ * Registers the capturer. Call before a realtime session is minted.
28
+ *
29
+ * @param capturer The rasterizer, or `null` to remove it.
30
+ */
31
+ Register(capturer: ElementFrameCapturer | null): void;
32
+ }
33
+ //# sourceMappingURL=channel-frame-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-frame-capture.d.ts","sourceRoot":"","sources":["../../src/lib/channel-frame-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEpF,2EAA2E;AAC3E,qBAAa,mBAAoB,SAAQ,aAAa,CAAC,mBAAmB,CAAC;IACvE,OAAO,CAAC,QAAQ,CAAqC;IAErD,SAAS;IAIT,+BAA+B;IAC/B,WAAkB,QAAQ,IAAI,mBAAmB,CAEhD;IAED,yGAAyG;IACzG,IAAW,QAAQ,IAAI,oBAAoB,GAAG,IAAI,CAEjD;IAED;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,IAAI;CAG/D"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview The one optional collaborator that lets a realtime channel show the model PIXELS of a DOM surface:
3
+ * something that can turn a rendered element into a JPEG.
4
+ *
5
+ * MemberJunction core carries no DOM rasterizer (it would add a dependency every embed pays for), so a channel's pixel
6
+ * exposure stays at `state` until the HOST that wants the model to SEE surfaces registers one. A host registers it once at
7
+ * startup, before a session is minted, because whether a channel can source an inbound video track is decided at mint.
8
+ * `EnableChannelFrameCapture` (in `dom-frame-capture.ts`) registers the default, `html-to-image` based, rasterizer.
9
+ *
10
+ * @module @memberjunction/ng-realtime-channels
11
+ */
12
+ import { BaseSingleton } from '@memberjunction/global';
13
+ /** Holds the host's {@link ElementFrameCapturer}, if it registered one. */
14
+ export class ChannelFrameCapture extends BaseSingleton {
15
+ capturer = null;
16
+ constructor() {
17
+ super();
18
+ }
19
+ /** The process-wide holder. */
20
+ static get Instance() {
21
+ return super.getInstance();
22
+ }
23
+ /** The registered capturer, or `null` when the host registered none (channels then offer state only). */
24
+ get Capturer() {
25
+ return this.capturer;
26
+ }
27
+ /**
28
+ * Registers the capturer. Call before a realtime session is minted.
29
+ *
30
+ * @param capturer The rasterizer, or `null` to remove it.
31
+ */
32
+ Register(capturer) {
33
+ this.capturer = capturer;
34
+ }
35
+ }
36
+ //# sourceMappingURL=channel-frame-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-frame-capture.js","sourceRoot":"","sources":["../../src/lib/channel-frame-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAQvD,2EAA2E;AAC3E,MAAM,OAAO,mBAAoB,SAAQ,aAAkC;IAC/D,QAAQ,GAAgC,IAAI,CAAC;IAErD;QACI,KAAK,EAAE,CAAC;IACZ,CAAC;IAED,+BAA+B;IACxB,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAAuB,CAAC;IACpD,CAAC;IAED,yGAAyG;IACzG,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACI,QAAQ,CAAC,QAAqC;QACjD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ","sourcesContent":["/**\n * @fileoverview The one optional collaborator that lets a realtime channel show the model PIXELS of a DOM surface:\n * something that can turn a rendered element into a JPEG.\n *\n * MemberJunction core carries no DOM rasterizer (it would add a dependency every embed pays for), so a channel's pixel\n * exposure stays at `state` until the HOST that wants the model to SEE surfaces registers one. A host registers it once at\n * startup, before a session is minted, because whether a channel can source an inbound video track is decided at mint.\n * `EnableChannelFrameCapture` (in `dom-frame-capture.ts`) registers the default, `html-to-image` based, rasterizer.\n *\n * @module @memberjunction/ng-realtime-channels\n */\n\nimport { BaseSingleton } from '@memberjunction/global';\n\n/**\n * Renders an element as a base64-encoded JPEG (no data-URL prefix), or `null` when it cannot (not rendered, tainted canvas,\n * and so on). Must not throw; a channel treats a rejection as \"no frame\".\n */\nexport type ElementFrameCapturer = (element: HTMLElement) => Promise<string | null>;\n\n/** Holds the host's {@link ElementFrameCapturer}, if it registered one. */\nexport class ChannelFrameCapture extends BaseSingleton<ChannelFrameCapture> {\n private capturer: ElementFrameCapturer | null = null;\n\n protected constructor() {\n super();\n }\n\n /** The process-wide holder. */\n public static get Instance(): ChannelFrameCapture {\n return super.getInstance<ChannelFrameCapture>();\n }\n\n /** The registered capturer, or `null` when the host registered none (channels then offer state only). */\n public get Capturer(): ElementFrameCapturer | null {\n return this.capturer;\n }\n\n /**\n * Registers the capturer. Call before a realtime session is minted.\n *\n * @param capturer The rasterizer, or `null` to remove it.\n */\n public Register(capturer: ElementFrameCapturer | null): void {\n this.capturer = capturer;\n }\n}\n"]}