@ts-edge/cloudflare 0.1.0
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/dist/index.d.ts +52 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import "@ts-edge/wasm/wasm_exec.js";
|
|
2
|
+
import { type TailscaleNode } from "@ts-edge/wasm";
|
|
3
|
+
export type { TailscaleNode, TailnetSocket, NodeState, NodeConfig, ServeHandler, Server, Peer, } from "@ts-edge/wasm";
|
|
4
|
+
export interface CloudflareNodeConfig {
|
|
5
|
+
/** `import wasm from "@ts-edge/wasm/node.wasm"` — a CompiledWasm module. */
|
|
6
|
+
wasm: WebAssembly.Module;
|
|
7
|
+
authKey: string;
|
|
8
|
+
hostname?: string;
|
|
9
|
+
controlURL?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create the node and start it. Returns as soon as login is kicked off; await
|
|
13
|
+
* tailNode.waitUntilRunning() for readiness.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createCloudflareNode(config: CloudflareNodeConfig): Promise<TailscaleNode>;
|
|
16
|
+
/**
|
|
17
|
+
* A Durable Object base class that hosts one warm tailNode. Extend it and
|
|
18
|
+
* implement getConfig(); the node is created lazily and kept alive until
|
|
19
|
+
* stopTailscaleNode() (its control/DERP WebSockets stay open across requests,
|
|
20
|
+
* which also pins the DO in memory — billed wall-clock duration). Override
|
|
21
|
+
* fetch() to route requests; call this.getRunningTailscaleNode() to obtain
|
|
22
|
+
* the started, Running tailNode.
|
|
23
|
+
*/
|
|
24
|
+
export declare abstract class TailscaleNodeDO<Env = unknown> implements DurableObject {
|
|
25
|
+
#private;
|
|
26
|
+
protected ctx: DurableObjectState;
|
|
27
|
+
protected env: Env;
|
|
28
|
+
protected tailscaleNode: TailscaleNode | undefined;
|
|
29
|
+
constructor(ctx: DurableObjectState, env: Env);
|
|
30
|
+
/** Supply the node configuration (typically from env). */
|
|
31
|
+
protected abstract getConfig(): CloudflareNodeConfig | Promise<CloudflareNodeConfig>;
|
|
32
|
+
/**
|
|
33
|
+
* Optional setup hook, called once per node between creation and start() —
|
|
34
|
+
* attach event listeners (tailNode.on) and register servers (tailNode.serve /
|
|
35
|
+
* tailNode.listen) here, while the node is still offline, so no early state
|
|
36
|
+
* transition or inbound connection is missed.
|
|
37
|
+
*/
|
|
38
|
+
protected beforeStart?(tailNode: TailscaleNode): void | Promise<void>;
|
|
39
|
+
/** Start the node once and return it after it reaches Running. */
|
|
40
|
+
protected getRunningTailscaleNode(opts?: {
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
}): Promise<TailscaleNode>;
|
|
43
|
+
/**
|
|
44
|
+
* Fully stop the node, closing its control/DERP WebSockets. With no outgoing
|
|
45
|
+
* sockets left the DO can hibernate or be evicted, so duration billing stops.
|
|
46
|
+
* The node was ephemeral: the next getRunningTailscaleNode() creates a
|
|
47
|
+
* fresh one (new login, beforeStart runs again). Safe to call at any point,
|
|
48
|
+
* including mid-start.
|
|
49
|
+
*/
|
|
50
|
+
protected stopTailscaleNode(): Promise<void>;
|
|
51
|
+
fetch(_request: Request): Promise<Response>;
|
|
52
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Copyright (c) 2026 Sam Jean
|
|
2
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
// @ts-edge/cloudflare — run a WASM ephemeral Tailscale node inside a Cloudflare
|
|
4
|
+
// Worker. It supplies globalThis.Go (Go's wasm_exec), which workerd lacks, then
|
|
5
|
+
// wraps @ts-edge/wasm. The DERP/control transport uses workerd's native
|
|
6
|
+
// `new WebSocket(url)` client constructor directly.
|
|
7
|
+
import "@ts-edge/wasm/wasm_exec.js"; // side effect: installs globalThis.Go
|
|
8
|
+
import { createNode } from "@ts-edge/wasm";
|
|
9
|
+
/**
|
|
10
|
+
* Create the node and start it. Returns as soon as login is kicked off; await
|
|
11
|
+
* tailNode.waitUntilRunning() for readiness.
|
|
12
|
+
*/
|
|
13
|
+
export async function createCloudflareNode(config) {
|
|
14
|
+
const tailNode = await createNode(config);
|
|
15
|
+
await tailNode.start();
|
|
16
|
+
return tailNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A Durable Object base class that hosts one warm tailNode. Extend it and
|
|
20
|
+
* implement getConfig(); the node is created lazily and kept alive until
|
|
21
|
+
* stopTailscaleNode() (its control/DERP WebSockets stay open across requests,
|
|
22
|
+
* which also pins the DO in memory — billed wall-clock duration). Override
|
|
23
|
+
* fetch() to route requests; call this.getRunningTailscaleNode() to obtain
|
|
24
|
+
* the started, Running tailNode.
|
|
25
|
+
*/
|
|
26
|
+
export class TailscaleNodeDO {
|
|
27
|
+
ctx;
|
|
28
|
+
env;
|
|
29
|
+
tailscaleNode;
|
|
30
|
+
#starting;
|
|
31
|
+
constructor(ctx, env) {
|
|
32
|
+
this.ctx = ctx;
|
|
33
|
+
this.env = env;
|
|
34
|
+
}
|
|
35
|
+
/** Start the node once and return it after it reaches Running. */
|
|
36
|
+
async getRunningTailscaleNode(opts = {}) {
|
|
37
|
+
if (!this.#starting) {
|
|
38
|
+
this.#starting = (async () => {
|
|
39
|
+
const tailNode = await createNode(await this.getConfig());
|
|
40
|
+
this.tailscaleNode = tailNode;
|
|
41
|
+
if (this.beforeStart)
|
|
42
|
+
await this.beforeStart(tailNode);
|
|
43
|
+
await tailNode.start();
|
|
44
|
+
return tailNode;
|
|
45
|
+
})();
|
|
46
|
+
}
|
|
47
|
+
const tailNode = await this.#starting;
|
|
48
|
+
await tailNode.waitUntilRunning(opts);
|
|
49
|
+
return tailNode;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fully stop the node, closing its control/DERP WebSockets. With no outgoing
|
|
53
|
+
* sockets left the DO can hibernate or be evicted, so duration billing stops.
|
|
54
|
+
* The node was ephemeral: the next getRunningTailscaleNode() creates a
|
|
55
|
+
* fresh one (new login, beforeStart runs again). Safe to call at any point,
|
|
56
|
+
* including mid-start.
|
|
57
|
+
*/
|
|
58
|
+
async stopTailscaleNode() {
|
|
59
|
+
const starting = this.#starting;
|
|
60
|
+
this.#starting = undefined;
|
|
61
|
+
this.tailscaleNode = undefined;
|
|
62
|
+
if (!starting)
|
|
63
|
+
return;
|
|
64
|
+
const tailNode = await starting.catch(() => undefined);
|
|
65
|
+
await tailNode?.stop();
|
|
66
|
+
}
|
|
67
|
+
async fetch(_request) {
|
|
68
|
+
const tailNode = await this.getRunningTailscaleNode();
|
|
69
|
+
return new Response(`ts-edge node running: ${tailNode.ipv4 ?? "(no addr)"}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,wCAAwC;AAExC,gFAAgF;AAChF,gFAAgF;AAChF,wEAAwE;AACxE,oDAAoD;AAEpD,OAAO,4BAA4B,CAAC,CAAC,sCAAsC;AAC3E,OAAO,EAAE,UAAU,EAAsB,MAAM,eAAe,CAAC;AAmB/D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA4B;IAE5B,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACvB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAgB,eAAe;IAKvB;IACA;IALF,aAAa,CAA4B;IACnD,SAAS,CAAqC;IAE9C,YACY,GAAuB,EACvB,GAAQ;QADR,QAAG,GAAH,GAAG,CAAoB;QACvB,QAAG,GAAH,GAAG,CAAK;IACjB,CAAC;IAaJ,kEAAkE;IACxD,KAAK,CAAC,uBAAuB,CAAC,OAA+B,EAAE;QACvE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC3B,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC1D,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;gBAC9B,IAAI,IAAI,CAAC,WAAW;oBAAE,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;gBACvD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACvB,OAAO,QAAQ,CAAC;YAClB,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;QACtC,MAAM,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;OAMG;IACO,KAAK,CAAC,iBAAiB;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAiB;QAC3B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACtD,OAAO,IAAI,QAAQ,CAAC,yBAAyB,QAAQ,CAAC,IAAI,IAAI,WAAW,IAAI,CAAC,CAAC;IACjF,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ts-edge/cloudflare",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Run a WASM ephemeral Tailscale node inside a Cloudflare Worker / Durable Object.",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"author": "Sam Jean",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"prepare": "tsc -p tsconfig.json",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@ts-edge/wasm": "workspace:^"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@cloudflare/workers-types": "^4.20260702.1",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
27
|
+
}
|