Skip to content

Commit

Permalink
Merge pull request #28 from JacobLinCool/tunnel-class
Browse files Browse the repository at this point in the history
Tunnel class
  • Loading branch information
JacobLinCool authored Dec 11, 2024
2 parents 9681e9c + 063c96f commit 8d5fb54
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 163 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-gorillas-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cloudflared": minor
---

Tunnel class with custom output parser
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
- macos-latest
version:
- "latest"
- "2024.8.2"
- "2024.12.1"
- "2024.10.1"
- "2024.8.3"
- "2024.6.1"
- "2024.4.1"
- "2024.2.1"

name: "${{ matrix.os }} - ${{ matrix.version }}"
runs-on: ${{ matrix.os }}
Expand Down
48 changes: 16 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,29 +77,30 @@ spawn(bin, ["--version"], { stdio: "inherit" });

Checkout [`examples/tunnel.js`](examples/tunnel.js).

`Tunnel` is inherited from `EventEmitter`, so you can listen to the events it emits, checkout [`examples/events.mjs`](examples/events.mjs).

```js
import { tunnel } from "cloudflared";
import { Tunnel } from "cloudflared";

console.log("Cloudflared Tunnel Example.");
main();

async function main() {
// run: cloudflared tunnel --hello-world
const { url, connections, child, stop } = tunnel({ "--hello-world": null });
const tunnel = Tunnel.quick();

// show the url
const url = new Promise((resolve) => tunnel.once("url", resolve));
console.log("LINK:", await url);

// wait for the all 4 connections to be established
const conns = await Promise.all(connections);

// show the connections
console.log("Connections Ready!", conns);
// wait for connection to be established
const conn = new Promise((resolve) => tunnel.once("connected", resolve));
console.log("CONN:", await conn);

// stop the tunnel after 15 seconds
setTimeout(stop, 15_000);
setTimeout(tunnel.stop, 15_000);

child.on("exit", (code) => {
tunnel.on("exit", (code) => {
console.log("tunnel process exited with code", code);
});
}
Expand All @@ -108,29 +109,12 @@ async function main() {
```sh
❯ node examples/tunnel.js
Cloudflared Tunnel Example.
LINK: https://aimed-our-bite-brought.trycloudflare.com
Connections Ready! [
{
id: 'd4681cd9-217d-40e2-9e15-427f9fb77856',
ip: '198.41.200.23',
location: 'MIA'
},
{
id: 'b40d2cdd-0b99-4838-b1eb-9a58a6999123',
ip: '198.41.192.107',
location: 'LAX'
},
{
id: '55545211-3f63-4722-99f1-d5fea688dabf',
ip: '198.41.200.53',
location: 'MIA'
},
{
id: 'f3d5938a-d48c-463c-a4f7-a158782a0ddb',
ip: '198.41.192.77',
location: 'LAX'
}
]
LINK: https://mailto-davis-wilderness-facts.trycloudflare.com
CONN: {
id: 'df1b8330-44ea-4ecb-bb93-8a32400f6d1c',
ip: '198.41.200.193',
location: 'tpe01'
}
tunnel process exited with code 0
```

Expand Down
45 changes: 45 additions & 0 deletions examples/events.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Tunnel, ConfigHandler } from "cloudflared";

const token = process.env.CLOUDFLARED_TOKEN;
if (!token) {
throw new Error("CLOUDFLARED_TOKEN is not set");
}

const tunnel = Tunnel.withToken(token);
const handler = new ConfigHandler(tunnel);

handler.on("config", ({ config }) => {
console.log("Config", config);
});

tunnel.on("url", (url) => {
console.log("Tunnel is ready at", url);
});

tunnel.on("connected", (connection) => {
console.log("Connected to", connection);
});

tunnel.on("disconnected", (connection) => {
console.log("Disconnected from", connection);
});

tunnel.on("stdout", (data) => {
console.log("Tunnel stdout", data);
});

tunnel.on("stderr", (data) => {
console.error("Tunnel stderr", data);
});

tunnel.on("exit", (code, signal) => {
console.log("Tunnel exited with code", code, "and signal", signal);
});

tunnel.on("error", (error) => {
console.error("Error", error);
});

process.on("SIGINT", () => {
console.log("Tunnel stopped", tunnel.stop());
});
16 changes: 7 additions & 9 deletions examples/tunnel.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
const { tunnel } = require("cloudflared");
const { Tunnel } = require("cloudflared");

console.log("Cloudflared Tunnel Example.");
main();

async function main() {
// run: cloudflared tunnel --hello-world
const { url, connections, child, stop } = tunnel({ "--hello-world": null });
const tunnel = Tunnel.quick();

// show the url
const url = new Promise((resolve) => tunnel.once("url", resolve));
console.log("LINK:", await url);

// wait for the all 4 connections to be established
const conns = await Promise.all(connections);

// show the connections
console.log("Connections Ready!", conns);
const conn = new Promise((resolve) => tunnel.once("connected", resolve));
console.log("CONN:", await conn);

// stop the tunnel after 15 seconds
setTimeout(stop, 15_000);
setTimeout(tunnel.stop, 15_000);

child.on("exit", (code) => {
tunnel.on("exit", (code) => {
console.log("tunnel process exited with code", code);
});
}
16 changes: 7 additions & 9 deletions examples/tunnel.mjs
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { tunnel } from "cloudflared";
import { Tunnel } from "cloudflared";

console.log("Cloudflared Tunnel Example.");
main();

async function main() {
// run: cloudflared tunnel --hello-world
const { url, connections, child, stop } = tunnel({ "--hello-world": null });
const tunnel = Tunnel.quick();

// show the url
const url = new Promise((resolve) => tunnel.once("url", resolve));
console.log("LINK:", await url);

// wait for the all 4 connections to be established
const conns = await Promise.all(connections);

// show the connections
console.log("Connections Ready!", conns);
const conn = new Promise((resolve) => tunnel.once("connected", resolve));
console.log("CONN:", await conn);

// stop the tunnel after 15 seconds
setTimeout(stop, 15_000);
setTimeout(tunnel.stop, 15_000);

child.on("exit", (code) => {
tunnel.on("exit", (code) => {
console.log("tunnel process exited with code", code);
});
}
83 changes: 39 additions & 44 deletions src/_tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ChildProcess } from "node:child_process";
import fs from "node:fs";
import { bin, install, tunnel, service } from "../lib.js";
import { Tunnel, bin, install, service } from "../lib.js";
import { describe, it, expect, beforeAll } from "vitest";

process.env.VERBOSE = "1";

describe(
"install",
{ timeout: 60_000 },
() => {
it("should install binary", async () => {
if (fs.existsSync(bin)) {
Expand All @@ -18,61 +19,55 @@ describe(
expect(fs.existsSync(bin)).toBe(true);
});
},
{ timeout: 60_000 },
);

describe(
"tunnel",
{ timeout: 60_000 },
() => {
it("should create a tunnel", async () => {
const { url, connections, child, stop } = tunnel({
"--url": "localhost:8080",
"--no-autoupdate": "true",
});
const tunnel = new Tunnel(["tunnel", "--url", "localhost:8080", "--no-autoupdate"]);
const url = new Promise((resolve) => tunnel.once("url", resolve));
expect(await url).toMatch(/https?:\/\/[^\s]+/);
await connections[0]; // quick tunnel only has one connection
expect(child).toBeInstanceOf(ChildProcess);
stop();
const conn = new Promise((resolve) => tunnel.once("connected", resolve));
await conn; // quick tunnel only has one connection
expect(tunnel.process).toBeInstanceOf(ChildProcess);
tunnel.stop();
});
},
{ timeout: 60_000 },
);

describe(
"service",
() => {
const TOKEN = process.env.TUNNEL_TOKEN;
const should_run =
TOKEN &&
["darwin", "linux"].includes(process.platform) &&
!(process.platform === "linux" && process.getuid?.() !== 0);
if (should_run) {
beforeAll(() => {
if (service.exists()) {
service.uninstall();
}
});
}

it("should work", async (ctx) => {
if (!should_run) {
ctx.skip();
describe("service", { timeout: 60_000 }, () => {
const TOKEN = process.env.TUNNEL_TOKEN;
const should_run =
TOKEN &&
["darwin", "linux"].includes(process.platform) &&
!(process.platform === "linux" && process.getuid?.() !== 0);
if (should_run) {
beforeAll(() => {
if (service.exists()) {
service.uninstall();
}
expect(service.exists()).toBe(false);
service.install(TOKEN);
});
}

await new Promise((r) => setTimeout(r, 15_000));
it("should work", async (ctx) => {
if (!should_run) {
ctx.skip();
}
expect(service.exists()).toBe(false);
service.install(TOKEN);

expect(service.exists()).toBe(true);
const current = service.current();
expect(current.tunnelID.length).toBeGreaterThan(0);
expect(current.connectorID.length).toBeGreaterThan(0);
expect(current.connections.length).toBeGreaterThan(0);
expect(current.metrics.length).toBeGreaterThan(0);
expect(current.config.ingress?.length).toBeGreaterThan(0);
await new Promise((r) => setTimeout(r, 15_000));

service.uninstall();
});
},
{ timeout: 60_000 },
);
expect(service.exists()).toBe(true);
const current = service.current();
expect(current.tunnelID.length).toBeGreaterThan(0);
expect(current.connectorID.length).toBeGreaterThan(0);
expect(current.connections.length).toBeGreaterThan(0);
expect(current.metrics.length).toBeGreaterThan(0);
expect(current.config.ingress?.length).toBeGreaterThan(0);

service.uninstall();
});
});
Loading

0 comments on commit 8d5fb54

Please sign in to comment.