tailscale-web 0.1.10
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/LICENSE +21 -0
- package/README.md +220 -0
- package/dist/index.d.ts +261 -0
- package/dist/main.wasm +0 -0
- package/dist/tailscale-web.es.js +629 -0
- package/dist/tailscale-web.umd.js +2 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adriano Sela Aviles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# tailscale-web
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.org/package/tailscale-web)
|
|
4
|
+
[](https://github.com/adrianosela/tailscale-web/issues)
|
|
5
|
+
[](https://github.com/adrianosela/tailscale-web/blob/master/LICENSE)
|
|
6
|
+
[](https://goreportcard.com/report/github.com/adrianosela/tailscale-web)
|
|
7
|
+
|
|
8
|
+
Run a [Tailscale](https://tailscale.com) device directly in the browser. Make HTTP requests, open TCP connections, ping hosts, and use exit nodes for networking beyond Tailscale devices — all from a web page, with no server-side proxy required.
|
|
9
|
+
|
|
10
|
+
<details>
|
|
11
|
+
<summary>**Click here for motivation**</summary>
|
|
12
|
+
|
|
13
|
+
### Motivation
|
|
14
|
+
|
|
15
|
+
Tailscale software readily compiles for JavaScript runtimes (via WebAssembly). However, there isn't (as of February 2026) a JavaScript library that makes it easy to set-up Tailscale in the browser. You have to write the Go code yourself, compile it to WebAssembly bytecode (with GOOS=js GOARCH=wasm) yourself, and bundle it yourself alongside the JavaScript shim that sets up the Go runtime (a.k.a. `wasm_exec.js`).
|
|
16
|
+
|
|
17
|
+
Once you are done doing all that, then you have to implement any network clients you need. For example, if you want to perform HTTP requests over the Tailscale network, you have to implement a bridge (in Go) for your JavaScript to call. This is why this library includes common clients (TCP, ICMP, HTTP... with more to come).
|
|
18
|
+
|
|
19
|
+
</details>
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
### Web
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">
|
|
27
|
+
import { network } from "https://esm.sh/tailscale-web"
|
|
28
|
+
</script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Node / npm
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install tailscale-web
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { network } from "tailscale-web"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
### Web
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<!DOCTYPE html>
|
|
47
|
+
<html>
|
|
48
|
+
<body>
|
|
49
|
+
<script type="module">
|
|
50
|
+
import { network } from "https://esm.sh/tailscale-web"
|
|
51
|
+
|
|
52
|
+
await network.init({
|
|
53
|
+
hostname: "my-app",
|
|
54
|
+
onAuthRequired(url) {
|
|
55
|
+
window.open(url, "_blank", "width=600,height=700")
|
|
56
|
+
},
|
|
57
|
+
onAuthComplete() {
|
|
58
|
+
console.log("connected!")
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const resp = await network.fetch("http://my-server/api/data")
|
|
63
|
+
console.log(await resp.json())
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Node / npm
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { network } from "tailscale-web"
|
|
73
|
+
|
|
74
|
+
await network.init({
|
|
75
|
+
hostname: "my-app",
|
|
76
|
+
onAuthRequired(url) {
|
|
77
|
+
|
|
78
|
+
// Open the URL however your environment allows
|
|
79
|
+
console.log("Authenticate at:", url)
|
|
80
|
+
},
|
|
81
|
+
onAuthComplete() {
|
|
82
|
+
console.log("connected!")
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const resp = await network.fetch("http://my-server/api/data")
|
|
87
|
+
console.log(await resp.json())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
In a browser, state is persisted to `localStorage` automatically, so the device reconnects on the next page load without requiring login again. Pass a `storage` adapter in `init()` to use a custom backend.
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
|
|
94
|
+
### `network.init(options?)`
|
|
95
|
+
|
|
96
|
+
Loads the WASM, starts the Tailscale node, and waits until it is authenticated and ready. Must be called before any other method.
|
|
97
|
+
|
|
98
|
+
If the node has persisted state it reconnects automatically; otherwise the OAuth flow is started and `onAuthRequired` is called with the login URL. Rejects if the auth URL does not arrive within 60 seconds, or if the user does not complete authentication within 5 minutes.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
await network.init({
|
|
102
|
+
// device name on the tailnet (default: "tailscale-web")
|
|
103
|
+
hostname?: string
|
|
104
|
+
|
|
105
|
+
// custom store; defaults to localStorage (browser) or in-memory (elsewhere)
|
|
106
|
+
storage?: StorageAdapter
|
|
107
|
+
|
|
108
|
+
// key prefix for the default store (default: "tailscale-web")
|
|
109
|
+
// keys are written as "{prefix}_{stateKey}"
|
|
110
|
+
storagePrefix?: string
|
|
111
|
+
|
|
112
|
+
// custom coordination server URL
|
|
113
|
+
controlUrl?: string
|
|
114
|
+
|
|
115
|
+
// called when login is needed
|
|
116
|
+
onAuthRequired?: (url: string) => void
|
|
117
|
+
|
|
118
|
+
// called once authenticated
|
|
119
|
+
onAuthComplete?: () => void
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Example: use sessionStorage as the backend
|
|
125
|
+
await network.init({
|
|
126
|
+
hostname: "my-app",
|
|
127
|
+
storage: {
|
|
128
|
+
get: key => sessionStorage.getItem(key),
|
|
129
|
+
set: (key, val) => sessionStorage.setItem(key, val),
|
|
130
|
+
},
|
|
131
|
+
onAuthRequired(url) { window.open(url, "_blank", "width=600,height=700") },
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `network.fetch(url, init?)`
|
|
136
|
+
|
|
137
|
+
Make an HTTP/HTTPS request through the tailnet. Supports `method`, `headers`, and `body`. Does not yet support `AbortSignal`, streaming bodies or responses, or other advanced Fetch API options (`mode`, `credentials`, `cache`, `redirect`).
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const resp = await network.fetch("https://internal-service/api", {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/json" },
|
|
143
|
+
body: JSON.stringify({ key: "value" }),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
console.log(resp.status) // 200
|
|
147
|
+
console.log(resp.ok) // true
|
|
148
|
+
const data = await resp.json()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `network.ping(addr)`
|
|
152
|
+
|
|
153
|
+
ICMP ping a peer and measure round-trip time. `addr` may be a hostname or Tailscale IP.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const result = await network.ping("my-server")
|
|
157
|
+
// { alive: true, rttMs: 3.2, nodeName: "my-server", nodeIP: "100.x.x.x", ... }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `network.dialTCP(addr)`
|
|
161
|
+
|
|
162
|
+
Open a raw TCP connection through the tailnet. Returns a `Connection` object.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const conn = await network.dialTCP("my-server:8080")
|
|
166
|
+
|
|
167
|
+
conn.onData(data => {
|
|
168
|
+
console.log(new TextDecoder().decode(data))
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
conn.write("hello\n")
|
|
172
|
+
conn.close()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Exit nodes
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// list available exit nodes
|
|
179
|
+
const nodes = network.listExitNodes()
|
|
180
|
+
// [{ id, hostName, dnsName, tailscaleIP, active, online }, ...]
|
|
181
|
+
|
|
182
|
+
// activate an exit node
|
|
183
|
+
await network.setExitNode(nodes[0].id)
|
|
184
|
+
|
|
185
|
+
// clear the exit node (route traffic directly)
|
|
186
|
+
await network.setExitNode()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Routes & preferences
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// accept subnet routes advertised by peers
|
|
193
|
+
await network.setAcceptRoutes(true)
|
|
194
|
+
|
|
195
|
+
// current preferences
|
|
196
|
+
const prefs = network.getPrefs()
|
|
197
|
+
// { acceptRoutes: true, exitNodeId: "..." }
|
|
198
|
+
|
|
199
|
+
// full routing table
|
|
200
|
+
const routes = network.getRoutes()
|
|
201
|
+
// [{ prefix, via, isPrimary, isExitRoute }, ...]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### DNS
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const dns = network.getDNS()
|
|
208
|
+
// {
|
|
209
|
+
// resolvers: string[]
|
|
210
|
+
// routes: Record<string, string[]> // split-DNS: suffix → resolvers
|
|
211
|
+
// domains: string[] // search domains
|
|
212
|
+
// extraRecords: { name, type, value }[]
|
|
213
|
+
// magicDNS: boolean
|
|
214
|
+
// }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Notes
|
|
218
|
+
|
|
219
|
+
- **WASM size.** The Tailscale WASM binary is ~35 MB. It is loaded once and cached by the browser.
|
|
220
|
+
- **Auth persistence.** Tailscale auth state is stored in `localStorage` (or your custom adapter) under a key prefix. Call `localStorage.clear()` or remove the prefixed keys to log out.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export declare interface Connection {
|
|
2
|
+
/** Register a handler for incoming data. Must be called before write() — data can arrive as soon as the connection is established. */
|
|
3
|
+
onData(handler: (data: Uint8Array) => void): void;
|
|
4
|
+
/** Send data over the connection. Accepts a Uint8Array or a string. */
|
|
5
|
+
write(data: Uint8Array | string): void;
|
|
6
|
+
/** Close the connection and release all resources. */
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare interface DNSInfo {
|
|
11
|
+
/** Global nameservers. */
|
|
12
|
+
resolvers: string[];
|
|
13
|
+
/** Split-DNS map: suffix → dedicated resolver addresses. */
|
|
14
|
+
routes: Record<string, string[]>;
|
|
15
|
+
/** Search/split-DNS domains. */
|
|
16
|
+
domains: string[];
|
|
17
|
+
/** Custom DNS records pushed by the control plane. */
|
|
18
|
+
extraRecords: {
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}[];
|
|
23
|
+
/** Whether MagicDNS proxied resolution is enabled. */
|
|
24
|
+
magicDNS: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare interface ExitNode {
|
|
28
|
+
/** Stable node ID — pass to setExitNode() to activate. */
|
|
29
|
+
id: string;
|
|
30
|
+
hostName: string;
|
|
31
|
+
/** MagicDNS FQDN (ends with a dot). */
|
|
32
|
+
dnsName: string;
|
|
33
|
+
/** Primary Tailscale IPv4 address. */
|
|
34
|
+
tailscaleIP: string;
|
|
35
|
+
/** Whether this node is the currently active exit node. */
|
|
36
|
+
active: boolean;
|
|
37
|
+
online: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export declare interface InitOptions {
|
|
41
|
+
/** Device name as it appears on the tailnet. Default: "tailscale-web" */
|
|
42
|
+
hostname?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Custom storage backend for persisting Tailscale state.
|
|
45
|
+
* If omitted, defaults to localStorage in browser environments,
|
|
46
|
+
* or an in-memory store (no persistence) elsewhere.
|
|
47
|
+
*/
|
|
48
|
+
storage?: StorageAdapter | null;
|
|
49
|
+
/** Key prefix used by the default localStorage store. Keys are written as "{prefix}_{stateKey}". Default: "tailscale-web" */
|
|
50
|
+
storagePrefix?: string;
|
|
51
|
+
/** Override the Tailscale coordination server URL. */
|
|
52
|
+
controlUrl?: string;
|
|
53
|
+
/** Called with the OAuth URL when interactive login is needed. */
|
|
54
|
+
onAuthRequired?: (url: string) => void;
|
|
55
|
+
/** Called once the device is authenticated and connected. */
|
|
56
|
+
onAuthComplete?: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare const network: {
|
|
60
|
+
/**
|
|
61
|
+
* Initialize and connect the Tailscale node. Must be called before any
|
|
62
|
+
* other method. Resolves once the node is authenticated and ready.
|
|
63
|
+
*
|
|
64
|
+
* If the node has persisted state from a previous session it reconnects
|
|
65
|
+
* automatically. Otherwise the OAuth flow is triggered via onAuthRequired.
|
|
66
|
+
* Rejects if the auth URL does not arrive within 60 seconds, or if the
|
|
67
|
+
* user does not complete authentication within 5 minutes.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* await network.init({
|
|
71
|
+
* hostname: "my-app",
|
|
72
|
+
* onAuthRequired(url) {
|
|
73
|
+
* window.open(url, "_blank", "width=600,height=700")
|
|
74
|
+
* },
|
|
75
|
+
* onAuthComplete() {
|
|
76
|
+
* console.log("connected!")
|
|
77
|
+
* },
|
|
78
|
+
* })
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Custom storage backend (e.g. sessionStorage or any key/value store)
|
|
82
|
+
* await network.init({
|
|
83
|
+
* hostname: "my-app",
|
|
84
|
+
* storage: {
|
|
85
|
+
* get: key => sessionStorage.getItem(key),
|
|
86
|
+
* set: (key, val) => sessionStorage.setItem(key, val),
|
|
87
|
+
* },
|
|
88
|
+
* onAuthRequired(url) { console.log("Authenticate at:", url) },
|
|
89
|
+
* })
|
|
90
|
+
*/
|
|
91
|
+
init(options?: InitOptions): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Send an ICMP ping to addr and measure round-trip time.
|
|
94
|
+
* addr may be a hostname or Tailscale IP.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const result = await network.ping("my-server")
|
|
98
|
+
* if (result.alive) {
|
|
99
|
+
* console.log(`rtt: ${result.rttMs.toFixed(3)} ms ip: ${result.nodeIP}`)
|
|
100
|
+
* } else {
|
|
101
|
+
* console.warn("unreachable:", result.err)
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
104
|
+
ping(addr: string): Promise<PingResult>;
|
|
105
|
+
/**
|
|
106
|
+
* Open a raw TCP connection through the Tailscale network.
|
|
107
|
+
* Returns a Connection object for sending and receiving data.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* const conn = await network.dialTCP("my-server:8080")
|
|
111
|
+
*
|
|
112
|
+
* conn.onData(data => {
|
|
113
|
+
* console.log(new TextDecoder().decode(data))
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* conn.write("hello\n")
|
|
117
|
+
* conn.close()
|
|
118
|
+
*/
|
|
119
|
+
dialTCP(addr: string): Promise<Connection>;
|
|
120
|
+
/**
|
|
121
|
+
* Make an HTTP request through the Tailscale network. Supports method,
|
|
122
|
+
* headers, and body. Does not yet support AbortSignal, streaming bodies
|
|
123
|
+
* or responses, or other advanced Fetch API options.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const resp = await network.fetch("https://internal-service/api", {
|
|
127
|
+
* method: "POST",
|
|
128
|
+
* headers: { "Content-Type": "application/json" },
|
|
129
|
+
* body: JSON.stringify({ key: "value" }),
|
|
130
|
+
* })
|
|
131
|
+
* console.log(resp.status, resp.ok)
|
|
132
|
+
* const data = await resp.json()
|
|
133
|
+
*/
|
|
134
|
+
fetch(url: string, init?: RequestInit_2): Promise<Response_2>;
|
|
135
|
+
/**
|
|
136
|
+
* Return the current preferences (acceptRoutes, exitNodeId).
|
|
137
|
+
* Synchronous — no await needed. Must be called after init() resolves.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const { acceptRoutes, exitNodeId } = network.getPrefs()
|
|
141
|
+
* console.log("exit node:", exitNodeId || "(none)")
|
|
142
|
+
*/
|
|
143
|
+
getPrefs(): Prefs;
|
|
144
|
+
/**
|
|
145
|
+
* Enable or disable acceptance of subnet routes advertised by peers.
|
|
146
|
+
* Equivalent to `tailscale set --accept-routes`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* await network.setAcceptRoutes(true)
|
|
150
|
+
*/
|
|
151
|
+
setAcceptRoutes(accept: boolean): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Return all peers that advertise exit-node capability.
|
|
154
|
+
* Synchronous — no await needed. Returns an empty array if called before init() resolves.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* const nodes = network.listExitNodes()
|
|
158
|
+
* for (const n of nodes) {
|
|
159
|
+
* console.log(n.hostName, n.tailscaleIP, n.online ? "online" : "offline")
|
|
160
|
+
* }
|
|
161
|
+
*/
|
|
162
|
+
listExitNodes(): ExitNode[];
|
|
163
|
+
/**
|
|
164
|
+
* Activate an exit node by its stable node ID.
|
|
165
|
+
* Pass an empty string (or omit) to clear the exit node.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* // Activate the first available online exit node
|
|
169
|
+
* const node = network.listExitNodes().find(n => n.online)
|
|
170
|
+
* if (node) await network.setExitNode(node.id)
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* // Clear the active exit node
|
|
174
|
+
* await network.setExitNode()
|
|
175
|
+
*/
|
|
176
|
+
setExitNode(id?: string): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Return the full routing table (self + all peers).
|
|
179
|
+
* Synchronous — no await needed. Returns an empty array if called before init() resolves.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const routes = network.getRoutes()
|
|
183
|
+
* for (const r of routes) {
|
|
184
|
+
* console.log(r.prefix, "via", r.via, r.isExitRoute ? "(exit)" : "")
|
|
185
|
+
* }
|
|
186
|
+
*/
|
|
187
|
+
getRoutes(): Route[];
|
|
188
|
+
/**
|
|
189
|
+
* Return the current Tailscale-managed DNS configuration.
|
|
190
|
+
* Synchronous — no await needed. Returns an empty DNSInfo object if called before init() resolves.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* const dns = network.getDNS()
|
|
194
|
+
* console.log("resolvers:", dns.resolvers)
|
|
195
|
+
* console.log("MagicDNS:", dns.magicDNS)
|
|
196
|
+
* for (const [suffix, resolvers] of Object.entries(dns.routes)) {
|
|
197
|
+
* console.log(`split-DNS: ${suffix} → ${resolvers.join(", ")}`)
|
|
198
|
+
* }
|
|
199
|
+
*/
|
|
200
|
+
getDNS(): DNSInfo;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export declare interface PingResult {
|
|
204
|
+
alive: boolean;
|
|
205
|
+
/** Round-trip time in milliseconds. Only meaningful when alive is true. */
|
|
206
|
+
rttMs: number;
|
|
207
|
+
/** MagicDNS name of the destination peer. */
|
|
208
|
+
nodeName: string;
|
|
209
|
+
/** Tailscale IP of the destination. */
|
|
210
|
+
nodeIP: string;
|
|
211
|
+
/** Direct UDP endpoint used if a direct path exists (e.g. an IP:port string). */
|
|
212
|
+
endpoint: string;
|
|
213
|
+
/** DERP relay region code (e.g. "nyc") if traffic was relayed; empty if direct. */
|
|
214
|
+
derpRegionCode: string;
|
|
215
|
+
/** Error reason when alive is false. */
|
|
216
|
+
err: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export declare interface Prefs {
|
|
220
|
+
/** Whether subnet routes advertised by peers are accepted. */
|
|
221
|
+
acceptRoutes: boolean;
|
|
222
|
+
/** Stable node ID of the active exit node, or empty string if none. */
|
|
223
|
+
exitNodeId: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
declare interface RequestInit_2 {
|
|
227
|
+
method?: string;
|
|
228
|
+
headers?: Record<string, string>;
|
|
229
|
+
body?: string | Uint8Array;
|
|
230
|
+
}
|
|
231
|
+
export { RequestInit_2 as RequestInit }
|
|
232
|
+
|
|
233
|
+
declare interface Response_2 {
|
|
234
|
+
status: number;
|
|
235
|
+
statusText: string;
|
|
236
|
+
ok: boolean;
|
|
237
|
+
headers: Record<string, string>;
|
|
238
|
+
text(): Promise<string>;
|
|
239
|
+
json(): Promise<unknown>;
|
|
240
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
241
|
+
bytes(): Promise<Uint8Array>;
|
|
242
|
+
}
|
|
243
|
+
export { Response_2 as Response }
|
|
244
|
+
|
|
245
|
+
export declare interface Route {
|
|
246
|
+
/** CIDR prefix (e.g. a subnet or default route). */
|
|
247
|
+
prefix: string;
|
|
248
|
+
/** Display name of the advertising node, or "self". */
|
|
249
|
+
via: string;
|
|
250
|
+
/** Whether this node is the primary (active) router for the prefix. */
|
|
251
|
+
isPrimary: boolean;
|
|
252
|
+
/** Whether this is a default/exit route (0.0.0.0/0 or ::/0). */
|
|
253
|
+
isExitRoute: boolean;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export declare interface StorageAdapter {
|
|
257
|
+
get(key: string): string | null;
|
|
258
|
+
set(key: string, value: string): void;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { }
|
package/dist/main.wasm
ADDED
|
Binary file
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const n = () => {
|
|
3
|
+
const c = new Error("not implemented");
|
|
4
|
+
return c.code = "ENOSYS", c;
|
|
5
|
+
};
|
|
6
|
+
if (!globalThis.fs) {
|
|
7
|
+
let c = "";
|
|
8
|
+
globalThis.fs = {
|
|
9
|
+
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 },
|
|
10
|
+
// unused
|
|
11
|
+
writeSync(i, s) {
|
|
12
|
+
c += g.decode(s);
|
|
13
|
+
const r = c.lastIndexOf(`
|
|
14
|
+
`);
|
|
15
|
+
return r != -1 && (console.log(c.substring(0, r)), c = c.substring(r + 1)), s.length;
|
|
16
|
+
},
|
|
17
|
+
write(i, s, r, a, y, u) {
|
|
18
|
+
if (r !== 0 || a !== s.length || y !== null) {
|
|
19
|
+
u(n());
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const w = this.writeSync(i, s);
|
|
23
|
+
u(null, w);
|
|
24
|
+
},
|
|
25
|
+
chmod(i, s, r) {
|
|
26
|
+
r(n());
|
|
27
|
+
},
|
|
28
|
+
chown(i, s, r, a) {
|
|
29
|
+
a(n());
|
|
30
|
+
},
|
|
31
|
+
close(i, s) {
|
|
32
|
+
s(n());
|
|
33
|
+
},
|
|
34
|
+
fchmod(i, s, r) {
|
|
35
|
+
r(n());
|
|
36
|
+
},
|
|
37
|
+
fchown(i, s, r, a) {
|
|
38
|
+
a(n());
|
|
39
|
+
},
|
|
40
|
+
fstat(i, s) {
|
|
41
|
+
s(n());
|
|
42
|
+
},
|
|
43
|
+
fsync(i, s) {
|
|
44
|
+
s(null);
|
|
45
|
+
},
|
|
46
|
+
ftruncate(i, s, r) {
|
|
47
|
+
r(n());
|
|
48
|
+
},
|
|
49
|
+
lchown(i, s, r, a) {
|
|
50
|
+
a(n());
|
|
51
|
+
},
|
|
52
|
+
link(i, s, r) {
|
|
53
|
+
r(n());
|
|
54
|
+
},
|
|
55
|
+
lstat(i, s) {
|
|
56
|
+
s(n());
|
|
57
|
+
},
|
|
58
|
+
mkdir(i, s, r) {
|
|
59
|
+
r(n());
|
|
60
|
+
},
|
|
61
|
+
open(i, s, r, a) {
|
|
62
|
+
a(n());
|
|
63
|
+
},
|
|
64
|
+
read(i, s, r, a, y, u) {
|
|
65
|
+
u(n());
|
|
66
|
+
},
|
|
67
|
+
readdir(i, s) {
|
|
68
|
+
s(n());
|
|
69
|
+
},
|
|
70
|
+
readlink(i, s) {
|
|
71
|
+
s(n());
|
|
72
|
+
},
|
|
73
|
+
rename(i, s, r) {
|
|
74
|
+
r(n());
|
|
75
|
+
},
|
|
76
|
+
rmdir(i, s) {
|
|
77
|
+
s(n());
|
|
78
|
+
},
|
|
79
|
+
stat(i, s) {
|
|
80
|
+
s(n());
|
|
81
|
+
},
|
|
82
|
+
symlink(i, s, r) {
|
|
83
|
+
r(n());
|
|
84
|
+
},
|
|
85
|
+
truncate(i, s, r) {
|
|
86
|
+
r(n());
|
|
87
|
+
},
|
|
88
|
+
unlink(i, s) {
|
|
89
|
+
s(n());
|
|
90
|
+
},
|
|
91
|
+
utimes(i, s, r, a) {
|
|
92
|
+
a(n());
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (globalThis.process || (globalThis.process = {
|
|
97
|
+
getuid() {
|
|
98
|
+
return -1;
|
|
99
|
+
},
|
|
100
|
+
getgid() {
|
|
101
|
+
return -1;
|
|
102
|
+
},
|
|
103
|
+
geteuid() {
|
|
104
|
+
return -1;
|
|
105
|
+
},
|
|
106
|
+
getegid() {
|
|
107
|
+
return -1;
|
|
108
|
+
},
|
|
109
|
+
getgroups() {
|
|
110
|
+
throw n();
|
|
111
|
+
},
|
|
112
|
+
pid: -1,
|
|
113
|
+
ppid: -1,
|
|
114
|
+
umask() {
|
|
115
|
+
throw n();
|
|
116
|
+
},
|
|
117
|
+
cwd() {
|
|
118
|
+
throw n();
|
|
119
|
+
},
|
|
120
|
+
chdir() {
|
|
121
|
+
throw n();
|
|
122
|
+
}
|
|
123
|
+
}), globalThis.path || (globalThis.path = {
|
|
124
|
+
resolve(...c) {
|
|
125
|
+
return c.join("/");
|
|
126
|
+
}
|
|
127
|
+
}), !globalThis.crypto)
|
|
128
|
+
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
|
129
|
+
if (!globalThis.performance)
|
|
130
|
+
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
|
131
|
+
if (!globalThis.TextEncoder)
|
|
132
|
+
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
|
133
|
+
if (!globalThis.TextDecoder)
|
|
134
|
+
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
|
135
|
+
const h = new TextEncoder("utf-8"), g = new TextDecoder("utf-8");
|
|
136
|
+
globalThis.Go = class {
|
|
137
|
+
constructor() {
|
|
138
|
+
this.argv = ["js"], this.env = {}, this.exit = (t) => {
|
|
139
|
+
t !== 0 && console.warn("exit code:", t);
|
|
140
|
+
}, this._exitPromise = new Promise((t) => {
|
|
141
|
+
this._resolveExitPromise = t;
|
|
142
|
+
}), this._pendingEvent = null, this._scheduledTimeouts = /* @__PURE__ */ new Map(), this._nextCallbackTimeoutID = 1;
|
|
143
|
+
const c = (t, e) => {
|
|
144
|
+
this.mem.setUint32(t + 0, e, !0), this.mem.setUint32(t + 4, Math.floor(e / 4294967296), !0);
|
|
145
|
+
}, i = (t) => {
|
|
146
|
+
const e = this.mem.getUint32(t + 0, !0), o = this.mem.getInt32(t + 4, !0);
|
|
147
|
+
return e + o * 4294967296;
|
|
148
|
+
}, s = (t) => {
|
|
149
|
+
const e = this.mem.getFloat64(t, !0);
|
|
150
|
+
if (e === 0)
|
|
151
|
+
return;
|
|
152
|
+
if (!isNaN(e))
|
|
153
|
+
return e;
|
|
154
|
+
const o = this.mem.getUint32(t, !0);
|
|
155
|
+
return this._values[o];
|
|
156
|
+
}, r = (t, e) => {
|
|
157
|
+
if (typeof e == "number" && e !== 0) {
|
|
158
|
+
if (isNaN(e)) {
|
|
159
|
+
this.mem.setUint32(t + 4, 2146959360, !0), this.mem.setUint32(t, 0, !0);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.mem.setFloat64(t, e, !0);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (e === void 0) {
|
|
166
|
+
this.mem.setFloat64(t, 0, !0);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let l = this._ids.get(e);
|
|
170
|
+
l === void 0 && (l = this._idPool.pop(), l === void 0 && (l = this._values.length), this._values[l] = e, this._goRefCounts[l] = 0, this._ids.set(e, l)), this._goRefCounts[l]++;
|
|
171
|
+
let m = 0;
|
|
172
|
+
switch (typeof e) {
|
|
173
|
+
case "object":
|
|
174
|
+
e !== null && (m = 1);
|
|
175
|
+
break;
|
|
176
|
+
case "string":
|
|
177
|
+
m = 2;
|
|
178
|
+
break;
|
|
179
|
+
case "symbol":
|
|
180
|
+
m = 3;
|
|
181
|
+
break;
|
|
182
|
+
case "function":
|
|
183
|
+
m = 4;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
this.mem.setUint32(t + 4, 2146959360 | m, !0), this.mem.setUint32(t, l, !0);
|
|
187
|
+
}, a = (t) => {
|
|
188
|
+
const e = i(t + 0), o = i(t + 8);
|
|
189
|
+
return new Uint8Array(this._inst.exports.mem.buffer, e, o);
|
|
190
|
+
}, y = (t) => {
|
|
191
|
+
const e = i(t + 0), o = i(t + 8), l = new Array(o);
|
|
192
|
+
for (let m = 0; m < o; m++)
|
|
193
|
+
l[m] = s(e + m * 8);
|
|
194
|
+
return l;
|
|
195
|
+
}, u = (t) => {
|
|
196
|
+
const e = i(t + 0), o = i(t + 8);
|
|
197
|
+
return g.decode(new DataView(this._inst.exports.mem.buffer, e, o));
|
|
198
|
+
}, w = (t, e) => (this._inst.exports.testExport0(), this._inst.exports.testExport(t, e)), d = Date.now() - performance.now();
|
|
199
|
+
this.importObject = {
|
|
200
|
+
_gotest: {
|
|
201
|
+
add: (t, e) => t + e,
|
|
202
|
+
callExport: w
|
|
203
|
+
},
|
|
204
|
+
gojs: {
|
|
205
|
+
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
|
206
|
+
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
|
207
|
+
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
|
208
|
+
// This changes the SP, thus we have to update the SP used by the imported function.
|
|
209
|
+
// func wasmExit(code int32)
|
|
210
|
+
"runtime.wasmExit": (t) => {
|
|
211
|
+
t >>>= 0;
|
|
212
|
+
const e = this.mem.getInt32(t + 8, !0);
|
|
213
|
+
this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(e);
|
|
214
|
+
},
|
|
215
|
+
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
|
216
|
+
"runtime.wasmWrite": (t) => {
|
|
217
|
+
t >>>= 0;
|
|
218
|
+
const e = i(t + 8), o = i(t + 16), l = this.mem.getInt32(t + 24, !0);
|
|
219
|
+
fs.writeSync(e, new Uint8Array(this._inst.exports.mem.buffer, o, l));
|
|
220
|
+
},
|
|
221
|
+
// func resetMemoryDataView()
|
|
222
|
+
"runtime.resetMemoryDataView": (t) => {
|
|
223
|
+
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
224
|
+
},
|
|
225
|
+
// func nanotime1() int64
|
|
226
|
+
"runtime.nanotime1": (t) => {
|
|
227
|
+
t >>>= 0, c(t + 8, (d + performance.now()) * 1e6);
|
|
228
|
+
},
|
|
229
|
+
// func walltime() (sec int64, nsec int32)
|
|
230
|
+
"runtime.walltime": (t) => {
|
|
231
|
+
t >>>= 0;
|
|
232
|
+
const e = (/* @__PURE__ */ new Date()).getTime();
|
|
233
|
+
c(t + 8, e / 1e3), this.mem.setInt32(t + 16, e % 1e3 * 1e6, !0);
|
|
234
|
+
},
|
|
235
|
+
// func scheduleTimeoutEvent(delay int64) int32
|
|
236
|
+
"runtime.scheduleTimeoutEvent": (t) => {
|
|
237
|
+
t >>>= 0;
|
|
238
|
+
const e = this._nextCallbackTimeoutID;
|
|
239
|
+
this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(e, setTimeout(
|
|
240
|
+
() => {
|
|
241
|
+
for (this._resume(); this._scheduledTimeouts.has(e); )
|
|
242
|
+
console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume();
|
|
243
|
+
},
|
|
244
|
+
i(t + 8)
|
|
245
|
+
)), this.mem.setInt32(t + 16, e, !0);
|
|
246
|
+
},
|
|
247
|
+
// func clearTimeoutEvent(id int32)
|
|
248
|
+
"runtime.clearTimeoutEvent": (t) => {
|
|
249
|
+
t >>>= 0;
|
|
250
|
+
const e = this.mem.getInt32(t + 8, !0);
|
|
251
|
+
clearTimeout(this._scheduledTimeouts.get(e)), this._scheduledTimeouts.delete(e);
|
|
252
|
+
},
|
|
253
|
+
// func getRandomData(r []byte)
|
|
254
|
+
"runtime.getRandomData": (t) => {
|
|
255
|
+
t >>>= 0, crypto.getRandomValues(a(t + 8));
|
|
256
|
+
},
|
|
257
|
+
// func finalizeRef(v ref)
|
|
258
|
+
"syscall/js.finalizeRef": (t) => {
|
|
259
|
+
t >>>= 0;
|
|
260
|
+
const e = this.mem.getUint32(t + 8, !0);
|
|
261
|
+
if (this._goRefCounts[e]--, this._goRefCounts[e] === 0) {
|
|
262
|
+
const o = this._values[e];
|
|
263
|
+
this._values[e] = null, this._ids.delete(o), this._idPool.push(e);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
// func stringVal(value string) ref
|
|
267
|
+
"syscall/js.stringVal": (t) => {
|
|
268
|
+
t >>>= 0, r(t + 24, u(t + 8));
|
|
269
|
+
},
|
|
270
|
+
// func valueGet(v ref, p string) ref
|
|
271
|
+
"syscall/js.valueGet": (t) => {
|
|
272
|
+
t >>>= 0;
|
|
273
|
+
const e = Reflect.get(s(t + 8), u(t + 16));
|
|
274
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 32, e);
|
|
275
|
+
},
|
|
276
|
+
// func valueSet(v ref, p string, x ref)
|
|
277
|
+
"syscall/js.valueSet": (t) => {
|
|
278
|
+
t >>>= 0, Reflect.set(s(t + 8), u(t + 16), s(t + 32));
|
|
279
|
+
},
|
|
280
|
+
// func valueDelete(v ref, p string)
|
|
281
|
+
"syscall/js.valueDelete": (t) => {
|
|
282
|
+
t >>>= 0, Reflect.deleteProperty(s(t + 8), u(t + 16));
|
|
283
|
+
},
|
|
284
|
+
// func valueIndex(v ref, i int) ref
|
|
285
|
+
"syscall/js.valueIndex": (t) => {
|
|
286
|
+
t >>>= 0, r(t + 24, Reflect.get(s(t + 8), i(t + 16)));
|
|
287
|
+
},
|
|
288
|
+
// valueSetIndex(v ref, i int, x ref)
|
|
289
|
+
"syscall/js.valueSetIndex": (t) => {
|
|
290
|
+
t >>>= 0, Reflect.set(s(t + 8), i(t + 16), s(t + 24));
|
|
291
|
+
},
|
|
292
|
+
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
|
293
|
+
"syscall/js.valueCall": (t) => {
|
|
294
|
+
t >>>= 0;
|
|
295
|
+
try {
|
|
296
|
+
const e = s(t + 8), o = Reflect.get(e, u(t + 16)), l = y(t + 32), m = Reflect.apply(o, e, l);
|
|
297
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 56, m), this.mem.setUint8(t + 64, 1);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 56, e), this.mem.setUint8(t + 64, 0);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
// func valueInvoke(v ref, args []ref) (ref, bool)
|
|
303
|
+
"syscall/js.valueInvoke": (t) => {
|
|
304
|
+
t >>>= 0;
|
|
305
|
+
try {
|
|
306
|
+
const e = s(t + 8), o = y(t + 16), l = Reflect.apply(e, void 0, o);
|
|
307
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 40, l), this.mem.setUint8(t + 48, 1);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 40, e), this.mem.setUint8(t + 48, 0);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
// func valueNew(v ref, args []ref) (ref, bool)
|
|
313
|
+
"syscall/js.valueNew": (t) => {
|
|
314
|
+
t >>>= 0;
|
|
315
|
+
try {
|
|
316
|
+
const e = s(t + 8), o = y(t + 16), l = Reflect.construct(e, o);
|
|
317
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 40, l), this.mem.setUint8(t + 48, 1);
|
|
318
|
+
} catch (e) {
|
|
319
|
+
t = this._inst.exports.getsp() >>> 0, r(t + 40, e), this.mem.setUint8(t + 48, 0);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
// func valueLength(v ref) int
|
|
323
|
+
"syscall/js.valueLength": (t) => {
|
|
324
|
+
t >>>= 0, c(t + 16, parseInt(s(t + 8).length));
|
|
325
|
+
},
|
|
326
|
+
// valuePrepareString(v ref) (ref, int)
|
|
327
|
+
"syscall/js.valuePrepareString": (t) => {
|
|
328
|
+
t >>>= 0;
|
|
329
|
+
const e = h.encode(String(s(t + 8)));
|
|
330
|
+
r(t + 16, e), c(t + 24, e.length);
|
|
331
|
+
},
|
|
332
|
+
// valueLoadString(v ref, b []byte)
|
|
333
|
+
"syscall/js.valueLoadString": (t) => {
|
|
334
|
+
t >>>= 0;
|
|
335
|
+
const e = s(t + 8);
|
|
336
|
+
a(t + 16).set(e);
|
|
337
|
+
},
|
|
338
|
+
// func valueInstanceOf(v ref, t ref) bool
|
|
339
|
+
"syscall/js.valueInstanceOf": (t) => {
|
|
340
|
+
t >>>= 0, this.mem.setUint8(t + 24, s(t + 8) instanceof s(t + 16) ? 1 : 0);
|
|
341
|
+
},
|
|
342
|
+
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
|
343
|
+
"syscall/js.copyBytesToGo": (t) => {
|
|
344
|
+
t >>>= 0;
|
|
345
|
+
const e = a(t + 8), o = s(t + 32);
|
|
346
|
+
if (!(o instanceof Uint8Array || o instanceof Uint8ClampedArray)) {
|
|
347
|
+
this.mem.setUint8(t + 48, 0);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const l = o.subarray(0, e.length);
|
|
351
|
+
e.set(l), c(t + 40, l.length), this.mem.setUint8(t + 48, 1);
|
|
352
|
+
},
|
|
353
|
+
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
|
354
|
+
"syscall/js.copyBytesToJS": (t) => {
|
|
355
|
+
t >>>= 0;
|
|
356
|
+
const e = s(t + 8), o = a(t + 16);
|
|
357
|
+
if (!(e instanceof Uint8Array || e instanceof Uint8ClampedArray)) {
|
|
358
|
+
this.mem.setUint8(t + 48, 0);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const l = o.subarray(0, e.length);
|
|
362
|
+
e.set(l), c(t + 40, l.length), this.mem.setUint8(t + 48, 1);
|
|
363
|
+
},
|
|
364
|
+
debug: (t) => {
|
|
365
|
+
console.log(t);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async run(c) {
|
|
371
|
+
if (!(c instanceof WebAssembly.Instance))
|
|
372
|
+
throw new Error("Go.run: WebAssembly.Instance expected");
|
|
373
|
+
this._inst = c, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [
|
|
374
|
+
// JS values that Go currently has references to, indexed by reference id
|
|
375
|
+
NaN,
|
|
376
|
+
0,
|
|
377
|
+
null,
|
|
378
|
+
!0,
|
|
379
|
+
!1,
|
|
380
|
+
globalThis,
|
|
381
|
+
this
|
|
382
|
+
], this._goRefCounts = new Array(this._values.length).fill(1 / 0), this._ids = /* @__PURE__ */ new Map([
|
|
383
|
+
// mapping from JS values to reference ids
|
|
384
|
+
[0, 1],
|
|
385
|
+
[null, 2],
|
|
386
|
+
[!0, 3],
|
|
387
|
+
[!1, 4],
|
|
388
|
+
[globalThis, 5],
|
|
389
|
+
[this, 6]
|
|
390
|
+
]), this._idPool = [], this.exited = !1;
|
|
391
|
+
let i = 4096;
|
|
392
|
+
const s = (d) => {
|
|
393
|
+
const t = i, e = h.encode(d + "\0");
|
|
394
|
+
return new Uint8Array(this.mem.buffer, i, e.length).set(e), i += e.length, i % 8 !== 0 && (i += 8 - i % 8), t;
|
|
395
|
+
}, r = this.argv.length, a = [];
|
|
396
|
+
this.argv.forEach((d) => {
|
|
397
|
+
a.push(s(d));
|
|
398
|
+
}), a.push(0), Object.keys(this.env).sort().forEach((d) => {
|
|
399
|
+
a.push(s(`${d}=${this.env[d]}`));
|
|
400
|
+
}), a.push(0);
|
|
401
|
+
const u = i;
|
|
402
|
+
if (a.forEach((d) => {
|
|
403
|
+
this.mem.setUint32(i, d, !0), this.mem.setUint32(i + 4, 0, !0), i += 8;
|
|
404
|
+
}), i >= 12288)
|
|
405
|
+
throw new Error("total length of command line and environment variables exceeds limit");
|
|
406
|
+
this._inst.exports.run(r, u), this.exited && this._resolveExitPromise(), await this._exitPromise;
|
|
407
|
+
}
|
|
408
|
+
_resume() {
|
|
409
|
+
if (this.exited)
|
|
410
|
+
throw new Error("Go program has already exited");
|
|
411
|
+
this._inst.exports.resume(), this.exited && this._resolveExitPromise();
|
|
412
|
+
}
|
|
413
|
+
_makeFuncWrapper(c) {
|
|
414
|
+
const i = this;
|
|
415
|
+
return function() {
|
|
416
|
+
const s = { id: c, this: this, args: arguments };
|
|
417
|
+
return i._pendingEvent = s, i._resume(), s.result;
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
})();
|
|
422
|
+
const b = new URL("main.wasm", import.meta.url).href;
|
|
423
|
+
(() => {
|
|
424
|
+
const n = globalThis, h = "process";
|
|
425
|
+
n[h] ? n[h].pid == null && (n[h].pid = 1) : n[h] = { pid: 1 };
|
|
426
|
+
})();
|
|
427
|
+
let _ = !1;
|
|
428
|
+
async function x() {
|
|
429
|
+
if (_) return;
|
|
430
|
+
const n = new globalThis.Go(), h = await WebAssembly.instantiateStreaming(
|
|
431
|
+
fetch(b),
|
|
432
|
+
n.importObject
|
|
433
|
+
);
|
|
434
|
+
n.run(h.instance), _ = !0;
|
|
435
|
+
}
|
|
436
|
+
function f() {
|
|
437
|
+
return globalThis.__tailscaleWeb;
|
|
438
|
+
}
|
|
439
|
+
function T(n) {
|
|
440
|
+
return {
|
|
441
|
+
status: n.status,
|
|
442
|
+
statusText: n.statusText,
|
|
443
|
+
ok: n.ok,
|
|
444
|
+
headers: n.headers,
|
|
445
|
+
text: async () => new TextDecoder().decode(n.body),
|
|
446
|
+
json: async () => JSON.parse(new TextDecoder().decode(n.body)),
|
|
447
|
+
arrayBuffer: async () => n.body.buffer,
|
|
448
|
+
bytes: async () => n.body
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const p = {
|
|
452
|
+
/**
|
|
453
|
+
* Initialize and connect the Tailscale node. Must be called before any
|
|
454
|
+
* other method. Resolves once the node is authenticated and ready.
|
|
455
|
+
*
|
|
456
|
+
* If the node has persisted state from a previous session it reconnects
|
|
457
|
+
* automatically. Otherwise the OAuth flow is triggered via onAuthRequired.
|
|
458
|
+
* Rejects if the auth URL does not arrive within 60 seconds, or if the
|
|
459
|
+
* user does not complete authentication within 5 minutes.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* await network.init({
|
|
463
|
+
* hostname: "my-app",
|
|
464
|
+
* onAuthRequired(url) {
|
|
465
|
+
* window.open(url, "_blank", "width=600,height=700")
|
|
466
|
+
* },
|
|
467
|
+
* onAuthComplete() {
|
|
468
|
+
* console.log("connected!")
|
|
469
|
+
* },
|
|
470
|
+
* })
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* // Custom storage backend (e.g. sessionStorage or any key/value store)
|
|
474
|
+
* await network.init({
|
|
475
|
+
* hostname: "my-app",
|
|
476
|
+
* storage: {
|
|
477
|
+
* get: key => sessionStorage.getItem(key),
|
|
478
|
+
* set: (key, val) => sessionStorage.setItem(key, val),
|
|
479
|
+
* },
|
|
480
|
+
* onAuthRequired(url) { console.log("Authenticate at:", url) },
|
|
481
|
+
* })
|
|
482
|
+
*/
|
|
483
|
+
async init(n = {}) {
|
|
484
|
+
return await x(), f().init(n);
|
|
485
|
+
},
|
|
486
|
+
/**
|
|
487
|
+
* Send an ICMP ping to addr and measure round-trip time.
|
|
488
|
+
* addr may be a hostname or Tailscale IP.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* const result = await network.ping("my-server")
|
|
492
|
+
* if (result.alive) {
|
|
493
|
+
* console.log(`rtt: ${result.rttMs.toFixed(3)} ms ip: ${result.nodeIP}`)
|
|
494
|
+
* } else {
|
|
495
|
+
* console.warn("unreachable:", result.err)
|
|
496
|
+
* }
|
|
497
|
+
*/
|
|
498
|
+
async ping(n) {
|
|
499
|
+
return f().ping(n);
|
|
500
|
+
},
|
|
501
|
+
/**
|
|
502
|
+
* Open a raw TCP connection through the Tailscale network.
|
|
503
|
+
* Returns a Connection object for sending and receiving data.
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* const conn = await network.dialTCP("my-server:8080")
|
|
507
|
+
*
|
|
508
|
+
* conn.onData(data => {
|
|
509
|
+
* console.log(new TextDecoder().decode(data))
|
|
510
|
+
* })
|
|
511
|
+
*
|
|
512
|
+
* conn.write("hello\n")
|
|
513
|
+
* conn.close()
|
|
514
|
+
*/
|
|
515
|
+
async dialTCP(n) {
|
|
516
|
+
const h = await f().dialTCP(n);
|
|
517
|
+
return {
|
|
518
|
+
onData(g) {
|
|
519
|
+
h.onData(g);
|
|
520
|
+
},
|
|
521
|
+
write(g) {
|
|
522
|
+
h.write(
|
|
523
|
+
typeof g == "string" ? new TextEncoder().encode(g) : g
|
|
524
|
+
);
|
|
525
|
+
},
|
|
526
|
+
close() {
|
|
527
|
+
h.close();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
/**
|
|
532
|
+
* Make an HTTP request through the Tailscale network. Supports method,
|
|
533
|
+
* headers, and body. Does not yet support AbortSignal, streaming bodies
|
|
534
|
+
* or responses, or other advanced Fetch API options.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* const resp = await network.fetch("https://internal-service/api", {
|
|
538
|
+
* method: "POST",
|
|
539
|
+
* headers: { "Content-Type": "application/json" },
|
|
540
|
+
* body: JSON.stringify({ key: "value" }),
|
|
541
|
+
* })
|
|
542
|
+
* console.log(resp.status, resp.ok)
|
|
543
|
+
* const data = await resp.json()
|
|
544
|
+
*/
|
|
545
|
+
async fetch(n, h = {}) {
|
|
546
|
+
return T(await f().fetch(n, h));
|
|
547
|
+
},
|
|
548
|
+
/**
|
|
549
|
+
* Return the current preferences (acceptRoutes, exitNodeId).
|
|
550
|
+
* Synchronous — no await needed. Must be called after init() resolves.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* const { acceptRoutes, exitNodeId } = network.getPrefs()
|
|
554
|
+
* console.log("exit node:", exitNodeId || "(none)")
|
|
555
|
+
*/
|
|
556
|
+
getPrefs() {
|
|
557
|
+
return f().getPrefs();
|
|
558
|
+
},
|
|
559
|
+
/**
|
|
560
|
+
* Enable or disable acceptance of subnet routes advertised by peers.
|
|
561
|
+
* Equivalent to `tailscale set --accept-routes`.
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* await network.setAcceptRoutes(true)
|
|
565
|
+
*/
|
|
566
|
+
async setAcceptRoutes(n) {
|
|
567
|
+
return f().setAcceptRoutes(n);
|
|
568
|
+
},
|
|
569
|
+
/**
|
|
570
|
+
* Return all peers that advertise exit-node capability.
|
|
571
|
+
* Synchronous — no await needed. Returns an empty array if called before init() resolves.
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* const nodes = network.listExitNodes()
|
|
575
|
+
* for (const n of nodes) {
|
|
576
|
+
* console.log(n.hostName, n.tailscaleIP, n.online ? "online" : "offline")
|
|
577
|
+
* }
|
|
578
|
+
*/
|
|
579
|
+
listExitNodes() {
|
|
580
|
+
return Array.from(f().listExitNodes());
|
|
581
|
+
},
|
|
582
|
+
/**
|
|
583
|
+
* Activate an exit node by its stable node ID.
|
|
584
|
+
* Pass an empty string (or omit) to clear the exit node.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* // Activate the first available online exit node
|
|
588
|
+
* const node = network.listExitNodes().find(n => n.online)
|
|
589
|
+
* if (node) await network.setExitNode(node.id)
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* // Clear the active exit node
|
|
593
|
+
* await network.setExitNode()
|
|
594
|
+
*/
|
|
595
|
+
async setExitNode(n = "") {
|
|
596
|
+
return f().setExitNode(n);
|
|
597
|
+
},
|
|
598
|
+
/**
|
|
599
|
+
* Return the full routing table (self + all peers).
|
|
600
|
+
* Synchronous — no await needed. Returns an empty array if called before init() resolves.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* const routes = network.getRoutes()
|
|
604
|
+
* for (const r of routes) {
|
|
605
|
+
* console.log(r.prefix, "via", r.via, r.isExitRoute ? "(exit)" : "")
|
|
606
|
+
* }
|
|
607
|
+
*/
|
|
608
|
+
getRoutes() {
|
|
609
|
+
return Array.from(f().getRoutes());
|
|
610
|
+
},
|
|
611
|
+
/**
|
|
612
|
+
* Return the current Tailscale-managed DNS configuration.
|
|
613
|
+
* Synchronous — no await needed. Returns an empty DNSInfo object if called before init() resolves.
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* const dns = network.getDNS()
|
|
617
|
+
* console.log("resolvers:", dns.resolvers)
|
|
618
|
+
* console.log("MagicDNS:", dns.magicDNS)
|
|
619
|
+
* for (const [suffix, resolvers] of Object.entries(dns.routes)) {
|
|
620
|
+
* console.log(`split-DNS: ${suffix} → ${resolvers.join(", ")}`)
|
|
621
|
+
* }
|
|
622
|
+
*/
|
|
623
|
+
getDNS() {
|
|
624
|
+
return f().getDNS();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
export {
|
|
628
|
+
p as network
|
|
629
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(w,_){typeof exports=="object"&&typeof module<"u"?_(exports):typeof define=="function"&&define.amd?define(["exports"],_):(w=typeof globalThis<"u"?globalThis:w||self,_(w["tailscale-web"]={}))})(this,(function(w){"use strict";(()=>{const n=()=>{const c=new Error("not implemented");return c.code="ENOSYS",c};if(!globalThis.fs){let c="";globalThis.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1,O_DIRECTORY:-1},writeSync(i,s){c+=g.decode(s);const r=c.lastIndexOf(`
|
|
2
|
+
`);return r!=-1&&(console.log(c.substring(0,r)),c=c.substring(r+1)),s.length},write(i,s,r,a,y,h){if(r!==0||a!==s.length||y!==null){h(n());return}const b=this.writeSync(i,s);h(null,b)},chmod(i,s,r){r(n())},chown(i,s,r,a){a(n())},close(i,s){s(n())},fchmod(i,s,r){r(n())},fchown(i,s,r,a){a(n())},fstat(i,s){s(n())},fsync(i,s){s(null)},ftruncate(i,s,r){r(n())},lchown(i,s,r,a){a(n())},link(i,s,r){r(n())},lstat(i,s){s(n())},mkdir(i,s,r){r(n())},open(i,s,r,a){a(n())},read(i,s,r,a,y,h){h(n())},readdir(i,s){s(n())},readlink(i,s){s(n())},rename(i,s,r){r(n())},rmdir(i,s){s(n())},stat(i,s){s(n())},symlink(i,s,r){r(n())},truncate(i,s,r){r(n())},unlink(i,s){s(n())},utimes(i,s,r,a){a(n())}}}if(globalThis.process||(globalThis.process={getuid(){return-1},getgid(){return-1},geteuid(){return-1},getegid(){return-1},getgroups(){throw n()},pid:-1,ppid:-1,umask(){throw n()},cwd(){throw n()},chdir(){throw n()}}),globalThis.path||(globalThis.path={resolve(...c){return c.join("/")}}),!globalThis.crypto)throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");if(!globalThis.performance)throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");if(!globalThis.TextEncoder)throw new Error("globalThis.TextEncoder is not available, polyfill required");if(!globalThis.TextDecoder)throw new Error("globalThis.TextDecoder is not available, polyfill required");const u=new TextEncoder("utf-8"),g=new TextDecoder("utf-8");globalThis.Go=class{constructor(){this.argv=["js"],this.env={},this.exit=t=>{t!==0&&console.warn("exit code:",t)},this._exitPromise=new Promise(t=>{this._resolveExitPromise=t}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const c=(t,e)=>{this.mem.setUint32(t+0,e,!0),this.mem.setUint32(t+4,Math.floor(e/4294967296),!0)},i=t=>{const e=this.mem.getUint32(t+0,!0),o=this.mem.getInt32(t+4,!0);return e+o*4294967296},s=t=>{const e=this.mem.getFloat64(t,!0);if(e===0)return;if(!isNaN(e))return e;const o=this.mem.getUint32(t,!0);return this._values[o]},r=(t,e)=>{if(typeof e=="number"&&e!==0){if(isNaN(e)){this.mem.setUint32(t+4,2146959360,!0),this.mem.setUint32(t,0,!0);return}this.mem.setFloat64(t,e,!0);return}if(e===void 0){this.mem.setFloat64(t,0,!0);return}let l=this._ids.get(e);l===void 0&&(l=this._idPool.pop(),l===void 0&&(l=this._values.length),this._values[l]=e,this._goRefCounts[l]=0,this._ids.set(e,l)),this._goRefCounts[l]++;let m=0;switch(typeof e){case"object":e!==null&&(m=1);break;case"string":m=2;break;case"symbol":m=3;break;case"function":m=4;break}this.mem.setUint32(t+4,2146959360|m,!0),this.mem.setUint32(t,l,!0)},a=t=>{const e=i(t+0),o=i(t+8);return new Uint8Array(this._inst.exports.mem.buffer,e,o)},y=t=>{const e=i(t+0),o=i(t+8),l=new Array(o);for(let m=0;m<o;m++)l[m]=s(e+m*8);return l},h=t=>{const e=i(t+0),o=i(t+8);return g.decode(new DataView(this._inst.exports.mem.buffer,e,o))},b=(t,e)=>(this._inst.exports.testExport0(),this._inst.exports.testExport(t,e)),f=Date.now()-performance.now();this.importObject={_gotest:{add:(t,e)=>t+e,callExport:b},gojs:{"runtime.wasmExit":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(e)},"runtime.wasmWrite":t=>{t>>>=0;const e=i(t+8),o=i(t+16),l=this.mem.getInt32(t+24,!0);fs.writeSync(e,new Uint8Array(this._inst.exports.mem.buffer,o,l))},"runtime.resetMemoryDataView":t=>{this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":t=>{t>>>=0,c(t+8,(f+performance.now())*1e6)},"runtime.walltime":t=>{t>>>=0;const e=new Date().getTime();c(t+8,e/1e3),this.mem.setInt32(t+16,e%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{t>>>=0;const e=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(e,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(e);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},i(t+8))),this.mem.setInt32(t+16,e,!0)},"runtime.clearTimeoutEvent":t=>{t>>>=0;const e=this.mem.getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(e)),this._scheduledTimeouts.delete(e)},"runtime.getRandomData":t=>{t>>>=0,crypto.getRandomValues(a(t+8))},"syscall/js.finalizeRef":t=>{t>>>=0;const e=this.mem.getUint32(t+8,!0);if(this._goRefCounts[e]--,this._goRefCounts[e]===0){const o=this._values[e];this._values[e]=null,this._ids.delete(o),this._idPool.push(e)}},"syscall/js.stringVal":t=>{t>>>=0,r(t+24,h(t+8))},"syscall/js.valueGet":t=>{t>>>=0;const e=Reflect.get(s(t+8),h(t+16));t=this._inst.exports.getsp()>>>0,r(t+32,e)},"syscall/js.valueSet":t=>{t>>>=0,Reflect.set(s(t+8),h(t+16),s(t+32))},"syscall/js.valueDelete":t=>{t>>>=0,Reflect.deleteProperty(s(t+8),h(t+16))},"syscall/js.valueIndex":t=>{t>>>=0,r(t+24,Reflect.get(s(t+8),i(t+16)))},"syscall/js.valueSetIndex":t=>{t>>>=0,Reflect.set(s(t+8),i(t+16),s(t+24))},"syscall/js.valueCall":t=>{t>>>=0;try{const e=s(t+8),o=Reflect.get(e,h(t+16)),l=y(t+32),m=Reflect.apply(o,e,l);t=this._inst.exports.getsp()>>>0,r(t+56,m),this.mem.setUint8(t+64,1)}catch(e){t=this._inst.exports.getsp()>>>0,r(t+56,e),this.mem.setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{t>>>=0;try{const e=s(t+8),o=y(t+16),l=Reflect.apply(e,void 0,o);t=this._inst.exports.getsp()>>>0,r(t+40,l),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,r(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueNew":t=>{t>>>=0;try{const e=s(t+8),o=y(t+16),l=Reflect.construct(e,o);t=this._inst.exports.getsp()>>>0,r(t+40,l),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,r(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueLength":t=>{t>>>=0,c(t+16,parseInt(s(t+8).length))},"syscall/js.valuePrepareString":t=>{t>>>=0;const e=u.encode(String(s(t+8)));r(t+16,e),c(t+24,e.length)},"syscall/js.valueLoadString":t=>{t>>>=0;const e=s(t+8);a(t+16).set(e)},"syscall/js.valueInstanceOf":t=>{t>>>=0,this.mem.setUint8(t+24,s(t+8)instanceof s(t+16)?1:0)},"syscall/js.copyBytesToGo":t=>{t>>>=0;const e=a(t+8),o=s(t+32);if(!(o instanceof Uint8Array||o instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const l=o.subarray(0,e.length);e.set(l),c(t+40,l.length),this.mem.setUint8(t+48,1)},"syscall/js.copyBytesToJS":t=>{t>>>=0;const e=s(t+8),o=a(t+16);if(!(e instanceof Uint8Array||e instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}const l=o.subarray(0,e.length);e.set(l),c(t+40,l.length),this.mem.setUint8(t+48,1)},debug:t=>{console.log(t)}}}}async run(c){if(!(c instanceof WebAssembly.Instance))throw new Error("Go.run: WebAssembly.Instance expected");this._inst=c,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,globalThis,this],this._goRefCounts=new Array(this._values.length).fill(1/0),this._ids=new Map([[0,1],[null,2],[!0,3],[!1,4],[globalThis,5],[this,6]]),this._idPool=[],this.exited=!1;let i=4096;const s=f=>{const t=i,e=u.encode(f+"\0");return new Uint8Array(this.mem.buffer,i,e.length).set(e),i+=e.length,i%8!==0&&(i+=8-i%8),t},r=this.argv.length,a=[];this.argv.forEach(f=>{a.push(s(f))}),a.push(0),Object.keys(this.env).sort().forEach(f=>{a.push(s(`${f}=${this.env[f]}`))}),a.push(0);const h=i;if(a.forEach(f=>{this.mem.setUint32(i,f,!0),this.mem.setUint32(i+4,0,!0),i+=8}),i>=12288)throw new Error("total length of command line and environment variables exceeds limit");this._inst.exports.run(r,h),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(c){const i=this;return function(){const s={id:c,this:this,args:arguments};return i._pendingEvent=s,i._resume(),s.result}}}})();const _=typeof document>"u"&&typeof location>"u"?require("url").pathToFileURL(__dirname+"/main.wasm").href:new URL("main.wasm",typeof document>"u"?location.href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"&&document.currentScript.src||document.baseURI).href;(()=>{const n=globalThis,u="process";n[u]?n[u].pid==null&&(n[u].pid=1):n[u]={pid:1}})();let x=!1;async function p(){if(x)return;const n=new globalThis.Go,u=await WebAssembly.instantiateStreaming(fetch(_),n.importObject);n.run(u.instance),x=!0}function d(){return globalThis.__tailscaleWeb}function T(n){return{status:n.status,statusText:n.statusText,ok:n.ok,headers:n.headers,text:async()=>new TextDecoder().decode(n.body),json:async()=>JSON.parse(new TextDecoder().decode(n.body)),arrayBuffer:async()=>n.body.buffer,bytes:async()=>n.body}}const v={async init(n={}){return await p(),d().init(n)},async ping(n){return d().ping(n)},async dialTCP(n){const u=await d().dialTCP(n);return{onData(g){u.onData(g)},write(g){u.write(typeof g=="string"?new TextEncoder().encode(g):g)},close(){u.close()}}},async fetch(n,u={}){return T(await d().fetch(n,u))},getPrefs(){return d().getPrefs()},async setAcceptRoutes(n){return d().setAcceptRoutes(n)},listExitNodes(){return Array.from(d().listExitNodes())},async setExitNode(n=""){return d().setExitNode(n)},getRoutes(){return Array.from(d().getRoutes())},getDNS(){return d().getDNS()}};w.network=v,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tailscale-web",
|
|
3
|
+
"version": "0.1.10",
|
|
4
|
+
"description": "Run a Tailscale node in JS environments via WebAssembly",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/tailscale-web.umd.js",
|
|
7
|
+
"module": "./dist/tailscale-web.es.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/tailscale-web.es.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/tailscale-web.umd.js"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build:wasm": "GOOS=js GOARCH=wasm go build -ldflags='-s -w' -o src/wasm/main.wasm",
|
|
26
|
+
"build": "npm run build:wasm && vite build",
|
|
27
|
+
"docs": "typedoc"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/adrianosela/tailscale-web.git"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"tailscale",
|
|
35
|
+
"vpn",
|
|
36
|
+
"wasm",
|
|
37
|
+
"webassembly",
|
|
38
|
+
"networking"
|
|
39
|
+
],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/adrianosela/tailscale-web/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/adrianosela/tailscale-web#readme",
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typedoc": "^0.28.17",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vite": "^7.3.1",
|
|
50
|
+
"vite-plugin-dts": "^4.5.4"
|
|
51
|
+
}
|
|
52
|
+
}
|