wslog-proxy 0.0.0-stage → 0.1.1
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 +110 -2
- package/dist/cli/args.d.ts +14 -0
- package/dist/cli/args.js +98 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +78 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server/app.d.ts +19 -0
- package/dist/server/app.js +96 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/config.d.ts +51 -0
- package/dist/server/config.js +62 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/connections.d.ts +21 -0
- package/dist/server/connections.js +81 -0
- package/dist/server/connections.js.map +1 -0
- package/dist/server/context.d.ts +37 -0
- package/dist/server/context.js +64 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/demo/bots.d.ts +4 -0
- package/dist/server/demo/bots.js +90 -0
- package/dist/server/demo/bots.js.map +1 -0
- package/dist/server/demo/chat-server.d.ts +9 -0
- package/dist/server/demo/chat-server.js +90 -0
- package/dist/server/demo/chat-server.js.map +1 -0
- package/dist/server/demo/controller.d.ts +21 -0
- package/dist/server/demo/controller.js +37 -0
- package/dist/server/demo/controller.js.map +1 -0
- package/dist/server/events.d.ts +19 -0
- package/dist/server/events.js +19 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/http/api.d.ts +6 -0
- package/dist/server/http/api.js +89 -0
- package/dist/server/http/api.js.map +1 -0
- package/dist/server/http/auth.d.ts +26 -0
- package/dist/server/http/auth.js +78 -0
- package/dist/server/http/auth.js.map +1 -0
- package/dist/server/http/live.d.ts +26 -0
- package/dist/server/http/live.js +117 -0
- package/dist/server/http/live.js.map +1 -0
- package/dist/server/http/router.d.ts +31 -0
- package/dist/server/http/router.js +89 -0
- package/dist/server/http/router.js.map +1 -0
- package/dist/server/http/server.d.ts +17 -0
- package/dist/server/http/server.js +82 -0
- package/dist/server/http/server.js.map +1 -0
- package/dist/server/http/static.d.ts +3 -0
- package/dist/server/http/static.js +56 -0
- package/dist/server/http/static.js.map +1 -0
- package/dist/server/http/upgrade.d.ts +3 -0
- package/dist/server/http/upgrade.js +13 -0
- package/dist/server/http/upgrade.js.map +1 -0
- package/dist/server/masking.d.ts +11 -0
- package/dist/server/masking.js +104 -0
- package/dist/server/masking.js.map +1 -0
- package/dist/server/paths.d.ts +12 -0
- package/dist/server/paths.js +30 -0
- package/dist/server/paths.js.map +1 -0
- package/dist/server/plugins.d.ts +20 -0
- package/dist/server/plugins.js +4 -0
- package/dist/server/plugins.js.map +1 -0
- package/dist/server/proxy/headers.d.ts +4 -0
- package/dist/server/proxy/headers.js +39 -0
- package/dist/server/proxy/headers.js.map +1 -0
- package/dist/server/proxy/proxy.d.ts +41 -0
- package/dist/server/proxy/proxy.js +188 -0
- package/dist/server/proxy/proxy.js.map +1 -0
- package/dist/server/recorder.d.ts +34 -0
- package/dist/server/recorder.js +65 -0
- package/dist/server/recorder.js.map +1 -0
- package/dist/server/storage/index.d.ts +14 -0
- package/dist/server/storage/index.js +40 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/storage/jsonl.d.ts +30 -0
- package/dist/server/storage/jsonl.js +107 -0
- package/dist/server/storage/jsonl.js.map +1 -0
- package/dist/server/storage/memory.d.ts +24 -0
- package/dist/server/storage/memory.js +74 -0
- package/dist/server/storage/memory.js.map +1 -0
- package/dist/server/storage/sqlite.d.ts +23 -0
- package/dist/server/storage/sqlite.js +125 -0
- package/dist/server/storage/sqlite.js.map +1 -0
- package/dist/server/storage/types.d.ts +30 -0
- package/dist/server/storage/types.js +2 -0
- package/dist/server/storage/types.js.map +1 -0
- package/dist/server/targets.d.ts +41 -0
- package/dist/server/targets.js +156 -0
- package/dist/server/targets.js.map +1 -0
- package/dist/server/util/logger.d.ts +16 -0
- package/dist/server/util/logger.js +19 -0
- package/dist/server/util/logger.js.map +1 -0
- package/dist/shared/filter.d.ts +14 -0
- package/dist/shared/filter.js +41 -0
- package/dist/shared/filter.js.map +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/protocol.d.ts +124 -0
- package/dist/shared/protocol.js +9 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/web/assets/index-C3OwozRd.css +1 -0
- package/dist/web/assets/index-D2oUxHhH.js +11 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +15 -0
- package/package.json +84 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { JsonlStore } from './jsonl.js';
|
|
3
|
+
import { MemoryStore } from './memory.js';
|
|
4
|
+
import { SqliteStore } from './sqlite.js';
|
|
5
|
+
export { MemoryStore, JsonlStore, SqliteStore };
|
|
6
|
+
/**
|
|
7
|
+
* Store spec → factory. Specs look like `memory`, `./traffic.jsonl`,
|
|
8
|
+
* `./traffic.db`, or an explicit `jsonl:path` / `sqlite:path`.
|
|
9
|
+
*/
|
|
10
|
+
const factories = new Map([
|
|
11
|
+
['memory', async (_path, options) => new MemoryStore(options)],
|
|
12
|
+
['jsonl', (path, options) => JsonlStore.open(path, options)],
|
|
13
|
+
['sqlite', (path, options) => SqliteStore.open(path, options)],
|
|
14
|
+
]);
|
|
15
|
+
/** Register a custom storage backend, used as `--store <scheme>:<path>`. */
|
|
16
|
+
export function registerStore(scheme, factory) {
|
|
17
|
+
factories.set(scheme, factory);
|
|
18
|
+
}
|
|
19
|
+
export function parseStoreSpec(spec) {
|
|
20
|
+
const trimmed = spec.trim();
|
|
21
|
+
if (!trimmed || trimmed === 'memory')
|
|
22
|
+
return { scheme: 'memory', path: '' };
|
|
23
|
+
const explicit = /^([a-z][a-z0-9+-]*):(?!\/\/|\\)(.+)$/i.exec(trimmed);
|
|
24
|
+
if (explicit && factories.has(explicit[1].toLowerCase())) {
|
|
25
|
+
return { scheme: explicit[1].toLowerCase(), path: explicit[2] };
|
|
26
|
+
}
|
|
27
|
+
if (/\.jsonl?$/i.test(trimmed))
|
|
28
|
+
return { scheme: 'jsonl', path: trimmed };
|
|
29
|
+
if (/\.(db|sqlite3?)$/i.test(trimmed))
|
|
30
|
+
return { scheme: 'sqlite', path: trimmed };
|
|
31
|
+
throw new Error(`Unknown --store "${spec}". Use "memory", a .jsonl path, or a .db/.sqlite path.`);
|
|
32
|
+
}
|
|
33
|
+
export async function createStore(spec, options) {
|
|
34
|
+
const { scheme, path } = parseStoreSpec(spec);
|
|
35
|
+
const factory = factories.get(scheme);
|
|
36
|
+
if (!factory)
|
|
37
|
+
throw new Error(`No storage backend registered for "${scheme}"`);
|
|
38
|
+
return factory(path ? resolve(path) : '', options);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAIhD;;;GAGG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAuB;IAC9C,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;CAC/D,CAAC,CAAC;AAEH,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAqB;IACjE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAC5E,MAAM,QAAQ,GAAG,uCAAuC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvE,IAAI,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC1D,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC1E,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAClF,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,wDAAwD,CAAC,CAAC;AACpG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,OAAqB;IACnE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,MAAM,GAAG,CAAC,CAAC;IAC/E,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MessageRecord } from '../../shared/protocol.js';
|
|
2
|
+
import type { MessageQuery, MessageStore, StoreOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Append-only JSON Lines file with an in-memory index of the newest
|
|
5
|
+
* `maxMessages` records. Writes are batched per event-loop tick, and the
|
|
6
|
+
* file is compacted when it grows past twice the limit so disk use stays bounded.
|
|
7
|
+
*/
|
|
8
|
+
export declare class JsonlStore implements MessageStore {
|
|
9
|
+
private readonly path;
|
|
10
|
+
private readonly options;
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
private readonly memory;
|
|
13
|
+
private fd;
|
|
14
|
+
private pending;
|
|
15
|
+
private flushScheduled;
|
|
16
|
+
private linesInFile;
|
|
17
|
+
private constructor();
|
|
18
|
+
static open(path: string, options: StoreOptions): Promise<JsonlStore>;
|
|
19
|
+
private load;
|
|
20
|
+
append(record: MessageRecord): void;
|
|
21
|
+
get(id: number): MessageRecord | undefined;
|
|
22
|
+
query(query: MessageQuery): MessageRecord[];
|
|
23
|
+
clear(): void;
|
|
24
|
+
count(): number;
|
|
25
|
+
lastId(): number;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
private flush;
|
|
28
|
+
/** Replace the file with exactly `records` (pending writes are included in memory). */
|
|
29
|
+
private rewrite;
|
|
30
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { closeSync, createReadStream, existsSync, mkdirSync, openSync, renameSync, writeFileSync, writeSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { ensurePrivateFile } from '../paths.js';
|
|
5
|
+
import { MemoryStore } from './memory.js';
|
|
6
|
+
/**
|
|
7
|
+
* Append-only JSON Lines file with an in-memory index of the newest
|
|
8
|
+
* `maxMessages` records. Writes are batched per event-loop tick, and the
|
|
9
|
+
* file is compacted when it grows past twice the limit so disk use stays bounded.
|
|
10
|
+
*/
|
|
11
|
+
export class JsonlStore {
|
|
12
|
+
path;
|
|
13
|
+
options;
|
|
14
|
+
kind;
|
|
15
|
+
memory;
|
|
16
|
+
fd = -1;
|
|
17
|
+
pending = [];
|
|
18
|
+
flushScheduled = false;
|
|
19
|
+
linesInFile = 0;
|
|
20
|
+
constructor(path, options) {
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.kind = `jsonl:${path}`;
|
|
24
|
+
this.memory = new MemoryStore(options);
|
|
25
|
+
}
|
|
26
|
+
static async open(path, options) {
|
|
27
|
+
const store = new JsonlStore(path, options);
|
|
28
|
+
await store.load();
|
|
29
|
+
return store;
|
|
30
|
+
}
|
|
31
|
+
async load() {
|
|
32
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
33
|
+
ensurePrivateFile(this.path);
|
|
34
|
+
if (existsSync(this.path)) {
|
|
35
|
+
const lines = createInterface({ input: createReadStream(this.path, 'utf8'), crlfDelay: Infinity });
|
|
36
|
+
for await (const line of lines) {
|
|
37
|
+
if (!line.trim())
|
|
38
|
+
continue;
|
|
39
|
+
try {
|
|
40
|
+
this.memory.append(JSON.parse(line));
|
|
41
|
+
this.linesInFile++;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Skip a corrupt line (e.g. a partial write after a crash).
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.fd = openSync(this.path, 'a');
|
|
49
|
+
if (this.linesInFile > this.options.maxMessages)
|
|
50
|
+
this.rewrite(this.memory.all());
|
|
51
|
+
}
|
|
52
|
+
append(record) {
|
|
53
|
+
this.memory.append(record);
|
|
54
|
+
this.pending.push(`${JSON.stringify(record)}\n`);
|
|
55
|
+
if (++this.linesInFile > this.options.maxMessages * 2) {
|
|
56
|
+
this.rewrite(this.memory.all());
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!this.flushScheduled) {
|
|
60
|
+
this.flushScheduled = true;
|
|
61
|
+
setImmediate(() => this.flush());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
get(id) {
|
|
65
|
+
return this.memory.get(id);
|
|
66
|
+
}
|
|
67
|
+
query(query) {
|
|
68
|
+
return this.memory.query(query);
|
|
69
|
+
}
|
|
70
|
+
clear() {
|
|
71
|
+
this.memory.clear();
|
|
72
|
+
this.rewrite([]);
|
|
73
|
+
}
|
|
74
|
+
count() {
|
|
75
|
+
return this.memory.count();
|
|
76
|
+
}
|
|
77
|
+
lastId() {
|
|
78
|
+
return this.memory.lastId();
|
|
79
|
+
}
|
|
80
|
+
async close() {
|
|
81
|
+
this.flush();
|
|
82
|
+
if (this.fd !== -1)
|
|
83
|
+
closeSync(this.fd);
|
|
84
|
+
this.fd = -1;
|
|
85
|
+
}
|
|
86
|
+
flush() {
|
|
87
|
+
this.flushScheduled = false;
|
|
88
|
+
if (!this.pending.length || this.fd === -1)
|
|
89
|
+
return;
|
|
90
|
+
const chunk = this.pending.join('');
|
|
91
|
+
this.pending = [];
|
|
92
|
+
writeSync(this.fd, chunk);
|
|
93
|
+
}
|
|
94
|
+
/** Replace the file with exactly `records` (pending writes are included in memory). */
|
|
95
|
+
rewrite(records) {
|
|
96
|
+
this.pending = [];
|
|
97
|
+
if (this.fd !== -1)
|
|
98
|
+
closeSync(this.fd);
|
|
99
|
+
const tmp = `${this.path}.tmp`;
|
|
100
|
+
writeFileSync(tmp, records.map((r) => `${JSON.stringify(r)}\n`).join(''), { mode: 0o600 });
|
|
101
|
+
ensurePrivateFile(tmp);
|
|
102
|
+
renameSync(tmp, this.path);
|
|
103
|
+
this.linesInFile = records.length;
|
|
104
|
+
this.fd = openSync(this.path, 'a');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../../src/server/storage/jsonl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MessageRecord } from '../../shared/protocol.js';
|
|
2
|
+
import type { MessageQuery, MessageStore, StoreOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ring buffer of records ordered by id. Appends are O(1); `get` is O(1)
|
|
5
|
+
* because ids are increasing, so we can binary search.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MemoryStore implements MessageStore {
|
|
8
|
+
private readonly options;
|
|
9
|
+
readonly kind: string;
|
|
10
|
+
private items;
|
|
11
|
+
private start;
|
|
12
|
+
private maxId;
|
|
13
|
+
constructor(options: StoreOptions);
|
|
14
|
+
append(record: MessageRecord): void;
|
|
15
|
+
get(id: number): MessageRecord | undefined;
|
|
16
|
+
query(query: MessageQuery): MessageRecord[];
|
|
17
|
+
clear(): void;
|
|
18
|
+
count(): number;
|
|
19
|
+
lastId(): number;
|
|
20
|
+
/** All live records, oldest first. */
|
|
21
|
+
all(): MessageRecord[];
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
private indexOf;
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createMatcher } from '../../shared/filter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ring buffer of records ordered by id. Appends are O(1); `get` is O(1)
|
|
4
|
+
* because ids are increasing, so we can binary search.
|
|
5
|
+
*/
|
|
6
|
+
export class MemoryStore {
|
|
7
|
+
options;
|
|
8
|
+
kind = 'memory';
|
|
9
|
+
items = [];
|
|
10
|
+
start = 0;
|
|
11
|
+
maxId = 0;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
append(record) {
|
|
16
|
+
this.items.push(record);
|
|
17
|
+
this.maxId = Math.max(this.maxId, record.id);
|
|
18
|
+
if (this.count() > this.options.maxMessages)
|
|
19
|
+
this.start++;
|
|
20
|
+
// Compact occasionally so dropped records can be garbage collected.
|
|
21
|
+
if (this.start > 1024 && this.start > this.items.length / 2) {
|
|
22
|
+
this.items = this.items.slice(this.start);
|
|
23
|
+
this.start = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
get(id) {
|
|
27
|
+
const index = this.indexOf(id);
|
|
28
|
+
return index === -1 ? undefined : this.items[index];
|
|
29
|
+
}
|
|
30
|
+
query(query) {
|
|
31
|
+
const limit = query.limit ?? this.options.maxMessages;
|
|
32
|
+
const matches = createMatcher(query);
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let i = this.items.length - 1; i >= this.start && out.length < limit; i--) {
|
|
35
|
+
const record = this.items[i];
|
|
36
|
+
if (query.beforeId !== undefined && record.id >= query.beforeId)
|
|
37
|
+
continue;
|
|
38
|
+
if (matches(record))
|
|
39
|
+
out.push(record);
|
|
40
|
+
}
|
|
41
|
+
return out.reverse();
|
|
42
|
+
}
|
|
43
|
+
clear() {
|
|
44
|
+
this.items = [];
|
|
45
|
+
this.start = 0;
|
|
46
|
+
}
|
|
47
|
+
count() {
|
|
48
|
+
return this.items.length - this.start;
|
|
49
|
+
}
|
|
50
|
+
lastId() {
|
|
51
|
+
return this.maxId;
|
|
52
|
+
}
|
|
53
|
+
/** All live records, oldest first. */
|
|
54
|
+
all() {
|
|
55
|
+
return this.items.slice(this.start);
|
|
56
|
+
}
|
|
57
|
+
async close() { }
|
|
58
|
+
indexOf(id) {
|
|
59
|
+
let lo = this.start;
|
|
60
|
+
let hi = this.items.length - 1;
|
|
61
|
+
while (lo <= hi) {
|
|
62
|
+
const mid = (lo + hi) >>> 1;
|
|
63
|
+
const midId = this.items[mid].id;
|
|
64
|
+
if (midId === id)
|
|
65
|
+
return mid;
|
|
66
|
+
if (midId < id)
|
|
67
|
+
lo = mid + 1;
|
|
68
|
+
else
|
|
69
|
+
hi = mid - 1;
|
|
70
|
+
}
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/server/storage/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MessageRecord } from '../../shared/protocol.js';
|
|
2
|
+
import type { MessageQuery, MessageStore, StoreOptions } from './types.js';
|
|
3
|
+
/** SQLite storage via the built-in `node:sqlite` module (Node 22.13+). */
|
|
4
|
+
export declare class SqliteStore implements MessageStore {
|
|
5
|
+
private readonly db;
|
|
6
|
+
private readonly options;
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
private readonly insert;
|
|
9
|
+
private readonly prune;
|
|
10
|
+
private readonly byId;
|
|
11
|
+
private inserted;
|
|
12
|
+
private maxId;
|
|
13
|
+
private constructor();
|
|
14
|
+
static open(path: string, options: StoreOptions): Promise<SqliteStore>;
|
|
15
|
+
append(r: MessageRecord): void;
|
|
16
|
+
get(id: number): MessageRecord | undefined;
|
|
17
|
+
query(query: MessageQuery): MessageRecord[];
|
|
18
|
+
clear(): void;
|
|
19
|
+
count(): number;
|
|
20
|
+
lastId(): number;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
private enforceLimit;
|
|
23
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { ensurePrivateFile } from '../paths.js';
|
|
4
|
+
/** SQLite storage via the built-in `node:sqlite` module (Node 22.13+). */
|
|
5
|
+
export class SqliteStore {
|
|
6
|
+
db;
|
|
7
|
+
options;
|
|
8
|
+
kind;
|
|
9
|
+
insert;
|
|
10
|
+
prune;
|
|
11
|
+
byId;
|
|
12
|
+
inserted = 0;
|
|
13
|
+
maxId;
|
|
14
|
+
constructor(db, path, options) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.kind = `sqlite:${path}`;
|
|
18
|
+
db.exec(`
|
|
19
|
+
PRAGMA journal_mode = WAL;
|
|
20
|
+
PRAGMA synchronous = NORMAL;
|
|
21
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
22
|
+
id INTEGER PRIMARY KEY,
|
|
23
|
+
connection_id TEXT NOT NULL,
|
|
24
|
+
target_id TEXT NOT NULL,
|
|
25
|
+
direction TEXT NOT NULL,
|
|
26
|
+
time INTEGER NOT NULL,
|
|
27
|
+
size INTEGER NOT NULL,
|
|
28
|
+
kind TEXT NOT NULL,
|
|
29
|
+
payload TEXT NOT NULL,
|
|
30
|
+
truncated INTEGER NOT NULL DEFAULT 0
|
|
31
|
+
);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS messages_connection ON messages (connection_id, id);
|
|
33
|
+
`);
|
|
34
|
+
this.insert = db.prepare(`INSERT INTO messages (id, connection_id, target_id, direction, time, size, kind, payload, truncated)
|
|
35
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
36
|
+
this.prune = db.prepare('DELETE FROM messages WHERE id <= ?');
|
|
37
|
+
this.byId = db.prepare('SELECT * FROM messages WHERE id = ? AND id > ?');
|
|
38
|
+
this.maxId = Number(db.prepare('SELECT COALESCE(MAX(id), 0) AS id FROM messages').get()?.id ?? 0);
|
|
39
|
+
this.enforceLimit();
|
|
40
|
+
}
|
|
41
|
+
static async open(path, options) {
|
|
42
|
+
let sqlite;
|
|
43
|
+
try {
|
|
44
|
+
sqlite = (await import('node:sqlite'));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`SQLite storage needs Node.js 22.13 or newer (running ${process.version}). Use a .jsonl file instead.`);
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
50
|
+
// Before SQLite opens it: the -wal and -shm files copy the database's permissions.
|
|
51
|
+
ensurePrivateFile(path);
|
|
52
|
+
return new SqliteStore(new sqlite.DatabaseSync(path), path, options);
|
|
53
|
+
}
|
|
54
|
+
append(r) {
|
|
55
|
+
this.insert.run(r.id, r.connectionId, r.targetId, r.direction, r.time, r.size, r.kind, r.payload, r.truncated ? 1 : 0);
|
|
56
|
+
this.maxId = Math.max(this.maxId, r.id);
|
|
57
|
+
// Prune in batches rather than on every insert.
|
|
58
|
+
if (++this.inserted % 256 === 0)
|
|
59
|
+
this.enforceLimit();
|
|
60
|
+
}
|
|
61
|
+
get(id) {
|
|
62
|
+
const row = this.byId.get(id, this.maxId - this.options.maxMessages);
|
|
63
|
+
return row ? toRecord(row) : undefined;
|
|
64
|
+
}
|
|
65
|
+
query(query) {
|
|
66
|
+
const where = ['id > ?'];
|
|
67
|
+
const params = [this.maxId - this.options.maxMessages];
|
|
68
|
+
if (query.beforeId !== undefined) {
|
|
69
|
+
where.push('id < ?');
|
|
70
|
+
params.push(query.beforeId);
|
|
71
|
+
}
|
|
72
|
+
if (query.direction) {
|
|
73
|
+
where.push('direction = ?');
|
|
74
|
+
params.push(query.direction);
|
|
75
|
+
}
|
|
76
|
+
if (query.connectionId) {
|
|
77
|
+
where.push('connection_id = ?');
|
|
78
|
+
params.push(query.connectionId);
|
|
79
|
+
}
|
|
80
|
+
if (query.targetId) {
|
|
81
|
+
where.push('target_id = ?');
|
|
82
|
+
params.push(query.targetId);
|
|
83
|
+
}
|
|
84
|
+
if (query.q?.trim()) {
|
|
85
|
+
where.push("payload LIKE ? ESCAPE '\\'");
|
|
86
|
+
params.push(`%${query.q.trim().replace(/[\\%_]/g, '\\$&')}%`);
|
|
87
|
+
}
|
|
88
|
+
params.push(query.limit ?? this.options.maxMessages);
|
|
89
|
+
const rows = this.db
|
|
90
|
+
.prepare(`SELECT * FROM messages WHERE ${where.join(' AND ')} ORDER BY id DESC LIMIT ?`)
|
|
91
|
+
.all(...params);
|
|
92
|
+
return rows.reverse().map(toRecord);
|
|
93
|
+
}
|
|
94
|
+
clear() {
|
|
95
|
+
this.db.exec('DELETE FROM messages');
|
|
96
|
+
}
|
|
97
|
+
count() {
|
|
98
|
+
const row = this.db.prepare('SELECT COUNT(*) AS n FROM messages WHERE id > ?').get(this.maxId - this.options.maxMessages);
|
|
99
|
+
return Number(row?.n ?? 0);
|
|
100
|
+
}
|
|
101
|
+
lastId() {
|
|
102
|
+
return this.maxId;
|
|
103
|
+
}
|
|
104
|
+
async close() {
|
|
105
|
+
this.enforceLimit();
|
|
106
|
+
this.db.close();
|
|
107
|
+
}
|
|
108
|
+
enforceLimit() {
|
|
109
|
+
this.prune.run(this.maxId - this.options.maxMessages);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function toRecord(row) {
|
|
113
|
+
return {
|
|
114
|
+
id: row.id,
|
|
115
|
+
connectionId: row.connection_id,
|
|
116
|
+
targetId: row.target_id,
|
|
117
|
+
direction: row.direction,
|
|
118
|
+
time: row.time,
|
|
119
|
+
size: row.size,
|
|
120
|
+
kind: row.kind,
|
|
121
|
+
payload: row.payload,
|
|
122
|
+
truncated: row.truncated === 1,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/server/storage/sqlite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MessageFilter } from '../../shared/filter.js';
|
|
2
|
+
import type { MessageRecord } from '../../shared/protocol.js';
|
|
3
|
+
export interface MessageQuery extends MessageFilter {
|
|
4
|
+
/** Only messages with `id < beforeId` (for paging backwards). */
|
|
5
|
+
beforeId?: number;
|
|
6
|
+
/** Max results; the newest matching messages are returned, oldest first. */
|
|
7
|
+
limit?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Storage adapter contract. Implementations must keep at most `maxMessages`
|
|
11
|
+
* records and drop the oldest first. Records arrive already masked.
|
|
12
|
+
*
|
|
13
|
+
* To add a backend, implement this interface and register a factory in
|
|
14
|
+
* `storage/index.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export interface MessageStore {
|
|
17
|
+
/** Human-readable description, e.g. `memory` or `jsonl:./traffic.jsonl`. */
|
|
18
|
+
readonly kind: string;
|
|
19
|
+
append(record: MessageRecord): void;
|
|
20
|
+
get(id: number): MessageRecord | undefined;
|
|
21
|
+
query(query: MessageQuery): MessageRecord[];
|
|
22
|
+
clear(): void;
|
|
23
|
+
count(): number;
|
|
24
|
+
/** Highest id ever stored (0 if empty) so ids keep increasing across restarts. */
|
|
25
|
+
lastId(): number;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface StoreOptions {
|
|
29
|
+
maxMessages: number;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/storage/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type TargetInfo } from '../shared/protocol.js';
|
|
2
|
+
import type { EventBus } from './events.js';
|
|
3
|
+
import type { Logger } from './util/logger.js';
|
|
4
|
+
export declare class TargetError extends Error {
|
|
5
|
+
}
|
|
6
|
+
/** Normalize user input into a ws:// or wss:// URL, or throw a friendly error. */
|
|
7
|
+
export declare function normalizeTargetUrl(input: string): string;
|
|
8
|
+
/** Stable id from the URL so the proxy URL survives restarts. */
|
|
9
|
+
export declare function targetIdFor(url: string): string;
|
|
10
|
+
export declare class TargetRegistry {
|
|
11
|
+
private readonly bus;
|
|
12
|
+
private readonly targets;
|
|
13
|
+
constructor(bus: EventBus);
|
|
14
|
+
add(input: string, options?: {
|
|
15
|
+
demo?: boolean;
|
|
16
|
+
}): TargetInfo;
|
|
17
|
+
remove(id: string): boolean;
|
|
18
|
+
get(id: string): TargetInfo | undefined;
|
|
19
|
+
list(): TargetInfo[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Can wslog reach the target's host and port right now? Resolves `undefined`
|
|
23
|
+
* if so, otherwise why not, in words. A plain TCP connect, not a WebSocket
|
|
24
|
+
* handshake, so the target never sees a fake client.
|
|
25
|
+
*/
|
|
26
|
+
export declare function probeTarget(url: string, timeoutMs?: number): Promise<string | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Re-register targets saved by an earlier run, then keep `file` in sync.
|
|
29
|
+
* Demo targets are skipped: their server gets a new port on every start.
|
|
30
|
+
* The file is private (0600) because target URLs can carry credentials.
|
|
31
|
+
*/
|
|
32
|
+
export declare function persistTargets(registry: TargetRegistry, bus: EventBus, file: string, logger: Logger): void;
|
|
33
|
+
/**
|
|
34
|
+
* Build the upstream URL for a proxied request.
|
|
35
|
+
* `/p/<id>/chat?room=1` on target `ws://host:8080/ws` → `ws://host:8080/ws/chat?room=1`.
|
|
36
|
+
*
|
|
37
|
+
* The app's query string is passed through as sent (repeated keys, encoding,
|
|
38
|
+
* order), so signed URLs keep working. A key the app sends replaces the same
|
|
39
|
+
* key in the target URL.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildUpstreamUrl(target: TargetInfo, requestPath: string): string;
|