@olimsaidov/icdp 0.1.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.
@@ -0,0 +1,99 @@
1
+ import { CdpError, FrameInfo, TargetSummary } from "../protocol.mjs";
2
+
3
+ //#region src/host/index.d.ts
4
+ /** Minimal structural view of an iframe, so tests can fake it. */
5
+ type FrameElementLike = {
6
+ contentWindow: {
7
+ postMessage: (message: unknown, targetOrigin: string, transfer?: Transferable[]) => void;
8
+ } | null;
9
+ addEventListener: (type: "load", listener: () => void) => void;
10
+ removeEventListener: (type: "load", listener: () => void) => void;
11
+ };
12
+ /** Minimal structural view of the parent window, so tests can fake it. */
13
+ type WindowLike = {
14
+ addEventListener: (type: "message", listener: (event: MessageEvent) => void) => void;
15
+ removeEventListener: (type: "message", listener: (event: MessageEvent) => void) => void;
16
+ };
17
+ type PairOptions = {
18
+ /** Stable Target id for this Pairing. Survives reloads and navigations. */targetId: string;
19
+ /**
20
+ * Frame origins allowed to pair into this slot, or "*" to accept whatever
21
+ * the iframe element currently hosts.
22
+ */
23
+ origins: string[] | "*";
24
+ };
25
+ type LocalSession = {
26
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
27
+ onEvent(listener: (method: string, params: Record<string, unknown>) => void): () => void;
28
+ detach(): void;
29
+ };
30
+ type TargetEvent = {
31
+ kind: "targetCreated";
32
+ target: TargetSummary;
33
+ } | {
34
+ kind: "targetDestroyed";
35
+ targetId: string;
36
+ } | {
37
+ kind: "targetInfoChanged";
38
+ target: TargetSummary;
39
+ };
40
+ type PendingCall = {
41
+ consumerKey: string;
42
+ settle: (result: unknown, error?: CdpError) => void;
43
+ };
44
+ type Pairing = {
45
+ targetId: string;
46
+ iframe: FrameElementLike;
47
+ origins: string[] | "*";
48
+ port: MessagePort | null;
49
+ connected: boolean;
50
+ info: FrameInfo;
51
+ nextCommandId: number;
52
+ pending: Map<number, PendingCall>; /** domain -> consumer keys that currently have it enabled */
53
+ enables: Map<string, Set<string>>;
54
+ localSessions: Map<string, LocalSessionState>;
55
+ onLoad: () => void;
56
+ };
57
+ type LocalSessionState = {
58
+ key: string;
59
+ listeners: Set<(method: string, params: Record<string, unknown>) => void>;
60
+ };
61
+ type RelayUplinkOptions = {
62
+ /** Bridge WebSocket URL on the Relay, e.g. ws://host/icdp/host */url: string;
63
+ reconnectDelayMs?: number;
64
+ webSocketFactory?: (url: string) => WebSocket;
65
+ };
66
+ declare class IcdpHost {
67
+ private readonly win;
68
+ private readonly pairings;
69
+ private readonly targetListeners;
70
+ private nextLocalSession;
71
+ private uplink;
72
+ private readonly onWindowMessage;
73
+ constructor(win?: WindowLike);
74
+ /** Register an iframe slot as a Target. The Pairing owns target identity. */
75
+ pair(iframe: FrameElementLike, options: PairOptions): void;
76
+ /** Destroy a Pairing. This is the only way a Target dies. */
77
+ unpair(targetId: string): void;
78
+ targets(): TargetSummary[];
79
+ onTargets(listener: (event: TargetEvent) => void): () => void;
80
+ /** Attach a local consumer (e.g. a console panel) to a Target — no server involved. */
81
+ attach(targetId: string): LocalSession;
82
+ /** Connect the Relay uplink. Structurally just another consumer of this hub. */
83
+ connectRelay(options: RelayUplinkOptions): () => void;
84
+ destroy(): void;
85
+ private summary;
86
+ private emitTargetEvent;
87
+ private probe;
88
+ private handleWindowMessage;
89
+ private handleFrameMessage;
90
+ private failPending;
91
+ /** Route one command from a consumer to the Frame Agent, with enable ref-counting. */
92
+ dispatch(pairing: Pairing, consumerKey: string, method: string, params: Record<string, unknown>, settle: (result: unknown, error?: CdpError) => void): void;
93
+ dispatchTo(targetId: string, consumerKey: string, method: string, params: Record<string, unknown>, settle: (result: unknown, error?: CdpError) => void): void;
94
+ /** Drop a consumer's enable refs; send disables to the frame for domains it held last. */
95
+ releaseEnables(pairing: Pairing, consumerKey: string): void;
96
+ releaseEnablesFor(targetId: string, consumerKey: string): void;
97
+ }
98
+ //#endregion
99
+ export { FrameElementLike, IcdpHost, LocalSession, PairOptions, RelayUplinkOptions, TargetEvent, WindowLike };
@@ -0,0 +1,328 @@
1
+ import { CDP_SERVER_ERROR, isHandshakeMessage, parseJson } from "../protocol.mjs";
2
+ //#region src/host/index.ts
3
+ const TARGET_NOT_CONNECTED = "Target is not connected: the Frame Agent has not paired yet.";
4
+ function methodDomain(method) {
5
+ return method.split(".")[0] ?? method;
6
+ }
7
+ var IcdpHost = class {
8
+ win;
9
+ pairings = /* @__PURE__ */ new Map();
10
+ targetListeners = /* @__PURE__ */ new Set();
11
+ nextLocalSession = 1;
12
+ uplink = null;
13
+ onWindowMessage = (event) => this.handleWindowMessage(event);
14
+ constructor(win = window) {
15
+ this.win = win;
16
+ win.addEventListener("message", this.onWindowMessage);
17
+ }
18
+ /** Register an iframe slot as a Target. The Pairing owns target identity. */
19
+ pair(iframe, options) {
20
+ if (this.pairings.has(options.targetId)) throw new Error(`Target "${options.targetId}" is already paired`);
21
+ const pairing = {
22
+ targetId: options.targetId,
23
+ iframe,
24
+ origins: options.origins,
25
+ port: null,
26
+ connected: false,
27
+ info: {
28
+ title: options.targetId,
29
+ url: ""
30
+ },
31
+ nextCommandId: 1,
32
+ pending: /* @__PURE__ */ new Map(),
33
+ enables: /* @__PURE__ */ new Map(),
34
+ localSessions: /* @__PURE__ */ new Map(),
35
+ onLoad: () => this.probe(pairing)
36
+ };
37
+ this.pairings.set(options.targetId, pairing);
38
+ iframe.addEventListener("load", pairing.onLoad);
39
+ this.probe(pairing);
40
+ this.emitTargetEvent({
41
+ kind: "targetCreated",
42
+ target: this.summary(pairing)
43
+ });
44
+ }
45
+ /** Destroy a Pairing. This is the only way a Target dies. */
46
+ unpair(targetId) {
47
+ const pairing = this.pairings.get(targetId);
48
+ if (!pairing) return;
49
+ this.pairings.delete(targetId);
50
+ pairing.iframe.removeEventListener("load", pairing.onLoad);
51
+ this.failPending(pairing, "Target destroyed");
52
+ pairing.port?.close();
53
+ pairing.port = null;
54
+ this.emitTargetEvent({
55
+ kind: "targetDestroyed",
56
+ targetId
57
+ });
58
+ }
59
+ targets() {
60
+ return Array.from(this.pairings.values(), (pairing) => this.summary(pairing));
61
+ }
62
+ onTargets(listener) {
63
+ this.targetListeners.add(listener);
64
+ return () => this.targetListeners.delete(listener);
65
+ }
66
+ /** Attach a local consumer (e.g. a console panel) to a Target — no server involved. */
67
+ attach(targetId) {
68
+ const pairing = this.pairings.get(targetId);
69
+ if (!pairing) throw new Error(`Unknown target "${targetId}"`);
70
+ const state = {
71
+ key: `local-${this.nextLocalSession++}`,
72
+ listeners: /* @__PURE__ */ new Set()
73
+ };
74
+ pairing.localSessions.set(state.key, state);
75
+ return {
76
+ send: (method, params = {}) => new Promise((resolve, reject) => {
77
+ this.dispatch(pairing, state.key, method, params, (result, error) => {
78
+ if (error) reject(Object.assign(new Error(error.message), { code: error.code }));
79
+ else resolve(result);
80
+ });
81
+ }),
82
+ onEvent: (listener) => {
83
+ state.listeners.add(listener);
84
+ return () => state.listeners.delete(listener);
85
+ },
86
+ detach: () => {
87
+ pairing.localSessions.delete(state.key);
88
+ this.releaseEnables(pairing, state.key);
89
+ }
90
+ };
91
+ }
92
+ /** Connect the Relay uplink. Structurally just another consumer of this hub. */
93
+ connectRelay(options) {
94
+ this.uplink?.close();
95
+ this.uplink = new RelayUplink(this, options);
96
+ return () => {
97
+ this.uplink?.close();
98
+ this.uplink = null;
99
+ };
100
+ }
101
+ destroy() {
102
+ this.uplink?.close();
103
+ this.uplink = null;
104
+ for (const targetId of Array.from(this.pairings.keys())) this.unpair(targetId);
105
+ this.win.removeEventListener("message", this.onWindowMessage);
106
+ }
107
+ summary(pairing) {
108
+ return {
109
+ targetId: pairing.targetId,
110
+ ...pairing.info
111
+ };
112
+ }
113
+ emitTargetEvent(event) {
114
+ for (const listener of this.targetListeners) listener(event);
115
+ this.uplink?.handleTargetEvent(event);
116
+ }
117
+ probe(pairing) {
118
+ pairing.iframe.contentWindow?.postMessage({
119
+ icdp: "probe",
120
+ v: 1
121
+ }, "*");
122
+ }
123
+ handleWindowMessage(event) {
124
+ if (!isHandshakeMessage(event.data) || event.data.icdp !== "hello") return;
125
+ const pairing = Array.from(this.pairings.values()).find((candidate) => candidate.iframe.contentWindow !== null && candidate.iframe.contentWindow === event.source);
126
+ if (!pairing) return;
127
+ if (pairing.origins !== "*" && !pairing.origins.includes(event.origin)) return;
128
+ if (pairing.port) {
129
+ this.failPending(pairing, "Target reloaded");
130
+ pairing.port.close();
131
+ }
132
+ const channel = new MessageChannel();
133
+ pairing.port = channel.port1;
134
+ pairing.connected = true;
135
+ pairing.info = {
136
+ title: event.data.title,
137
+ url: event.data.url
138
+ };
139
+ pairing.enables.clear();
140
+ channel.port1.onmessage = (portEvent) => this.handleFrameMessage(pairing, String(portEvent.data));
141
+ pairing.iframe.contentWindow?.postMessage({
142
+ icdp: "welcome",
143
+ v: 1
144
+ }, event.origin === "null" ? "*" : event.origin, [channel.port2]);
145
+ this.emitTargetEvent({
146
+ kind: "targetInfoChanged",
147
+ target: this.summary(pairing)
148
+ });
149
+ }
150
+ handleFrameMessage(pairing, raw) {
151
+ const message = parseJson(raw);
152
+ if (!message) return;
153
+ if (message.id != null) {
154
+ const call = pairing.pending.get(Number(message.id));
155
+ if (!call) return;
156
+ pairing.pending.delete(Number(message.id));
157
+ call.settle(message.result ?? {}, message.error);
158
+ return;
159
+ }
160
+ if (!message.method) return;
161
+ const params = message.params ?? {};
162
+ for (const session of pairing.localSessions.values()) for (const listener of session.listeners) listener(message.method, params);
163
+ this.uplink?.handleFrameEvent(pairing.targetId, message.method, params);
164
+ }
165
+ failPending(pairing, reason) {
166
+ for (const [id, call] of pairing.pending) {
167
+ pairing.pending.delete(id);
168
+ call.settle(void 0, {
169
+ code: CDP_SERVER_ERROR,
170
+ message: reason
171
+ });
172
+ }
173
+ }
174
+ /** Route one command from a consumer to the Frame Agent, with enable ref-counting. */
175
+ dispatch(pairing, consumerKey, method, params, settle) {
176
+ const domain = methodDomain(method);
177
+ if (method.endsWith(".disable")) {
178
+ const holders = pairing.enables.get(domain);
179
+ holders?.delete(consumerKey);
180
+ if (holders && holders.size > 0) {
181
+ settle({});
182
+ return;
183
+ }
184
+ }
185
+ if (!pairing.port) {
186
+ settle(void 0, {
187
+ code: CDP_SERVER_ERROR,
188
+ message: TARGET_NOT_CONNECTED
189
+ });
190
+ return;
191
+ }
192
+ if (method.endsWith(".enable")) {
193
+ let holders = pairing.enables.get(domain);
194
+ if (!holders) {
195
+ holders = /* @__PURE__ */ new Set();
196
+ pairing.enables.set(domain, holders);
197
+ }
198
+ holders.add(consumerKey);
199
+ }
200
+ const commandId = pairing.nextCommandId++;
201
+ pairing.pending.set(commandId, {
202
+ consumerKey,
203
+ settle
204
+ });
205
+ pairing.port.postMessage(JSON.stringify({
206
+ id: commandId,
207
+ method,
208
+ params
209
+ }));
210
+ }
211
+ dispatchTo(targetId, consumerKey, method, params, settle) {
212
+ const pairing = this.pairings.get(targetId);
213
+ if (!pairing) {
214
+ settle(void 0, {
215
+ code: CDP_SERVER_ERROR,
216
+ message: `Unknown target "${targetId}"`
217
+ });
218
+ return;
219
+ }
220
+ this.dispatch(pairing, consumerKey, method, params, settle);
221
+ }
222
+ /** Drop a consumer's enable refs; send disables to the frame for domains it held last. */
223
+ releaseEnables(pairing, consumerKey) {
224
+ for (const [domain, holders] of pairing.enables) {
225
+ if (!holders.delete(consumerKey)) continue;
226
+ if (holders.size === 0 && pairing.port) {
227
+ const commandId = pairing.nextCommandId++;
228
+ pairing.pending.set(commandId, {
229
+ consumerKey,
230
+ settle: () => {}
231
+ });
232
+ pairing.port.postMessage(JSON.stringify({
233
+ id: commandId,
234
+ method: `${domain}.disable`,
235
+ params: {}
236
+ }));
237
+ }
238
+ }
239
+ }
240
+ releaseEnablesFor(targetId, consumerKey) {
241
+ const pairing = this.pairings.get(targetId);
242
+ if (pairing) this.releaseEnables(pairing, consumerKey);
243
+ }
244
+ };
245
+ var RelayUplink = class {
246
+ host;
247
+ options;
248
+ socket = null;
249
+ closed = false;
250
+ reconnectTimer;
251
+ constructor(host, options) {
252
+ this.host = host;
253
+ this.options = options;
254
+ this.connect();
255
+ }
256
+ connect() {
257
+ if (this.closed) return;
258
+ const socket = (this.options.webSocketFactory ?? ((url) => new WebSocket(url)))(this.options.url);
259
+ this.socket = socket;
260
+ socket.addEventListener("open", () => {
261
+ this.send({
262
+ kind: "ready",
263
+ v: 1,
264
+ targets: this.host.targets()
265
+ });
266
+ });
267
+ socket.addEventListener("message", (event) => {
268
+ const message = parseJson(String(event.data));
269
+ if (message) this.handleRelayMessage(message);
270
+ });
271
+ socket.addEventListener("close", () => {
272
+ if (this.socket === socket) this.socket = null;
273
+ this.scheduleReconnect();
274
+ });
275
+ socket.addEventListener("error", () => socket.close());
276
+ }
277
+ scheduleReconnect() {
278
+ if (this.closed || this.reconnectTimer !== void 0) return;
279
+ this.reconnectTimer = setTimeout(() => {
280
+ this.reconnectTimer = void 0;
281
+ this.connect();
282
+ }, this.options.reconnectDelayMs ?? 500);
283
+ }
284
+ close() {
285
+ this.closed = true;
286
+ if (this.reconnectTimer !== void 0) clearTimeout(this.reconnectTimer);
287
+ this.socket?.close();
288
+ this.socket = null;
289
+ }
290
+ send(message) {
291
+ if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(JSON.stringify(message));
292
+ }
293
+ handleRelayMessage(message) {
294
+ if (message.kind === "command") this.host.dispatchTo(message.targetId, `relay-${message.sessionId}`, message.method, message.params, (result, error) => {
295
+ this.send({
296
+ kind: "response",
297
+ sessionId: message.sessionId,
298
+ id: message.id,
299
+ ...error ? { error } : { result }
300
+ });
301
+ });
302
+ else if (message.kind === "detached") this.host.releaseEnablesFor(message.targetId, `relay-${message.sessionId}`);
303
+ }
304
+ handleFrameEvent(targetId, method, params) {
305
+ this.send({
306
+ kind: "event",
307
+ targetId,
308
+ method,
309
+ params
310
+ });
311
+ }
312
+ handleTargetEvent(event) {
313
+ if (event.kind === "targetCreated") this.send({
314
+ kind: "targetCreated",
315
+ target: event.target
316
+ });
317
+ else if (event.kind === "targetDestroyed") this.send({
318
+ kind: "targetDestroyed",
319
+ targetId: event.targetId
320
+ });
321
+ else this.send({
322
+ kind: "targetInfoChanged",
323
+ target: event.target
324
+ });
325
+ }
326
+ };
327
+ //#endregion
328
+ export { IcdpHost };
@@ -0,0 +1,102 @@
1
+ import Protocol from "devtools-protocol";
2
+
3
+ //#region src/protocol.d.ts
4
+ declare const PROTOCOL_VERSION = 1;
5
+ type CdpId = Protocol.integer | string;
6
+ /** A raw CDP message: command, response, or event. */
7
+ type CdpMessage = {
8
+ id?: CdpId;
9
+ method?: string;
10
+ params?: Record<string, unknown>;
11
+ sessionId?: string;
12
+ result?: unknown;
13
+ error?: CdpError;
14
+ };
15
+ type CdpError = {
16
+ code: number;
17
+ message: string;
18
+ };
19
+ declare const CDP_SERVER_ERROR = -32000;
20
+ declare const CDP_METHOD_NOT_FOUND = -32601;
21
+ /** Metadata a Frame Agent reports about its document. */
22
+ type FrameInfo = {
23
+ title: string;
24
+ url: string;
25
+ };
26
+ /** Target metadata as the Host reports it to the Relay. */
27
+ type TargetSummary = FrameInfo & {
28
+ targetId: string;
29
+ };
30
+ /** Sent by the Frame Agent to window.parent when it boots (and on probe). */
31
+ type HelloMessage = {
32
+ icdp: "hello";
33
+ v: number;
34
+ } & FrameInfo;
35
+ /** Sent by the Host to an iframe it doesn't yet have a channel for. */
36
+ type ProbeMessage = {
37
+ icdp: "probe";
38
+ v: number;
39
+ };
40
+ /** Sent by the Host in reply to hello, transferring a MessagePort. */
41
+ type WelcomeMessage = {
42
+ icdp: "welcome";
43
+ v: number;
44
+ };
45
+ type HandshakeMessage = HelloMessage | ProbeMessage | WelcomeMessage;
46
+ declare function isHandshakeMessage(data: unknown): data is HandshakeMessage;
47
+ /** Host -> Relay: announces itself and its current targets. New-wins: the Relay drops any previous Host. */
48
+ type BridgeReady = {
49
+ kind: "ready";
50
+ v: number;
51
+ targets: TargetSummary[];
52
+ };
53
+ /** Host -> Relay: a Pairing appeared. */
54
+ type BridgeTargetCreated = {
55
+ kind: "targetCreated";
56
+ target: TargetSummary;
57
+ };
58
+ /** Host -> Relay: a Pairing was destroyed by the Host. */
59
+ type BridgeTargetDestroyed = {
60
+ kind: "targetDestroyed";
61
+ targetId: string;
62
+ };
63
+ /** Host -> Relay: a Target's document changed (reload / navigation under a stable targetId). */
64
+ type BridgeTargetInfoChanged = {
65
+ kind: "targetInfoChanged";
66
+ target: TargetSummary;
67
+ };
68
+ /** Relay -> Host: a Client command routed to one session. */
69
+ type BridgeCommand = {
70
+ kind: "command";
71
+ sessionId: string;
72
+ targetId: string;
73
+ id: number;
74
+ method: string;
75
+ params: Record<string, unknown>;
76
+ };
77
+ /** Host -> Relay: the response to a BridgeCommand. */
78
+ type BridgeResponse = {
79
+ kind: "response";
80
+ sessionId: string;
81
+ id: number;
82
+ result?: unknown;
83
+ error?: CdpError;
84
+ };
85
+ /** Host -> Relay: a CDP event from a Target; the Relay fans it out to every session attached to it. */
86
+ type BridgeEvent = {
87
+ kind: "event";
88
+ targetId: string;
89
+ method: string;
90
+ params: Record<string, unknown>;
91
+ };
92
+ /** Relay -> Host: a session detached (Client disconnected or detached explicitly). */
93
+ type BridgeDetached = {
94
+ kind: "detached";
95
+ sessionId: string;
96
+ targetId: string;
97
+ };
98
+ type HostToRelayMessage = BridgeReady | BridgeTargetCreated | BridgeTargetDestroyed | BridgeTargetInfoChanged | BridgeResponse | BridgeEvent;
99
+ type RelayToHostMessage = BridgeCommand | BridgeDetached;
100
+ declare function parseJson<T>(raw: string | Buffer | ArrayBuffer | Uint8Array): T | null;
101
+ //#endregion
102
+ export { BridgeCommand, BridgeDetached, BridgeEvent, BridgeReady, BridgeResponse, BridgeTargetCreated, BridgeTargetDestroyed, BridgeTargetInfoChanged, CDP_METHOD_NOT_FOUND, CDP_SERVER_ERROR, CdpError, CdpId, CdpMessage, FrameInfo, HandshakeMessage, HelloMessage, HostToRelayMessage, PROTOCOL_VERSION, ProbeMessage, RelayToHostMessage, TargetSummary, WelcomeMessage, isHandshakeMessage, parseJson };
@@ -0,0 +1,16 @@
1
+ //#region src/protocol.ts
2
+ const PROTOCOL_VERSION = 1;
3
+ const CDP_SERVER_ERROR = -32e3;
4
+ const CDP_METHOD_NOT_FOUND = -32601;
5
+ function isHandshakeMessage(data) {
6
+ return typeof data === "object" && data !== null && "icdp" in data && (data.icdp === "hello" || data.icdp === "probe" || data.icdp === "welcome");
7
+ }
8
+ function parseJson(raw) {
9
+ try {
10
+ return JSON.parse(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ //#endregion
16
+ export { CDP_METHOD_NOT_FOUND, CDP_SERVER_ERROR, PROTOCOL_VERSION, isHandshakeMessage, parseJson };
@@ -0,0 +1,54 @@
1
+ import { TargetSummary } from "../protocol.mjs";
2
+
3
+ //#region src/relay/core.d.ts
4
+ /** Minimal socket surface the adapter must provide for each connection. */
5
+ type SocketLike = {
6
+ send(data: string): void;
7
+ close(code?: number, reason?: string): void;
8
+ };
9
+ type RelayCoreOptions = {
10
+ /** Reported by Browser.getVersion and /json/version. */product?: string; /** Absolute WebSocket URL of the browser endpoint, for /json payloads. */
11
+ browserWsUrl?: string;
12
+ };
13
+ declare class RelayCore {
14
+ private readonly product;
15
+ private readonly browserWsUrl;
16
+ private hostSocket;
17
+ private readonly clients;
18
+ private readonly sessions;
19
+ private readonly targets;
20
+ private readonly pending;
21
+ private nextBridgeId;
22
+ private nextSessionId;
23
+ constructor(options?: RelayCoreOptions);
24
+ /** A Host bridge connected. New-wins: any previous Host is dropped. */
25
+ hostConnected(socket: SocketLike): void;
26
+ hostDisconnected(socket: SocketLike): void;
27
+ hostMessage(socket: SocketLike, raw: string): void;
28
+ clientConnected(socket: SocketLike): void;
29
+ clientDisconnected(socket: SocketLike): void;
30
+ clientMessage(socket: SocketLike, raw: string): void;
31
+ jsonVersion(): Record<string, unknown>;
32
+ jsonList(): Array<Record<string, unknown>>;
33
+ status(): {
34
+ hostConnected: boolean;
35
+ targets: TargetSummary[];
36
+ clients: number;
37
+ };
38
+ private targetInfo;
39
+ private sendToClient;
40
+ private sendToHost;
41
+ private broadcastTargetEvent;
42
+ private addTarget;
43
+ private removeTarget;
44
+ private dropAllTargets;
45
+ private startSession;
46
+ private endSession;
47
+ private routeSessionCommand;
48
+ /** Session-scoped methods the Relay answers itself; undefined = forward to the frame. */
49
+ private sessionLevelResult;
50
+ /** Browser-level methods the Relay answers itself; undefined = not local. */
51
+ private browserLevelResult;
52
+ }
53
+ //#endregion
54
+ export { RelayCore, RelayCoreOptions, SocketLike };