aweshare 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 +246 -0
- package/apps/agent/dist/adapters.d.ts +15 -0
- package/apps/agent/dist/adapters.js +25 -0
- package/apps/agent/dist/adapters.js.map +1 -0
- package/apps/agent/dist/cli.d.ts +2 -0
- package/apps/agent/dist/cli.js +175 -0
- package/apps/agent/dist/cli.js.map +1 -0
- package/apps/agent/dist/config.d.ts +38 -0
- package/apps/agent/dist/config.js +150 -0
- package/apps/agent/dist/config.js.map +1 -0
- package/apps/agent/dist/connection.d.ts +40 -0
- package/apps/agent/dist/connection.js +359 -0
- package/apps/agent/dist/connection.js.map +1 -0
- package/apps/agent/dist/doctor.d.ts +14 -0
- package/apps/agent/dist/doctor.js +125 -0
- package/apps/agent/dist/doctor.js.map +1 -0
- package/apps/agent/dist/health.d.ts +15 -0
- package/apps/agent/dist/health.js +45 -0
- package/apps/agent/dist/health.js.map +1 -0
- package/apps/agent/dist/index.d.ts +5 -0
- package/apps/agent/dist/index.js +6 -0
- package/apps/agent/dist/index.js.map +1 -0
- package/apps/agent/package.json +20 -0
- package/apps/hub/dist/admin.d.ts +10 -0
- package/apps/hub/dist/admin.js +208 -0
- package/apps/hub/dist/admin.js.map +1 -0
- package/apps/hub/dist/auth.d.ts +17 -0
- package/apps/hub/dist/auth.js +28 -0
- package/apps/hub/dist/auth.js.map +1 -0
- package/apps/hub/dist/cli.d.ts +2 -0
- package/apps/hub/dist/cli.js +243 -0
- package/apps/hub/dist/cli.js.map +1 -0
- package/apps/hub/dist/config.d.ts +25 -0
- package/apps/hub/dist/config.js +53 -0
- package/apps/hub/dist/config.js.map +1 -0
- package/apps/hub/dist/consumer.d.ts +18 -0
- package/apps/hub/dist/consumer.js +157 -0
- package/apps/hub/dist/consumer.js.map +1 -0
- package/apps/hub/dist/db.d.ts +91 -0
- package/apps/hub/dist/db.js +236 -0
- package/apps/hub/dist/db.js.map +1 -0
- package/apps/hub/dist/http.d.ts +31 -0
- package/apps/hub/dist/http.js +118 -0
- package/apps/hub/dist/http.js.map +1 -0
- package/apps/hub/dist/limiter.d.ts +35 -0
- package/apps/hub/dist/limiter.js +78 -0
- package/apps/hub/dist/limiter.js.map +1 -0
- package/apps/hub/dist/limits.d.ts +30 -0
- package/apps/hub/dist/limits.js +52 -0
- package/apps/hub/dist/limits.js.map +1 -0
- package/apps/hub/dist/meter.d.ts +25 -0
- package/apps/hub/dist/meter.js +135 -0
- package/apps/hub/dist/meter.js.map +1 -0
- package/apps/hub/dist/sanitize.d.ts +3 -0
- package/apps/hub/dist/sanitize.js +56 -0
- package/apps/hub/dist/sanitize.js.map +1 -0
- package/apps/hub/dist/server.d.ts +25 -0
- package/apps/hub/dist/server.js +77 -0
- package/apps/hub/dist/server.js.map +1 -0
- package/apps/hub/dist/tokens.d.ts +9 -0
- package/apps/hub/dist/tokens.js +35 -0
- package/apps/hub/dist/tokens.js.map +1 -0
- package/apps/hub/dist/tunnel.d.ts +108 -0
- package/apps/hub/dist/tunnel.js +424 -0
- package/apps/hub/dist/tunnel.js.map +1 -0
- package/apps/hub/package.json +21 -0
- package/bin/aweshare.mjs +43 -0
- package/package.json +47 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort usage extraction from a response stream, on a side channel.
|
|
3
|
+
* Never throws, never blocks the relay; failures simply yield null counts.
|
|
4
|
+
* No content is retained beyond the bytes needed to read token counts.
|
|
5
|
+
*/
|
|
6
|
+
export class UsageExtractor {
|
|
7
|
+
protocol;
|
|
8
|
+
text = '';
|
|
9
|
+
promptTokens = null;
|
|
10
|
+
completionTokens = null;
|
|
11
|
+
isSse;
|
|
12
|
+
done = false;
|
|
13
|
+
constructor(protocol, contentType) {
|
|
14
|
+
this.protocol = protocol;
|
|
15
|
+
this.isSse = (contentType ?? '').includes('text/event-stream');
|
|
16
|
+
}
|
|
17
|
+
get streaming() {
|
|
18
|
+
return this.isSse;
|
|
19
|
+
}
|
|
20
|
+
onChunk(bytes) {
|
|
21
|
+
if (this.done)
|
|
22
|
+
return;
|
|
23
|
+
if (!this.isSse) {
|
|
24
|
+
// whole-body JSON: keep up to 4MB, parse once at finish
|
|
25
|
+
if (this.text.length < 4 * 1024 * 1024)
|
|
26
|
+
this.text += Buffer.from(bytes).toString('utf8');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.text += Buffer.from(bytes).toString('utf8');
|
|
30
|
+
// process complete SSE events, keep the partial tail
|
|
31
|
+
const boundary = this.text.lastIndexOf('\n\n');
|
|
32
|
+
if (boundary === -1)
|
|
33
|
+
return;
|
|
34
|
+
const complete = this.text.slice(0, boundary + 2);
|
|
35
|
+
this.text = this.text.slice(boundary + 2);
|
|
36
|
+
for (const event of complete.split('\n\n'))
|
|
37
|
+
this.consumeSseEvent(event);
|
|
38
|
+
}
|
|
39
|
+
finish() {
|
|
40
|
+
if (this.done)
|
|
41
|
+
return this.result();
|
|
42
|
+
this.done = true;
|
|
43
|
+
if (this.isSse && this.text.trim())
|
|
44
|
+
this.consumeSseEvent(this.text);
|
|
45
|
+
if (!this.isSse) {
|
|
46
|
+
try {
|
|
47
|
+
const json = JSON.parse(this.text);
|
|
48
|
+
this.consumeUsage(json);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// malformed or truncated body: counts stay null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return this.result();
|
|
55
|
+
}
|
|
56
|
+
consumeSseEvent(event) {
|
|
57
|
+
const data = event
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter((line) => line.startsWith('data:'))
|
|
60
|
+
.map((line) => line.slice(5).trim())
|
|
61
|
+
.join('');
|
|
62
|
+
if (!data || data === '[DONE]')
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
const json = JSON.parse(data);
|
|
66
|
+
this.consumeUsage(json);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// non-JSON data lines are ignored
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
consumeUsage(json) {
|
|
73
|
+
if (this.protocol === 'openai') {
|
|
74
|
+
const usage = json.usage;
|
|
75
|
+
if (usage && typeof usage === 'object') {
|
|
76
|
+
if (typeof usage.prompt_tokens === 'number')
|
|
77
|
+
this.promptTokens = usage.prompt_tokens;
|
|
78
|
+
if (typeof usage.completion_tokens === 'number') {
|
|
79
|
+
this.completionTokens = usage.completion_tokens;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (this.protocol === 'responses') {
|
|
85
|
+
// streaming: usage rides on the response.completed event
|
|
86
|
+
if (json.type === 'response.completed') {
|
|
87
|
+
const response = json.response;
|
|
88
|
+
const usage = response?.usage;
|
|
89
|
+
if (typeof usage?.input_tokens === 'number')
|
|
90
|
+
this.promptTokens = usage.input_tokens;
|
|
91
|
+
if (typeof usage?.output_tokens === 'number') {
|
|
92
|
+
this.completionTokens = usage.output_tokens;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// non-streaming message body
|
|
96
|
+
const usage = json.usage;
|
|
97
|
+
if (usage && typeof usage === 'object') {
|
|
98
|
+
if (typeof usage.input_tokens === 'number')
|
|
99
|
+
this.promptTokens = usage.input_tokens;
|
|
100
|
+
if (typeof usage.output_tokens === 'number')
|
|
101
|
+
this.completionTokens = usage.output_tokens;
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// anthropic: message_start carries input_tokens, message_delta carries output_tokens
|
|
106
|
+
if (json.type === 'message_start') {
|
|
107
|
+
const message = json.message;
|
|
108
|
+
const usage = message?.usage;
|
|
109
|
+
if (typeof usage?.input_tokens === 'number')
|
|
110
|
+
this.promptTokens = usage.input_tokens;
|
|
111
|
+
}
|
|
112
|
+
if (json.type === 'message_delta') {
|
|
113
|
+
const usage = json.usage;
|
|
114
|
+
if (typeof usage?.output_tokens === 'number') {
|
|
115
|
+
this.completionTokens = usage.output_tokens;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// non-streaming message body
|
|
119
|
+
const usage = json.usage;
|
|
120
|
+
if (usage && typeof usage === 'object') {
|
|
121
|
+
if (typeof usage.input_tokens === 'number' && this.promptTokens === null) {
|
|
122
|
+
this.promptTokens = usage.input_tokens;
|
|
123
|
+
}
|
|
124
|
+
if (typeof usage.output_tokens === 'number' && this.completionTokens === null) {
|
|
125
|
+
this.completionTokens = usage.output_tokens;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
result() {
|
|
130
|
+
if (this.promptTokens === null && this.completionTokens === null)
|
|
131
|
+
return null;
|
|
132
|
+
return { promptTokens: this.promptTokens, completionTokens: this.completionTokens };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.js","sourceRoot":"","sources":["../src/meter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Hop-by-hop and credential headers that must never enter the tunnel. */
|
|
2
|
+
const STRIP_REQUEST = new Set([
|
|
3
|
+
'host',
|
|
4
|
+
'connection',
|
|
5
|
+
'keep-alive',
|
|
6
|
+
'transfer-encoding',
|
|
7
|
+
'upgrade',
|
|
8
|
+
'te',
|
|
9
|
+
'trailer',
|
|
10
|
+
'content-length',
|
|
11
|
+
'expect',
|
|
12
|
+
// consumer credentials — the agent injects the upstream key, the two never meet
|
|
13
|
+
'authorization',
|
|
14
|
+
'x-api-key',
|
|
15
|
+
// cookies authenticate the consumer to the hub domain; upstream must never see them
|
|
16
|
+
'cookie',
|
|
17
|
+
]);
|
|
18
|
+
const STRIP_RESPONSE = new Set([
|
|
19
|
+
'connection',
|
|
20
|
+
'keep-alive',
|
|
21
|
+
'transfer-encoding',
|
|
22
|
+
'upgrade',
|
|
23
|
+
'te',
|
|
24
|
+
'trailer',
|
|
25
|
+
// we relay chunked; byte count is identical so a stale length is never correct
|
|
26
|
+
'content-length',
|
|
27
|
+
// agent-side pickHeaders already drops it; strip again in depth
|
|
28
|
+
'set-cookie',
|
|
29
|
+
]);
|
|
30
|
+
export function sanitizeTunnelRequestHeaders(req) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
33
|
+
const lower = key.toLowerCase();
|
|
34
|
+
if (STRIP_REQUEST.has(lower) || lower.startsWith('proxy-'))
|
|
35
|
+
continue;
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
out[lower] = value;
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(value)) {
|
|
40
|
+
out[lower] = value.join(', ');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
out['content-type'] = out['content-type'] ?? 'application/json';
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
export function sanitizeUpstreamResponseHeaders(headers) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
49
|
+
const lower = key.toLowerCase();
|
|
50
|
+
if (STRIP_RESPONSE.has(lower) || lower.startsWith('proxy-'))
|
|
51
|
+
continue;
|
|
52
|
+
out[lower] = value;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,MAAM;IACN,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,SAAS;IACT,IAAI;IACJ,SAAS;IACT,gBAAgB;IAChB,QAAQ;IACR,gFAAgF;IAChF,eAAe;IACf,WAAW;IACX,oFAAoF;IACpF,QAAQ;CACT,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,SAAS;IACT,IAAI;IACJ,SAAS;IACT,+EAA+E;IAC/E,gBAAgB;IAChB,gEAAgE;IAChE,YAAY;CACb,CAAC,CAAA;AAEF,MAAM,UAAU,4BAA4B,CAAC,GAAoB;IAC/D,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACpE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;QACpB,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IACD,GAAG,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,cAAc,CAAC,IAAI,kBAAkB,CAAA;IAC/D,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,OAA+B;IAE/B,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACrE,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;IACpB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import type Database from 'better-sqlite3';
|
|
3
|
+
import Logger from 'pino';
|
|
4
|
+
import type { AuthContext } from './auth.js';
|
|
5
|
+
import { type HubConfig, type HubPaths } from './config.js';
|
|
6
|
+
import { Router } from './http.js';
|
|
7
|
+
import { Tunnel } from './tunnel.js';
|
|
8
|
+
export interface HubServer {
|
|
9
|
+
server: Server;
|
|
10
|
+
db: Database.Database;
|
|
11
|
+
router: Router;
|
|
12
|
+
auth: AuthContext;
|
|
13
|
+
tunnel: Tunnel;
|
|
14
|
+
paths: HubPaths;
|
|
15
|
+
config: HubConfig;
|
|
16
|
+
log: Logger.Logger;
|
|
17
|
+
close: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateHubOptions {
|
|
20
|
+
paths?: HubPaths;
|
|
21
|
+
config?: HubConfig;
|
|
22
|
+
log?: Logger.Logger;
|
|
23
|
+
}
|
|
24
|
+
/** Assemble the hub: db + admin API + healthz. Tunnel and /v1 routes attach in later phases. */
|
|
25
|
+
export declare function createHubServer(opts?: CreateHubOptions): HubServer;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import Logger from 'pino';
|
|
3
|
+
import { registerAdminRoutes } from './admin.js';
|
|
4
|
+
import { ensureDataDir, hubConfigFromEnv, readAdminToken, resolvePaths, } from './config.js';
|
|
5
|
+
import { registerConsumerRoutes } from './consumer.js';
|
|
6
|
+
import { openDb } from './db.js';
|
|
7
|
+
import { Router, sendError, sendJson } from './http.js';
|
|
8
|
+
import { AliasSlots, ConsumerLimiter, TokenWindow } from './limiter.js';
|
|
9
|
+
import { loadOrCreateAdminToken, loadOrCreatePepper } from './tokens.js';
|
|
10
|
+
import { Tunnel } from './tunnel.js';
|
|
11
|
+
/** Assemble the hub: db + admin API + healthz. Tunnel and /v1 routes attach in later phases. */
|
|
12
|
+
export function createHubServer(opts = {}) {
|
|
13
|
+
const paths = opts.paths ?? resolvePaths();
|
|
14
|
+
const config = opts.config ?? hubConfigFromEnv();
|
|
15
|
+
const log = opts.log ?? Logger({ level: process.env.AWESHARE_LOG ?? 'info' });
|
|
16
|
+
ensureDataDir(paths);
|
|
17
|
+
const db = openDb(paths.dbFile);
|
|
18
|
+
const pepper = loadOrCreatePepper(paths);
|
|
19
|
+
const adminToken = readAdminToken(paths) ?? loadOrCreateAdminToken(paths);
|
|
20
|
+
const auth = { db, pepper, adminToken };
|
|
21
|
+
const router = new Router();
|
|
22
|
+
router.add('GET', '/healthz', (ctx) => sendJson(ctx.res, 200, { ok: true }));
|
|
23
|
+
registerAdminRoutes(router, { db, auth, log });
|
|
24
|
+
const tunnel = new Tunnel(db, auth, config, log);
|
|
25
|
+
registerConsumerRoutes(router, {
|
|
26
|
+
db,
|
|
27
|
+
auth,
|
|
28
|
+
config,
|
|
29
|
+
tunnel,
|
|
30
|
+
limiter: new ConsumerLimiter(),
|
|
31
|
+
tpm: new TokenWindow(),
|
|
32
|
+
slots: new AliasSlots(),
|
|
33
|
+
log,
|
|
34
|
+
});
|
|
35
|
+
const server = createServer(async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(req.url ?? '/', 'http://local');
|
|
38
|
+
const matched = router.match(req.method ?? 'GET', url.pathname);
|
|
39
|
+
if (matched === 'not-found') {
|
|
40
|
+
sendJson(res, 404, { error: { code: 'NOT_FOUND', message: url.pathname } });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (matched === 'method-not-allowed') {
|
|
44
|
+
sendJson(res, 405, { error: { code: 'METHOD_NOT_ALLOWED', message: url.pathname } });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await matched.handler({
|
|
48
|
+
req,
|
|
49
|
+
res,
|
|
50
|
+
params: matched.params,
|
|
51
|
+
query: url.searchParams,
|
|
52
|
+
url,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
sendError(res, err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
server.on('clientError', (_err, socket) => socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'));
|
|
60
|
+
tunnel.attach(server);
|
|
61
|
+
return {
|
|
62
|
+
server,
|
|
63
|
+
db,
|
|
64
|
+
router,
|
|
65
|
+
auth,
|
|
66
|
+
tunnel,
|
|
67
|
+
paths,
|
|
68
|
+
config,
|
|
69
|
+
log,
|
|
70
|
+
close: async () => {
|
|
71
|
+
tunnel.close();
|
|
72
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
73
|
+
db.close();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TokenRole } from 'aweshare-protocol';
|
|
2
|
+
import type { HubPaths } from './config.js';
|
|
3
|
+
/** 32 random bytes -> 43 base64url chars; matches TOKEN_PATTERN. */
|
|
4
|
+
export declare function generateToken(role: TokenRole): string;
|
|
5
|
+
export declare function hashToken(token: string, pepper: string): string;
|
|
6
|
+
export declare function loadOrCreatePepper(paths: HubPaths): string;
|
|
7
|
+
/** Admin token lives only in a server-local file; shown once at init. */
|
|
8
|
+
export declare function loadOrCreateAdminToken(paths: HubPaths): string;
|
|
9
|
+
export declare function safeEqual(a: string, b: string): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { TOKEN_PREFIXES } from 'aweshare-protocol';
|
|
4
|
+
/** 32 random bytes -> 43 base64url chars; matches TOKEN_PATTERN. */
|
|
5
|
+
export function generateToken(role) {
|
|
6
|
+
return TOKEN_PREFIXES[role] + randomBytes(32).toString('base64url');
|
|
7
|
+
}
|
|
8
|
+
export function hashToken(token, pepper) {
|
|
9
|
+
return createHash('sha256').update(`${pepper}:${token}`).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
export function loadOrCreatePepper(paths) {
|
|
12
|
+
if (existsSync(paths.pepperFile)) {
|
|
13
|
+
return readFileSync(paths.pepperFile, 'utf8').trim();
|
|
14
|
+
}
|
|
15
|
+
const pepper = randomBytes(32).toString('hex');
|
|
16
|
+
writeFileSync(paths.pepperFile, `${pepper}\n`, { mode: 0o600 });
|
|
17
|
+
return pepper;
|
|
18
|
+
}
|
|
19
|
+
/** Admin token lives only in a server-local file; shown once at init. */
|
|
20
|
+
export function loadOrCreateAdminToken(paths) {
|
|
21
|
+
if (existsSync(paths.adminTokenFile)) {
|
|
22
|
+
return readFileSync(paths.adminTokenFile, 'utf8').trim();
|
|
23
|
+
}
|
|
24
|
+
const token = generateToken('admin');
|
|
25
|
+
writeFileSync(paths.adminTokenFile, `${token}\n`, { mode: 0o600 });
|
|
26
|
+
return token;
|
|
27
|
+
}
|
|
28
|
+
export function safeEqual(a, b) {
|
|
29
|
+
const ba = Buffer.from(a);
|
|
30
|
+
const bb = Buffer.from(b);
|
|
31
|
+
if (ba.length !== bb.length)
|
|
32
|
+
return false;
|
|
33
|
+
return timingSafeEqual(ba, bb);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAc;IACrD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACxE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAe;IAChD,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACtD,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC9C,aAAa,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/D,OAAO,MAAM,CAAA;AACf,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,sBAAsB,CAAC,KAAe;IACpD,IAAI,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QACrC,OAAO,YAAY,CAAC,KAAK,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1D,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACpC,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC5C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzC,OAAO,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Server } from 'node:http';
|
|
3
|
+
import { type BackendStatus, type Protocol } from 'aweshare-protocol';
|
|
4
|
+
import type Database from 'better-sqlite3';
|
|
5
|
+
import type { Logger } from 'pino';
|
|
6
|
+
import { WebSocket } from 'ws';
|
|
7
|
+
import type { AuthContext } from './auth.js';
|
|
8
|
+
import type { HubConfig } from './config.js';
|
|
9
|
+
import { type OfferingRow } from './db.js';
|
|
10
|
+
export declare const HEARTBEAT_TIMEOUT_MS = 45000;
|
|
11
|
+
export interface Session {
|
|
12
|
+
producerId: number;
|
|
13
|
+
name: string;
|
|
14
|
+
ws: WebSocket;
|
|
15
|
+
backendStatus: Map<string, BackendStatus>;
|
|
16
|
+
lastHeartbeat: number;
|
|
17
|
+
requests: Map<string, TunnelRequest>;
|
|
18
|
+
}
|
|
19
|
+
export interface DispatchInit {
|
|
20
|
+
protocol: Protocol;
|
|
21
|
+
backendId: string;
|
|
22
|
+
method: string;
|
|
23
|
+
path: string;
|
|
24
|
+
query: Record<string, string> | null;
|
|
25
|
+
headers: Record<string, string>;
|
|
26
|
+
body: Uint8Array;
|
|
27
|
+
}
|
|
28
|
+
export interface RelayResult {
|
|
29
|
+
status: number | null;
|
|
30
|
+
errorCode: string | null;
|
|
31
|
+
canceled: boolean;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
bytesOut: number;
|
|
34
|
+
}
|
|
35
|
+
export interface RelayFailure {
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
status: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function statusForCode(code: string): number;
|
|
41
|
+
/**
|
|
42
|
+
* One dispatched consumer request: ticket + dispatch + response pump.
|
|
43
|
+
* Emits: 'head' {status, headers}, 'chunk' (Uint8Array), 'end', 'error' (RelayFailure).
|
|
44
|
+
* waitFinished() always resolves — never rejects — with the final RelayResult.
|
|
45
|
+
*/
|
|
46
|
+
export declare class TunnelRequest extends EventEmitter {
|
|
47
|
+
private readonly session;
|
|
48
|
+
private readonly init;
|
|
49
|
+
private readonly config;
|
|
50
|
+
readonly requestId: string;
|
|
51
|
+
readonly startedAt: number;
|
|
52
|
+
bytesOut: number;
|
|
53
|
+
private headPromise;
|
|
54
|
+
private resolveHead;
|
|
55
|
+
private rejectHead;
|
|
56
|
+
private finishedPromise;
|
|
57
|
+
private settleFinished;
|
|
58
|
+
private result;
|
|
59
|
+
private finished;
|
|
60
|
+
private headArrived;
|
|
61
|
+
private streamingStarted;
|
|
62
|
+
private bufferedChunks;
|
|
63
|
+
private pendingEnd;
|
|
64
|
+
private headTimer;
|
|
65
|
+
private idleTimer;
|
|
66
|
+
constructor(session: Session, init: DispatchInit, config: HubConfig);
|
|
67
|
+
dispatch(): Promise<void>;
|
|
68
|
+
waitHead(): Promise<{
|
|
69
|
+
status: number;
|
|
70
|
+
headers: Record<string, string>;
|
|
71
|
+
}>;
|
|
72
|
+
/** Flush chunks buffered between head and consumer attach; after this, chunks emit live. */
|
|
73
|
+
startStreaming(): void;
|
|
74
|
+
waitFinished(): Promise<RelayResult>;
|
|
75
|
+
/** Consumer went away; tell the agent to abort upstream. */
|
|
76
|
+
cancel(): void;
|
|
77
|
+
handleHead(status: number, headers: Record<string, string>): void;
|
|
78
|
+
handleChunk(payload: Uint8Array): void;
|
|
79
|
+
handleEnd(): void;
|
|
80
|
+
handleError(code: string, message: string): void;
|
|
81
|
+
handleSessionClosed(): void;
|
|
82
|
+
private fail;
|
|
83
|
+
private finalize;
|
|
84
|
+
private armIdleTimer;
|
|
85
|
+
private clearTimers;
|
|
86
|
+
private send;
|
|
87
|
+
private sendBestEffort;
|
|
88
|
+
}
|
|
89
|
+
/** WS endpoint /ws/v1/producer: upgrade auth, register, heartbeat, request routing. */
|
|
90
|
+
export declare class Tunnel {
|
|
91
|
+
private readonly db;
|
|
92
|
+
private readonly auth;
|
|
93
|
+
private readonly config;
|
|
94
|
+
private readonly log;
|
|
95
|
+
private sessions;
|
|
96
|
+
private wss;
|
|
97
|
+
private sweeper;
|
|
98
|
+
constructor(db: Database.Database, auth: AuthContext, config: HubConfig, log: Logger);
|
|
99
|
+
attach(server: Server): void;
|
|
100
|
+
getSession(producerId: number): Session | undefined;
|
|
101
|
+
offeringStatus(offering: OfferingRow): 'online' | 'degraded' | 'offline';
|
|
102
|
+
openRequest(session: Session, init: DispatchInit): TunnelRequest;
|
|
103
|
+
close(): void;
|
|
104
|
+
private onConnection;
|
|
105
|
+
private handleControl;
|
|
106
|
+
private validateOffering;
|
|
107
|
+
private sweep;
|
|
108
|
+
}
|