@cjhyy/code-shell-server 0.8.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.
Files changed (70) hide show
  1. package/README.md +77 -0
  2. package/dist/attachment-service.d.ts +80 -0
  3. package/dist/attachment-service.js +587 -0
  4. package/dist/bin/code-shell-serve.d.ts +2 -0
  5. package/dist/bin/code-shell-serve.js +7 -0
  6. package/dist/client-message-id.d.ts +1 -0
  7. package/dist/client-message-id.js +8 -0
  8. package/dist/image-byte-probe.d.ts +17 -0
  9. package/dist/image-byte-probe.js +330 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +10 -0
  12. package/dist/index.mobile-remote.d.ts +21 -0
  13. package/dist/index.mobile-remote.js +21 -0
  14. package/dist/index.serve.d.ts +3 -0
  15. package/dist/index.serve.js +3 -0
  16. package/dist/index.storage.d.ts +10 -0
  17. package/dist/index.storage.js +10 -0
  18. package/dist/index.worker.d.ts +2 -0
  19. package/dist/index.worker.js +2 -0
  20. package/dist/mobile-remote/access-passcode.d.ts +80 -0
  21. package/dist/mobile-remote/access-passcode.js +354 -0
  22. package/dist/mobile-remote/cloudflared-binary.d.ts +76 -0
  23. package/dist/mobile-remote/cloudflared-binary.js +242 -0
  24. package/dist/mobile-remote/codex-parse.d.ts +34 -0
  25. package/dist/mobile-remote/codex-parse.js +131 -0
  26. package/dist/mobile-remote/codex-room-agent.d.ts +94 -0
  27. package/dist/mobile-remote/codex-room-agent.js +213 -0
  28. package/dist/mobile-remote/mobile-attachments.d.ts +21 -0
  29. package/dist/mobile-remote/mobile-attachments.js +119 -0
  30. package/dist/mobile-remote/mobile-chat-turn.d.ts +32 -0
  31. package/dist/mobile-remote/mobile-chat-turn.js +67 -0
  32. package/dist/mobile-remote/mobile-history.d.ts +22 -0
  33. package/dist/mobile-remote/mobile-history.js +27 -0
  34. package/dist/mobile-remote/mobile-run-dispatch.d.ts +17 -0
  35. package/dist/mobile-remote/mobile-run-dispatch.js +56 -0
  36. package/dist/mobile-remote/mobile-static.d.ts +43 -0
  37. package/dist/mobile-remote/mobile-static.js +154 -0
  38. package/dist/mobile-remote/mobile-upload-service.d.ts +57 -0
  39. package/dist/mobile-remote/mobile-upload-service.js +370 -0
  40. package/dist/mobile-remote/pairing.d.ts +8 -0
  41. package/dist/mobile-remote/pairing.js +23 -0
  42. package/dist/mobile-remote/path-bins.d.ts +7 -0
  43. package/dist/mobile-remote/path-bins.js +16 -0
  44. package/dist/mobile-remote/pending-approvals.d.ts +14 -0
  45. package/dist/mobile-remote/pending-approvals.js +54 -0
  46. package/dist/mobile-remote/remote-host-manager.d.ts +117 -0
  47. package/dist/mobile-remote/remote-host-manager.js +345 -0
  48. package/dist/mobile-remote/resident-agent.d.ts +98 -0
  49. package/dist/mobile-remote/resident-agent.js +216 -0
  50. package/dist/mobile-remote/room-manager.d.ts +286 -0
  51. package/dist/mobile-remote/room-manager.js +765 -0
  52. package/dist/mobile-remote/trusted-device-store.d.ts +19 -0
  53. package/dist/mobile-remote/trusted-device-store.js +118 -0
  54. package/dist/mobile-remote/tunnel-manager.d.ts +105 -0
  55. package/dist/mobile-remote/tunnel-manager.js +375 -0
  56. package/dist/mobile-remote/types.d.ts +1 -0
  57. package/dist/mobile-remote/types.js +1 -0
  58. package/dist/mobile-remote/viewer-identity.d.ts +5 -0
  59. package/dist/mobile-remote/viewer-identity.js +5 -0
  60. package/dist/serve/cli.d.ts +15 -0
  61. package/dist/serve/cli.js +115 -0
  62. package/dist/serve/headless-server.d.ts +40 -0
  63. package/dist/serve/headless-server.js +276 -0
  64. package/dist/sessions-service.d.ts +73 -0
  65. package/dist/sessions-service.js +303 -0
  66. package/dist/static-files.d.ts +1 -0
  67. package/dist/static-files.js +25 -0
  68. package/dist/worker-bridge-core.d.ts +154 -0
  69. package/dist/worker-bridge-core.js +315 -0
  70. package/package.json +63 -0
