@rubriclab/bunl 0.0.23 → 0.1.24

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/build/client.js CHANGED
@@ -437,7 +437,7 @@ var open_default = open;
437
437
 
438
438
  // client.ts
439
439
  async function main({
440
- url,
440
+ port,
441
441
  domain,
442
442
  subdomain,
443
443
  open: open2
@@ -448,6 +448,7 @@ async function main({
448
448
  }).toString();
449
449
  const serverUrl = `ws://${domain}?${params}`;
450
450
  const socket = new WebSocket(serverUrl);
451
+ const url = `http://localhost:${port}`;
451
452
  socket.addEventListener("message", async (event) => {
452
453
  const data = JSON.parse(event.data);
453
454
  if (data.url) {
@@ -458,6 +459,7 @@ async function main({
458
459
  open_default(data.url);
459
460
  }
460
461
  if (data.method) {
462
+ console.log(`${data.method} ${data.pathname}`);
461
463
  const res = await fetch(`${url}${data.pathname || ""}`, {
462
464
  method: data.method,
463
465
  headers: data.headers,
@@ -465,63 +467,39 @@ async function main({
465
467
  });
466
468
  const { status, statusText, headers } = res;
467
469
  const body = await res.text();
468
- const serializedRes = JSON.stringify({
470
+ const payload = {
471
+ method: data.method,
469
472
  pathname: data.pathname,
470
473
  status,
471
474
  statusText,
472
475
  headers: Object.fromEntries(headers),
473
476
  body
474
- });
475
- socket.send(serializedRes);
477
+ };
478
+ socket.send(JSON.stringify(payload));
476
479
  }
477
480
  });
478
481
  socket.addEventListener("open", (event) => {
479
482
  if (!event.target.readyState)
480
- throw "Not ready";
483
+ throw "not ready";
481
484
  });
482
485
  socket.addEventListener("close", () => {
483
- console.log(`failed to connect to server`);
486
+ console.warn("server closed connection");
484
487
  process.exit();
485
488
  });
486
489
  }
487
490
  var { values } = parseArgs({
488
491
  args: process.argv,
489
492
  options: {
490
- port: {
491
- type: "string",
492
- required: true,
493
- short: "p"
494
- },
495
- domain: {
496
- type: "string",
497
- default: "localhost:1234",
498
- short: "d"
499
- },
500
- subdomain: {
501
- type: "string",
502
- short: "s"
503
- },
504
- open: {
505
- type: "boolean",
506
- short: "o"
507
- },
508
- version: {
509
- type: "boolean",
510
- short: "v"
511
- }
493
+ port: { type: "string", short: "p", default: "3000" },
494
+ domain: { type: "string", short: "d", default: "localhost:1234" },
495
+ subdomain: { type: "string", short: "s" },
496
+ open: { type: "boolean", short: "o" },
497
+ version: { type: "boolean", short: "v" }
512
498
  },
513
499
  allowPositionals: true
514
500
  });
515
501
  if (values.version) {
516
- console.log("0.0.23");
502
+ console.log("0.1.24");
517
503
  process.exit();
518
504
  }
519
- if (!values.port)
520
- throw "pass -p 3000";
521
- var { port, domain, subdomain, open: open2 } = values;
522
- main({
523
- url: `localhost:${port}`,
524
- domain,
525
- subdomain,
526
- open: open2
527
- });
505
+ main(values);
package/client.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import { parseArgs } from "util";
2
2
  import browser from "open";
3
+ import type { Payload } from "./types";
3
4
 
4
5
  async function main({
5
- url,
6
+ port,
6
7
  domain,
7
8
  subdomain,
8
9
  open,
9
10
  }: {
10
- url: string;
11
+ port?: string;
11
12
  domain?: string;
12
13
  subdomain?: string;
13
14
  open?: boolean;
@@ -19,6 +20,8 @@ async function main({
19
20
  const serverUrl = `ws://${domain}?${params}`;
20
21
  const socket = new WebSocket(serverUrl);
21
22
 
23
+ const url = `http://localhost:${port}`;
24
+
22
25
  socket.addEventListener("message", async (event) => {
23
26
  const data = JSON.parse(event.data as string);
24
27
 
@@ -28,6 +31,8 @@ async function main({
28
31
  }
29
32
 
30
33
  if (data.method) {
34
+ console.log(`\x1b[32m${data.method}\x1b[0m ${data.pathname}`);
35
+
31
36
  const res = await fetch(`${url}${data.pathname || ""}`, {
32
37
  method: data.method,
33
38
  headers: data.headers,
@@ -37,24 +42,25 @@ async function main({
37
42
  const { status, statusText, headers } = res;
38
43
  const body = await res.text();
39
44
 
40
- const serializedRes = JSON.stringify({
45
+ const payload: Payload = {
46
+ method: data.method,
41
47
  pathname: data.pathname,
42
48
  status,
43
49
  statusText,
44
50
  headers: Object.fromEntries(headers),
45
51
  body,
46
- });
52
+ };
47
53
 
48
- socket.send(serializedRes);
54
+ socket.send(JSON.stringify(payload));
49
55
  }
50
56
  });
51
57
 
52
58
  socket.addEventListener("open", (event) => {
53
- if (!event.target.readyState) throw "Not ready";
59
+ if (!event.target.readyState) throw "not ready";
54
60
  });
55
61
 
56
62
  socket.addEventListener("close", () => {
57
- console.log(`\x1b[31mfailed to connect to server\x1b[0m`);
63
+ console.warn("server closed connection");
58
64
  process.exit();
59
65
  });
60
66
  }
@@ -67,28 +73,11 @@ async function main({
67
73
  const { values } = parseArgs({
68
74
  args: process.argv,
69
75
  options: {
70
- port: {
71
- type: "string",
72
- required: true,
73
- short: "p",
74
- },
75
- domain: {
76
- type: "string",
77
- default: "localhost:1234",
78
- short: "d",
79
- },
80
- subdomain: {
81
- type: "string",
82
- short: "s",
83
- },
84
- open: {
85
- type: "boolean",
86
- short: "o",
87
- },
88
- version: {
89
- type: "boolean",
90
- short: "v",
91
- },
76
+ port: { type: "string", short: "p", default: "3000" },
77
+ domain: { type: "string", short: "d", default: "localhost:1234" },
78
+ subdomain: { type: "string", short: "s" },
79
+ open: { type: "boolean", short: "o" },
80
+ version: { type: "boolean", short: "v" },
92
81
  },
93
82
  allowPositionals: true,
94
83
  });
@@ -98,13 +87,4 @@ if (values.version) {
98
87
  process.exit();
99
88
  }
100
89
 
101
- if (!values.port) throw "pass -p 3000";
102
-
103
- const { port, domain, subdomain, open } = values;
104
-
105
- main({
106
- url: `localhost:${port}`,
107
- domain,
108
- subdomain,
109
- open,
110
- });
90
+ main(values);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "name": "@rubriclab/bunl",
6
6
  "description": "Expose localhost to the world",
7
- "version": "0.0.23",
7
+ "version": "0.1.24",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -20,9 +20,9 @@
20
20
  "main": "build/client.js",
21
21
  "scripts": {
22
22
  "server": "bun server.ts",
23
- "dev:server": "bun --hot server.ts",
24
- "client": "bun --hot client.ts",
25
- "demo": "bun --hot demo.ts",
23
+ "dev:server": "bun --watch server.ts",
24
+ "client": "bun --watch client.ts",
25
+ "demo": "bun --watch demo.ts",
26
26
  "client:upgrade": "bun rm -g @rubriclab/bunl && bun i -g @rubriclab/bunl@latest",
27
27
  "build": "BUILD=build/client.js && bun build client.ts --outdir build --target bun && echo -e \"#! /usr/bin/env bun\n$(cat $BUILD)\" > $BUILD",
28
28
  "npm:publish": "bun run build && npm publish"
package/server.ts CHANGED
@@ -1,14 +1,13 @@
1
- import { serve, sleep, type ServerWebSocket } from "bun";
1
+ import { serve, type ServerWebSocket } from "bun";
2
2
  import { uid } from "./utils";
3
-
4
- type Client = { id: string };
3
+ import type { Client, Payload } from "./types";
5
4
 
6
5
  const port = Bun.env.PORT || 1234;
7
6
  const scheme = Bun.env.SCHEME || "http";
8
7
  const domain = Bun.env.DOMAIN || `localhost:${port}`;
9
8
 
10
9
  const clients = new Map<string, ServerWebSocket<Client>>();
11
- const clientData = new Map<string, string>();
10
+ const requesters = new Map<string, WritableStream>();
12
11
 
13
12
  serve<Client>({
14
13
  port,
@@ -28,65 +27,57 @@ serve<Client>({
28
27
  const subdomain = reqUrl.hostname.split(".")[0];
29
28
 
30
29
  if (!clients.has(subdomain)) {
31
- return new Response("client not found", { status: 404 });
30
+ return new Response(`${subdomain} not found`, { status: 404 });
32
31
  }
33
32
 
34
33
  // The magic: forward the req to the client
35
34
  const client = clients.get(subdomain)!;
36
35
  const { method, url, headers: reqHeaders } = req;
37
36
  const reqBody = await req.text();
38
- const pathname = new URL(url).pathname || "";
39
- client.send(
40
- JSON.stringify({ method, pathname, body: reqBody, headers: reqHeaders })
41
- );
42
-
43
- // Wait for the client to cache its response above
44
- await sleep(1);
37
+ const pathname = new URL(url).pathname;
38
+ const payload: Payload = {
39
+ method,
40
+ pathname,
41
+ body: reqBody,
42
+ headers: reqHeaders,
43
+ };
45
44
 
46
- let retries = 5;
47
- let res = clientData.get(`${subdomain}/${pathname}`);
45
+ const { writable, readable } = new TransformStream();
48
46
 
49
- // Poll every second for the client to respond
50
- // TODO: replace poll with a client-triggered callback
51
- while (!res) {
52
- await sleep(1000);
53
- retries--;
47
+ requesters.set(`${method}:${subdomain}${pathname}`, writable);
48
+ client.send(JSON.stringify(payload));
54
49
 
55
- res = clientData.get(`${subdomain}/${pathname}`);
56
-
57
- if (retries < 1) {
58
- return new Response("client not responding", { status: 500 });
59
- }
60
- }
61
-
62
- const { status, statusText, headers, body } = JSON.parse(res);
63
- delete headers["content-encoding"];
50
+ const res = await readable.getReader().read();
51
+ const { status, statusText, headers, body } = JSON.parse(res.value);
64
52
 
65
53
  return new Response(body, { status, statusText, headers });
66
54
  },
67
55
  websocket: {
68
56
  open(ws) {
69
57
  clients.set(ws.data.id, ws);
70
- console.log(
71
- `\x1b[32mconnected to ${ws.data.id} (${clients.size} total)\x1b[0m`
72
- );
58
+ console.log(`\x1b[32m+ ${ws.data.id} (${clients.size} total)\x1b[0m`);
73
59
  ws.send(
74
60
  JSON.stringify({
75
61
  url: `${scheme}://${ws.data.id}.${domain}`,
76
62
  })
77
63
  );
78
64
  },
79
- message(ws, message: string) {
80
- console.log("message from", ws.data.id);
65
+ message: async ({ data: { id } }, message: string) => {
66
+ console.log("message from", id);
67
+
68
+ const { method, pathname } = JSON.parse(message) as Payload;
69
+ const writable = requesters.get(`${method}:${id}${pathname}`);
70
+ if (!writable) throw "connection not found";
81
71
 
82
- const { pathname } = JSON.parse(message);
83
- clientData.set(`${ws.data.id}/${pathname}`, message);
72
+ const writer = writable.getWriter();
73
+ await writer.write(message);
74
+ await writer.close();
84
75
  },
85
- close(ws) {
86
- console.log("closing", ws.data.id);
87
- clients.delete(ws.data.id);
76
+ close({ data }) {
77
+ console.log("closing", data.id);
78
+ clients.delete(data.id);
88
79
  },
89
80
  },
90
81
  });
91
82
 
92
- console.log(`Websocket server up at ws://${domain}`);
83
+ console.log(`websocket server up at ws://${domain}`);
package/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type Client = { id: string };
2
+
3
+ export type Payload = {
4
+ status?: number;
5
+ statusText?: string;
6
+ method?: string;
7
+ pathname?: string;
8
+ body: string;
9
+ headers: object;
10
+ };