@symbo.ls/tunnel 3.14.1 → 3.14.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @symbo.ls/tunnel
2
+
3
+ ## 3.14.3
4
+
5
+ ### Patch Changes
6
+
7
+ - c8dfb91: Fix tunnel exports to match the actual build output. Source lives in `src/`, esbuild's `--outbase=.` preserves that structure, so dist/esm/src/{client,server}.js is what gets built — the prior `./dist/esm/{client,server}.js` paths in the package's `import`/`default` exports pointed at non-existent files. Consumers (cli's tunnel.js subcommand, agents, workspace deploy) failed ERR_MODULE_NOT_FOUND on `@symbo.ls/tunnel/client`.
8
+
9
+ ## 3.14.2
10
+
11
+ ### Patch Changes
12
+
13
+ - b49b982: bin entries now point at dist/esm/bin/\* so external installs from npm
14
+ include the full bin tree, not just the entry. The dist-only files
15
+ filter excluded the source bin/ siblings, breaking ERR_MODULE_NOT_FOUND
16
+ in workspace deploy + agent-runner Docker on `npm install -g @symbo.ls/cli`.
@@ -1 +1 @@
1
- var d,g=Object.defineProperty,a=(s,e)=>g(s,"name",{value:e,configurable:!0}),c={REGISTER:"register",REGISTERED:"registered",ERROR:"error",REQUEST:"request",REQUEST_BODY:"request_body",REQUEST_END:"request_end",RESPONSE:"response",RESPONSE_BODY:"response_body",RESPONSE_END:"response_end",PING:"ping",PONG:"pong"},E=a(()=>Math.random().toString(36).slice(2)+Date.now().toString(36),"randomId"),b=a(()=>{const s=["calm","bold","brisk","quick","bright","soft","warm","cool","keen","lush"],e=["fox","owl","wolf","hawk","lark","wren","seal","deer","moth","cub"],t=s[Math.floor(Math.random()*s.length)],n=e[Math.floor(Math.random()*e.length)],o=Math.random().toString(36).slice(2,6);return`${t}-${n}-${o}`},"randomSubdomain"),S=(d=class{constructor(e,t){this.state=e,this.env=t,this.client=null,this.subdomain=null,this.root="tunnel.symbo.ls",this.pending=new Map}async fetch(e){const t=new URL(e.url);return t.pathname==="/_register"?this.register(e,t):!this.client||this.client.readyState!==1?new Response(`No tunnel registered for ${this.subdomain||"this subdomain"}.`,{status:502}):this.proxyRequest(e)}async register(e,t){if(e.headers.get("upgrade")!=="websocket")return new Response("expected websocket",{status:426});if(this.subdomain=t.searchParams.get("subdomain"),this.root=t.searchParams.get("root")||this.root,this.client)try{this.client.close(1e3,"replaced")}catch{}const n=new WebSocketPair,[o,r]=[n[0],n[1]];r.accept(),this.client=r,r.addEventListener("message",h=>this.onClientMessage(h));const l=a(()=>{this.client=null,this.failAllPending("tunnel closed")},"onClose");return r.addEventListener("close",l),r.addEventListener("error",l),r.send(JSON.stringify({type:c.REGISTERED,subdomain:this.subdomain,publicUrl:`https://${this.subdomain}.${this.root}`})),new Response(null,{status:101,webSocket:o})}failAllPending(e){for(const[,t]of this.pending)if(!t.headSent)t.resolveHead(new Response(e,{status:502}));else try{t.controller.error(new Error(e))}catch{}this.pending.clear()}onClientMessage(e){let t;try{t=JSON.parse(typeof e.data=="string"?e.data:new TextDecoder().decode(e.data))}catch{return}const n=t.id&&this.pending.get(t.id);if(n){if(t.type===c.RESPONSE){const o=new ReadableStream({start:a(r=>{n.controller=r},"start")});n.headSent=!0,n.resolveHead(new Response(o,{status:t.status||200,headers:y(t.headers||{})}))}else if(t.type===c.RESPONSE_BODY)t.chunk&&n.controller&&n.controller.enqueue(w(t.chunk));else if(t.type===c.RESPONSE_END){try{n.controller&&n.controller.close()}catch{}this.pending.delete(t.id)}}}async proxyRequest(e){const t=E(),n=new URL(e.url),o={};for(const[i,p]of e.headers)o[i]=p;const r=["GET","HEAD"].includes(e.method)?null:new Uint8Array(await e.arrayBuffer()),l={controller:null,headSent:!1},h=new Promise(i=>{l.resolveHead=i});this.pending.set(t,l),this.client.send(JSON.stringify({type:c.REQUEST,id:t,method:e.method,url:n.pathname+n.search,headers:o})),r&&r.byteLength&&this.client.send(JSON.stringify({type:c.REQUEST_BODY,id:t,chunk:N(r)})),this.client.send(JSON.stringify({type:c.REQUEST_END,id:t}));const u=new Promise(i=>setTimeout(()=>{this.pending.has(t)&&(this.pending.delete(t),i(new Response("Tunnel request timed out",{status:504})))},3e4));return Promise.race([h,u])}},a(d,"TunnelRoom"),d),R=new Set(["connection","keep-alive","transfer-encoding","upgrade","proxy-authenticate","proxy-authorization","te","trailer","content-length"]),y=a(s=>{const e={};for(const[t,n]of Object.entries(s))R.has(t.toLowerCase())||(e[t]=n);return e},"sanitizeHeaders"),N=a(s=>{let e="";for(let t=0;t<s.length;t++)e+=String.fromCharCode(s[t]);return btoa(e)},"bytesToBase64"),w=a(s=>{const e=atob(s),t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);return t},"base64ToBytes"),O=a((s,e)=>!s||(s=s.split(":")[0].toLowerCase(),s===e)||!s.endsWith("."+e)?null:s.slice(0,-("."+e).length),"extractSubdomain"),_={async fetch(s,e){const t=new URL(s.url),n=e.TUNNEL_ROOT_DOMAIN||"tunnel.symbo.ls",o=s.headers.get("host")||"";if(t.pathname==="/_tunnel"&&s.headers.get("upgrade")==="websocket"){if(e.TUNNEL_AUTH_TOKEN&&(t.searchParams.get("token")||s.headers.get("x-tunnel-token"))!==e.TUNNEL_AUTH_TOKEN)return new Response("unauthorized",{status:401});const u=t.searchParams.get("subdomain"),i=u&&/^[a-z0-9-]{2,40}$/.test(u)?u:b(),p=e.TUNNEL_ROOM.idFromName(i),m=e.TUNNEL_ROOM.get(p),f=new Request(`https://internal/_register?subdomain=${i}&root=${encodeURIComponent(n)}`,s);return m.fetch(f)}const r=O(o,n);if(!r)return t.pathname==="/health"?new Response(JSON.stringify({ok:!0}),{headers:{"content-type":"application/json"}}):new Response("tunnel.symbo.ls \u2014 run `smbls tunnel <port>` to expose a local server.\n",{headers:{"content-type":"text/plain"}});const l=e.TUNNEL_ROOM.idFromName(r);return e.TUNNEL_ROOM.get(l).fetch(s)}};export{S as TunnelRoom,_ as default};
1
+ var f=Object.defineProperty,a=(t,e)=>f(t,"name",{value:e,configurable:!0}),l={REGISTER:"register",REGISTERED:"registered",ERROR:"error",REQUEST:"request",REQUEST_BODY:"request_body",REQUEST_END:"request_end",RESPONSE:"response",RESPONSE_BODY:"response_body",RESPONSE_END:"response_end",PING:"ping",PONG:"pong"},g=a(()=>Math.random().toString(36).slice(2)+Date.now().toString(36),"randomId"),E=a(()=>{const t=["calm","bold","brisk","quick","bright","soft","warm","cool","keen","lush"],e=["fox","owl","wolf","hawk","lark","wren","seal","deer","moth","cub"],n=t[Math.floor(Math.random()*t.length)],s=e[Math.floor(Math.random()*e.length)],r=Math.random().toString(36).slice(2,6);return`${n}-${s}-${r}`},"randomSubdomain"),b=class{static{a(this,"TunnelRoom")}constructor(t,e){this.state=t,this.env=e,this.client=null,this.subdomain=null,this.root="tunnel.symbo.ls",this.pending=new Map}async fetch(t){const e=new URL(t.url);return e.pathname==="/_register"?this.register(t,e):!this.client||this.client.readyState!==1?new Response(`No tunnel registered for ${this.subdomain||"this subdomain"}.`,{status:502}):this.proxyRequest(t)}async register(t,e){if(t.headers.get("upgrade")!=="websocket")return new Response("expected websocket",{status:426});if(this.subdomain=e.searchParams.get("subdomain"),this.root=e.searchParams.get("root")||this.root,this.client)try{this.client.close(1e3,"replaced")}catch{}const n=new WebSocketPair,[s,r]=[n[0],n[1]];r.accept(),this.client=r,r.addEventListener("message",c=>this.onClientMessage(c));const i=a(()=>{this.client=null,this.failAllPending("tunnel closed")},"onClose");return r.addEventListener("close",i),r.addEventListener("error",i),r.send(JSON.stringify({type:l.REGISTERED,subdomain:this.subdomain,publicUrl:`https://${this.subdomain}.${this.root}`})),new Response(null,{status:101,webSocket:s})}failAllPending(t){for(const[,e]of this.pending)if(!e.headSent)e.resolveHead(new Response(t,{status:502}));else try{e.controller.error(new Error(t))}catch{}this.pending.clear()}onClientMessage(t){let e;try{e=JSON.parse(typeof t.data=="string"?t.data:new TextDecoder().decode(t.data))}catch{return}const n=e.id&&this.pending.get(e.id);if(n){if(e.type===l.RESPONSE){const s=new ReadableStream({start:a(r=>{n.controller=r},"start")});n.headSent=!0,n.resolveHead(new Response(s,{status:e.status||200,headers:R(e.headers||{})}))}else if(e.type===l.RESPONSE_BODY)e.chunk&&n.controller&&n.controller.enqueue(N(e.chunk));else if(e.type===l.RESPONSE_END){try{n.controller&&n.controller.close()}catch{}this.pending.delete(e.id)}}}async proxyRequest(t){const e=g(),n=new URL(t.url),s={};for(const[o,d]of t.headers)s[o]=d;const r=["GET","HEAD"].includes(t.method)?null:new Uint8Array(await t.arrayBuffer()),i={controller:null,headSent:!1},c=new Promise(o=>{i.resolveHead=o});this.pending.set(e,i),this.client.send(JSON.stringify({type:l.REQUEST,id:e,method:t.method,url:n.pathname+n.search,headers:s})),r&&r.byteLength&&this.client.send(JSON.stringify({type:l.REQUEST_BODY,id:e,chunk:y(r)})),this.client.send(JSON.stringify({type:l.REQUEST_END,id:e}));const u=new Promise(o=>setTimeout(()=>{this.pending.has(e)&&(this.pending.delete(e),o(new Response("Tunnel request timed out",{status:504})))},3e4));return Promise.race([c,u])}},S=new Set(["connection","keep-alive","transfer-encoding","upgrade","proxy-authenticate","proxy-authorization","te","trailer","content-length"]),R=a(t=>{const e={};for(const[n,s]of Object.entries(t))S.has(n.toLowerCase())||(e[n]=s);return e},"sanitizeHeaders"),y=a(t=>{let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return btoa(e)},"bytesToBase64"),N=a(t=>{const e=atob(t),n=new Uint8Array(e.length);for(let s=0;s<e.length;s++)n[s]=e.charCodeAt(s);return n},"base64ToBytes"),w=a((t,e)=>!t||(t=t.split(":")[0].toLowerCase(),t===e)||!t.endsWith("."+e)?null:t.slice(0,-("."+e).length),"extractSubdomain"),O={async fetch(t,e){const n=new URL(t.url),s=e.TUNNEL_ROOT_DOMAIN||"tunnel.symbo.ls",r=t.headers.get("host")||"";if(n.pathname==="/_tunnel"&&t.headers.get("upgrade")==="websocket"){if(e.TUNNEL_AUTH_TOKEN&&(n.searchParams.get("token")||t.headers.get("x-tunnel-token"))!==e.TUNNEL_AUTH_TOKEN)return new Response("unauthorized",{status:401});const o=n.searchParams.get("subdomain"),d=o&&/^[a-z0-9-]{2,40}$/.test(o)?o:E(),h=e.TUNNEL_ROOM.idFromName(d),p=e.TUNNEL_ROOM.get(h),m=new Request(`https://internal/_register?subdomain=${d}&root=${encodeURIComponent(s)}`,t);return p.fetch(m)}const i=w(r,s);if(!i)return n.pathname==="/health"?new Response(JSON.stringify({ok:!0}),{headers:{"content-type":"application/json"}}):new Response("tunnel.symbo.ls \u2014 run `smbls tunnel <port>` to expose a local server.\n",{headers:{"content-type":"text/plain"}});const c=e.TUNNEL_ROOM.idFromName(i);return e.TUNNEL_ROOM.get(c).fetch(t)}};export{b as TunnelRoom,O as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/tunnel",
3
- "version": "3.14.1",
3
+ "version": "3.14.3",
4
4
  "description": "Expose localhost through tunnel.symbo.ls",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",
@@ -13,26 +13,26 @@
13
13
  },