@@ -0,0 +1,330 @@
1
+ const PNG_SIGNATURE = Buffer.from("89504e470d0a1a0a", "hex");
2
+ const JPEG_SOF_MARKERS = new Set([
3
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
4
+ ]);
5
+ function invalid(mime, detail) {
6
+ throw new Error(`invalid ${mime} image structure: ${detail}`);
7
+ }
8
+ function ascii(buffer, start, length) {
9
+ return buffer.toString("ascii", start, start + length);
10
+ }
11
+ function detectMime(buffer) {
12
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(PNG_SIGNATURE))
13
+ return "image/png";
14
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
15
+ return "image/jpeg";
16
+ }
17
+ if (buffer.length >= 6 &&
18
+ (ascii(buffer, 0, 6) === "GIF87a" || ascii(buffer, 0, 6) === "GIF89a")) {
19
+ return "image/gif";
20
+ }
21
+ if (buffer.length >= 12 && ascii(buffer, 0, 4) === "RIFF" && ascii(buffer, 8, 4) === "WEBP") {
22
+ return "image/webp";
23
+ }
24
+ return null;
25
+ }
26
+ function crc32(buffer, start, end) {
27
+ let crc = 0xffffffff;
28
+ for (let index = start; index < end; index += 1) {
29
+ crc ^= buffer[index];
30
+ for (let bit = 0; bit < 8; bit += 1) {
31
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
32
+ }
33
+ }
34
+ return (crc ^ 0xffffffff) >>> 0;
35
+ }
36
+ function probePng(buffer) {
37
+ if (buffer.length < 33)
38
+ invalid("PNG", "truncated header");
39
+ let offset = 8;
40
+ let width = 0;
41
+ let height = 0;
42
+ let chunks = 0;
43
+ let sawData = false;
44
+ while (offset < buffer.length) {
45
+ if (offset + 12 > buffer.length)
46
+ invalid("PNG", "truncated chunk header");
47
+ const length = buffer.readUInt32BE(offset);
48
+ const typeStart = offset + 4;
49
+ const dataStart = typeStart + 4;
50
+ const dataEnd = dataStart + length;
51
+ const chunkEnd = dataEnd + 4;
52
+ if (chunkEnd > buffer.length)
53
+ invalid("PNG", "truncated chunk payload");
54
+ const type = ascii(buffer, typeStart, 4);
55
+ if (!/^[A-Za-z]{4}$/.test(type))
56
+ invalid("PNG", "invalid chunk type");
57
+ if (buffer.readUInt32BE(dataEnd) !== crc32(buffer, typeStart, dataEnd)) {
58
+ invalid("PNG", `${type} CRC mismatch`);
59
+ }
60
+ if (chunks === 0) {
61
+ if (type !== "IHDR" || length !== 13)
62
+ invalid("PNG", "IHDR must be first");
63
+ width = buffer.readUInt32BE(dataStart);
64
+ height = buffer.readUInt32BE(dataStart + 4);
65
+ if (width === 0 || height === 0)
66
+ invalid("PNG", "zero dimensions");
67
+ if (buffer[dataStart + 10] !== 0 || buffer[dataStart + 11] !== 0) {
68
+ invalid("PNG", "unsupported compression or filter method");
69
+ }
70
+ if ((buffer[dataStart + 12] ?? 2) > 1)
71
+ invalid("PNG", "invalid interlace method");
72
+ }
73
+ if (type === "IDAT")
74
+ sawData = true;
75
+ if (type === "IEND") {
76
+ if (length !== 0 || !sawData || chunkEnd !== buffer.length) {
77
+ invalid("PNG", "invalid IEND or missing image data");
78
+ }
79
+ return { mime: "image/png", width, height };
80
+ }
81
+ offset = chunkEnd;
82
+ chunks += 1;
83
+ }
84
+ return invalid("PNG", "missing IEND");
85
+ }
86
+ function probeJpeg(buffer, allowTrailingBytes = false) {
87
+ if (buffer.length < 16)
88
+ invalid("JPEG", "truncated header");
89
+ let offset = 2;
90
+ let width = 0;
91
+ let height = 0;
92
+ let sawScan = false;
93
+ let sawQuantization = false;
94
+ let sawEntropyTable = false;
95
+ while (offset < buffer.length) {
96
+ if (buffer[offset] !== 0xff)
97
+ invalid("JPEG", "expected marker");
98
+ while (buffer[offset] === 0xff)
99
+ offset += 1;
100
+ if (offset >= buffer.length)
101
+ invalid("JPEG", "truncated marker");
102
+ const marker = buffer[offset++];
103
+ if (marker === 0xd9) {
104
+ if (!width || !height || !sawScan || !sawQuantization || !sawEntropyTable) {
105
+ invalid("JPEG", "missing frame, tables, or scan");
106
+ }
107
+ if (offset !== buffer.length && !allowTrailingBytes) {
108
+ invalid("JPEG", "trailing bytes after EOI");
109
+ }
110
+ return {
111
+ image: { mime: "image/jpeg", width, height },
112
+ consumedBytes: offset,
113
+ };
114
+ }
115
+ if (marker === 0x00 || marker === 0xd8)
116
+ invalid("JPEG", "invalid marker ordering");
117
+ if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7))
118
+ continue;
119
+ if (offset + 2 > buffer.length)
120
+ invalid("JPEG", "truncated segment length");
121
+ const segmentLength = buffer.readUInt16BE(offset);
122
+ if (segmentLength < 2 || offset + segmentLength > buffer.length) {
123
+ invalid("JPEG", "truncated segment");
124
+ }
125
+ const payload = offset + 2;
126
+ const segmentEnd = offset + segmentLength;
127
+ if (JPEG_SOF_MARKERS.has(marker)) {
128
+ if (segmentLength < 8)
129
+ invalid("JPEG", "short SOF segment");
130
+ height = buffer.readUInt16BE(payload + 1);
131
+ width = buffer.readUInt16BE(payload + 3);
132
+ if (!width || !height)
133
+ invalid("JPEG", "zero dimensions");
134
+ }
135
+ else if (marker === 0xdb) {
136
+ sawQuantization = true;
137
+ }
138
+ else if (marker === 0xc4 || marker === 0xcc) {
139
+ sawEntropyTable = true;
140
+ }
141
+ else if (marker === 0xda) {
142
+ if (segmentLength < 6)
143
+ invalid("JPEG", "short SOS segment");
144
+ sawScan = true;
145
+ offset = segmentEnd;
146
+ while (offset < buffer.length) {
147
+ if (buffer[offset] !== 0xff) {
148
+ offset += 1;
149
+ continue;
150
+ }
151
+ let next = offset + 1;
152
+ while (buffer[next] === 0xff)
153
+ next += 1;
154
+ if (next >= buffer.length)
155
+ invalid("JPEG", "truncated entropy marker");
156
+ const code = buffer[next];
157
+ if (code === 0x00 || (code >= 0xd0 && code <= 0xd7)) {
158
+ offset = next + 1;
159
+ continue;
160
+ }
161
+ // Let the outer marker parser consume EOI or a progressive scan marker.
162
+ break;
163
+ }
164
+ continue;
165
+ }
166
+ offset = segmentEnd;
167
+ }
168
+ return invalid("JPEG", "missing EOI");
169
+ }
170
+ function validateImageInput(declaredMime, bytes) {
171
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
172
+ const mime = declaredMime === "image/jpg" ? "image/jpeg" : declaredMime;
173
+ const detected = detectMime(buffer);
174
+ if (!detected)
175
+ throw new Error("image byte signature is not a supported image");
176
+ if (detected !== mime) {
177
+ throw new Error(`image MIME ${mime} does not match byte signature ${detected}`);
178
+ }
179
+ return { buffer, detected };
180
+ }
181
+ function skipGifSubBlocks(buffer, start) {
182
+ let offset = start;
183
+ while (offset < buffer.length) {
184
+ const length = buffer[offset++];
185
+ if (length === 0)
186
+ return offset;
187
+ if (offset + length > buffer.length)
188
+ invalid("GIF", "truncated data sub-block");
189
+ offset += length;
190
+ }
191
+ return invalid("GIF", "unterminated data sub-blocks");
192
+ }
193
+ function probeGif(buffer) {
194
+ if (buffer.length < 14)
195
+ invalid("GIF", "truncated logical screen descriptor");
196
+ const width = buffer.readUInt16LE(6);
197
+ const height = buffer.readUInt16LE(8);
198
+ if (!width || !height)
199
+ invalid("GIF", "zero dimensions");
200
+ const globalTableBytes = buffer[10] & 0x80 ? 3 * 2 ** ((buffer[10] & 0x07) + 1) : 0;
201
+ let offset = 13 + globalTableBytes;
202
+ if (offset > buffer.length)
203
+ invalid("GIF", "truncated global color table");
204
+ let sawImage = false;
205
+ while (offset < buffer.length) {
206
+ const introducer = buffer[offset];
207
+ if (introducer === 0x3b) {
208
+ if (!sawImage || offset + 1 !== buffer.length)
209
+ invalid("GIF", "invalid trailer");
210
+ return { mime: "image/gif", width, height };
211
+ }
212
+ if (introducer === 0x21) {
213
+ if (offset + 2 > buffer.length)
214
+ invalid("GIF", "truncated extension");
215
+ offset = skipGifSubBlocks(buffer, offset + 2);
216
+ continue;
217
+ }
218
+ if (introducer !== 0x2c || offset + 10 > buffer.length) {
219
+ invalid("GIF", "invalid image block");
220
+ }
221
+ const packed = buffer[offset + 9];
222
+ offset += 10;
223
+ if (packed & 0x80)
224
+ offset += 3 * 2 ** ((packed & 0x07) + 1);
225
+ if (offset >= buffer.length)
226
+ invalid("GIF", "truncated local table or LZW header");
227
+ const minimumCodeSize = buffer[offset++];
228
+ if (minimumCodeSize < 2 || minimumCodeSize > 11)
229
+ invalid("GIF", "invalid LZW code size");
230
+ offset = skipGifSubBlocks(buffer, offset);
231
+ sawImage = true;
232
+ }
233
+ return invalid("GIF", "missing trailer");
234
+ }
235
+ function readUint24LE(buffer, offset) {
236
+ return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16);
237
+ }
238
+ function probeWebp(buffer) {
239
+ if (buffer.length < 20)
240
+ invalid("WebP", "truncated RIFF header");
241
+ if (buffer.readUInt32LE(4) !== buffer.length - 8)
242
+ invalid("WebP", "RIFF size mismatch");
243
+ let offset = 12;
244
+ let width = 0;
245
+ let height = 0;
246
+ let sawImagePayload = false;
247
+ while (offset < buffer.length) {
248
+ if (offset + 8 > buffer.length)
249
+ invalid("WebP", "truncated chunk header");
250
+ const type = ascii(buffer, offset, 4);
251
+ const size = buffer.readUInt32LE(offset + 4);
252
+ const payload = offset + 8;
253
+ const end = payload + size;
254
+ const paddedEnd = end + (size & 1);
255
+ if (end > buffer.length || paddedEnd > buffer.length)
256
+ invalid("WebP", "truncated chunk");
257
+ if (type === "VP8 ") {
258
+ if (size < 10 ||
259
+ buffer[payload + 3] !== 0x9d ||
260
+ buffer[payload + 4] !== 0x01 ||
261
+ buffer[payload + 5] !== 0x2a) {
262
+ invalid("WebP", "invalid VP8 frame header");
263
+ }
264
+ width = buffer.readUInt16LE(payload + 6) & 0x3fff;
265
+ height = buffer.readUInt16LE(payload + 8) & 0x3fff;
266
+ sawImagePayload = true;
267
+ }
268
+ else if (type === "VP8L") {
269
+ if (size < 5 || buffer[payload] !== 0x2f)
270
+ invalid("WebP", "invalid VP8L header");
271
+ const bits = buffer.readUInt32LE(payload + 1);
272
+ if (bits >>> 29)
273
+ invalid("WebP", "unsupported VP8L version");
274
+ width = (bits & 0x3fff) + 1;
275
+ height = ((bits >>> 14) & 0x3fff) + 1;
276
+ sawImagePayload = true;
277
+ }
278
+ else if (type === "VP8X") {
279
+ if (size !== 10)
280
+ invalid("WebP", "invalid VP8X header");
281
+ width = readUint24LE(buffer, payload + 4) + 1;
282
+ height = readUint24LE(buffer, payload + 7) + 1;
283
+ }
284
+ else if (type === "ANMF") {
285
+ if (size < 16)
286
+ invalid("WebP", "short animation frame");
287
+ sawImagePayload = true;
288
+ }
289
+ offset = paddedEnd;
290
+ }
291
+ if (offset !== buffer.length || !width || !height || !sawImagePayload) {
292
+ invalid("WebP", "missing dimensions or image payload");
293
+ }
294
+ return { mime: "image/webp", width, height };
295
+ }
296
+ /** Strictly verify byte signature, bounded container structure, and declared MIME. */
297
+ export function probeImageBytes(declaredMime, bytes) {
298
+ const { buffer, detected } = validateImageInput(declaredMime, bytes);
299
+ switch (detected) {
300
+ case "image/png":
301
+ return probePng(buffer);
302
+ case "image/jpeg":
303
+ return probeJpeg(buffer).image;
304
+ case "image/gif":
305
+ return probeGif(buffer);
306
+ case "image/webp":
307
+ return probeWebp(buffer);
308
+ }
309
+ }
310
+ /**
311
+ * Verify untrusted image bytes and remove data after a complete JPEG EOI marker.
312
+ * Other image formats remain strict, and the returned JPEG is probed again after truncation.
313
+ */
314
+ export function sanitizeImageBytes(declaredMime, bytes) {
315
+ const { buffer, detected } = validateImageInput(declaredMime, bytes);
316
+ if (detected !== "image/jpeg") {
317
+ return { image: probeImageBytes(declaredMime, buffer), bytes: buffer, trailingBytes: 0 };
318
+ }
319
+ const probed = probeJpeg(buffer, true);
320
+ const trailingBytes = buffer.length - probed.consumedBytes;
321
+ if (trailingBytes === 0) {
322
+ return { image: probed.image, bytes: buffer, trailingBytes };
323
+ }
324
+ const sanitized = buffer.subarray(0, probed.consumedBytes);
325
+ return {
326
+ image: probeImageBytes("image/jpeg", sanitized),
327
+ bytes: sanitized,
328
+ trailingBytes,
329
+ };
330
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @cjhyy/code-shell-server — the Electron-free server/transport layer extracted
3
+ * from the desktop main process (mobile web remote host, pairing, passcode
4
+ * gate, cloudflared tunnel, rooms/resident agents, uploads) plus the disk
5
+ * session/attachment services it depends on. Precursor of the auth gateway.
6
+ */
7
+ export * from "./index.storage.js";
8
+ export * from "./index.worker.js";
9
+ export * from "./index.mobile-remote.js";
10
+ export * from "./index.serve.js";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @cjhyy/code-shell-server — the Electron-free server/transport layer extracted
3
+ * from the desktop main process (mobile web remote host, pairing, passcode
4
+ * gate, cloudflared tunnel, rooms/resident agents, uploads) plus the disk
5
+ * session/attachment services it depends on. Precursor of the auth gateway.
6
+ */
7
+ export * from "./index.storage.js";
8
+ export * from "./index.worker.js";
9
+ export * from "./index.mobile-remote.js";
10
+ export * from "./index.serve.js";
@@ -0,0 +1,21 @@
1
+ /** Mobile HTTP/WebSocket transport, rooms, uploads, pairing, and tunnels. */
2
+ export * from "./mobile-remote/access-passcode.js";
3
+ export * from "./mobile-remote/cloudflared-binary.js";
4
+ export * from "./mobile-remote/codex-parse.js";
5
+ export * from "./mobile-remote/codex-room-agent.js";
6
+ export * from "./mobile-remote/mobile-attachments.js";
7
+ export * from "./mobile-remote/mobile-chat-turn.js";
8
+ export * from "./mobile-remote/mobile-history.js";
9
+ export * from "./mobile-remote/mobile-run-dispatch.js";
10
+ export * from "./mobile-remote/mobile-static.js";
11
+ export * from "./mobile-remote/mobile-upload-service.js";
12
+ export * from "./mobile-remote/pairing.js";
13
+ export * from "./mobile-remote/path-bins.js";
14
+ export * from "./mobile-remote/pending-approvals.js";
15
+ export * from "./mobile-remote/remote-host-manager.js";
16
+ export * from "./mobile-remote/resident-agent.js";
17
+ export * from "./mobile-remote/room-manager.js";
18
+ export * from "./mobile-remote/trusted-device-store.js";
19
+ export * from "./mobile-remote/tunnel-manager.js";
20
+ export * from "./mobile-remote/types.js";
21
+ export * from "./mobile-remote/viewer-identity.js";
@@ -0,0 +1,21 @@
1
+ /** Mobile HTTP/WebSocket transport, rooms, uploads, pairing, and tunnels. */
2
+ export * from "./mobile-remote/access-passcode.js";
3
+ export * from "./mobile-remote/cloudflared-binary.js";
4
+ export * from "./mobile-remote/codex-parse.js";
5
+ export * from "./mobile-remote/codex-room-agent.js";
6
+ export * from "./mobile-remote/mobile-attachments.js";
7
+ export * from "./mobile-remote/mobile-chat-turn.js";
8
+ export * from "./mobile-remote/mobile-history.js";
9
+ export * from "./mobile-remote/mobile-run-dispatch.js";
10
+ export * from "./mobile-remote/mobile-static.js";
11
+ export * from "./mobile-remote/mobile-upload-service.js";
12
+ export * from "./mobile-remote/pairing.js";
13
+ export * from "./mobile-remote/path-bins.js";
14
+ export * from "./mobile-remote/pending-approvals.js";
15
+ export * from "./mobile-remote/remote-host-manager.js";
16
+ export * from "./mobile-remote/resident-agent.js";
17
+ export * from "./mobile-remote/room-manager.js";
18
+ export * from "./mobile-remote/trusted-device-store.js";
19
+ export * from "./mobile-remote/tunnel-manager.js";
20
+ export * from "./mobile-remote/types.js";
21
+ export * from "./mobile-remote/viewer-identity.js";
@@ -0,0 +1,3 @@
1
+ /** Ready-made headless HTTP/WebSocket host and CLI composition helpers. */
2
+ export * from "./serve/headless-server.js";
3
+ export { parseServeArgs, resolveWebAppRoot, resolveWorkerEntry, runServeCli } from "./serve/cli.js";
@@ -0,0 +1,3 @@
1
+ /** Ready-made headless HTTP/WebSocket host and CLI composition helpers. */
2
+ export * from "./serve/headless-server.js";
3
+ export { parseServeArgs, resolveWebAppRoot, resolveWorkerEntry, runServeCli } from "./serve/cli.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Disk-backed session and attachment services.
3
+ *
4
+ * This entry intentionally excludes HTTP/WebSocket hosts and external-agent
5
+ * composition so storage-only consumers do not evaluate transport modules.
6
+ */
7
+ export * from "./attachment-service.js";
8
+ export * from "./client-message-id.js";
9
+ export * from "./image-byte-probe.js";
10
+ export * from "./sessions-service.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Disk-backed session and attachment services.
3
+ *
4
+ * This entry intentionally excludes HTTP/WebSocket hosts and external-agent
5
+ * composition so storage-only consumers do not evaluate transport modules.
6
+ */
7
+ export * from "./attachment-service.js";
8
+ export * from "./client-message-id.js";
9
+ export * from "./image-byte-probe.js";
10
+ export * from "./sessions-service.js";
@@ -0,0 +1,2 @@
1
+ /** Transport-agnostic stdio worker bridge for Node hosts. */
2
+ export * from "./worker-bridge-core.js";
@@ -0,0 +1,2 @@
1
+ /** Transport-agnostic stdio worker bridge for Node hosts. */
2
+ export * from "./worker-bridge-core.js";
@@ -0,0 +1,80 @@
1
+ /** Minimal structural views of node:http req/res so `gate` is unit-testable
2
+ * without a real server. */
3
+ interface GateRequest {
4
+ url?: string;
5
+ headers: {
6
+ cookie?: string | undefined;
7
+ [k: string]: string | string[] | undefined;
8
+ };
9
+ }
10
+ interface GateResponse {
11
+ statusCode?: number;
12
+ writeHead(code: number, headers?: Record<string, string>): unknown;
13
+ setHeader(name: string, value: string): void;
14
+ end(chunk?: string): void;
15
+ }
16
+ export interface AccessPasscodeOptions {
17
+ /** `<userData>/mobile-remote/access.json`. */
18
+ filePath: string;
19
+ /** Injectable clock for deterministic rate-limit tests. */
20
+ now?: () => number;
21
+ maxAttempts?: number;
22
+ lockoutMs?: number;
23
+ /** Remember-token validity window (default 30 days). Also drives the cookie
24
+ * Max-Age so the browser drops the cookie when the server would reject it. */
25
+ tokenMaxAgeMs?: number;
26
+ }
27
+ /**
28
+ * The public-tunnel access gate. A single shared passcode (set by the desktop
29
+ * user) guards the tunnel; a phone that enters it correctly receives a signed
30
+ * "remember" token stored in a cookie so it is not re-challenged. Security
31
+ * properties this unit guarantees:
32
+ * - the passcode is only ever stored as a salted scrypt hash (no plaintext);
33
+ * - remember-tokens are HMACs over a per-passcode secret, so rotating the
34
+ * passcode invalidates every outstanding token;
35
+ * - brute-force is rate-limited: N consecutive wrong attempts lock the gate
36
+ * for a window, during which even the correct passcode is refused.
37
+ * The trusted-device + pairing layers still sit behind this (defense in depth).
38
+ */
39
+ export declare class AccessPasscode {
40
+ private readonly filePath;
41
+ private readonly now;
42
+ private readonly maxAttempts;
43
+ private readonly lockoutMs;
44
+ private readonly tokenMaxAgeMs;
45
+ private failures;
46
+ private lockedUntil;
47
+ constructor(opts: AccessPasscodeOptions);
48
+ isSet(): boolean;
49
+ /** Set (or rotate) the passcode. Rotating `secret` invalidates old tokens. */
50
+ set(passcode: string): void;
51
+ /**
52
+ * Verify a passcode. On success returns a fresh remember-token and resets the
53
+ * failure counter; on failure (or while rate-limited) returns null and counts
54
+ * a failure. While locked out, even a correct passcode returns null.
55
+ */
56
+ verify(passcode: string): string | null;
57
+ /** Validate a remember-token: HMAC signature must match AND be over the
58
+ * current secret (so it dies when the passcode is rotated), AND the signed
59
+ * issue timestamp must be within the validity window (so a stolen/synced
60
+ * cookie cannot grant access indefinitely). */
61
+ verifyToken(token: string): boolean;
62
+ /**
63
+ * HTTP gate. Returns true (allow) when the request carries a valid remember
64
+ * token (cookie) or the correct passcode (query/header); on a fresh correct
65
+ * passcode it also sets the remember cookie so the phone is not re-challenged.
66
+ * Returns false after writing either a redirect or a 401 challenge — the
67
+ * caller must stop in both cases.
68
+ */
69
+ gate(req: GateRequest, res: GateResponse): boolean;
70
+ /** True if a WS upgrade carrying these headers/url is allowed (no body to
71
+ * write). Used by the WS gate which cannot send a challenge page. */
72
+ allows(req: GateRequest): boolean;
73
+ private isLocked;
74
+ private issueToken;
75
+ private sign;
76
+ private hashPasscode;
77
+ private read;
78
+ private write;
79
+ }
80
+ export {};