Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 072362a8e6 | |||
| b628a5f964 | |||
| 19e8003c77 | |||
| 93592bf909 | |||
| 73fc4ea28e | |||
| 5321e5f0e0 |
25
changelog.md
25
changelog.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-18 - 3.1.1 - fix(readme)
|
||||||
|
update README: add issue reporting/security section, document connection tokens and token utilities, clarify architecture/API and improve examples/formatting
|
||||||
|
|
||||||
|
- Added an 'Issue Reporting and Security' section linking to community.foss.global for bug/security reports and contributor onboarding.
|
||||||
|
- Documented connection tokens: encodeConnectionToken/decodeConnectionToken utilities, token format (base64url), and examples for hub and edge provisioning.
|
||||||
|
- Clarified Hub/Edge usage and examples: condensed event handlers, added token-based start() example, and provided explicit config alternative.
|
||||||
|
- Improved README formatting: added emojis, rephrased architecture descriptions, fixed wording and license path capitalization, and expanded example scenarios and interfaces.
|
||||||
|
|
||||||
|
## 2026-02-17 - 3.1.0 - feat(edge)
|
||||||
|
support connection tokens when starting an edge and add token encode/decode utilities
|
||||||
|
|
||||||
|
- Add classes.token.ts with encodeConnectionToken/decodeConnectionToken using a base64url compact JSON format
|
||||||
|
- Export token utilities from ts/index.ts
|
||||||
|
- RemoteIngressEdge.start now accepts a { token } option and decodes it to an IEdgeConfig before starting
|
||||||
|
- Add tests covering export availability, encode→decode roundtrip, malformed token, and missing fields
|
||||||
|
- Non-breaking change — recommend a minor version bump
|
||||||
|
|
||||||
|
## 2026-02-17 - 3.0.4 - fix(build)
|
||||||
|
bump dev dependencies, update build script, and refresh README docs
|
||||||
|
|
||||||
|
- Bumped devDependencies: @git.zone/tsbuild ^2.1.25 → ^4.1.2, @git.zone/tsbundle ^2.0.5 → ^2.8.3, @git.zone/tsrun ^1.2.46 → ^2.0.1, @git.zone/tstest ^1.0.44 → ^3.1.8, @push.rocks/tapbundle ^5.0.15 → ^6.0.3, @types/node ^20.8.7 → ^25.2.3
|
||||||
|
- Bumped runtime dependency: @push.rocks/qenv ^6.0.5 → ^6.1.3
|
||||||
|
- Changed build script: replaced "tsbuild --web --allowimplicitany" with "tsbuild tsfolders --allowimplicitany" (kept tsrust invocation)
|
||||||
|
- README updates: added RustBridge notes (localPaths must be full file paths), production binary naming conventions, rust core uses ring as rustls provider; removed emoji from example console output; clarified stunIntervalSecs is optional; renamed example status variable to edgeStatus; minor wire-protocol formatting and wording/legal text tweaks
|
||||||
|
|
||||||
## 2026-02-17 - 3.0.3 - fix(rust,ts)
|
## 2026-02-17 - 3.0.3 - fix(rust,ts)
|
||||||
initialize rustls ring CryptoProvider at startup; add rustls dependency and features; make native binary lookup platform-aware
|
initialize rustls ring CryptoProvider at startup; add rustls dependency and features; make native binary lookup platform-aware
|
||||||
|
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/remoteingress",
|
"name": "@serve.zone/remoteingress",
|
||||||
"version": "3.0.3",
|
"version": "3.1.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.",
|
"description": "Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -10,20 +10,20 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/ --web)",
|
"test": "(tstest test/ --web)",
|
||||||
"build": "(tsbuild --web --allowimplicitany && tsrust)",
|
"build": "(tsbuild tsfolders --allowimplicitany && tsrust)",
|
||||||
"buildDocs": "(tsdoc)"
|
"buildDocs": "(tsdoc)"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.25",
|
"@git.zone/tsbuild": "^4.1.2",
|
||||||
"@git.zone/tsbundle": "^2.0.5",
|
"@git.zone/tsbundle": "^2.8.3",
|
||||||
"@git.zone/tsrun": "^1.2.46",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tsrust": "^1.3.0",
|
"@git.zone/tsrust": "^1.3.0",
|
||||||
"@git.zone/tstest": "^1.0.44",
|
"@git.zone/tstest": "^3.1.8",
|
||||||
"@push.rocks/tapbundle": "^5.0.15",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^20.8.7"
|
"@types/node": "^25.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/qenv": "^6.0.5",
|
"@push.rocks/qenv": "^6.1.3",
|
||||||
"@push.rocks/smartrust": "^1.2.1"
|
"@push.rocks/smartrust": "^1.2.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
174
pnpm-lock.yaml
generated
174
pnpm-lock.yaml
generated
@@ -9,33 +9,33 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv':
|
'@push.rocks/qenv':
|
||||||
specifier: ^6.0.5
|
specifier: ^6.1.3
|
||||||
version: 6.1.3
|
version: 6.1.3
|
||||||
'@push.rocks/smartrust':
|
'@push.rocks/smartrust':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^2.1.25
|
specifier: ^4.1.2
|
||||||
version: 2.7.3
|
version: 4.1.2
|
||||||
'@git.zone/tsbundle':
|
'@git.zone/tsbundle':
|
||||||
specifier: ^2.0.5
|
specifier: ^2.8.3
|
||||||
version: 2.8.3
|
version: 2.8.3
|
||||||
'@git.zone/tsrun':
|
'@git.zone/tsrun':
|
||||||
specifier: ^1.2.46
|
specifier: ^2.0.1
|
||||||
version: 1.6.2
|
version: 2.0.1
|
||||||
'@git.zone/tsrust':
|
'@git.zone/tsrust':
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0
|
version: 1.3.0
|
||||||
'@git.zone/tstest':
|
'@git.zone/tstest':
|
||||||
specifier: ^1.0.44
|
specifier: ^3.1.8
|
||||||
version: 1.11.5(socks@2.8.7)(typescript@5.9.3)
|
version: 3.1.8(socks@2.8.7)(typescript@5.9.3)
|
||||||
'@push.rocks/tapbundle':
|
'@push.rocks/tapbundle':
|
||||||
specifier: ^5.0.15
|
specifier: ^6.0.3
|
||||||
version: 5.6.3(socks@2.8.7)
|
version: 6.0.3(socks@2.8.7)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.8.7
|
specifier: ^25.2.3
|
||||||
version: 20.19.33
|
version: 25.2.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -427,8 +427,8 @@ packages:
|
|||||||
'@esm-bundle/chai@4.3.4-fix.0':
|
'@esm-bundle/chai@4.3.4-fix.0':
|
||||||
resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==}
|
resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==}
|
||||||
|
|
||||||
'@git.zone/tsbuild@2.7.3':
|
'@git.zone/tsbuild@4.1.2':
|
||||||
resolution: {integrity: sha512-GMM6VU6TcVvYINfV6b1ZVGZXYhdtriYyAHifvrn8IdRar6thIN3ig3N2J/S1kmX2KLrBbx0JyF3tNChHdNR+wA==}
|
resolution: {integrity: sha512-S518ulKveO76pS6jrAELrnFaCw5nDAIZD9j6QzVmLYDiZuJmlRwPK3/2E8ugQ+b7ffpkwJ9MT685ooEGDcWQ4Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsbundle@2.8.3':
|
'@git.zone/tsbundle@2.8.3':
|
||||||
@@ -439,16 +439,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-dkgaDBTzZJ53lAV72r7OW/W7l/KqpkncFuPojr11JO35OKAbjjDhZbAwPv4oGX9NplyXrhC5VJRPNX/orqNTHA==}
|
resolution: {integrity: sha512-dkgaDBTzZJ53lAV72r7OW/W7l/KqpkncFuPojr11JO35OKAbjjDhZbAwPv4oGX9NplyXrhC5VJRPNX/orqNTHA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsrun@1.6.2':
|
'@git.zone/tsrun@2.0.1':
|
||||||
resolution: {integrity: sha512-SOHbQqBg3/769/jPQcdpPCmugdEtIJINiG0O6aWx+su91GvGhheha5dAhccsCutJYErr+aJcBqBYuUYfhOfkFQ==}
|
resolution: {integrity: sha512-NEcnsjvlC1o3Z6SS3VhKCf6Ev+Sh4EAinmggslrIR/ppMrvjDbXNFXoyr3PB+GLeSAR0JRZ1fGvVYjpEzjBdIg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsrust@1.3.0':
|
'@git.zone/tsrust@1.3.0':
|
||||||
resolution: {integrity: sha512-dvmTAiM04Pkd7J1Gail3fu7aasmILQhC5vKL71/g6HYhpvl16/c+Dj3We5G4HsFr0jvAr+Xu570ZGEuZrtRcCg==}
|
resolution: {integrity: sha512-dvmTAiM04Pkd7J1Gail3fu7aasmILQhC5vKL71/g6HYhpvl16/c+Dj3We5G4HsFr0jvAr+Xu570ZGEuZrtRcCg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tstest@1.11.5':
|
'@git.zone/tstest@3.1.8':
|
||||||
resolution: {integrity: sha512-7YHFNGMjUd3WOFXi0DlUieQcdxzwYqxL7n2XDE7SOUd8XpMxVsGsY2SuwBKXlbT10By/H3thQTsy+Hjy9ahGWA==}
|
resolution: {integrity: sha512-nmiLGeOkKMkLDyIk5BUBLx5ExskFbKHKlPdrWCARPVFkU4cAAiuIyJWVfLwISoS0TO/zSInLqArPwIc76yvaNw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@hapi/bourne@3.0.0':
|
'@hapi/bourne@3.0.0':
|
||||||
@@ -693,6 +693,9 @@ packages:
|
|||||||
'@push.rocks/smartbucket@3.3.10':
|
'@push.rocks/smartbucket@3.3.10':
|
||||||
resolution: {integrity: sha512-0H2MioALspC8Aj0Q1FPCs2w4k2u9oJg7Q5yM8+1TZo7aRfrdxgM5HQ7z3apUaqC3ZEDewW6vSlttjHFHhMEC3A==}
|
resolution: {integrity: sha512-0H2MioALspC8Aj0Q1FPCs2w4k2u9oJg7Q5yM8+1TZo7aRfrdxgM5HQ7z3apUaqC3ZEDewW6vSlttjHFHhMEC3A==}
|
||||||
|
|
||||||
|
'@push.rocks/smartbucket@4.4.1':
|
||||||
|
resolution: {integrity: sha512-68GFLgJKW+LXvuN+yuV8O/FozGMecraoT+PkI5whdRPFe7N3u2iYIHWAUjvQvVU4ygpdJv0kih2JDf5k3PYycw==}
|
||||||
|
|
||||||
'@push.rocks/smartbuffer@3.0.5':
|
'@push.rocks/smartbuffer@3.0.5':
|
||||||
resolution: {integrity: sha512-pWYF08Mn8s/KF/9nHRk7pZPzuMjmYVQay2c5gGexdayxn1W4eCSYYhWH73vR2JBfGeGq/izbRNuUuEaIEeTIKA==}
|
resolution: {integrity: sha512-pWYF08Mn8s/KF/9nHRk7pZPzuMjmYVQay2c5gGexdayxn1W4eCSYYhWH73vR2JBfGeGq/izbRNuUuEaIEeTIKA==}
|
||||||
|
|
||||||
@@ -733,9 +736,6 @@ packages:
|
|||||||
'@push.rocks/smartexit@1.1.0':
|
'@push.rocks/smartexit@1.1.0':
|
||||||
resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==}
|
resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==}
|
||||||
|
|
||||||
'@push.rocks/smartexpect@1.6.1':
|
|
||||||
resolution: {integrity: sha512-NFQXEPkGiMNxyvFwKyzDWe3ADYdf8KNvIcV7TGNZZT3uPQtk65te4Q+a1cWErjP/61yE9XdYiQA66QQp+TV9IQ==}
|
|
||||||
|
|
||||||
'@push.rocks/smartexpect@2.5.0':
|
'@push.rocks/smartexpect@2.5.0':
|
||||||
resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==}
|
resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==}
|
||||||
|
|
||||||
@@ -850,6 +850,9 @@ packages:
|
|||||||
'@push.rocks/smarts3@2.2.7':
|
'@push.rocks/smarts3@2.2.7':
|
||||||
resolution: {integrity: sha512-9ZXGMlmUL2Wd+YJO0xOB8KyqPf4V++fWJvTq4s76bnqEuaCr9OLfq6czhban+i4cD3ZdIjehfuHqctzjuLw8Jw==}
|
resolution: {integrity: sha512-9ZXGMlmUL2Wd+YJO0xOB8KyqPf4V++fWJvTq4s76bnqEuaCr9OLfq6czhban+i4cD3ZdIjehfuHqctzjuLw8Jw==}
|
||||||
|
|
||||||
|
'@push.rocks/smarts3@3.0.3':
|
||||||
|
resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==}
|
||||||
|
|
||||||
'@push.rocks/smartshell@3.3.0':
|
'@push.rocks/smartshell@3.3.0':
|
||||||
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
|
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
|
||||||
|
|
||||||
@@ -892,8 +895,8 @@ packages:
|
|||||||
'@push.rocks/smartyaml@3.0.4':
|
'@push.rocks/smartyaml@3.0.4':
|
||||||
resolution: {integrity: sha512-1JRt+hnoc2zHw3AW+vXKlCdSVwqOmY/01fu+2HBviS0UDjoZCa+/rp6E3GaQb5lEEafKi8ENbffAfjXXp3N2xQ==}
|
resolution: {integrity: sha512-1JRt+hnoc2zHw3AW+vXKlCdSVwqOmY/01fu+2HBviS0UDjoZCa+/rp6E3GaQb5lEEafKi8ENbffAfjXXp3N2xQ==}
|
||||||
|
|
||||||
'@push.rocks/tapbundle@5.6.3':
|
'@push.rocks/tapbundle@6.0.3':
|
||||||
resolution: {integrity: sha512-hFzsf59rg1K70i45llj7PCyyCZp7JW19XRR+Q1gge1T0pBN8Wi53aYqP/2qtxdMiNVe2s3ESp6VJZv3sLOMYPQ==}
|
resolution: {integrity: sha512-SuP14V6TPdtd1y1CYTvwTKJdpHa7EzY55NfaaEMxW4oRKvHgJiOiPEiR/IrtL9tSiDMSfrx12waTMgZheYaBug==}
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@3.5.0':
|
'@push.rocks/taskbuffer@3.5.0':
|
||||||
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
||||||
@@ -1495,12 +1498,12 @@ packages:
|
|||||||
'@types/node-forge@1.3.14':
|
'@types/node-forge@1.3.14':
|
||||||
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
|
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
|
||||||
|
|
||||||
'@types/node@20.19.33':
|
|
||||||
resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==}
|
|
||||||
|
|
||||||
'@types/node@22.19.11':
|
'@types/node@22.19.11':
|
||||||
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
|
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
|
||||||
|
|
||||||
|
'@types/node@25.2.3':
|
||||||
|
resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
|
||||||
|
|
||||||
'@types/parse5@6.0.3':
|
'@types/parse5@6.0.3':
|
||||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||||
|
|
||||||
@@ -3983,6 +3986,9 @@ packages:
|
|||||||
undici-types@6.21.0:
|
undici-types@6.21.0:
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||||
|
|
||||||
|
undici-types@7.16.0:
|
||||||
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
unified@11.0.5:
|
unified@11.0.5:
|
||||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||||
|
|
||||||
@@ -4916,13 +4922,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 4.3.20
|
'@types/chai': 4.3.20
|
||||||
|
|
||||||
'@git.zone/tsbuild@2.7.3':
|
'@git.zone/tsbuild@4.1.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@git.zone/tspublish': 1.11.0
|
'@git.zone/tspublish': 1.11.0
|
||||||
'@push.rocks/early': 4.0.4
|
'@push.rocks/early': 4.0.4
|
||||||
'@push.rocks/smartcli': 4.0.20
|
'@push.rocks/smartcli': 4.0.20
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 13.1.2
|
||||||
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
@@ -4984,9 +4991,9 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@git.zone/tsrun@1.6.2':
|
'@git.zone/tsrun@2.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
|
|
||||||
@@ -5005,26 +5012,28 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@git.zone/tstest@1.11.5(socks@2.8.7)(typescript@5.9.3)':
|
'@git.zone/tstest@3.1.8(socks@2.8.7)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedserver': 3.0.80
|
'@api.global/typedserver': 3.0.80
|
||||||
'@git.zone/tsbundle': 2.8.3
|
'@git.zone/tsbundle': 2.8.3
|
||||||
'@git.zone/tsrun': 1.6.2
|
'@git.zone/tsrun': 2.0.1
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartbrowser': 2.0.8(typescript@5.9.3)
|
'@push.rocks/smartbrowser': 2.0.8(typescript@5.9.3)
|
||||||
|
'@push.rocks/smartchok': 1.2.0
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 6.0.0
|
||||||
'@push.rocks/smartexpect': 2.5.0
|
'@push.rocks/smartexpect': 2.5.0
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||||
'@push.rocks/smartpath': 5.1.0
|
'@push.rocks/smartnetwork': 4.4.0
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 2.1.0
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
'@push.rocks/smarts3': 2.2.7
|
'@push.rocks/smarts3': 3.0.3
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
'@push.rocks/smarttime': 4.2.3
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
@@ -5169,7 +5178,7 @@ snapshots:
|
|||||||
'@jest/schemas': 29.6.3
|
'@jest/schemas': 29.6.3
|
||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-reports': 3.0.4
|
'@types/istanbul-reports': 3.0.4
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
'@types/yargs': 17.0.35
|
'@types/yargs': 17.0.35
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
|
|
||||||
@@ -5549,6 +5558,21 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
|
'@push.rocks/smartbucket@4.4.1':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.990.0
|
||||||
|
'@push.rocks/smartmime': 2.0.4
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartrx': 3.0.10
|
||||||
|
'@push.rocks/smartstream': 3.2.5
|
||||||
|
'@push.rocks/smartstring': 4.1.0
|
||||||
|
'@push.rocks/smartunique': 3.0.9
|
||||||
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
minimatch: 10.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- aws-crt
|
||||||
|
|
||||||
'@push.rocks/smartbuffer@3.0.5':
|
'@push.rocks/smartbuffer@3.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
uint8array-extras: 1.5.0
|
uint8array-extras: 1.5.0
|
||||||
@@ -5658,12 +5682,6 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
tree-kill: 1.2.2
|
tree-kill: 1.2.2
|
||||||
|
|
||||||
'@push.rocks/smartexpect@1.6.1':
|
|
||||||
dependencies:
|
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
|
||||||
fast-deep-equal: 3.1.3
|
|
||||||
|
|
||||||
'@push.rocks/smartexpect@2.5.0':
|
'@push.rocks/smartexpect@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
@@ -5986,6 +6004,16 @@ snapshots:
|
|||||||
- aws-crt
|
- aws-crt
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@push.rocks/smarts3@3.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartbucket': 4.4.1
|
||||||
|
'@push.rocks/smartfs': 1.3.1
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
'@push.rocks/smartxml': 2.0.0
|
||||||
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- aws-crt
|
||||||
|
|
||||||
'@push.rocks/smartshell@3.3.0':
|
'@push.rocks/smartshell@3.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
@@ -6095,7 +6123,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yaml: 2.8.2
|
yaml: 2.8.2
|
||||||
|
|
||||||
'@push.rocks/tapbundle@5.6.3(socks@2.8.7)':
|
'@push.rocks/tapbundle@6.0.3(socks@2.8.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@open-wc/testing': 4.0.0
|
'@open-wc/testing': 4.0.0
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
@@ -6103,7 +6131,7 @@ snapshots:
|
|||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 5.0.13
|
||||||
'@push.rocks/smartexpect': 1.6.1
|
'@push.rocks/smartexpect': 2.5.0
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
'@push.rocks/smartmongo': 2.2.0(socks@2.8.7)
|
||||||
@@ -6708,14 +6736,14 @@ snapshots:
|
|||||||
|
|
||||||
'@types/accepts@1.3.7':
|
'@types/accepts@1.3.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/babel__code-frame@7.27.0': {}
|
'@types/babel__code-frame@7.27.0': {}
|
||||||
|
|
||||||
'@types/body-parser@1.19.6':
|
'@types/body-parser@1.19.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/buffer-json@2.0.3': {}
|
'@types/buffer-json@2.0.3': {}
|
||||||
|
|
||||||
@@ -6732,17 +6760,17 @@ snapshots:
|
|||||||
|
|
||||||
'@types/clean-css@4.2.11':
|
'@types/clean-css@4.2.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
'@types/co-body@6.1.3':
|
'@types/co-body@6.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
'@types/qs': 6.14.0
|
'@types/qs': 6.14.0
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/content-disposition@0.5.9': {}
|
'@types/content-disposition@0.5.9': {}
|
||||||
|
|
||||||
@@ -6753,11 +6781,11 @@ snapshots:
|
|||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/express': 5.0.6
|
'@types/express': 5.0.6
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/cors@2.8.19':
|
'@types/cors@2.8.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/debounce@1.2.4': {}
|
'@types/debounce@1.2.4': {}
|
||||||
|
|
||||||
@@ -6769,7 +6797,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/express-serve-static-core@5.1.1':
|
'@types/express-serve-static-core@5.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
'@types/qs': 6.14.0
|
'@types/qs': 6.14.0
|
||||||
'@types/range-parser': 1.2.7
|
'@types/range-parser': 1.2.7
|
||||||
'@types/send': 1.2.1
|
'@types/send': 1.2.1
|
||||||
@@ -6783,7 +6811,7 @@ snapshots:
|
|||||||
'@types/fs-extra@11.0.4':
|
'@types/fs-extra@11.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/jsonfile': 6.1.4
|
'@types/jsonfile': 6.1.4
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6817,7 +6845,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/jsonfile@6.1.4':
|
'@types/jsonfile@6.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/keygrip@1.0.6': {}
|
'@types/keygrip@1.0.6': {}
|
||||||
|
|
||||||
@@ -6834,7 +6862,7 @@ snapshots:
|
|||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/koa-compose': 3.2.9
|
'@types/koa-compose': 3.2.9
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6848,20 +6876,20 @@ snapshots:
|
|||||||
|
|
||||||
'@types/mute-stream@0.0.4':
|
'@types/mute-stream@0.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/node-forge@1.3.14':
|
'@types/node-forge@1.3.14':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/node@20.19.33':
|
|
||||||
dependencies:
|
|
||||||
undici-types: 6.21.0
|
|
||||||
|
|
||||||
'@types/node@22.19.11':
|
'@types/node@22.19.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
'@types/node@25.2.3':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.16.0
|
||||||
|
|
||||||
'@types/parse5@6.0.3': {}
|
'@types/parse5@6.0.3': {}
|
||||||
|
|
||||||
'@types/ping@0.4.4': {}
|
'@types/ping@0.4.4': {}
|
||||||
@@ -6876,18 +6904,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/s3rver@3.7.4':
|
'@types/s3rver@3.7.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/semver@7.7.1': {}
|
'@types/semver@7.7.1': {}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/serve-static@2.2.0':
|
'@types/serve-static@2.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/sinon-chai@3.2.12':
|
'@types/sinon-chai@3.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6906,11 +6934,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/tar-stream@3.1.4':
|
'@types/tar-stream@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/through2@2.0.41':
|
'@types/through2@2.0.41':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5': {}
|
'@types/triple-beam@1.3.5': {}
|
||||||
|
|
||||||
@@ -6938,11 +6966,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/ws@7.4.7':
|
'@types/ws@7.4.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
|
|
||||||
'@types/yargs-parser@21.0.3': {}
|
'@types/yargs-parser@21.0.3': {}
|
||||||
|
|
||||||
@@ -6952,7 +6980,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
@@ -7557,7 +7585,7 @@ snapshots:
|
|||||||
engine.io@6.6.4:
|
engine.io@6.6.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/cors': 2.8.19
|
'@types/cors': 2.8.19
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
accepts: 1.3.8
|
accepts: 1.3.8
|
||||||
base64id: 2.0.0
|
base64id: 2.0.0
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
@@ -8271,7 +8299,7 @@ snapshots:
|
|||||||
jest-util@29.7.0:
|
jest-util@29.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
'@types/node': 20.19.33
|
'@types/node': 25.2.3
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.9.0
|
ci-info: 3.9.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -9779,6 +9807,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
unified@11.0.5:
|
unified@11.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|||||||
@@ -7,3 +7,6 @@
|
|||||||
* STUN-based public IP discovery at the edge (Cloudflare STUN server)
|
* STUN-based public IP discovery at the edge (Cloudflare STUN server)
|
||||||
* Cross-compiled Rust binary for linux/amd64 and linux/arm64
|
* Cross-compiled Rust binary for linux/amd64 and linux/arm64
|
||||||
* Old `ConnectorPublic`/`ConnectorPrivate` classes no longer exist (removed in v2.0.0/v3.0.0)
|
* Old `ConnectorPublic`/`ConnectorPrivate` classes no longer exist (removed in v2.0.0/v3.0.0)
|
||||||
|
* `localPaths` in RustBridge config must be full file paths (not directories) — smartrust's `RustBinaryLocator` checks `isExecutable()` on each entry directly
|
||||||
|
* Production binaries are named with platform suffixes: `remoteingress-bin_linux_amd64`, `remoteingress-bin_linux_arm64`
|
||||||
|
* Rust core uses `ring` as the rustls CryptoProvider (explicitly installed in main.rs, aws-lc-rs disabled via default-features=false)
|
||||||
|
|||||||
177
readme.md
177
readme.md
@@ -14,15 +14,15 @@ pnpm install @serve.zone/remoteingress
|
|||||||
|
|
||||||
## 🏗️ Architecture
|
## 🏗️ Architecture
|
||||||
|
|
||||||
`@serve.zone/remoteingress` uses a **Hub ↔ Edge** topology with a high-performance Rust core and a TypeScript API surface:
|
`@serve.zone/remoteingress` uses a **Hub/Edge** topology with a high-performance Rust core and a TypeScript API surface:
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────┐ TLS Tunnel ┌─────────────────────┐
|
┌─────────────────────┐ TLS Tunnel ┌─────────────────────┐
|
||||||
│ Network Edge │ ◄══════════════════════════► │ Private Cluster │
|
│ Network Edge │ ◄══════════════════════════► │ Private Cluster │
|
||||||
│ │ (multiplexed frames + │ │
|
│ │ (multiplexed frames + │ │
|
||||||
│ RemoteIngressEdge │ shared-secret auth) │ RemoteIngressHub │
|
│ RemoteIngressEdge │ shared-secret auth) │ RemoteIngressHub │
|
||||||
│ Listens on :80,:443│ │ Forwards to │
|
│ Accepts client TCP │ │ Forwards to │
|
||||||
│ Accepts client TCP │ │ SmartProxy on │
|
│ connections │ │ SmartProxy on │
|
||||||
│ │ │ local ports │
|
│ │ │ local ports │
|
||||||
└─────────────────────┘ └─────────────────────┘
|
└─────────────────────┘ └─────────────────────┘
|
||||||
▲ │
|
▲ │
|
||||||
@@ -32,26 +32,27 @@ pnpm install @serve.zone/remoteingress
|
|||||||
|
|
||||||
| Component | Role |
|
| Component | Role |
|
||||||
|-----------|------|
|
|-----------|------|
|
||||||
| **RemoteIngressEdge** | Deployed at the network edge (e.g. a VPS or cloud instance). Listens on public ports, accepts raw TCP connections, and multiplexes them over a single TLS tunnel to the hub. |
|
| **RemoteIngressEdge** | Deployed at the network edge (e.g. a VPS or cloud instance). Accepts raw TCP connections and multiplexes them over a single TLS tunnel to the hub. |
|
||||||
| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams, and forwards each to SmartProxy with a PROXY protocol v1 header so the real client IP is preserved. |
|
| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams, and forwards each to SmartProxy with a PROXY protocol v1 header so the real client IP is preserved. |
|
||||||
| **Rust Binary** (`remoteingress-bin`) | The performance-critical networking core. Managed via `@push.rocks/smartrust` RustBridge IPC — you never interact with it directly. Cross-compiled for `linux/amd64` and `linux/arm64`. |
|
| **Rust Binary** (`remoteingress-bin`) | The performance-critical networking core. Managed via `@push.rocks/smartrust` RustBridge IPC — you never interact with it directly. Cross-compiled for `linux/amd64` and `linux/arm64`. |
|
||||||
|
|
||||||
### 🔑 Key Features
|
### ✨ Key Features
|
||||||
|
|
||||||
- 🔒 **TLS-encrypted tunnel** between edge and hub (auto-generated self-signed cert or bring your own)
|
- 🔒 **TLS-encrypted tunnel** between edge and hub (auto-generated self-signed cert or bring your own)
|
||||||
- 🔀 **Multiplexed streams** — thousands of client connections flow over a single tunnel
|
- 🔀 **Multiplexed streams** — thousands of client connections flow over a single tunnel
|
||||||
- 🌐 **PROXY protocol v1** — SmartProxy sees the real client IP, not the tunnel IP
|
- 🌐 **PROXY protocol v1** — SmartProxy sees the real client IP, not the tunnel IP
|
||||||
- 🔐 **Shared-secret authentication** — edges must present valid credentials to connect
|
- 🔑 **Shared-secret authentication** — edges must present valid credentials to connect
|
||||||
|
- 🎫 **Connection tokens** — encode all connection details into a single opaque string
|
||||||
- 📡 **STUN-based public IP discovery** — the edge automatically discovers its public IP via Cloudflare STUN
|
- 📡 **STUN-based public IP discovery** — the edge automatically discovers its public IP via Cloudflare STUN
|
||||||
- 🔄 **Auto-reconnect** with exponential backoff if the tunnel drops
|
- 🔄 **Auto-reconnect** with exponential backoff if the tunnel drops
|
||||||
- 📢 **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring
|
- 📣 **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring
|
||||||
- ⚡ **Rust core** — all frame encoding, TLS, and TCP proxying happen in native code for maximum throughput
|
- ⚡ **Rust core** — all frame encoding, TLS, and TCP proxying happen in native code for maximum throughput
|
||||||
|
|
||||||
## Usage
|
## 🚀 Usage
|
||||||
|
|
||||||
Both classes are imported from the package and communicate with the Rust binary under the hood. All you need to do is configure and start them.
|
Both classes are imported from the package and communicate with the Rust binary under the hood. All you need to do is configure and start them.
|
||||||
|
|
||||||
### Setting up the Hub (private cluster side)
|
### Setting Up the Hub (Private Cluster Side)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { RemoteIngressHub } from '@serve.zone/remoteingress';
|
import { RemoteIngressHub } from '@serve.zone/remoteingress';
|
||||||
@@ -60,21 +61,21 @@ const hub = new RemoteIngressHub();
|
|||||||
|
|
||||||
// Listen for events
|
// Listen for events
|
||||||
hub.on('edgeConnected', ({ edgeId }) => {
|
hub.on('edgeConnected', ({ edgeId }) => {
|
||||||
console.log(`✅ Edge ${edgeId} connected`);
|
console.log(`Edge ${edgeId} connected`);
|
||||||
});
|
});
|
||||||
hub.on('edgeDisconnected', ({ edgeId }) => {
|
hub.on('edgeDisconnected', ({ edgeId }) => {
|
||||||
console.log(`❌ Edge ${edgeId} disconnected`);
|
console.log(`Edge ${edgeId} disconnected`);
|
||||||
});
|
});
|
||||||
hub.on('streamOpened', ({ edgeId, streamId }) => {
|
hub.on('streamOpened', ({ edgeId, streamId }) => {
|
||||||
console.log(`🔗 Stream ${streamId} opened from edge ${edgeId}`);
|
console.log(`Stream ${streamId} opened from edge ${edgeId}`);
|
||||||
});
|
});
|
||||||
hub.on('streamClosed', ({ edgeId, streamId }) => {
|
hub.on('streamClosed', ({ edgeId, streamId }) => {
|
||||||
console.log(`🔗 Stream ${streamId} closed from edge ${edgeId}`);
|
console.log(`Stream ${streamId} closed from edge ${edgeId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the hub — it will listen for incoming edge TLS connections
|
// Start the hub — it will listen for incoming edge TLS connections
|
||||||
await hub.start({
|
await hub.start({
|
||||||
tunnelPort: 8443, // port edges connect to (default: 8443)
|
tunnelPort: 8443, // port edges connect to (default: 8443)
|
||||||
targetHost: '127.0.0.1', // SmartProxy host to forward streams to (default: 127.0.0.1)
|
targetHost: '127.0.0.1', // SmartProxy host to forward streams to (default: 127.0.0.1)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,37 +100,50 @@ console.log(status);
|
|||||||
await hub.stop();
|
await hub.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting up the Edge (network edge side)
|
### Setting Up the Edge (Network Edge Side)
|
||||||
|
|
||||||
|
The edge can be configured in two ways: with an **opaque connection token** (recommended) or with explicit config fields.
|
||||||
|
|
||||||
|
#### Option A: Connection Token (Recommended)
|
||||||
|
|
||||||
|
A single token encodes all connection details — ideal for provisioning edges at scale:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { RemoteIngressEdge } from '@serve.zone/remoteingress';
|
import { RemoteIngressEdge } from '@serve.zone/remoteingress';
|
||||||
|
|
||||||
const edge = new RemoteIngressEdge();
|
const edge = new RemoteIngressEdge();
|
||||||
|
|
||||||
// Listen for events
|
edge.on('tunnelConnected', () => console.log('Tunnel established'));
|
||||||
edge.on('tunnelConnected', () => {
|
edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
|
||||||
console.log('🟢 Tunnel to hub established');
|
edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
|
||||||
});
|
|
||||||
edge.on('tunnelDisconnected', () => {
|
// Single token contains hubHost, hubPort, edgeId, and secret
|
||||||
console.log('🔴 Tunnel to hub lost — will auto-reconnect');
|
await edge.start({
|
||||||
});
|
token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwicCI6ODQ0MywiZSI6ImVkZ2UtbnljLTAxIiwicyI6InN1cGVyc2VjcmV0dG9rZW4xIn0',
|
||||||
edge.on('publicIpDiscovered', ({ ip }) => {
|
});
|
||||||
console.log(`🌐 Public IP: ${ip}`);
|
```
|
||||||
});
|
|
||||||
|
#### Option B: Explicit Config
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { RemoteIngressEdge } from '@serve.zone/remoteingress';
|
||||||
|
|
||||||
|
const edge = new RemoteIngressEdge();
|
||||||
|
|
||||||
|
edge.on('tunnelConnected', () => console.log('Tunnel established'));
|
||||||
|
edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
|
||||||
|
edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
|
||||||
|
|
||||||
// Start the edge — it connects to the hub and starts listening for clients
|
|
||||||
await edge.start({
|
await edge.start({
|
||||||
hubHost: 'hub.example.com', // hostname or IP of the hub
|
hubHost: 'hub.example.com', // hostname or IP of the hub
|
||||||
hubPort: 8443, // must match hub's tunnelPort (default: 8443)
|
hubPort: 8443, // must match hub's tunnelPort (default: 8443)
|
||||||
edgeId: 'edge-nyc-01', // unique edge identifier
|
edgeId: 'edge-nyc-01', // unique edge identifier
|
||||||
secret: 'supersecrettoken1', // must match the hub's allowed edge secret
|
secret: 'supersecrettoken1', // must match the hub's allowed edge secret
|
||||||
listenPorts: [80, 443], // public ports to accept TCP connections on
|
|
||||||
stunIntervalSecs: 300, // STUN refresh interval in seconds (default: 300)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check status at any time
|
// Check status at any time
|
||||||
const status = await edge.getStatus();
|
const edgeStatus = await edge.getStatus();
|
||||||
console.log(status);
|
console.log(edgeStatus);
|
||||||
// {
|
// {
|
||||||
// running: true,
|
// running: true,
|
||||||
// connected: true,
|
// connected: true,
|
||||||
@@ -142,9 +156,39 @@ console.log(status);
|
|||||||
await edge.stop();
|
await edge.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
### API Reference
|
### 🎫 Connection Tokens
|
||||||
|
|
||||||
#### `RemoteIngressHub`
|
Connection tokens let you distribute a single opaque string instead of four separate config values. The hub operator generates the token; the edge operator just pastes it in.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { encodeConnectionToken, decodeConnectionToken } from '@serve.zone/remoteingress';
|
||||||
|
|
||||||
|
// Hub side: generate a token for a new edge
|
||||||
|
const token = encodeConnectionToken({
|
||||||
|
hubHost: 'hub.example.com',
|
||||||
|
hubPort: 8443,
|
||||||
|
edgeId: 'edge-nyc-01',
|
||||||
|
secret: 'supersecrettoken1',
|
||||||
|
});
|
||||||
|
console.log(token);
|
||||||
|
// => 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...'
|
||||||
|
|
||||||
|
// Edge side: inspect a token (optional — start() does this automatically)
|
||||||
|
const data = decodeConnectionToken(token);
|
||||||
|
console.log(data);
|
||||||
|
// {
|
||||||
|
// hubHost: 'hub.example.com',
|
||||||
|
// hubPort: 8443,
|
||||||
|
// edgeId: 'edge-nyc-01',
|
||||||
|
// secret: 'supersecrettoken1'
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environment variables, CLI arguments, or store in config files.
|
||||||
|
|
||||||
|
## 📖 API Reference
|
||||||
|
|
||||||
|
### `RemoteIngressHub`
|
||||||
|
|
||||||
| Method / Property | Description |
|
| Method / Property | Description |
|
||||||
|-------------------|-------------|
|
|-------------------|-------------|
|
||||||
@@ -156,18 +200,48 @@ await edge.stop();
|
|||||||
|
|
||||||
**Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`
|
**Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`
|
||||||
|
|
||||||
#### `RemoteIngressEdge`
|
### `RemoteIngressEdge`
|
||||||
|
|
||||||
| Method / Property | Description |
|
| Method / Property | Description |
|
||||||
|-------------------|-------------|
|
|-------------------|-------------|
|
||||||
| `start(config)` | Spawns the Rust binary, connects to the hub, and starts listening on the specified ports. |
|
| `start(config)` | Spawns the Rust binary and connects to the hub. Accepts `{ token: string }` or `IEdgeConfig`. |
|
||||||
| `stop()` | Gracefully shuts down the edge and kills the Rust process. |
|
| `stop()` | Gracefully shuts down the edge and kills the Rust process. |
|
||||||
| `getStatus()` | Returns current edge status including connection state, public IP, and active streams. |
|
| `getStatus()` | Returns current edge status including connection state, public IP, and active streams. |
|
||||||
| `running` | `boolean` — whether the Rust binary is alive. |
|
| `running` | `boolean` — whether the Rust binary is alive. |
|
||||||
|
|
||||||
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`
|
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`
|
||||||
|
|
||||||
### 🔌 Wire Protocol
|
### Token Utilities
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `encodeConnectionToken(data)` | Encodes `IConnectionTokenData` into a base64url token string. |
|
||||||
|
| `decodeConnectionToken(token)` | Decodes a token back into `IConnectionTokenData`. Throws on malformed or incomplete tokens. |
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IHubConfig {
|
||||||
|
tunnelPort?: number; // default: 8443
|
||||||
|
targetHost?: string; // default: '127.0.0.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IEdgeConfig {
|
||||||
|
hubHost: string;
|
||||||
|
hubPort?: number; // default: 8443
|
||||||
|
edgeId: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IConnectionTokenData {
|
||||||
|
hubHost: string;
|
||||||
|
hubPort: number;
|
||||||
|
edgeId: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 Wire Protocol
|
||||||
|
|
||||||
The tunnel uses a custom binary frame protocol over TLS:
|
The tunnel uses a custom binary frame protocol over TLS:
|
||||||
|
|
||||||
@@ -185,13 +259,38 @@ The tunnel uses a custom binary frame protocol over TLS:
|
|||||||
|
|
||||||
Max payload size per frame: **16 MB**.
|
Max payload size per frame: **16 MB**.
|
||||||
|
|
||||||
### Example Scenarios
|
## 💡 Example Scenarios
|
||||||
|
|
||||||
1. **Expose a private Kubernetes cluster to the internet** — Deploy an Edge on a public VPS, configure your DNS to point to the VPS IP. The Edge tunnels all traffic to the Hub running inside the cluster, which hands it off to SmartProxy/DcRouter. Your cluster stays fully private — no public-facing ports needed.
|
### 1. Expose a Private Kubernetes Cluster to the Internet
|
||||||
|
|
||||||
2. **Multi-region edge ingress** — Run multiple Edges in different geographic regions (NYC, Frankfurt, Tokyo) all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. The Hub sees the real client IPs via PROXY protocol regardless of which edge they connected through.
|
Deploy an Edge on a public VPS, point your DNS to the VPS IP. The Edge tunnels all traffic to the Hub running inside the cluster, which hands it off to SmartProxy/DcRouter. Your cluster stays fully private — no public-facing ports needed.
|
||||||
|
|
||||||
3. **Secure API exposure** — Your backend runs on a private network with no direct internet access. An Edge on a minimal cloud instance acts as the only public entry point. TLS tunnel + shared-secret auth ensure only your authorized Edge can forward traffic.
|
### 2. Multi-Region Edge Ingress
|
||||||
|
|
||||||
|
Run multiple Edges in different geographic regions (NYC, Frankfurt, Tokyo) all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. The Hub sees the real client IPs via PROXY protocol regardless of which edge they connected through.
|
||||||
|
|
||||||
|
### 3. Secure API Exposure
|
||||||
|
|
||||||
|
Your backend runs on a private network with no direct internet access. An Edge on a minimal cloud instance acts as the only public entry point. TLS tunnel + shared-secret auth ensure only your authorized Edge can forward traffic.
|
||||||
|
|
||||||
|
### 4. Token-Based Edge Provisioning
|
||||||
|
|
||||||
|
Generate connection tokens on the hub side and distribute them to edge operators. Each edge only needs a single token string to connect — no manual configuration of host, port, ID, and secret.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Hub operator generates token
|
||||||
|
const token = encodeConnectionToken({
|
||||||
|
hubHost: 'hub.prod.example.com',
|
||||||
|
hubPort: 8443,
|
||||||
|
edgeId: 'edge-tokyo-01',
|
||||||
|
secret: 'generated-secret-abc123',
|
||||||
|
});
|
||||||
|
// Send `token` to the edge operator via secure channel
|
||||||
|
|
||||||
|
// Edge operator starts with just the token
|
||||||
|
const edge = new RemoteIngressEdge();
|
||||||
|
await edge.start({ token });
|
||||||
|
```
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
|
|||||||
49
test/test.ts
49
test/test.ts
@@ -9,4 +9,53 @@ tap.test('should export RemoteIngressEdge', async () => {
|
|||||||
expect(remoteingress.RemoteIngressEdge).toBeTypeOf('function');
|
expect(remoteingress.RemoteIngressEdge).toBeTypeOf('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('should export encodeConnectionToken and decodeConnectionToken', async () => {
|
||||||
|
expect(remoteingress.encodeConnectionToken).toBeTypeOf('function');
|
||||||
|
expect(remoteingress.decodeConnectionToken).toBeTypeOf('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should roundtrip encode → decode a connection token', async () => {
|
||||||
|
const data: remoteingress.IConnectionTokenData = {
|
||||||
|
hubHost: 'hub.example.com',
|
||||||
|
hubPort: 8443,
|
||||||
|
edgeId: 'edge-001',
|
||||||
|
secret: 'super-secret-key',
|
||||||
|
};
|
||||||
|
const token = remoteingress.encodeConnectionToken(data);
|
||||||
|
const decoded = remoteingress.decodeConnectionToken(token);
|
||||||
|
expect(decoded.hubHost).toEqual(data.hubHost);
|
||||||
|
expect(decoded.hubPort).toEqual(data.hubPort);
|
||||||
|
expect(decoded.edgeId).toEqual(data.edgeId);
|
||||||
|
expect(decoded.secret).toEqual(data.secret);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should throw on malformed token', async () => {
|
||||||
|
let error: Error | undefined;
|
||||||
|
try {
|
||||||
|
remoteingress.decodeConnectionToken('not-valid-json!!!');
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
}
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(error!.message).toInclude('Invalid connection token');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should throw on token with missing fields', async () => {
|
||||||
|
// Encode a partial object (missing 'p' and 's')
|
||||||
|
const partial = Buffer.from(JSON.stringify({ h: 'host', e: 'edge' }), 'utf-8')
|
||||||
|
.toString('base64')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/, '');
|
||||||
|
|
||||||
|
let error: Error | undefined;
|
||||||
|
try {
|
||||||
|
remoteingress.decodeConnectionToken(partial);
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
}
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect(error!.message).toInclude('missing required fields');
|
||||||
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/remoteingress',
|
name: '@serve.zone/remoteingress',
|
||||||
version: '3.0.3',
|
version: '3.1.1',
|
||||||
description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.'
|
description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { decodeConnectionToken } from './classes.token.js';
|
||||||
|
|
||||||
// Command map for the edge side of remoteingress-bin
|
// Command map for the edge side of remoteingress-bin
|
||||||
type TEdgeCommands = {
|
type TEdgeCommands = {
|
||||||
@@ -13,8 +14,6 @@ type TEdgeCommands = {
|
|||||||
hubPort: number;
|
hubPort: number;
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
listenPorts: number[];
|
|
||||||
stunIntervalSecs?: number;
|
|
||||||
};
|
};
|
||||||
result: { started: boolean };
|
result: { started: boolean };
|
||||||
};
|
};
|
||||||
@@ -39,8 +38,6 @@ export interface IEdgeConfig {
|
|||||||
hubPort?: number;
|
hubPort?: number;
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
listenPorts: number[];
|
|
||||||
stunIntervalSecs?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RemoteIngressEdge extends EventEmitter {
|
export class RemoteIngressEdge extends EventEmitter {
|
||||||
@@ -86,20 +83,33 @@ export class RemoteIngressEdge extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the edge — spawns the Rust binary and connects to the hub.
|
* Start the edge — spawns the Rust binary and connects to the hub.
|
||||||
|
* Accepts either a connection token or an explicit IEdgeConfig.
|
||||||
*/
|
*/
|
||||||
public async start(config: IEdgeConfig): Promise<void> {
|
public async start(config: { token: string } | IEdgeConfig): Promise<void> {
|
||||||
|
let edgeConfig: IEdgeConfig;
|
||||||
|
|
||||||
|
if ('token' in config) {
|
||||||
|
const decoded = decodeConnectionToken(config.token);
|
||||||
|
edgeConfig = {
|
||||||
|
hubHost: decoded.hubHost,
|
||||||
|
hubPort: decoded.hubPort,
|
||||||
|
edgeId: decoded.edgeId,
|
||||||
|
secret: decoded.secret,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
edgeConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
const spawned = await this.bridge.spawn();
|
const spawned = await this.bridge.spawn();
|
||||||
if (!spawned) {
|
if (!spawned) {
|
||||||
throw new Error('Failed to spawn remoteingress-bin');
|
throw new Error('Failed to spawn remoteingress-bin');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bridge.sendCommand('startEdge', {
|
await this.bridge.sendCommand('startEdge', {
|
||||||
hubHost: config.hubHost,
|
hubHost: edgeConfig.hubHost,
|
||||||
hubPort: config.hubPort ?? 8443,
|
hubPort: edgeConfig.hubPort ?? 8443,
|
||||||
edgeId: config.edgeId,
|
edgeId: edgeConfig.edgeId,
|
||||||
secret: config.secret,
|
secret: edgeConfig.secret,
|
||||||
listenPorts: config.listenPorts,
|
|
||||||
stunIntervalSecs: config.stunIntervalSecs,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.started = true;
|
this.started = true;
|
||||||
|
|||||||
66
ts/classes.token.ts
Normal file
66
ts/classes.token.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Connection token utilities for RemoteIngress edge connections.
|
||||||
|
* A token is a base64url-encoded compact JSON object carrying hub connection details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IConnectionTokenData {
|
||||||
|
hubHost: string;
|
||||||
|
hubPort: number;
|
||||||
|
edgeId: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode connection data into a single opaque token string (base64url).
|
||||||
|
*/
|
||||||
|
export function encodeConnectionToken(data: IConnectionTokenData): string {
|
||||||
|
const compact = JSON.stringify({
|
||||||
|
h: data.hubHost,
|
||||||
|
p: data.hubPort,
|
||||||
|
e: data.edgeId,
|
||||||
|
s: data.secret,
|
||||||
|
});
|
||||||
|
// base64url: standard base64 with + → -, / → _, trailing = stripped
|
||||||
|
return Buffer.from(compact, 'utf-8')
|
||||||
|
.toString('base64')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a connection token back into its constituent fields.
|
||||||
|
* Throws on malformed or incomplete tokens.
|
||||||
|
*/
|
||||||
|
export function decodeConnectionToken(token: string): IConnectionTokenData {
|
||||||
|
let parsed: { h?: unknown; p?: unknown; e?: unknown; s?: unknown };
|
||||||
|
try {
|
||||||
|
// Restore standard base64 from base64url
|
||||||
|
let base64 = token.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
// Re-add padding
|
||||||
|
const remainder = base64.length % 4;
|
||||||
|
if (remainder === 2) base64 += '==';
|
||||||
|
else if (remainder === 3) base64 += '=';
|
||||||
|
|
||||||
|
const json = Buffer.from(base64, 'base64').toString('utf-8');
|
||||||
|
parsed = JSON.parse(json);
|
||||||
|
} catch {
|
||||||
|
throw new Error('Invalid connection token');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof parsed.h !== 'string' ||
|
||||||
|
typeof parsed.p !== 'number' ||
|
||||||
|
typeof parsed.e !== 'string' ||
|
||||||
|
typeof parsed.s !== 'string'
|
||||||
|
) {
|
||||||
|
throw new Error('Invalid connection token: missing required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hubHost: parsed.h,
|
||||||
|
hubPort: parsed.p,
|
||||||
|
edgeId: parsed.e,
|
||||||
|
secret: parsed.s,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './classes.remoteingresshub.js';
|
export * from './classes.remoteingresshub.js';
|
||||||
export * from './classes.remoteingressedge.js';
|
export * from './classes.remoteingressedge.js';
|
||||||
|
export * from './classes.token.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user