@authloop-ai/core 0.2.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/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/cdp.d.ts +25 -0
- package/dist/cdp.js +162 -0
- package/dist/cdp.js.map +1 -0
- package/dist/crypto.d.ts +38 -0
- package/dist/crypto.js +69 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +64 -0
- package/dist/session.js +223 -0
- package/dist/session.js.map +1 -0
- package/dist/stream.d.ts +36 -0
- package/dist/stream.js +321 -0
- package/dist/stream.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AuthLoop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @authloop-ai/core
|
|
2
|
+
|
|
3
|
+
Core engine for [AuthLoop](https://authloop.ai) — CDP screencast, end-to-end encryption, and WebSocket relay.
|
|
4
|
+
|
|
5
|
+
This package powers the MCP server and OpenClaw plugin. You typically don't use it directly — use [`@authloop-ai/mcp`](../mcp) or [`@authloop-ai/openclaw-authloop`](../openclaw-plugin) instead.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- **CDP client** — connects to a Chromium browser via Chrome DevTools Protocol, captures screencast frames (JPEG), dispatches keystrokes and input events
|
|
10
|
+
- **E2EE** — ECDH P-256 key exchange + AES-256-GCM encryption for all user input between the human's browser and the agent's machine
|
|
11
|
+
- **BrowserStream** — WebSocket relay that streams CDP frames to the human and relays encrypted input back to the browser
|
|
12
|
+
- **Session lifecycle** — creates sessions via the AuthLoop API, manages the PENDING → ACTIVE → RESOLVED state machine, handles cleanup on disconnect
|
|
13
|
+
|
|
14
|
+
## Exports
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// Session management
|
|
18
|
+
export { startSession, waitForStatus, stopSession } from "./session.js";
|
|
19
|
+
export type { ToHumanInput, StartSessionOutput, SessionStatusOutput, SessionStatus };
|
|
20
|
+
|
|
21
|
+
// CDP screencast + input dispatch
|
|
22
|
+
export { BrowserStream } from "./stream.js";
|
|
23
|
+
export { CdpClient } from "./cdp.js";
|
|
24
|
+
|
|
25
|
+
// End-to-end encryption
|
|
26
|
+
export { E2EESession } from "./crypto.js";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## When to use this directly
|
|
30
|
+
|
|
31
|
+
Only if you're building a custom integration that isn't covered by the MCP server, OpenClaw plugin, or SDK. For example:
|
|
32
|
+
|
|
33
|
+
- A custom agent runtime that needs direct control over the screencast stream
|
|
34
|
+
- A browser extension that manages its own CDP connection
|
|
35
|
+
- A relay server with custom transport (e.g., WebRTC instead of WebSocket)
|
|
36
|
+
|
|
37
|
+
For most use cases, use the higher-level packages instead.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/dist/cdp.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin CDP WebSocket client. Uses native WebSocket (Node 22+).
|
|
3
|
+
* Supports both direct WebSocket URLs (ws://, wss://) and HTTP-based CDP
|
|
4
|
+
* endpoints (http://, https://) — auto-discovers the WebSocket debugger URL
|
|
5
|
+
* via /json/version for HTTP endpoints.
|
|
6
|
+
* No auto-reconnect — a CDP drop means the session is dead.
|
|
7
|
+
*/
|
|
8
|
+
type EventHandler = (params: Record<string, unknown>) => void;
|
|
9
|
+
export declare class CdpClient {
|
|
10
|
+
private cdpUrl;
|
|
11
|
+
private ws;
|
|
12
|
+
private nextId;
|
|
13
|
+
private pending;
|
|
14
|
+
private listeners;
|
|
15
|
+
private closeHandlers;
|
|
16
|
+
private closed;
|
|
17
|
+
constructor(cdpUrl: string);
|
|
18
|
+
/** Register a callback for when the CDP connection closes */
|
|
19
|
+
onClose(handler: () => void): void;
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
22
|
+
on(event: string, handler: EventHandler): void;
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
package/dist/cdp.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin CDP WebSocket client. Uses native WebSocket (Node 22+).
|
|
3
|
+
* Supports both direct WebSocket URLs (ws://, wss://) and HTTP-based CDP
|
|
4
|
+
* endpoints (http://, https://) — auto-discovers the WebSocket debugger URL
|
|
5
|
+
* via /json/version for HTTP endpoints.
|
|
6
|
+
* No auto-reconnect — a CDP drop means the session is dead.
|
|
7
|
+
*/
|
|
8
|
+
import createDebug from "debug";
|
|
9
|
+
const debug = createDebug("authloop:cdp");
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a CDP URL to a page-level WebSocket URL.
|
|
12
|
+
* - ws:// / wss:// URLs are returned as-is (assumed to be a page target).
|
|
13
|
+
* - http:// / https:// URLs are treated as CDP HTTP endpoints — we call
|
|
14
|
+
* /json to find the first "page" target and use its webSocketDebuggerUrl.
|
|
15
|
+
* Falls back to /json/version (browser-level) if no page targets exist.
|
|
16
|
+
*/
|
|
17
|
+
async function resolveWebSocketUrl(cdpUrl) {
|
|
18
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
19
|
+
debug("CDP URL is already WebSocket: %s", cdpUrl);
|
|
20
|
+
return cdpUrl;
|
|
21
|
+
}
|
|
22
|
+
const base = cdpUrl.replace(/\/+$/, "");
|
|
23
|
+
// Try /json first to get a page-level target (required for Page.startScreencast)
|
|
24
|
+
try {
|
|
25
|
+
const listUrl = `${base}/json`;
|
|
26
|
+
debug("discovering page targets from %s", listUrl);
|
|
27
|
+
const res = await fetch(listUrl);
|
|
28
|
+
if (res.ok) {
|
|
29
|
+
const targets = (await res.json());
|
|
30
|
+
// Filter to page targets with WebSocket URLs
|
|
31
|
+
const pages = targets.filter((t) => t.type === "page" && t.webSocketDebuggerUrl);
|
|
32
|
+
// Prefer real web pages over chrome internal pages (about:, chrome:, chrome-extension:)
|
|
33
|
+
const webPage = pages.find((t) => t.url && /^https?:\/\//.test(t.url));
|
|
34
|
+
const page = webPage ?? pages[0];
|
|
35
|
+
if (page) {
|
|
36
|
+
debug("discovered page target: %s (%s)", page.title ?? page.url, page.webSocketDebuggerUrl);
|
|
37
|
+
return page.webSocketDebuggerUrl;
|
|
38
|
+
}
|
|
39
|
+
debug("no page targets found, falling back to /json/version");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
debug("/json failed, falling back to /json/version");
|
|
44
|
+
}
|
|
45
|
+
// Fallback: browser-level endpoint
|
|
46
|
+
const versionUrl = `${base}/json/version`;
|
|
47
|
+
debug("discovering WebSocket URL from %s", versionUrl);
|
|
48
|
+
const res = await fetch(versionUrl);
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new Error(`CDP discovery failed: ${versionUrl} returned ${res.status}`);
|
|
51
|
+
}
|
|
52
|
+
const data = (await res.json());
|
|
53
|
+
if (!data.webSocketDebuggerUrl) {
|
|
54
|
+
throw new Error(`CDP discovery: no webSocketDebuggerUrl in ${versionUrl} response`);
|
|
55
|
+
}
|
|
56
|
+
debug("discovered browser WebSocket URL: %s", data.webSocketDebuggerUrl);
|
|
57
|
+
return data.webSocketDebuggerUrl;
|
|
58
|
+
}
|
|
59
|
+
export class CdpClient {
|
|
60
|
+
cdpUrl;
|
|
61
|
+
ws = null;
|
|
62
|
+
nextId = 1;
|
|
63
|
+
pending = new Map();
|
|
64
|
+
listeners = new Map();
|
|
65
|
+
closeHandlers = [];
|
|
66
|
+
closed = false;
|
|
67
|
+
constructor(cdpUrl) {
|
|
68
|
+
this.cdpUrl = cdpUrl;
|
|
69
|
+
}
|
|
70
|
+
/** Register a callback for when the CDP connection closes */
|
|
71
|
+
onClose(handler) {
|
|
72
|
+
this.closeHandlers.push(handler);
|
|
73
|
+
}
|
|
74
|
+
async connect() {
|
|
75
|
+
const wsUrl = await resolveWebSocketUrl(this.cdpUrl);
|
|
76
|
+
debug("connecting to %s", wsUrl);
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const ws = new WebSocket(wsUrl);
|
|
79
|
+
this.ws = ws;
|
|
80
|
+
ws.addEventListener("open", () => {
|
|
81
|
+
debug("connected");
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
ws.addEventListener("error", (e) => {
|
|
85
|
+
if (!this.closed) {
|
|
86
|
+
debug("connection error: %s", e.message ?? "unknown");
|
|
87
|
+
reject(new Error(`CDP connection error: ${e.message ?? "unknown"}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
ws.addEventListener("message", (event) => {
|
|
91
|
+
const data = JSON.parse(String(event.data));
|
|
92
|
+
// Response to a command
|
|
93
|
+
if (data.id !== undefined) {
|
|
94
|
+
const pending = this.pending.get(data.id);
|
|
95
|
+
if (pending) {
|
|
96
|
+
this.pending.delete(data.id);
|
|
97
|
+
if (data.error) {
|
|
98
|
+
debug("command %d error: %s", data.id, data.error.message);
|
|
99
|
+
pending.reject(new Error(`CDP error: ${data.error.message}`));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
debug("command %d ok", data.id);
|
|
103
|
+
pending.resolve(data.result);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Event
|
|
109
|
+
if (data.method) {
|
|
110
|
+
debug("event: %s", data.method);
|
|
111
|
+
const handlers = this.listeners.get(data.method);
|
|
112
|
+
if (handlers) {
|
|
113
|
+
for (const handler of handlers) {
|
|
114
|
+
handler(data.params ?? {});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
ws.addEventListener("close", () => {
|
|
120
|
+
debug("connection closed, rejecting %d pending calls", this.pending.size);
|
|
121
|
+
this.closed = true;
|
|
122
|
+
for (const [, pending] of this.pending) {
|
|
123
|
+
pending.reject(new Error("CDP connection closed"));
|
|
124
|
+
}
|
|
125
|
+
this.pending.clear();
|
|
126
|
+
for (const handler of this.closeHandlers) {
|
|
127
|
+
handler();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
send(method, params) {
|
|
133
|
+
if (!this.ws || this.closed) {
|
|
134
|
+
return Promise.reject(new Error("CDP not connected"));
|
|
135
|
+
}
|
|
136
|
+
const id = this.nextId++;
|
|
137
|
+
debug("send #%d %s", id, method);
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
this.pending.set(id, { resolve, reject });
|
|
140
|
+
this.ws.send(JSON.stringify({ id, method, params }));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
on(event, handler) {
|
|
144
|
+
let set = this.listeners.get(event);
|
|
145
|
+
if (!set) {
|
|
146
|
+
set = new Set();
|
|
147
|
+
this.listeners.set(event, set);
|
|
148
|
+
}
|
|
149
|
+
set.add(handler);
|
|
150
|
+
}
|
|
151
|
+
close() {
|
|
152
|
+
debug("closing, %d pending calls", this.pending.size);
|
|
153
|
+
this.closed = true;
|
|
154
|
+
for (const [, pending] of this.pending) {
|
|
155
|
+
pending.reject(new Error("CDP client closed"));
|
|
156
|
+
}
|
|
157
|
+
this.pending.clear();
|
|
158
|
+
this.ws?.close();
|
|
159
|
+
this.ws = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=cdp.js.map
|
package/dist/cdp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../src/cdp.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end encryption for the keystroke relay.
|
|
3
|
+
*
|
|
4
|
+
* Uses ECDH (P-256) key exchange + AES-256-GCM.
|
|
5
|
+
* The relay only sees ciphertext — it cannot read passwords or OTPs.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. MCP generates ECDH P-256 keypair, sends public key to viewer via relay
|
|
9
|
+
* 2. Viewer generates ECDH P-256 keypair, sends public key to MCP via relay
|
|
10
|
+
* 3. Both sides derive the same shared secret using ECDH
|
|
11
|
+
* 4. Viewer encrypts input events with AES-256-GCM before sending
|
|
12
|
+
* 5. MCP decrypts input events after receiving
|
|
13
|
+
* 6. Frames (screenshots) are NOT encrypted — they show visible page content only
|
|
14
|
+
*/
|
|
15
|
+
export declare class E2EESession {
|
|
16
|
+
private ecdh;
|
|
17
|
+
private sharedSecret;
|
|
18
|
+
private _publicKey;
|
|
19
|
+
constructor();
|
|
20
|
+
/** Our public key to send to the viewer */
|
|
21
|
+
get publicKey(): string;
|
|
22
|
+
/** Derive shared secret from the viewer's public key */
|
|
23
|
+
deriveSecret(viewerPublicKey: string): void;
|
|
24
|
+
/** Check if key exchange is complete */
|
|
25
|
+
get ready(): boolean;
|
|
26
|
+
/** Decrypt a message from the viewer */
|
|
27
|
+
decrypt(encrypted: {
|
|
28
|
+
iv: string;
|
|
29
|
+
ciphertext: string;
|
|
30
|
+
tag: string;
|
|
31
|
+
}): string;
|
|
32
|
+
/** Encrypt a message to the viewer (for testing / future use) */
|
|
33
|
+
encrypt(plaintext: string): {
|
|
34
|
+
iv: string;
|
|
35
|
+
ciphertext: string;
|
|
36
|
+
tag: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end encryption for the keystroke relay.
|
|
3
|
+
*
|
|
4
|
+
* Uses ECDH (P-256) key exchange + AES-256-GCM.
|
|
5
|
+
* The relay only sees ciphertext — it cannot read passwords or OTPs.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. MCP generates ECDH P-256 keypair, sends public key to viewer via relay
|
|
9
|
+
* 2. Viewer generates ECDH P-256 keypair, sends public key to MCP via relay
|
|
10
|
+
* 3. Both sides derive the same shared secret using ECDH
|
|
11
|
+
* 4. Viewer encrypts input events with AES-256-GCM before sending
|
|
12
|
+
* 5. MCP decrypts input events after receiving
|
|
13
|
+
* 6. Frames (screenshots) are NOT encrypted — they show visible page content only
|
|
14
|
+
*/
|
|
15
|
+
import { createECDH, createDecipheriv, createCipheriv, randomBytes } from "node:crypto";
|
|
16
|
+
import createDebug from "debug";
|
|
17
|
+
const debug = createDebug("authloop:crypto");
|
|
18
|
+
export class E2EESession {
|
|
19
|
+
ecdh = createECDH("prime256v1");
|
|
20
|
+
sharedSecret = null;
|
|
21
|
+
_publicKey;
|
|
22
|
+
constructor() {
|
|
23
|
+
this.ecdh.generateKeys();
|
|
24
|
+
this._publicKey = this.ecdh.getPublicKey("base64");
|
|
25
|
+
debug("generated keypair, public key: %s...", this._publicKey.slice(0, 20));
|
|
26
|
+
}
|
|
27
|
+
/** Our public key to send to the viewer */
|
|
28
|
+
get publicKey() {
|
|
29
|
+
return this._publicKey;
|
|
30
|
+
}
|
|
31
|
+
/** Derive shared secret from the viewer's public key */
|
|
32
|
+
deriveSecret(viewerPublicKey) {
|
|
33
|
+
const secret = this.ecdh.computeSecret(Buffer.from(viewerPublicKey, "base64"));
|
|
34
|
+
// Use first 32 bytes as AES-256 key
|
|
35
|
+
this.sharedSecret = secret.subarray(0, 32);
|
|
36
|
+
debug("shared secret derived");
|
|
37
|
+
}
|
|
38
|
+
/** Check if key exchange is complete */
|
|
39
|
+
get ready() {
|
|
40
|
+
return this.sharedSecret !== null;
|
|
41
|
+
}
|
|
42
|
+
/** Decrypt a message from the viewer */
|
|
43
|
+
decrypt(encrypted) {
|
|
44
|
+
if (!this.sharedSecret)
|
|
45
|
+
throw new Error("E2EE not ready — key exchange incomplete");
|
|
46
|
+
const iv = Buffer.from(encrypted.iv, "base64");
|
|
47
|
+
const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
|
|
48
|
+
const tag = Buffer.from(encrypted.tag, "base64");
|
|
49
|
+
const decipher = createDecipheriv("aes-256-gcm", this.sharedSecret, iv);
|
|
50
|
+
decipher.setAuthTag(tag);
|
|
51
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
52
|
+
return decrypted.toString("utf8");
|
|
53
|
+
}
|
|
54
|
+
/** Encrypt a message to the viewer (for testing / future use) */
|
|
55
|
+
encrypt(plaintext) {
|
|
56
|
+
if (!this.sharedSecret)
|
|
57
|
+
throw new Error("E2EE not ready — key exchange incomplete");
|
|
58
|
+
const iv = randomBytes(12);
|
|
59
|
+
const cipher = createCipheriv("aes-256-gcm", this.sharedSecret, iv);
|
|
60
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
61
|
+
const tag = cipher.getAuthTag();
|
|
62
|
+
return {
|
|
63
|
+
iv: iv.toString("base64"),
|
|
64
|
+
ciphertext: ciphertext.toString("base64"),
|
|
65
|
+
tag: tag.toString("base64"),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { startSession, waitForStatus, stopSession, _resetActiveSession, _getActiveSession, type ToHumanInput, type StartSessionOutput, type SessionStatusOutput, type SessionStatus, } from "./session.js";
|
|
2
|
+
export { BrowserStream, type StreamResult } from "./stream.js";
|
|
3
|
+
export { CdpClient } from "./cdp.js";
|
|
4
|
+
export { E2EESession } from "./crypto.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { startSession, waitForStatus, stopSession, _resetActiveSession, _getActiveSession, } from "./session.js";
|
|
2
|
+
export { BrowserStream } from "./stream.js";
|
|
3
|
+
export { CdpClient } from "./cdp.js";
|
|
4
|
+
export { E2EESession } from "./crypto.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,iBAAiB,GAKlB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle: startSession → (agent sends URL) → waitForStatus → resolved → cleanup
|
|
3
|
+
*
|
|
4
|
+
* startSession() creates the session, connects WebSocket, starts CDP screencast in the
|
|
5
|
+
* background, and returns immediately with the session_url for the agent to send to the human.
|
|
6
|
+
*
|
|
7
|
+
* waitForStatus() blocks until the session reaches a terminal state (resolved/cancelled/timeout/error).
|
|
8
|
+
*
|
|
9
|
+
* stopSession() cleans up the stream, WebSocket, and CDP connection.
|
|
10
|
+
*/
|
|
11
|
+
import { AuthLoop } from "@authloop-ai/sdk";
|
|
12
|
+
import { BrowserStream, type StreamResult } from "./stream.js";
|
|
13
|
+
export interface ToHumanInput {
|
|
14
|
+
service: string;
|
|
15
|
+
cdpUrl: string;
|
|
16
|
+
context?: {
|
|
17
|
+
url?: string;
|
|
18
|
+
blockerType?: "otp" | "password" | "captcha" | "security_question" | "document_upload" | "other";
|
|
19
|
+
hint?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface StartSessionOutput {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
sessionUrl: string;
|
|
25
|
+
}
|
|
26
|
+
export type SessionStatus = "streaming" | StreamResult;
|
|
27
|
+
export interface SessionStatusOutput {
|
|
28
|
+
sessionId: string;
|
|
29
|
+
sessionUrl: string;
|
|
30
|
+
status: SessionStatus;
|
|
31
|
+
}
|
|
32
|
+
interface ActiveSession {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
sessionUrl: string;
|
|
35
|
+
status: SessionStatus;
|
|
36
|
+
authloop: AuthLoop;
|
|
37
|
+
stream: BrowserStream | null;
|
|
38
|
+
ws: WebSocket;
|
|
39
|
+
cdpUrl: string;
|
|
40
|
+
startTime: number;
|
|
41
|
+
/** Resolves when status changes from "streaming" to a terminal state */
|
|
42
|
+
onTerminal: Promise<StreamResult>;
|
|
43
|
+
resolveTerminal: ((result: StreamResult) => void) | null;
|
|
44
|
+
}
|
|
45
|
+
/** @internal — exposed for testing only */
|
|
46
|
+
export declare function _resetActiveSession(): void;
|
|
47
|
+
/** @internal — exposed for testing only */
|
|
48
|
+
export declare function _getActiveSession(): ActiveSession | null;
|
|
49
|
+
/**
|
|
50
|
+
* Start a session: create via API, connect WebSocket, start CDP screencast in background.
|
|
51
|
+
* Returns immediately with session_url for the agent to send to the human.
|
|
52
|
+
*/
|
|
53
|
+
export declare function startSession(authloop: AuthLoop, options: ToHumanInput): Promise<StartSessionOutput>;
|
|
54
|
+
/**
|
|
55
|
+
* Block until the session reaches a terminal state.
|
|
56
|
+
* Returns null if no session is active.
|
|
57
|
+
* Auto-cleans up after returning.
|
|
58
|
+
*/
|
|
59
|
+
export declare function waitForStatus(): Promise<SessionStatusOutput | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Stop the active session and clean up all resources.
|
|
62
|
+
*/
|
|
63
|
+
export declare function stopSession(): Promise<void>;
|
|
64
|
+
export {};
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle: startSession → (agent sends URL) → waitForStatus → resolved → cleanup
|
|
3
|
+
*
|
|
4
|
+
* startSession() creates the session, connects WebSocket, starts CDP screencast in the
|
|
5
|
+
* background, and returns immediately with the session_url for the agent to send to the human.
|
|
6
|
+
*
|
|
7
|
+
* waitForStatus() blocks until the session reaches a terminal state (resolved/cancelled/timeout/error).
|
|
8
|
+
*
|
|
9
|
+
* stopSession() cleans up the stream, WebSocket, and CDP connection.
|
|
10
|
+
*/
|
|
11
|
+
import createDebug from "debug";
|
|
12
|
+
import { BrowserStream } from "./stream.js";
|
|
13
|
+
const debug = createDebug("authloop:session");
|
|
14
|
+
const perf = createDebug("authloop:perf");
|
|
15
|
+
let active = null;
|
|
16
|
+
/** @internal — exposed for testing only */
|
|
17
|
+
export function _resetActiveSession() {
|
|
18
|
+
active = null;
|
|
19
|
+
}
|
|
20
|
+
/** @internal — exposed for testing only */
|
|
21
|
+
export function _getActiveSession() {
|
|
22
|
+
return active;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Transition the active session to a terminal status.
|
|
26
|
+
* Resolves the terminal promise so waitForStatus() unblocks.
|
|
27
|
+
*/
|
|
28
|
+
function setTerminalStatus(result) {
|
|
29
|
+
if (!active || active.status !== "streaming")
|
|
30
|
+
return;
|
|
31
|
+
debug("terminal status: %s", result);
|
|
32
|
+
active.status = result;
|
|
33
|
+
active.resolveTerminal?.(result);
|
|
34
|
+
active.resolveTerminal = null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Start a session: create via API, connect WebSocket, start CDP screencast in background.
|
|
38
|
+
* Returns immediately with session_url for the agent to send to the human.
|
|
39
|
+
*/
|
|
40
|
+
export async function startSession(authloop, options) {
|
|
41
|
+
if (active) {
|
|
42
|
+
throw new Error("A session is already in progress");
|
|
43
|
+
}
|
|
44
|
+
debug("startSession: service=%s", options.service);
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
// 1. Create session via SDK
|
|
47
|
+
const session = await authloop.toHuman({
|
|
48
|
+
service: options.service,
|
|
49
|
+
cdpUrl: options.cdpUrl,
|
|
50
|
+
context: options.context,
|
|
51
|
+
});
|
|
52
|
+
debug("session created: id=%s url=%s", session.sessionId, session.sessionUrl);
|
|
53
|
+
perf("[perf:session] session created: %dms", Date.now() - startTime);
|
|
54
|
+
// 2. Connect WebSocket immediately
|
|
55
|
+
const wsUrl = `${session.streamUrl}?token=${encodeURIComponent(session.streamToken)}&role=agent`;
|
|
56
|
+
debug("connecting to relay WebSocket");
|
|
57
|
+
const wsConnectStart = Date.now();
|
|
58
|
+
const ws = await new Promise((resolve, reject) => {
|
|
59
|
+
const socket = new WebSocket(wsUrl);
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
socket.close();
|
|
62
|
+
reject(new Error("WebSocket connection timed out after 15s"));
|
|
63
|
+
}, 15000);
|
|
64
|
+
socket.addEventListener("open", () => {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
resolve(socket);
|
|
67
|
+
});
|
|
68
|
+
socket.addEventListener("error", (e) => {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
reject(new Error(`WebSocket error: ${e.message ?? "connection failed"}`));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
perf("[perf:stream] WebSocket connect: %dms", Date.now() - wsConnectStart);
|
|
74
|
+
debug("relay connected");
|
|
75
|
+
// 3. Set up terminal promise
|
|
76
|
+
let resolveTerminal = null;
|
|
77
|
+
const onTerminal = new Promise((resolve) => {
|
|
78
|
+
resolveTerminal = resolve;
|
|
79
|
+
});
|
|
80
|
+
// 4. Set up active session
|
|
81
|
+
active = {
|
|
82
|
+
sessionId: session.sessionId,
|
|
83
|
+
sessionUrl: session.sessionUrl,
|
|
84
|
+
status: "streaming",
|
|
85
|
+
authloop,
|
|
86
|
+
stream: null,
|
|
87
|
+
ws,
|
|
88
|
+
cdpUrl: options.cdpUrl,
|
|
89
|
+
startTime,
|
|
90
|
+
onTerminal,
|
|
91
|
+
resolveTerminal,
|
|
92
|
+
};
|
|
93
|
+
// 5. Listen for events in background
|
|
94
|
+
ws.addEventListener("message", (event) => {
|
|
95
|
+
if (!active || typeof event.data !== "string")
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
const msg = JSON.parse(event.data);
|
|
99
|
+
if (msg.type === "viewer_connected" && !active.stream) {
|
|
100
|
+
debug("viewer connected, starting browser stream");
|
|
101
|
+
startStreaming().catch((err) => {
|
|
102
|
+
debug("failed to start streaming: %s", err.message);
|
|
103
|
+
setTerminalStatus("error");
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (msg.type === "session_expired") {
|
|
107
|
+
debug("session expired");
|
|
108
|
+
setTerminalStatus("timeout");
|
|
109
|
+
}
|
|
110
|
+
else if (msg.type === "session_cancelled") {
|
|
111
|
+
debug("session cancelled");
|
|
112
|
+
setTerminalStatus("cancelled");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// ignore parse errors
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
ws.addEventListener("close", () => {
|
|
120
|
+
if (active && active.status === "streaming") {
|
|
121
|
+
debug("relay WebSocket closed unexpectedly");
|
|
122
|
+
setTerminalStatus("error");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
perf("[perf:session] startSession total: %dms", Date.now() - startTime);
|
|
126
|
+
return {
|
|
127
|
+
sessionId: session.sessionId,
|
|
128
|
+
sessionUrl: session.sessionUrl,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Start CDP screencast and wire up resolution events.
|
|
133
|
+
* Called automatically when viewer connects.
|
|
134
|
+
*/
|
|
135
|
+
async function startStreaming() {
|
|
136
|
+
if (!active)
|
|
137
|
+
return;
|
|
138
|
+
const stream = new BrowserStream({
|
|
139
|
+
ws: active.ws,
|
|
140
|
+
cdpUrl: active.cdpUrl,
|
|
141
|
+
});
|
|
142
|
+
active.stream = stream;
|
|
143
|
+
await stream.start();
|
|
144
|
+
debug("browser stream started");
|
|
145
|
+
// Wait for resolution in background
|
|
146
|
+
stream.waitForResolution().then(async (result) => {
|
|
147
|
+
if (!active)
|
|
148
|
+
return;
|
|
149
|
+
debug("stream result: %s", result);
|
|
150
|
+
// Tell the API the outcome
|
|
151
|
+
if (result === "resolved") {
|
|
152
|
+
debug("resolving session %s", active.sessionId);
|
|
153
|
+
await active.authloop.resolveSession(active.sessionId).catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
else if (result === "cancelled") {
|
|
156
|
+
debug("cancelling session %s", active.sessionId);
|
|
157
|
+
await active.authloop.cancelSession(active.sessionId).catch(() => { });
|
|
158
|
+
}
|
|
159
|
+
setTerminalStatus(result);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Block until the session reaches a terminal state.
|
|
164
|
+
* Returns null if no session is active.
|
|
165
|
+
* Auto-cleans up after returning.
|
|
166
|
+
*/
|
|
167
|
+
export async function waitForStatus() {
|
|
168
|
+
if (!active)
|
|
169
|
+
return null;
|
|
170
|
+
debug("waitForStatus: waiting for terminal status...");
|
|
171
|
+
// If already terminal, return immediately
|
|
172
|
+
if (active.status !== "streaming") {
|
|
173
|
+
const result = {
|
|
174
|
+
sessionId: active.sessionId,
|
|
175
|
+
sessionUrl: active.sessionUrl,
|
|
176
|
+
status: active.status,
|
|
177
|
+
};
|
|
178
|
+
debug("waitForStatus: already terminal=%s", active.status);
|
|
179
|
+
await cleanup();
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
// Block until terminal
|
|
183
|
+
const terminalResult = await active.onTerminal;
|
|
184
|
+
// active may have been cleared by stopSession() during the wait
|
|
185
|
+
if (!active)
|
|
186
|
+
return null;
|
|
187
|
+
const result = {
|
|
188
|
+
sessionId: active.sessionId,
|
|
189
|
+
sessionUrl: active.sessionUrl,
|
|
190
|
+
status: terminalResult,
|
|
191
|
+
};
|
|
192
|
+
debug("waitForStatus: resolved with status=%s", terminalResult);
|
|
193
|
+
await cleanup();
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Stop the active session and clean up all resources.
|
|
198
|
+
*/
|
|
199
|
+
export async function stopSession() {
|
|
200
|
+
if (!active)
|
|
201
|
+
return;
|
|
202
|
+
debug("stopSession: sessionId=%s", active.sessionId);
|
|
203
|
+
// Cancel on the API if still streaming
|
|
204
|
+
if (active.status === "streaming") {
|
|
205
|
+
await active.authloop.cancelSession(active.sessionId).catch(() => { });
|
|
206
|
+
}
|
|
207
|
+
// Resolve the terminal promise so waitForStatus() unblocks
|
|
208
|
+
setTerminalStatus("cancelled");
|
|
209
|
+
await cleanup();
|
|
210
|
+
}
|
|
211
|
+
async function cleanup() {
|
|
212
|
+
if (!active)
|
|
213
|
+
return;
|
|
214
|
+
const session = active;
|
|
215
|
+
active = null;
|
|
216
|
+
await session.stream?.stop();
|
|
217
|
+
if (!session.stream) {
|
|
218
|
+
session.ws.close();
|
|
219
|
+
}
|
|
220
|
+
perf("[perf:session] session duration: %ds", Math.round((Date.now() - session.startTime) / 1000));
|
|
221
|
+
debug("session cleaned up");
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket + CDP bridge.
|
|
3
|
+
* Captures browser screencast frames via CDP, sends them as binary JPEG over WebSocket.
|
|
4
|
+
* Receives input events (clicks, keystrokes, scroll) from the human via WebSocket,
|
|
5
|
+
* dispatches them to the browser via CDP.
|
|
6
|
+
*
|
|
7
|
+
* The WebSocket is pre-connected by session.ts and passed in — this class does not
|
|
8
|
+
* manage the WebSocket connection lifecycle.
|
|
9
|
+
*/
|
|
10
|
+
export type StreamResult = "resolved" | "cancelled" | "error" | "timeout";
|
|
11
|
+
export declare class BrowserStream {
|
|
12
|
+
private opts;
|
|
13
|
+
private ws;
|
|
14
|
+
private cdp;
|
|
15
|
+
private e2ee;
|
|
16
|
+
private resolveWait;
|
|
17
|
+
private stopped;
|
|
18
|
+
private frameCount;
|
|
19
|
+
private lastFrameData;
|
|
20
|
+
private startTime;
|
|
21
|
+
private firstFrameSent;
|
|
22
|
+
constructor(opts: {
|
|
23
|
+
ws: WebSocket;
|
|
24
|
+
cdpUrl: string;
|
|
25
|
+
});
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
waitForResolution(): Promise<StreamResult>;
|
|
28
|
+
stop(): Promise<void>;
|
|
29
|
+
private handleMessage;
|
|
30
|
+
private dispatchMouseClick;
|
|
31
|
+
private dispatchKeyEvent;
|
|
32
|
+
/** Convert modifier flags to CDP bitmask: Alt=1, Ctrl=2, Meta=4, Shift=8 */
|
|
33
|
+
private getModifiers;
|
|
34
|
+
private dispatchPaste;
|
|
35
|
+
private dispatchScroll;
|
|
36
|
+
}
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket + CDP bridge.
|
|
3
|
+
* Captures browser screencast frames via CDP, sends them as binary JPEG over WebSocket.
|
|
4
|
+
* Receives input events (clicks, keystrokes, scroll) from the human via WebSocket,
|
|
5
|
+
* dispatches them to the browser via CDP.
|
|
6
|
+
*
|
|
7
|
+
* The WebSocket is pre-connected by session.ts and passed in — this class does not
|
|
8
|
+
* manage the WebSocket connection lifecycle.
|
|
9
|
+
*/
|
|
10
|
+
import createDebug from "debug";
|
|
11
|
+
import { CdpClient } from "./cdp.js";
|
|
12
|
+
import { E2EESession } from "./crypto.js";
|
|
13
|
+
const debug = createDebug("authloop:stream");
|
|
14
|
+
const perf = createDebug("authloop:perf");
|
|
15
|
+
/** Windows virtual key codes for CDP Input.dispatchKeyEvent */
|
|
16
|
+
const KEY_CODES = {
|
|
17
|
+
Backspace: 8, Tab: 9, Enter: 13, Shift: 16, Control: 17, Alt: 18,
|
|
18
|
+
Escape: 27, " ": 32, PageUp: 33, PageDown: 34, End: 35, Home: 36,
|
|
19
|
+
ArrowLeft: 37, ArrowUp: 38, ArrowRight: 39, ArrowDown: 40,
|
|
20
|
+
Insert: 45, Delete: 46,
|
|
21
|
+
Meta: 91, ContextMenu: 93,
|
|
22
|
+
F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117,
|
|
23
|
+
F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123,
|
|
24
|
+
};
|
|
25
|
+
export class BrowserStream {
|
|
26
|
+
opts;
|
|
27
|
+
ws;
|
|
28
|
+
cdp = null;
|
|
29
|
+
e2ee = new E2EESession();
|
|
30
|
+
resolveWait = null;
|
|
31
|
+
stopped = false;
|
|
32
|
+
frameCount = 0;
|
|
33
|
+
lastFrameData = null;
|
|
34
|
+
startTime = 0;
|
|
35
|
+
firstFrameSent = false;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
this.ws = opts.ws;
|
|
39
|
+
}
|
|
40
|
+
async start() {
|
|
41
|
+
this.startTime = Date.now();
|
|
42
|
+
// 1. Connect to CDP
|
|
43
|
+
debug("connecting to CDP: %s", this.opts.cdpUrl);
|
|
44
|
+
let t0 = Date.now();
|
|
45
|
+
this.cdp = new CdpClient(this.opts.cdpUrl);
|
|
46
|
+
this.cdp.onClose(() => {
|
|
47
|
+
if (!this.stopped) {
|
|
48
|
+
debug("CDP disconnected unexpectedly");
|
|
49
|
+
this.resolveWait?.("error");
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
await this.cdp.connect();
|
|
53
|
+
perf("[perf:stream] CDP connect: %dms", Date.now() - t0);
|
|
54
|
+
debug("CDP connected");
|
|
55
|
+
// 2. Send our E2EE public key so the viewer can derive the shared secret
|
|
56
|
+
this.ws.send(JSON.stringify({ type: "pubkey", key: this.e2ee.publicKey }));
|
|
57
|
+
debug("sent E2EE public key");
|
|
58
|
+
// 3. Listen for messages from the relay (input events + control)
|
|
59
|
+
this.ws.addEventListener("message", (event) => {
|
|
60
|
+
if (this.stopped)
|
|
61
|
+
return;
|
|
62
|
+
if (typeof event.data === "string") {
|
|
63
|
+
try {
|
|
64
|
+
const msg = JSON.parse(event.data);
|
|
65
|
+
this.handleMessage(msg);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
debug("failed to parse message (dropped)");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
this.ws.addEventListener("close", () => {
|
|
73
|
+
if (!this.stopped) {
|
|
74
|
+
debug("relay WebSocket closed unexpectedly");
|
|
75
|
+
this.resolveWait?.("error");
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// 4. Forward CDP screencast frames as binary JPEG with metadata header
|
|
79
|
+
this.cdp.on("Page.screencastFrame", (params) => {
|
|
80
|
+
if (this.stopped)
|
|
81
|
+
return;
|
|
82
|
+
const sessionId = params.sessionId;
|
|
83
|
+
const data = params.data;
|
|
84
|
+
const metadata = params.metadata;
|
|
85
|
+
// ACK so CDP sends the next frame
|
|
86
|
+
this.cdp?.send("Page.screencastFrameAck", { sessionId }).catch(() => { });
|
|
87
|
+
// Send frame with metadata as a JSON message, then binary JPEG
|
|
88
|
+
const jpegBuffer = Buffer.from(data, "base64");
|
|
89
|
+
const metaJson = JSON.stringify({
|
|
90
|
+
type: "frame",
|
|
91
|
+
deviceWidth: metadata?.deviceWidth,
|
|
92
|
+
deviceHeight: metadata?.deviceHeight,
|
|
93
|
+
offsetTop: metadata?.offsetTop ?? 0,
|
|
94
|
+
pageScaleFactor: metadata?.pageScaleFactor ?? 1,
|
|
95
|
+
scrollOffsetX: metadata?.scrollOffsetX ?? 0,
|
|
96
|
+
scrollOffsetY: metadata?.scrollOffsetY ?? 0,
|
|
97
|
+
jpegSize: jpegBuffer.byteLength,
|
|
98
|
+
});
|
|
99
|
+
// Cache latest frame so we can send it when a viewer connects
|
|
100
|
+
this.lastFrameData = { meta: metaJson, jpeg: jpegBuffer };
|
|
101
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
102
|
+
this.ws.send(metaJson);
|
|
103
|
+
this.ws.send(jpegBuffer);
|
|
104
|
+
this.frameCount++;
|
|
105
|
+
if (!this.firstFrameSent) {
|
|
106
|
+
this.firstFrameSent = true;
|
|
107
|
+
const frameTime = Date.now();
|
|
108
|
+
perf("[perf:stream] CDP → first screencast frame: %dms", frameTime - this.startTime);
|
|
109
|
+
perf("[perf:stream] first frame → first relay send: %dms", Date.now() - frameTime);
|
|
110
|
+
}
|
|
111
|
+
if (this.frameCount % 100 === 0) {
|
|
112
|
+
debug("sent %d frames", this.frameCount);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// 5. Start screencast
|
|
117
|
+
await this.cdp.send("Page.startScreencast", {
|
|
118
|
+
format: "jpeg",
|
|
119
|
+
quality: 60,
|
|
120
|
+
maxWidth: 1280,
|
|
121
|
+
maxHeight: 720,
|
|
122
|
+
everyNthFrame: 1,
|
|
123
|
+
});
|
|
124
|
+
debug("screencast started");
|
|
125
|
+
perf("[perf:stream] total start() time: %dms", Date.now() - this.startTime);
|
|
126
|
+
}
|
|
127
|
+
waitForResolution() {
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
if (this.stopped) {
|
|
130
|
+
resolve("error");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.resolveWait = resolve;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async stop() {
|
|
137
|
+
if (this.stopped)
|
|
138
|
+
return;
|
|
139
|
+
this.stopped = true;
|
|
140
|
+
debug("stopping stream (sent %d frames)", this.frameCount);
|
|
141
|
+
perf("[perf:stream] frames published: %d", this.frameCount);
|
|
142
|
+
perf("[perf:stream] session duration: %ds", Math.round((Date.now() - this.startTime) / 1000));
|
|
143
|
+
this.cdp?.send("Page.stopScreencast").catch(() => { });
|
|
144
|
+
this.cdp?.close();
|
|
145
|
+
this.cdp = null;
|
|
146
|
+
this.ws.close();
|
|
147
|
+
debug("stream stopped");
|
|
148
|
+
}
|
|
149
|
+
handleMessage(msg) {
|
|
150
|
+
const type = msg.type;
|
|
151
|
+
// Handle E2EE key exchange
|
|
152
|
+
if (type === "pubkey") {
|
|
153
|
+
debug("received viewer public key, deriving shared secret");
|
|
154
|
+
this.e2ee.deriveSecret(msg.key);
|
|
155
|
+
perf("[perf:stream] E2EE key exchange: %dms", Date.now() - this.startTime);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Handle encrypted messages — decrypt then process as normal
|
|
159
|
+
if (type === "encrypted") {
|
|
160
|
+
if (!this.e2ee.ready) {
|
|
161
|
+
debug("received encrypted message but E2EE not ready, dropping");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const plaintext = this.e2ee.decrypt(msg.payload);
|
|
166
|
+
const decrypted = JSON.parse(plaintext);
|
|
167
|
+
debug("decrypted: %s", decrypted.type);
|
|
168
|
+
this.handleMessage(decrypted);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
debug("decryption failed: %s", err.message);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Control messages from the relay (always plaintext)
|
|
176
|
+
switch (type) {
|
|
177
|
+
case "session_expired":
|
|
178
|
+
debug("session expired");
|
|
179
|
+
this.resolveWait?.("timeout");
|
|
180
|
+
return;
|
|
181
|
+
case "session_cancelled":
|
|
182
|
+
debug("session cancelled");
|
|
183
|
+
this.resolveWait?.("cancelled");
|
|
184
|
+
return;
|
|
185
|
+
case "viewer_connected":
|
|
186
|
+
debug("viewer connected");
|
|
187
|
+
if (this.lastFrameData && this.ws.readyState === WebSocket.OPEN) {
|
|
188
|
+
debug("sending cached frame to new viewer");
|
|
189
|
+
this.ws.send(this.lastFrameData.meta);
|
|
190
|
+
this.ws.send(this.lastFrameData.jpeg);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
case "viewer_disconnected":
|
|
194
|
+
debug("viewer disconnected");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Input events — must be decrypted (no plaintext fallback)
|
|
198
|
+
// These arrive only after E2EE decrypt (recursive handleMessage call)
|
|
199
|
+
switch (type) {
|
|
200
|
+
case "click":
|
|
201
|
+
case "dblclick":
|
|
202
|
+
this.dispatchMouseClick(msg);
|
|
203
|
+
break;
|
|
204
|
+
case "keydown":
|
|
205
|
+
case "keyup":
|
|
206
|
+
case "keypress":
|
|
207
|
+
this.dispatchKeyEvent(msg);
|
|
208
|
+
break;
|
|
209
|
+
case "scroll":
|
|
210
|
+
this.dispatchScroll(msg);
|
|
211
|
+
break;
|
|
212
|
+
case "paste":
|
|
213
|
+
this.dispatchPaste(msg);
|
|
214
|
+
break;
|
|
215
|
+
case "back":
|
|
216
|
+
debug("navigate back");
|
|
217
|
+
this.cdp?.send("Runtime.evaluate", { expression: "history.back()" }).catch(() => { });
|
|
218
|
+
break;
|
|
219
|
+
case "forward":
|
|
220
|
+
debug("navigate forward");
|
|
221
|
+
this.cdp?.send("Runtime.evaluate", { expression: "history.forward()" }).catch(() => { });
|
|
222
|
+
break;
|
|
223
|
+
case "reload":
|
|
224
|
+
debug("reload page");
|
|
225
|
+
this.cdp?.send("Page.reload").catch(() => { });
|
|
226
|
+
break;
|
|
227
|
+
case "resolved":
|
|
228
|
+
debug("viewer marked auth as complete");
|
|
229
|
+
this.resolveWait?.("resolved");
|
|
230
|
+
break;
|
|
231
|
+
case "cancelled":
|
|
232
|
+
debug("viewer cancelled the session");
|
|
233
|
+
this.resolveWait?.("cancelled");
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
debug("unknown message type: %s (dropped)", type);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
dispatchMouseClick(msg) {
|
|
241
|
+
if (!this.cdp)
|
|
242
|
+
return;
|
|
243
|
+
const x = msg.x;
|
|
244
|
+
const y = msg.y;
|
|
245
|
+
const button = msg.button || "left";
|
|
246
|
+
const clickCount = msg.type === "dblclick" ? 2 : 1;
|
|
247
|
+
this.cdp
|
|
248
|
+
.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button, clickCount })
|
|
249
|
+
.then(() => this.cdp?.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button, clickCount }))
|
|
250
|
+
.catch(() => { });
|
|
251
|
+
}
|
|
252
|
+
dispatchKeyEvent(msg) {
|
|
253
|
+
if (!this.cdp)
|
|
254
|
+
return;
|
|
255
|
+
const type = msg.type;
|
|
256
|
+
const key = msg.key;
|
|
257
|
+
const code = msg.code;
|
|
258
|
+
const modifiers = this.getModifiers(msg);
|
|
259
|
+
const keyCode = key ? KEY_CODES[key] ?? 0 : 0;
|
|
260
|
+
if (type === "keypress") {
|
|
261
|
+
// Printable character — send char only (viewer sends keyup separately)
|
|
262
|
+
const text = key && key.length === 1 ? key : undefined;
|
|
263
|
+
if (text) {
|
|
264
|
+
this.cdp.send("Input.dispatchKeyEvent", {
|
|
265
|
+
type: "char", text, modifiers,
|
|
266
|
+
}).catch(() => { });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else if (type === "keydown") {
|
|
270
|
+
// Special keys (Backspace, Enter, arrows, etc.) or modified keys (Ctrl+A)
|
|
271
|
+
const isModifiedPrintable = key && key.length === 1 && modifiers > 0;
|
|
272
|
+
const text = isModifiedPrintable ? key : undefined;
|
|
273
|
+
this.cdp.send("Input.dispatchKeyEvent", {
|
|
274
|
+
type: "rawKeyDown", key, code, text, modifiers,
|
|
275
|
+
windowsVirtualKeyCode: keyCode || (key && key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0),
|
|
276
|
+
nativeVirtualKeyCode: keyCode || (key && key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0),
|
|
277
|
+
}).catch(() => { });
|
|
278
|
+
}
|
|
279
|
+
else if (type === "keyup") {
|
|
280
|
+
this.cdp.send("Input.dispatchKeyEvent", {
|
|
281
|
+
type: "keyUp", key, code, modifiers,
|
|
282
|
+
windowsVirtualKeyCode: keyCode || (key && key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0),
|
|
283
|
+
nativeVirtualKeyCode: keyCode || (key && key.length === 1 ? key.toUpperCase().charCodeAt(0) : 0),
|
|
284
|
+
}).catch(() => { });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/** Convert modifier flags to CDP bitmask: Alt=1, Ctrl=2, Meta=4, Shift=8 */
|
|
288
|
+
getModifiers(msg) {
|
|
289
|
+
let m = 0;
|
|
290
|
+
if (msg.altKey)
|
|
291
|
+
m |= 1;
|
|
292
|
+
if (msg.ctrlKey)
|
|
293
|
+
m |= 2;
|
|
294
|
+
if (msg.metaKey)
|
|
295
|
+
m |= 4;
|
|
296
|
+
if (msg.shiftKey)
|
|
297
|
+
m |= 8;
|
|
298
|
+
return m;
|
|
299
|
+
}
|
|
300
|
+
dispatchPaste(msg) {
|
|
301
|
+
if (!this.cdp)
|
|
302
|
+
return;
|
|
303
|
+
const text = msg.text;
|
|
304
|
+
if (text) {
|
|
305
|
+
debug("paste: %d chars", text.length);
|
|
306
|
+
this.cdp.send("Input.insertText", { text }).catch(() => { });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
dispatchScroll(msg) {
|
|
310
|
+
if (!this.cdp)
|
|
311
|
+
return;
|
|
312
|
+
const x = msg.x;
|
|
313
|
+
const y = msg.y;
|
|
314
|
+
const deltaX = msg.deltaX || 0;
|
|
315
|
+
const deltaY = msg.deltaY || 0;
|
|
316
|
+
this.cdp
|
|
317
|
+
.send("Input.dispatchMouseEvent", { type: "mouseWheel", x, y, deltaX, deltaY })
|
|
318
|
+
.catch(() => { });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authloop-ai/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Core engine for AuthLoop — CDP screencast, E2EE, WebSocket relay",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "AuthLoop <hello@authloop.ai> (https://authloop.ai)",
|
|
8
|
+
"homepage": "https://github.com/authloop-ai/authloop/tree/main/packages/core#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/authloop-ai/authloop.git",
|
|
12
|
+
"directory": "packages/core"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/authloop-ai/authloop/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"authloop",
|
|
19
|
+
"authentication",
|
|
20
|
+
"cdp",
|
|
21
|
+
"screencast",
|
|
22
|
+
"e2ee",
|
|
23
|
+
"websocket",
|
|
24
|
+
"human-in-the-loop"
|
|
25
|
+
],
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"debug": "^4.4.3",
|
|
44
|
+
"@authloop-ai/sdk": "0.2.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/debug": "^4.1.12",
|
|
48
|
+
"@types/node": "^22.0.0",
|
|
49
|
+
"typescript": "5.9.3"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc",
|
|
53
|
+
"check-types": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run"
|
|
55
|
+
}
|
|
56
|
+
}
|