14
14
  "./package.json": "./package.json",
15
15
  "./server": {
16
- "development": "./server.js",
17
- "import": "./dist/esm/server.js",
18
- "require": "./dist/cjs/server.cjs",
19
- "default": "./dist/esm/server.js"
16
+ "development": "./src/server.js",
17
+ "import": "./dist/esm/src/server.js",
18
+ "require": "./dist/cjs/src/server.cjs",
19
+ "default": "./dist/esm/src/server.js"
20
20
  },
21
21
  "./client": {
22
- "development": "./client.js",
23
- "import": "./dist/esm/client.js",
24
- "require": "./dist/cjs/client.cjs",
25
- "default": "./dist/esm/client.js"
22
+ "development": "./src/client.js",
23
+ "import": "./dist/esm/src/client.js",
24
+ "require": "./dist/cjs/src/client.cjs",
25
+ "default": "./dist/esm/src/client.js"
26
26
  }
27
27
  },
28
28
  "bin": {
29
- "smbls-tunnel-server": "bin/server.js",
30
- "smbls-tunnel": "bin/client.js"
29
+ "smbls-tunnel-server": "./dist/esm/bin/server.js",
30
+ "smbls-tunnel": "./dist/esm/bin/client.js"
31
31
  },
32
32
  "scripts": {
33
33
  "start": "node bin/server.js",
34
- "build:esm": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -name \"*.test.js\") --target=es2020 --format=esm --outbase=. --outdir=dist/esm --minify --log-level=warning",
35
- "build:cjs": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -name \"*.test.js\") --target=node18 --format=cjs --outbase=. --outdir=dist/cjs --out-extension:.js=.cjs --minify --log-level=warning",
34
+ "build:esm": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -path \"*/demo/*\" -not -path \"*/demos/*\" -not -path \"*/examples/*\" -not -path \"*/fixtures/*\" -not -name \"*.test.js\") --target=es2022 --format=esm --outbase=. --outdir=dist/esm --minify --log-level=warning",
35
+ "build:cjs": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -path \"*/demo/*\" -not -path \"*/demos/*\" -not -path \"*/examples/*\" -not -path \"*/fixtures/*\" -not -name \"*.test.js\") --target=node20 --format=cjs --outbase=. --outdir=dist/cjs --out-extension:.js=.cjs --minify --log-level=warning",
36
36
  "build": "rimraf dist && npm run build:esm && npm run build:cjs",
37
37
  "prepublishOnly": "npm run build"
38
38
  },
package/bin/client.js DELETED
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createTunnelClient } from '../src/client.js'
3
-
4
- const args = process.argv.slice(2)
5
- const opts = { localPort: null }
6
- for (let i = 0; i < args.length; i++) {
7
- const a = args[i]
8
- if (a === '--host') opts.host = args[++i]
9
- else if (a === '--subdomain' || a === '-s') opts.subdomain = args[++i]
10
- else if (a === '--token') opts.token = args[++i]
11
- else if (a === '--insecure') { opts.secure = false; opts.port = 80 }
12
- else if (/^\d+$/.test(a)) opts.localPort = parseInt(a, 10)
13
- }
14
- if (!opts.localPort) {
15
- console.error('Usage: smbls-tunnel <localPort> [--subdomain name] [--host tunnel.symbo.ls] [--token X] [--insecure]')
16
- process.exit(1)
17
- }
18
- createTunnelClient(opts)
package/bin/server.js DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createTunnelServer } from '../src/server.js'
3
-
4
- const port = parseInt(process.env.PORT || '3000', 10)
5
- const { httpServer, rootDomain } = createTunnelServer()
6
- httpServer.listen(port, () => {
7
- console.log(`tunnel server listening on :${port} (root: ${rootDomain})`)
8
- })