@seedprotocol/gateway-hyper 0.5.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/README.md +40 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +11 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/tunnel/connect.d.ts +7 -0
- package/dist/tunnel/connect.d.ts.map +1 -0
- package/dist/tunnel/protocol.d.ts +21 -0
- package/dist/tunnel/protocol.d.ts.map +1 -0
- package/dist/tunnel/proxy.d.ts +7 -0
- package/dist/tunnel/proxy.d.ts.map +1 -0
- package/dist/tunnel/serve.d.ts +6 -0
- package/dist/tunnel/serve.d.ts.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @seedprotocol/gateway-hyper
|
|
2
|
+
|
|
3
|
+
Expose a local **seed-protocol-server + ar.io gateway** stack (via Traefik or direct upstream) to SDK users over **HyperDHT**, with a localhost HTTP sidecar for apps and RSS-style tooling.
|
|
4
|
+
|
|
5
|
+
See [docs/GATEWAY_HYPER.md](../../docs/GATEWAY_HYPER.md) for operator and developer runbooks.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @seedprotocol/gateway-hyper
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node 20+ and Holepunch native addons.
|
|
14
|
+
|
|
15
|
+
## CLI
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Operator
|
|
19
|
+
seed gateway tunnel serve --upstream http://127.0.0.1:80 --key-file ./.seed/gateway-tunnel/operator.key.json
|
|
20
|
+
|
|
21
|
+
# Client sidecar
|
|
22
|
+
seed gateway tunnel connect <z32-key> --port 1984
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Library
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { serveTunnel, connectTunnel } from '@seedprotocol/gateway-hyper'
|
|
29
|
+
|
|
30
|
+
const operator = await serveTunnel({ upstream: 'http://127.0.0.1:80' })
|
|
31
|
+
console.log(operator.key)
|
|
32
|
+
|
|
33
|
+
const client = await connectTunnel({ key: operator.key, port: 1984 })
|
|
34
|
+
console.log(client.baseUrl) // http://127.0.0.1:1984
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Tests
|
|
38
|
+
|
|
39
|
+
- Unit tests always run.
|
|
40
|
+
- DHT integration: `GATEWAY_HYPER_TESTS=1 bun run test`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,8BAA8B,KAAK,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { encodePublicKeyZ32, decodePublicKey, loadOrCreateOperatorKeypair } from './keys';
|
|
2
|
+
export { serveTunnel } from './tunnel/serve';
|
|
3
|
+
export { connectTunnel, localGatewayUrl } from './tunnel/connect';
|
|
4
|
+
export { writeFrame, readFrame, writeTunnelRequest, readTunnelRequest, writeTunnelResponse, readTunnelResponse, flattenRequestHeaders, filterResponseHeaders, MAX_FRAME_BYTES, } from './tunnel/protocol';
|
|
5
|
+
export type { TunnelMeta, TunnelResponseMeta, ServeTunnelOptions, ServeTunnelResult, ConnectTunnelOptions, ConnectTunnelResult, OperatorKeyPair, } from './types';
|
|
6
|
+
export { DEFAULT_SEED_GATEWAY_HYPER_KEY } from './constants';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,QAAQ,CAAA;AAEzF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEjE,OAAO,EACL,UAAU,EACV,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAE1B,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,GAChB,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import e from "node:fs";
|
|
2
|
+
import t from "node:path";
|
|
3
|
+
import n from "hyperdht";
|
|
4
|
+
import r from "hypercore-id-encoding";
|
|
5
|
+
import i from "node:http";
|
|
6
|
+
import { URL as a } from "node:url";
|
|
7
|
+
//#region src/keys.ts
|
|
8
|
+
function o(e) {
|
|
9
|
+
return e.toString("hex");
|
|
10
|
+
}
|
|
11
|
+
function s(e) {
|
|
12
|
+
return Buffer.from(e, "hex");
|
|
13
|
+
}
|
|
14
|
+
function c(e) {
|
|
15
|
+
return r.encode(e);
|
|
16
|
+
}
|
|
17
|
+
function l(e) {
|
|
18
|
+
let t = e.trim();
|
|
19
|
+
if (!t) throw Error("Gateway Hyper key is empty");
|
|
20
|
+
try {
|
|
21
|
+
return Buffer.from(r.decode(t));
|
|
22
|
+
} catch {
|
|
23
|
+
if (/^[a-fA-F0-9]{64}$/.test(t)) return s(t);
|
|
24
|
+
throw Error(`Invalid Gateway Hyper key (expected z32 or 64-char hex): ${t.slice(0, 12)}…`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function u(r) {
|
|
28
|
+
let i = t.resolve(r);
|
|
29
|
+
if (e.existsSync(i)) {
|
|
30
|
+
let t = e.readFileSync(i, "utf8"), n = JSON.parse(t);
|
|
31
|
+
if (!n.publicKey || !n.secretKey) throw Error(`Invalid key file at ${i}: missing publicKey or secretKey`);
|
|
32
|
+
let r = s(n.publicKey);
|
|
33
|
+
return {
|
|
34
|
+
publicKey: r,
|
|
35
|
+
secretKey: s(n.secretKey),
|
|
36
|
+
z32: c(r)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
let a = t.dirname(i);
|
|
40
|
+
e.mkdirSync(a, { recursive: !0 });
|
|
41
|
+
let l = n.keyPair(), u = {
|
|
42
|
+
publicKey: o(l.publicKey),
|
|
43
|
+
secretKey: o(l.secretKey),
|
|
44
|
+
encoding: "hex"
|
|
45
|
+
};
|
|
46
|
+
return e.writeFileSync(i, `${JSON.stringify(u, null, 2)}\n`, { mode: 384 }), {
|
|
47
|
+
publicKey: l.publicKey,
|
|
48
|
+
secretKey: l.secretKey,
|
|
49
|
+
z32: c(l.publicKey)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function d() {
|
|
53
|
+
let e = n.keyPair();
|
|
54
|
+
return {
|
|
55
|
+
publicKey: e.publicKey,
|
|
56
|
+
secretKey: e.secretKey,
|
|
57
|
+
z32: c(e.publicKey)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/tunnel/protocol.ts
|
|
62
|
+
var f = 64 * 1024 * 1024;
|
|
63
|
+
function p(e, t) {
|
|
64
|
+
return new Promise((n, r) => {
|
|
65
|
+
let i = [], a = 0, o = (r) => {
|
|
66
|
+
if (i.push(r), a += r.length, a >= t) {
|
|
67
|
+
l();
|
|
68
|
+
let r = Buffer.concat(i);
|
|
69
|
+
n(r.subarray(0, t));
|
|
70
|
+
let a = r.subarray(t);
|
|
71
|
+
a.length > 0 && e.unshift(a);
|
|
72
|
+
}
|
|
73
|
+
}, s = (e) => {
|
|
74
|
+
l(), r(e);
|
|
75
|
+
}, c = () => {
|
|
76
|
+
l(), r(/* @__PURE__ */ Error(`Unexpected end of stream (expected ${t} bytes, got ${a})`));
|
|
77
|
+
}, l = () => {
|
|
78
|
+
e.off("data", o), e.off("error", s), e.off("end", c);
|
|
79
|
+
};
|
|
80
|
+
e.on("data", o), e.on("error", s), e.on("end", c);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function m(e, t) {
|
|
84
|
+
return new Promise((n, r) => {
|
|
85
|
+
e.write(t, (e) => {
|
|
86
|
+
e ? r(e) : n();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function h(e, t) {
|
|
91
|
+
if (t.length > 67108864) throw Error(`Frame too large: ${t.length} bytes (max ${f})`);
|
|
92
|
+
let n = Buffer.alloc(4);
|
|
93
|
+
n.writeUInt32BE(t.length, 0), await m(e, n), t.length > 0 && await m(e, t);
|
|
94
|
+
}
|
|
95
|
+
async function g(e) {
|
|
96
|
+
let t = (await p(e, 4)).readUInt32BE(0);
|
|
97
|
+
if (t === 0) return Buffer.alloc(0);
|
|
98
|
+
if (t > 67108864) throw Error(`Frame too large: ${t} bytes (max ${f})`);
|
|
99
|
+
return p(e, t);
|
|
100
|
+
}
|
|
101
|
+
async function _(e, t, n) {
|
|
102
|
+
await h(e, Buffer.from(JSON.stringify(t), "utf8")), await h(e, n);
|
|
103
|
+
}
|
|
104
|
+
async function v(e) {
|
|
105
|
+
let t = await g(e), n = JSON.parse(t.toString("utf8"));
|
|
106
|
+
if (!n.method || !n.path) throw Error("Invalid tunnel request meta: missing method or path");
|
|
107
|
+
return n.headers = n.headers ?? {}, {
|
|
108
|
+
meta: n,
|
|
109
|
+
body: await g(e)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function y(e, t, n) {
|
|
113
|
+
await h(e, Buffer.from(JSON.stringify(t), "utf8")), await h(e, n);
|
|
114
|
+
}
|
|
115
|
+
async function b(e) {
|
|
116
|
+
let t = await g(e), n = JSON.parse(t.toString("utf8"));
|
|
117
|
+
if (typeof n.status != "number") throw Error("Invalid tunnel response meta: missing status");
|
|
118
|
+
return n.headers = n.headers ?? {}, {
|
|
119
|
+
meta: n,
|
|
120
|
+
body: await g(e)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function x(e) {
|
|
124
|
+
let t = {};
|
|
125
|
+
for (let [n, r] of Object.entries(e)) r != null && (t[n] = Array.isArray(r) ? r.join(", ") : r);
|
|
126
|
+
return t;
|
|
127
|
+
}
|
|
128
|
+
function S(e) {
|
|
129
|
+
let t = new Set([
|
|
130
|
+
"connection",
|
|
131
|
+
"keep-alive",
|
|
132
|
+
"transfer-encoding",
|
|
133
|
+
"upgrade"
|
|
134
|
+
]), n = {};
|
|
135
|
+
for (let [r, i] of Object.entries(e)) t.has(r.toLowerCase()) || i != null && (n[r] = Array.isArray(i) ? i.join(", ") : i);
|
|
136
|
+
return n;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/tunnel/proxy.ts
|
|
140
|
+
async function C(e, t, n) {
|
|
141
|
+
let r = e.replace(/\/$/, ""), o = new a(t.path, `${r}/`);
|
|
142
|
+
return new Promise((e, r) => {
|
|
143
|
+
let a = i.request(o, {
|
|
144
|
+
method: t.method,
|
|
145
|
+
headers: t.headers
|
|
146
|
+
}, (t) => {
|
|
147
|
+
let n = [];
|
|
148
|
+
t.on("data", (e) => n.push(e)), t.on("end", () => {
|
|
149
|
+
e({
|
|
150
|
+
status: t.statusCode ?? 502,
|
|
151
|
+
headers: S(t.headers),
|
|
152
|
+
body: Buffer.concat(n)
|
|
153
|
+
});
|
|
154
|
+
}), t.on("error", r);
|
|
155
|
+
});
|
|
156
|
+
a.on("error", r), n.length > 0 && a.write(n), a.end();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/tunnel/serve.ts
|
|
161
|
+
function w(e, t) {
|
|
162
|
+
(async () => {
|
|
163
|
+
try {
|
|
164
|
+
for (;;) {
|
|
165
|
+
let { meta: n, body: r } = await v(e), i = await C(t, n, r);
|
|
166
|
+
await y(e, {
|
|
167
|
+
status: i.status,
|
|
168
|
+
headers: i.headers
|
|
169
|
+
}, i.body);
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
e.destroy();
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
}
|
|
176
|
+
async function T(e) {
|
|
177
|
+
let t = e.upstream.replace(/\/$/, ""), r = u(e.keyFile ?? ".seed/gateway-tunnel/operator.key.json"), i = new n(), a = i.createServer((e) => {
|
|
178
|
+
w(e, t);
|
|
179
|
+
});
|
|
180
|
+
return await a.listen(r), {
|
|
181
|
+
key: r.z32,
|
|
182
|
+
upstream: t,
|
|
183
|
+
close: async () => {
|
|
184
|
+
await a.close(), await i.destroy();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/tunnel/connect.ts
|
|
190
|
+
async function E(e) {
|
|
191
|
+
let t = [];
|
|
192
|
+
for await (let n of e) t.push(Buffer.isBuffer(n) ? n : Buffer.from(n));
|
|
193
|
+
return Buffer.concat(t);
|
|
194
|
+
}
|
|
195
|
+
function D(e) {
|
|
196
|
+
return i.createServer((t, n) => {
|
|
197
|
+
(async () => {
|
|
198
|
+
try {
|
|
199
|
+
let r = await E(t), i = t.url ?? "/", a = {
|
|
200
|
+
method: t.method ?? "GET",
|
|
201
|
+
path: i,
|
|
202
|
+
headers: x(t.headers)
|
|
203
|
+
}, { meta: o, body: s } = await e(async (e) => (await _(e, a, r), b(e)));
|
|
204
|
+
n.writeHead(o.status, o.headers), n.end(s);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
207
|
+
n.headersSent || n.writeHead(502, { "Content-Type": "text/plain; charset=utf-8" }), n.end(`Gateway Hyper tunnel error: ${t}`);
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async function O(e) {
|
|
213
|
+
let t = e.host ?? "127.0.0.1", r = e.port ?? 1984, i = l(e.key), a = d(), o = new n(), s = null, c = null, u = async () => s && !s.destroyed ? s : c || (c = new Promise((e, t) => {
|
|
214
|
+
let n = o.connect(i, { keyPair: {
|
|
215
|
+
publicKey: a.publicKey,
|
|
216
|
+
secretKey: a.secretKey
|
|
217
|
+
} }), r = () => {
|
|
218
|
+
s = n, c = null, e(n);
|
|
219
|
+
};
|
|
220
|
+
n.once("open", r), n.once("connect", r), n.once("error", (e) => {
|
|
221
|
+
c = null, t(e);
|
|
222
|
+
});
|
|
223
|
+
}), c), f = Promise.resolve(), p = async (e) => {
|
|
224
|
+
let t = f.then(() => u()).then(e);
|
|
225
|
+
return f = t.catch(() => {}), t;
|
|
226
|
+
}, m = D((e) => p(e));
|
|
227
|
+
await new Promise((e, n) => {
|
|
228
|
+
m.once("error", n), m.listen(r, t, () => e());
|
|
229
|
+
});
|
|
230
|
+
let h = m.address(), g = h && typeof h == "object" ? h.port : r, _ = `http://${t}:${g}`;
|
|
231
|
+
return {
|
|
232
|
+
key: e.key,
|
|
233
|
+
host: t,
|
|
234
|
+
port: g,
|
|
235
|
+
baseUrl: _,
|
|
236
|
+
close: async () => {
|
|
237
|
+
await new Promise((e) => m.close(() => e())), s && !s.destroyed && s.destroy(), await o.destroy();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function k(e, t = "") {
|
|
242
|
+
let n = e.replace(/\/$/, "");
|
|
243
|
+
return t ? t.startsWith("/") ? `${n}${t}` : `${n}/${t}` : n;
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/constants.ts
|
|
247
|
+
var A = "";
|
|
248
|
+
//#endregion
|
|
249
|
+
export { A as DEFAULT_SEED_GATEWAY_HYPER_KEY, f as MAX_FRAME_BYTES, O as connectTunnel, l as decodePublicKey, c as encodePublicKeyZ32, S as filterResponseHeaders, x as flattenRequestHeaders, u as loadOrCreateOperatorKeypair, k as localGatewayUrl, g as readFrame, v as readTunnelRequest, b as readTunnelResponse, T as serveTunnel, h as writeFrame, _ as writeTunnelRequest, y as writeTunnelResponse };
|
|
250
|
+
|
|
251
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/keys.ts","../src/tunnel/protocol.ts","../src/tunnel/proxy.ts","../src/tunnel/serve.ts","../src/tunnel/connect.ts","../src/constants.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport HyperDHT from 'hyperdht'\nimport ID from 'hypercore-id-encoding'\nimport type { OperatorKeyPair } from './types'\n\nexport interface PersistedKeyFile {\n publicKey: string\n secretKey: string\n encoding: 'hex'\n}\n\nfunction toHex(buf: Buffer): string {\n return buf.toString('hex')\n}\n\nfunction fromHex(hex: string): Buffer {\n return Buffer.from(hex, 'hex')\n}\n\nexport function encodePublicKeyZ32(publicKey: Buffer): string {\n return ID.encode(publicKey)\n}\n\nexport function decodePublicKey(key: string): Buffer {\n const trimmed = key.trim()\n if (!trimmed) {\n throw new Error('Gateway Hyper key is empty')\n }\n try {\n return Buffer.from(ID.decode(trimmed))\n } catch {\n if (/^[a-fA-F0-9]{64}$/.test(trimmed)) {\n return fromHex(trimmed)\n }\n throw new Error(`Invalid Gateway Hyper key (expected z32 or 64-char hex): ${trimmed.slice(0, 12)}…`)\n }\n}\n\nexport function loadOrCreateOperatorKeypair(keyFile: string): OperatorKeyPair {\n const resolved = path.resolve(keyFile)\n if (fs.existsSync(resolved)) {\n const raw = fs.readFileSync(resolved, 'utf8')\n const parsed = JSON.parse(raw) as PersistedKeyFile\n if (!parsed.publicKey || !parsed.secretKey) {\n throw new Error(`Invalid key file at ${resolved}: missing publicKey or secretKey`)\n }\n const publicKey = fromHex(parsed.publicKey)\n const secretKey = fromHex(parsed.secretKey)\n return {\n publicKey,\n secretKey,\n z32: encodePublicKeyZ32(publicKey),\n }\n }\n\n const dir = path.dirname(resolved)\n fs.mkdirSync(dir, { recursive: true })\n const kp = HyperDHT.keyPair()\n const persisted: PersistedKeyFile = {\n publicKey: toHex(kp.publicKey),\n secretKey: toHex(kp.secretKey),\n encoding: 'hex',\n }\n fs.writeFileSync(resolved, `${JSON.stringify(persisted, null, 2)}\\n`, { mode: 0o600 })\n return {\n publicKey: kp.publicKey,\n secretKey: kp.secretKey,\n z32: encodePublicKeyZ32(kp.publicKey),\n }\n}\n\nexport function createEphemeralClientKeypair(): OperatorKeyPair {\n const kp = HyperDHT.keyPair()\n return {\n publicKey: kp.publicKey,\n secretKey: kp.secretKey,\n z32: encodePublicKeyZ32(kp.publicKey),\n }\n}\n","import type { Readable, Writable } from 'node:stream'\nimport type { TunnelMeta, TunnelResponseMeta } from '../types'\n\n/** Maximum single frame payload (64 MiB). */\nexport const MAX_FRAME_BYTES = 64 * 1024 * 1024\n\nfunction readExact(stream: Readable, length: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let total = 0\n\n const onData = (chunk: Buffer) => {\n chunks.push(chunk)\n total += chunk.length\n if (total >= length) {\n cleanup()\n const buf = Buffer.concat(chunks)\n resolve(buf.subarray(0, length))\n const extra = buf.subarray(length)\n if (extra.length > 0) {\n stream.unshift(extra)\n }\n }\n }\n\n const onError = (err: Error) => {\n cleanup()\n reject(err)\n }\n\n const onEnd = () => {\n cleanup()\n reject(new Error(`Unexpected end of stream (expected ${length} bytes, got ${total})`))\n }\n\n const cleanup = () => {\n stream.off('data', onData)\n stream.off('error', onError)\n stream.off('end', onEnd)\n }\n\n stream.on('data', onData)\n stream.on('error', onError)\n stream.on('end', onEnd)\n })\n}\n\nfunction writeBuffer(stream: Writable, buf: Buffer): Promise<void> {\n return new Promise((resolve, reject) => {\n stream.write(buf, (err) => {\n if (err) reject(err)\n else resolve()\n })\n })\n}\n\nexport async function writeFrame(stream: Writable, payload: Buffer): Promise<void> {\n if (payload.length > MAX_FRAME_BYTES) {\n throw new Error(`Frame too large: ${payload.length} bytes (max ${MAX_FRAME_BYTES})`)\n }\n const header = Buffer.alloc(4)\n header.writeUInt32BE(payload.length, 0)\n await writeBuffer(stream, header)\n if (payload.length > 0) {\n await writeBuffer(stream, payload)\n }\n}\n\nexport async function readFrame(stream: Readable): Promise<Buffer> {\n const header = await readExact(stream, 4)\n const length = header.readUInt32BE(0)\n if (length === 0) {\n return Buffer.alloc(0)\n }\n if (length > MAX_FRAME_BYTES) {\n throw new Error(`Frame too large: ${length} bytes (max ${MAX_FRAME_BYTES})`)\n }\n return readExact(stream, length)\n}\n\nexport async function writeTunnelRequest(\n stream: Writable,\n meta: TunnelMeta,\n body: Buffer,\n): Promise<void> {\n const metaFrame = Buffer.from(JSON.stringify(meta), 'utf8')\n await writeFrame(stream, metaFrame)\n await writeFrame(stream, body)\n}\n\nexport async function readTunnelRequest(\n stream: Readable,\n): Promise<{ meta: TunnelMeta; body: Buffer }> {\n const metaBuf = await readFrame(stream)\n const meta = JSON.parse(metaBuf.toString('utf8')) as TunnelMeta\n if (!meta.method || !meta.path) {\n throw new Error('Invalid tunnel request meta: missing method or path')\n }\n meta.headers = meta.headers ?? {}\n const body = await readFrame(stream)\n return { meta, body }\n}\n\nexport async function writeTunnelResponse(\n stream: Writable,\n meta: TunnelResponseMeta,\n body: Buffer,\n): Promise<void> {\n const metaFrame = Buffer.from(JSON.stringify(meta), 'utf8')\n await writeFrame(stream, metaFrame)\n await writeFrame(stream, body)\n}\n\nexport async function readTunnelResponse(\n stream: Readable,\n): Promise<{ meta: TunnelResponseMeta; body: Buffer }> {\n const metaBuf = await readFrame(stream)\n const meta = JSON.parse(metaBuf.toString('utf8')) as TunnelResponseMeta\n if (typeof meta.status !== 'number') {\n throw new Error('Invalid tunnel response meta: missing status')\n }\n meta.headers = meta.headers ?? {}\n const body = await readFrame(stream)\n return { meta, body }\n}\n\n/** Normalize header values to strings for tunnel transport. */\nexport function flattenRequestHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\n/** Pick response headers safe to forward to the local HTTP client. */\nexport function filterResponseHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n const skip = new Set([\n 'connection',\n 'keep-alive',\n 'transfer-encoding',\n 'upgrade',\n ])\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (skip.has(key.toLowerCase())) continue\n if (value == null) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n","import http from 'node:http'\nimport { URL } from 'node:url'\nimport { filterResponseHeaders } from './protocol'\nimport type { TunnelMeta } from '../types'\n\nexport async function proxyTunnelRequest(\n upstreamBase: string,\n meta: TunnelMeta,\n body: Buffer,\n): Promise<{ status: number; headers: Record<string, string>; body: Buffer }> {\n const base = upstreamBase.replace(/\\/$/, '')\n const targetUrl = new URL(meta.path, `${base}/`)\n\n return new Promise((resolve, reject) => {\n const req = http.request(\n targetUrl,\n {\n method: meta.method,\n headers: meta.headers,\n },\n (res) => {\n const chunks: Buffer[] = []\n res.on('data', (chunk: Buffer) => chunks.push(chunk))\n res.on('end', () => {\n resolve({\n status: res.statusCode ?? 502,\n headers: filterResponseHeaders(res.headers as Record<string, string | string[] | undefined>),\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n },\n )\n req.on('error', reject)\n if (body.length > 0) {\n req.write(body)\n }\n req.end()\n })\n}\n","import type { Duplex } from 'node:stream'\nimport HyperDHT from 'hyperdht'\nimport { readTunnelRequest, writeTunnelResponse } from './protocol'\nimport { proxyTunnelRequest } from './proxy'\nimport { loadOrCreateOperatorKeypair } from '../keys'\nimport type { ServeTunnelOptions, ServeTunnelResult } from '../types'\n\nfunction handleConnection(\n socket: Duplex,\n upstream: string,\n): void {\n void (async () => {\n try {\n while (true) {\n const { meta, body } = await readTunnelRequest(socket)\n const result = await proxyTunnelRequest(upstream, meta, body)\n await writeTunnelResponse(\n socket,\n { status: result.status, headers: result.headers },\n result.body,\n )\n }\n } catch {\n socket.destroy()\n }\n })()\n}\n\n/**\n * Operator: accept Hyperswarm/DHT connections and proxy framed HTTP to upstream Traefik.\n */\nexport async function serveTunnel(options: ServeTunnelOptions): Promise<ServeTunnelResult> {\n const upstream = options.upstream.replace(/\\/$/, '')\n const keyFile = options.keyFile ?? '.seed/gateway-tunnel/operator.key.json'\n const keypair = loadOrCreateOperatorKeypair(keyFile)\n\n const dht = new HyperDHT()\n const server = dht.createServer((socket) => {\n handleConnection(socket as Duplex, upstream)\n })\n\n await server.listen(keypair)\n\n return {\n key: keypair.z32,\n upstream,\n close: async () => {\n await server.close()\n await dht.destroy()\n },\n }\n}\n","import http from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport HyperDHT from 'hyperdht'\nimport {\n flattenRequestHeaders,\n readTunnelResponse,\n writeTunnelRequest,\n} from './protocol'\nimport { createEphemeralClientKeypair, decodePublicKey } from '../keys'\nimport type { ConnectTunnelOptions, ConnectTunnelResult } from '../types'\n\nasync function readRequestBody(req: IncomingMessage): Promise<Buffer> {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))\n }\n return Buffer.concat(chunks)\n}\n\nfunction createLocalServer(\n runOnTunnel: <T>(fn: (socket: Duplex) => Promise<T>) => Promise<T>,\n): http.Server {\n return http.createServer((req, res) => {\n void (async () => {\n try {\n const body = await readRequestBody(req)\n const path = req.url ?? '/'\n const meta = {\n method: req.method ?? 'GET',\n path,\n headers: flattenRequestHeaders(req.headers as Record<string, string | string[] | undefined>),\n }\n\n const { meta: responseMeta, body: responseBody } = await runOnTunnel(async (socket) => {\n await writeTunnelRequest(socket, meta, body)\n return readTunnelResponse(socket)\n })\n\n res.writeHead(responseMeta.status, responseMeta.headers)\n res.end(responseBody)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' })\n }\n res.end(`Gateway Hyper tunnel error: ${message}`)\n }\n })()\n })\n}\n\n/**\n * Client: dial operator key and expose a localhost HTTP server that forwards through the tunnel.\n */\nexport async function connectTunnel(options: ConnectTunnelOptions): Promise<ConnectTunnelResult> {\n const host = options.host ?? '127.0.0.1'\n const port = options.port ?? 1984\n const remotePublicKey = decodePublicKey(options.key)\n const clientKey = createEphemeralClientKeypair()\n\n const dht = new HyperDHT()\n let activeSocket: Duplex | null = null\n let connecting: Promise<Duplex> | null = null\n\n const getSocket = async (): Promise<Duplex> => {\n if (activeSocket && !activeSocket.destroyed) {\n return activeSocket\n }\n if (connecting) {\n return connecting\n }\n connecting = new Promise<Duplex>((resolve, reject) => {\n const socket = dht.connect(remotePublicKey, {\n keyPair: { publicKey: clientKey.publicKey, secretKey: clientKey.secretKey },\n }) as Duplex\n const onReady = () => {\n activeSocket = socket\n connecting = null\n resolve(socket)\n }\n socket.once('open', onReady)\n socket.once('connect', onReady)\n socket.once('error', (err: Error) => {\n connecting = null\n reject(err)\n })\n })\n return connecting\n }\n\n /** Serialize tunnel transactions on one persistent socket. */\n let tunnelQueue: Promise<unknown> = Promise.resolve()\n const runOnTunnel = async <T>(fn: (socket: Duplex) => Promise<T>): Promise<T> => {\n const run = tunnelQueue.then(() => getSocket()).then(fn)\n tunnelQueue = run.catch(() => {})\n return run\n }\n\n const server = createLocalServer((handler) => runOnTunnel(handler))\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => resolve())\n })\n\n const address = server.address()\n const boundPort =\n address && typeof address === 'object' ? address.port : port\n\n const baseUrl = `http://${host}:${boundPort}`\n\n return {\n key: options.key,\n host,\n port: boundPort,\n baseUrl,\n close: async () => {\n await new Promise<void>((resolve) => server.close(() => resolve()))\n if (activeSocket && !activeSocket.destroyed) {\n activeSocket.destroy()\n }\n await dht.destroy()\n },\n }\n}\n\nexport function localGatewayUrl(baseUrl: string, path = ''): string {\n const base = baseUrl.replace(/\\/$/, '')\n if (!path) return base\n return path.startsWith('/') ? `${base}${path}` : `${base}/${path}`\n}\n","/**\n * Official Seed Protocol gateway Hyper tunnel operator key (z32).\n * Empty until ops generate and back up the first operator keypair.\n */\nexport const DEFAULT_SEED_GATEWAY_HYPER_KEY = ''\n"],"mappings":"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"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OperatorKeyPair } from './types';
|
|
2
|
+
export interface PersistedKeyFile {
|
|
3
|
+
publicKey: string;
|
|
4
|
+
secretKey: string;
|
|
5
|
+
encoding: 'hex';
|
|
6
|
+
}
|
|
7
|
+
export declare function encodePublicKeyZ32(publicKey: Buffer): string;
|
|
8
|
+
export declare function decodePublicKey(key: string): Buffer;
|
|
9
|
+
export declare function loadOrCreateOperatorKeypair(keyFile: string): OperatorKeyPair;
|
|
10
|
+
export declare function createEphemeralClientKeypair(): OperatorKeyPair;
|
|
11
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAA;CAChB;AAUD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAanD;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CA+B5E;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAO9D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConnectTunnelOptions, ConnectTunnelResult } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Client: dial operator key and expose a localhost HTTP server that forwards through the tunnel.
|
|
4
|
+
*/
|
|
5
|
+
export declare function connectTunnel(options: ConnectTunnelOptions): Promise<ConnectTunnelResult>;
|
|
6
|
+
export declare function localGatewayUrl(baseUrl: string, path?: string): string;
|
|
7
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/tunnel/connect.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AA0CzE;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsE/F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAIlE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Readable, Writable } from 'node:stream';
|
|
2
|
+
import type { TunnelMeta, TunnelResponseMeta } from '../types';
|
|
3
|
+
/** Maximum single frame payload (64 MiB). */
|
|
4
|
+
export declare const MAX_FRAME_BYTES: number;
|
|
5
|
+
export declare function writeFrame(stream: Writable, payload: Buffer): Promise<void>;
|
|
6
|
+
export declare function readFrame(stream: Readable): Promise<Buffer>;
|
|
7
|
+
export declare function writeTunnelRequest(stream: Writable, meta: TunnelMeta, body: Buffer): Promise<void>;
|
|
8
|
+
export declare function readTunnelRequest(stream: Readable): Promise<{
|
|
9
|
+
meta: TunnelMeta;
|
|
10
|
+
body: Buffer;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function writeTunnelResponse(stream: Writable, meta: TunnelResponseMeta, body: Buffer): Promise<void>;
|
|
13
|
+
export declare function readTunnelResponse(stream: Readable): Promise<{
|
|
14
|
+
meta: TunnelResponseMeta;
|
|
15
|
+
body: Buffer;
|
|
16
|
+
}>;
|
|
17
|
+
/** Normalize header values to strings for tunnel transport. */
|
|
18
|
+
export declare function flattenRequestHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
19
|
+
/** Pick response headers safe to forward to the local HTTP client. */
|
|
20
|
+
export declare function filterResponseHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
21
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/tunnel/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE9D,6CAA6C;AAC7C,eAAO,MAAM,eAAe,QAAmB,CAAA;AAoD/C,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjF;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAUjE;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAS7C;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CASrD;AAED,+DAA+D;AAC/D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/tunnel/proxy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B5E"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServeTunnelOptions, ServeTunnelResult } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Operator: accept Hyperswarm/DHT connections and proxy framed HTTP to upstream Traefik.
|
|
4
|
+
*/
|
|
5
|
+
export declare function serveTunnel(options: ServeTunnelOptions): Promise<ServeTunnelResult>;
|
|
6
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/tunnel/serve.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAuBrE;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBzF"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type TunnelMeta = {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export type TunnelResponseMeta = {
|
|
7
|
+
status: number;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
export type ServeTunnelOptions = {
|
|
11
|
+
/** Upstream HTTP origin (e.g. http://127.0.0.1:80) */
|
|
12
|
+
upstream: string;
|
|
13
|
+
/** Operator Ed25519 keypair file (JSON) — created on first run if missing */
|
|
14
|
+
keyFile?: string;
|
|
15
|
+
/** Directory for DHT state (optional, in-memory if unset) */
|
|
16
|
+
storePath?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ServeTunnelResult = {
|
|
19
|
+
/** Operator public key (z32) */
|
|
20
|
+
key: string;
|
|
21
|
+
upstream: string;
|
|
22
|
+
close: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export type ConnectTunnelOptions = {
|
|
25
|
+
/** Operator public key (z32 or hex) */
|
|
26
|
+
key: string;
|
|
27
|
+
/** Local HTTP bind host */
|
|
28
|
+
host?: string;
|
|
29
|
+
/** Local HTTP bind port */
|
|
30
|
+
port?: number;
|
|
31
|
+
/** Optional store path for DHT client state */
|
|
32
|
+
storePath?: string;
|
|
33
|
+
};
|
|
34
|
+
export type ConnectTunnelResult = {
|
|
35
|
+
key: string;
|
|
36
|
+
host: string;
|
|
37
|
+
port: number;
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
close: () => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
export type OperatorKeyPair = {
|
|
42
|
+
publicKey: Buffer;
|
|
43
|
+
secretKey: Buffer;
|
|
44
|
+
z32: string;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@seedprotocol/gateway-hyper",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Hyperswarm HTTP tunnel for Seed Protocol gateway + upload API infrastructure",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json && vite build",
|
|
28
|
+
"test": "vitest run --reporter=default",
|
|
29
|
+
"prepublishOnly": "bun run build && node ../../scripts/sync-versions.js"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"b4a": "^1.6.7",
|
|
33
|
+
"hypercore-id-encoding": "^1.3.0",
|
|
34
|
+
"hyperdht": "^6.20.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "latest",
|
|
38
|
+
"@types/node": "^24.10.1",
|
|
39
|
+
"rimraf": "^6.0.1",
|
|
40
|
+
"typescript": "~5.9.3",
|
|
41
|
+
"vite": "^8.0.0",
|
|
42
|
+
"vitest": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"author": "Keith Axline <keith@journodao.xyz>",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|