@ts-edge/wasm 1.100.0-1
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/node.wasm +0 -0
- package/build/wasm_exec.js +575 -0
- package/dist/abi.d.ts +61 -0
- package/dist/abi.js +4 -0
- package/dist/abi.js.map +1 -0
- package/dist/emitter.d.ts +12 -0
- package/dist/emitter.js +30 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/dist/wait.d.ts +5 -0
- package/dist/wait.js +13 -0
- package/dist/wait.js.map +1 -0
- package/package.json +57 -0
package/build/node.wasm
ADDED
|
Binary file
|
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// Copyright 2018 The Go Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style
|
|
3
|
+
// license that can be found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
(() => {
|
|
8
|
+
const enosys = () => {
|
|
9
|
+
const err = new Error("not implemented");
|
|
10
|
+
err.code = "ENOSYS";
|
|
11
|
+
return err;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (!globalThis.fs) {
|
|
15
|
+
let outputBuf = "";
|
|
16
|
+
globalThis.fs = {
|
|
17
|
+
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
|
|
18
|
+
writeSync(fd, buf) {
|
|
19
|
+
outputBuf += decoder.decode(buf);
|
|
20
|
+
const nl = outputBuf.lastIndexOf("\n");
|
|
21
|
+
if (nl != -1) {
|
|
22
|
+
console.log(outputBuf.substring(0, nl));
|
|
23
|
+
outputBuf = outputBuf.substring(nl + 1);
|
|
24
|
+
}
|
|
25
|
+
return buf.length;
|
|
26
|
+
},
|
|
27
|
+
write(fd, buf, offset, length, position, callback) {
|
|
28
|
+
if (offset !== 0 || length !== buf.length || position !== null) {
|
|
29
|
+
callback(enosys());
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const n = this.writeSync(fd, buf);
|
|
33
|
+
callback(null, n);
|
|
34
|
+
},
|
|
35
|
+
chmod(path, mode, callback) { callback(enosys()); },
|
|
36
|
+
chown(path, uid, gid, callback) { callback(enosys()); },
|
|
37
|
+
close(fd, callback) { callback(enosys()); },
|
|
38
|
+
fchmod(fd, mode, callback) { callback(enosys()); },
|
|
39
|
+
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
|
40
|
+
fstat(fd, callback) { callback(enosys()); },
|
|
41
|
+
fsync(fd, callback) { callback(null); },
|
|
42
|
+
ftruncate(fd, length, callback) { callback(enosys()); },
|
|
43
|
+
lchown(path, uid, gid, callback) { callback(enosys()); },
|
|
44
|
+
link(path, link, callback) { callback(enosys()); },
|
|
45
|
+
lstat(path, callback) { callback(enosys()); },
|
|
46
|
+
mkdir(path, perm, callback) { callback(enosys()); },
|
|
47
|
+
open(path, flags, mode, callback) { callback(enosys()); },
|
|
48
|
+
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
|
49
|
+
readdir(path, callback) { callback(enosys()); },
|
|
50
|
+
readlink(path, callback) { callback(enosys()); },
|
|
51
|
+
rename(from, to, callback) { callback(enosys()); },
|
|
52
|
+
rmdir(path, callback) { callback(enosys()); },
|
|
53
|
+
stat(path, callback) { callback(enosys()); },
|
|
54
|
+
symlink(path, link, callback) { callback(enosys()); },
|
|
55
|
+
truncate(path, length, callback) { callback(enosys()); },
|
|
56
|
+
unlink(path, callback) { callback(enosys()); },
|
|
57
|
+
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!globalThis.process) {
|
|
62
|
+
globalThis.process = {
|
|
63
|
+
getuid() { return -1; },
|
|
64
|
+
getgid() { return -1; },
|
|
65
|
+
geteuid() { return -1; },
|
|
66
|
+
getegid() { return -1; },
|
|
67
|
+
getgroups() { throw enosys(); },
|
|
68
|
+
pid: -1,
|
|
69
|
+
ppid: -1,
|
|
70
|
+
umask() { throw enosys(); },
|
|
71
|
+
cwd() { throw enosys(); },
|
|
72
|
+
chdir() { throw enosys(); },
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!globalThis.path) {
|
|
77
|
+
globalThis.path = {
|
|
78
|
+
resolve(...pathSegments) {
|
|
79
|
+
return pathSegments.join("/");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!globalThis.crypto) {
|
|
85
|
+
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!globalThis.performance) {
|
|
89
|
+
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!globalThis.TextEncoder) {
|
|
93
|
+
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!globalThis.TextDecoder) {
|
|
97
|
+
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const encoder = new TextEncoder("utf-8");
|
|
101
|
+
const decoder = new TextDecoder("utf-8");
|
|
102
|
+
|
|
103
|
+
globalThis.Go = class {
|
|
104
|
+
constructor() {
|
|
105
|
+
this.argv = ["js"];
|
|
106
|
+
this.env = {};
|
|
107
|
+
this.exit = (code) => {
|
|
108
|
+
if (code !== 0) {
|
|
109
|
+
console.warn("exit code:", code);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
this._exitPromise = new Promise((resolve) => {
|
|
113
|
+
this._resolveExitPromise = resolve;
|
|
114
|
+
});
|
|
115
|
+
this._pendingEvent = null;
|
|
116
|
+
this._scheduledTimeouts = new Map();
|
|
117
|
+
this._nextCallbackTimeoutID = 1;
|
|
118
|
+
|
|
119
|
+
const setInt64 = (addr, v) => {
|
|
120
|
+
this.mem.setUint32(addr + 0, v, true);
|
|
121
|
+
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const setInt32 = (addr, v) => {
|
|
125
|
+
this.mem.setUint32(addr + 0, v, true);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getInt64 = (addr) => {
|
|
129
|
+
const low = this.mem.getUint32(addr + 0, true);
|
|
130
|
+
const high = this.mem.getInt32(addr + 4, true);
|
|
131
|
+
return low + high * 4294967296;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const loadValue = (addr) => {
|
|
135
|
+
const f = this.mem.getFloat64(addr, true);
|
|
136
|
+
if (f === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (!isNaN(f)) {
|
|
140
|
+
return f;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const id = this.mem.getUint32(addr, true);
|
|
144
|
+
return this._values[id];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const storeValue = (addr, v) => {
|
|
148
|
+
const nanHead = 0x7FF80000;
|
|
149
|
+
|
|
150
|
+
if (typeof v === "number" && v !== 0) {
|
|
151
|
+
if (isNaN(v)) {
|
|
152
|
+
this.mem.setUint32(addr + 4, nanHead, true);
|
|
153
|
+
this.mem.setUint32(addr, 0, true);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.mem.setFloat64(addr, v, true);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (v === undefined) {
|
|
161
|
+
this.mem.setFloat64(addr, 0, true);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let id = this._ids.get(v);
|
|
166
|
+
if (id === undefined) {
|
|
167
|
+
id = this._idPool.pop();
|
|
168
|
+
if (id === undefined) {
|
|
169
|
+
id = this._values.length;
|
|
170
|
+
}
|
|
171
|
+
this._values[id] = v;
|
|
172
|
+
this._goRefCounts[id] = 0;
|
|
173
|
+
this._ids.set(v, id);
|
|
174
|
+
}
|
|
175
|
+
this._goRefCounts[id]++;
|
|
176
|
+
let typeFlag = 0;
|
|
177
|
+
switch (typeof v) {
|
|
178
|
+
case "object":
|
|
179
|
+
if (v !== null) {
|
|
180
|
+
typeFlag = 1;
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case "string":
|
|
184
|
+
typeFlag = 2;
|
|
185
|
+
break;
|
|
186
|
+
case "symbol":
|
|
187
|
+
typeFlag = 3;
|
|
188
|
+
break;
|
|
189
|
+
case "function":
|
|
190
|
+
typeFlag = 4;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
|
194
|
+
this.mem.setUint32(addr, id, true);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const loadSlice = (addr) => {
|
|
198
|
+
const array = getInt64(addr + 0);
|
|
199
|
+
const len = getInt64(addr + 8);
|
|
200
|
+
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const loadSliceOfValues = (addr) => {
|
|
204
|
+
const array = getInt64(addr + 0);
|
|
205
|
+
const len = getInt64(addr + 8);
|
|
206
|
+
const a = new Array(len);
|
|
207
|
+
for (let i = 0; i < len; i++) {
|
|
208
|
+
a[i] = loadValue(array + i * 8);
|
|
209
|
+
}
|
|
210
|
+
return a;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const loadString = (addr) => {
|
|
214
|
+
const saddr = getInt64(addr + 0);
|
|
215
|
+
const len = getInt64(addr + 8);
|
|
216
|
+
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const testCallExport = (a, b) => {
|
|
220
|
+
this._inst.exports.testExport0();
|
|
221
|
+
return this._inst.exports.testExport(a, b);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const timeOrigin = Date.now() - performance.now();
|
|
225
|
+
this.importObject = {
|
|
226
|
+
_gotest: {
|
|
227
|
+
add: (a, b) => a + b,
|
|
228
|
+
callExport: testCallExport,
|
|
229
|
+
},
|
|
230
|
+
gojs: {
|
|
231
|
+
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
|
232
|
+
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
|
233
|
+
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
|
234
|
+
// This changes the SP, thus we have to update the SP used by the imported function.
|
|
235
|
+
|
|
236
|
+
// func wasmExit(code int32)
|
|
237
|
+
"runtime.wasmExit": (sp) => {
|
|
238
|
+
sp >>>= 0;
|
|
239
|
+
const code = this.mem.getInt32(sp + 8, true);
|
|
240
|
+
this.exited = true;
|
|
241
|
+
delete this._inst;
|
|
242
|
+
delete this._values;
|
|
243
|
+
delete this._goRefCounts;
|
|
244
|
+
delete this._ids;
|
|
245
|
+
delete this._idPool;
|
|
246
|
+
this.exit(code);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
|
250
|
+
"runtime.wasmWrite": (sp) => {
|
|
251
|
+
sp >>>= 0;
|
|
252
|
+
const fd = getInt64(sp + 8);
|
|
253
|
+
const p = getInt64(sp + 16);
|
|
254
|
+
const n = this.mem.getInt32(sp + 24, true);
|
|
255
|
+
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// func resetMemoryDataView()
|
|
259
|
+
"runtime.resetMemoryDataView": (sp) => {
|
|
260
|
+
sp >>>= 0;
|
|
261
|
+
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// func nanotime1() int64
|
|
265
|
+
"runtime.nanotime1": (sp) => {
|
|
266
|
+
sp >>>= 0;
|
|
267
|
+
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// func walltime() (sec int64, nsec int32)
|
|
271
|
+
"runtime.walltime": (sp) => {
|
|
272
|
+
sp >>>= 0;
|
|
273
|
+
const msec = (new Date).getTime();
|
|
274
|
+
setInt64(sp + 8, msec / 1000);
|
|
275
|
+
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// func scheduleTimeoutEvent(delay int64) int32
|
|
279
|
+
"runtime.scheduleTimeoutEvent": (sp) => {
|
|
280
|
+
sp >>>= 0;
|
|
281
|
+
const id = this._nextCallbackTimeoutID;
|
|
282
|
+
this._nextCallbackTimeoutID++;
|
|
283
|
+
this._scheduledTimeouts.set(id, setTimeout(
|
|
284
|
+
() => {
|
|
285
|
+
this._resume();
|
|
286
|
+
while (this._scheduledTimeouts.has(id)) {
|
|
287
|
+
// for some reason Go failed to register the timeout event, log and try again
|
|
288
|
+
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
|
289
|
+
console.warn("scheduleTimeoutEvent: missed timeout event");
|
|
290
|
+
this._resume();
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
getInt64(sp + 8),
|
|
294
|
+
));
|
|
295
|
+
this.mem.setInt32(sp + 16, id, true);
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// func clearTimeoutEvent(id int32)
|
|
299
|
+
"runtime.clearTimeoutEvent": (sp) => {
|
|
300
|
+
sp >>>= 0;
|
|
301
|
+
const id = this.mem.getInt32(sp + 8, true);
|
|
302
|
+
clearTimeout(this._scheduledTimeouts.get(id));
|
|
303
|
+
this._scheduledTimeouts.delete(id);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// func getRandomData(r []byte)
|
|
307
|
+
"runtime.getRandomData": (sp) => {
|
|
308
|
+
sp >>>= 0;
|
|
309
|
+
crypto.getRandomValues(loadSlice(sp + 8));
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
// func finalizeRef(v ref)
|
|
313
|
+
"syscall/js.finalizeRef": (sp) => {
|
|
314
|
+
sp >>>= 0;
|
|
315
|
+
const id = this.mem.getUint32(sp + 8, true);
|
|
316
|
+
this._goRefCounts[id]--;
|
|
317
|
+
if (this._goRefCounts[id] === 0) {
|
|
318
|
+
const v = this._values[id];
|
|
319
|
+
this._values[id] = null;
|
|
320
|
+
this._ids.delete(v);
|
|
321
|
+
this._idPool.push(id);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// func stringVal(value string) ref
|
|
326
|
+
"syscall/js.stringVal": (sp) => {
|
|
327
|
+
sp >>>= 0;
|
|
328
|
+
storeValue(sp + 24, loadString(sp + 8));
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
// func valueGet(v ref, p string) ref
|
|
332
|
+
"syscall/js.valueGet": (sp) => {
|
|
333
|
+
sp >>>= 0;
|
|
334
|
+
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
|
335
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
336
|
+
storeValue(sp + 32, result);
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// func valueSet(v ref, p string, x ref)
|
|
340
|
+
"syscall/js.valueSet": (sp) => {
|
|
341
|
+
sp >>>= 0;
|
|
342
|
+
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
// func valueDelete(v ref, p string)
|
|
346
|
+
"syscall/js.valueDelete": (sp) => {
|
|
347
|
+
sp >>>= 0;
|
|
348
|
+
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// func valueIndex(v ref, i int) ref
|
|
352
|
+
"syscall/js.valueIndex": (sp) => {
|
|
353
|
+
sp >>>= 0;
|
|
354
|
+
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
// valueSetIndex(v ref, i int, x ref)
|
|
358
|
+
"syscall/js.valueSetIndex": (sp) => {
|
|
359
|
+
sp >>>= 0;
|
|
360
|
+
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
|
364
|
+
"syscall/js.valueCall": (sp) => {
|
|
365
|
+
sp >>>= 0;
|
|
366
|
+
try {
|
|
367
|
+
const v = loadValue(sp + 8);
|
|
368
|
+
const m = Reflect.get(v, loadString(sp + 16));
|
|
369
|
+
const args = loadSliceOfValues(sp + 32);
|
|
370
|
+
const result = Reflect.apply(m, v, args);
|
|
371
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
372
|
+
storeValue(sp + 56, result);
|
|
373
|
+
this.mem.setUint8(sp + 64, 1);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
376
|
+
storeValue(sp + 56, err);
|
|
377
|
+
this.mem.setUint8(sp + 64, 0);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
// func valueInvoke(v ref, args []ref) (ref, bool)
|
|
382
|
+
"syscall/js.valueInvoke": (sp) => {
|
|
383
|
+
sp >>>= 0;
|
|
384
|
+
try {
|
|
385
|
+
const v = loadValue(sp + 8);
|
|
386
|
+
const args = loadSliceOfValues(sp + 16);
|
|
387
|
+
const result = Reflect.apply(v, undefined, args);
|
|
388
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
389
|
+
storeValue(sp + 40, result);
|
|
390
|
+
this.mem.setUint8(sp + 48, 1);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
393
|
+
storeValue(sp + 40, err);
|
|
394
|
+
this.mem.setUint8(sp + 48, 0);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
// func valueNew(v ref, args []ref) (ref, bool)
|
|
399
|
+
"syscall/js.valueNew": (sp) => {
|
|
400
|
+
sp >>>= 0;
|
|
401
|
+
try {
|
|
402
|
+
const v = loadValue(sp + 8);
|
|
403
|
+
const args = loadSliceOfValues(sp + 16);
|
|
404
|
+
const result = Reflect.construct(v, args);
|
|
405
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
406
|
+
storeValue(sp + 40, result);
|
|
407
|
+
this.mem.setUint8(sp + 48, 1);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
410
|
+
storeValue(sp + 40, err);
|
|
411
|
+
this.mem.setUint8(sp + 48, 0);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
// func valueLength(v ref) int
|
|
416
|
+
"syscall/js.valueLength": (sp) => {
|
|
417
|
+
sp >>>= 0;
|
|
418
|
+
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
// valuePrepareString(v ref) (ref, int)
|
|
422
|
+
"syscall/js.valuePrepareString": (sp) => {
|
|
423
|
+
sp >>>= 0;
|
|
424
|
+
const str = encoder.encode(String(loadValue(sp + 8)));
|
|
425
|
+
storeValue(sp + 16, str);
|
|
426
|
+
setInt64(sp + 24, str.length);
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// valueLoadString(v ref, b []byte)
|
|
430
|
+
"syscall/js.valueLoadString": (sp) => {
|
|
431
|
+
sp >>>= 0;
|
|
432
|
+
const str = loadValue(sp + 8);
|
|
433
|
+
loadSlice(sp + 16).set(str);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// func valueInstanceOf(v ref, t ref) bool
|
|
437
|
+
"syscall/js.valueInstanceOf": (sp) => {
|
|
438
|
+
sp >>>= 0;
|
|
439
|
+
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
|
443
|
+
"syscall/js.copyBytesToGo": (sp) => {
|
|
444
|
+
sp >>>= 0;
|
|
445
|
+
const dst = loadSlice(sp + 8);
|
|
446
|
+
const src = loadValue(sp + 32);
|
|
447
|
+
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
|
448
|
+
this.mem.setUint8(sp + 48, 0);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const toCopy = src.subarray(0, dst.length);
|
|
452
|
+
dst.set(toCopy);
|
|
453
|
+
setInt64(sp + 40, toCopy.length);
|
|
454
|
+
this.mem.setUint8(sp + 48, 1);
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
|
458
|
+
"syscall/js.copyBytesToJS": (sp) => {
|
|
459
|
+
sp >>>= 0;
|
|
460
|
+
const dst = loadValue(sp + 8);
|
|
461
|
+
const src = loadSlice(sp + 16);
|
|
462
|
+
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
|
463
|
+
this.mem.setUint8(sp + 48, 0);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const toCopy = src.subarray(0, dst.length);
|
|
467
|
+
dst.set(toCopy);
|
|
468
|
+
setInt64(sp + 40, toCopy.length);
|
|
469
|
+
this.mem.setUint8(sp + 48, 1);
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
"debug": (value) => {
|
|
473
|
+
console.log(value);
|
|
474
|
+
},
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async run(instance) {
|
|
480
|
+
if (!(instance instanceof WebAssembly.Instance)) {
|
|
481
|
+
throw new Error("Go.run: WebAssembly.Instance expected");
|
|
482
|
+
}
|
|
483
|
+
this._inst = instance;
|
|
484
|
+
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
485
|
+
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
|
486
|
+
NaN,
|
|
487
|
+
0,
|
|
488
|
+
null,
|
|
489
|
+
true,
|
|
490
|
+
false,
|
|
491
|
+
globalThis,
|
|
492
|
+
this,
|
|
493
|
+
];
|
|
494
|
+
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
|
495
|
+
this._ids = new Map([ // mapping from JS values to reference ids
|
|
496
|
+
[0, 1],
|
|
497
|
+
[null, 2],
|
|
498
|
+
[true, 3],
|
|
499
|
+
[false, 4],
|
|
500
|
+
[globalThis, 5],
|
|
501
|
+
[this, 6],
|
|
502
|
+
]);
|
|
503
|
+
this._idPool = []; // unused ids that have been garbage collected
|
|
504
|
+
this.exited = false; // whether the Go program has exited
|
|
505
|
+
|
|
506
|
+
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
|
507
|
+
let offset = 4096;
|
|
508
|
+
|
|
509
|
+
const strPtr = (str) => {
|
|
510
|
+
const ptr = offset;
|
|
511
|
+
const bytes = encoder.encode(str + "\0");
|
|
512
|
+
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
|
513
|
+
offset += bytes.length;
|
|
514
|
+
if (offset % 8 !== 0) {
|
|
515
|
+
offset += 8 - (offset % 8);
|
|
516
|
+
}
|
|
517
|
+
return ptr;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const argc = this.argv.length;
|
|
521
|
+
|
|
522
|
+
const argvPtrs = [];
|
|
523
|
+
this.argv.forEach((arg) => {
|
|
524
|
+
argvPtrs.push(strPtr(arg));
|
|
525
|
+
});
|
|
526
|
+
argvPtrs.push(0);
|
|
527
|
+
|
|
528
|
+
const keys = Object.keys(this.env).sort();
|
|
529
|
+
keys.forEach((key) => {
|
|
530
|
+
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
|
531
|
+
});
|
|
532
|
+
argvPtrs.push(0);
|
|
533
|
+
|
|
534
|
+
const argv = offset;
|
|
535
|
+
argvPtrs.forEach((ptr) => {
|
|
536
|
+
this.mem.setUint32(offset, ptr, true);
|
|
537
|
+
this.mem.setUint32(offset + 4, 0, true);
|
|
538
|
+
offset += 8;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
|
542
|
+
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
|
543
|
+
const wasmMinDataAddr = 4096 + 8192;
|
|
544
|
+
if (offset >= wasmMinDataAddr) {
|
|
545
|
+
throw new Error("total length of command line and environment variables exceeds limit");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
this._inst.exports.run(argc, argv);
|
|
549
|
+
if (this.exited) {
|
|
550
|
+
this._resolveExitPromise();
|
|
551
|
+
}
|
|
552
|
+
await this._exitPromise;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
_resume() {
|
|
556
|
+
if (this.exited) {
|
|
557
|
+
throw new Error("Go program has already exited");
|
|
558
|
+
}
|
|
559
|
+
this._inst.exports.resume();
|
|
560
|
+
if (this.exited) {
|
|
561
|
+
this._resolveExitPromise();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_makeFuncWrapper(id) {
|
|
566
|
+
const go = this;
|
|
567
|
+
return function () {
|
|
568
|
+
const event = { id: id, this: this, args: arguments };
|
|
569
|
+
go._pendingEvent = event;
|
|
570
|
+
go._resume();
|
|
571
|
+
return event.result;
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
})();
|
package/dist/abi.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A tailnet peer snapshot as marshalled by the Go side. */
|
|
2
|
+
export interface Peer {
|
|
3
|
+
name: string;
|
|
4
|
+
addrs: string[];
|
|
5
|
+
online: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** A running server handle returned by serve()/listen(). */
|
|
8
|
+
export interface Server {
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A streamed HTTP body crossing the ABI: read() resolves the next chunk and
|
|
13
|
+
* null at end-of-stream; cancel() abandons the stream early.
|
|
14
|
+
*/
|
|
15
|
+
export interface BodyReaderABI {
|
|
16
|
+
read(): Promise<Uint8Array | null | undefined>;
|
|
17
|
+
cancel(): void;
|
|
18
|
+
}
|
|
19
|
+
/** The response to NodeABI.fetch. Resolves once headers arrive; the body
|
|
20
|
+
* streams on demand. */
|
|
21
|
+
export interface RawResponse {
|
|
22
|
+
status: number;
|
|
23
|
+
statusText: string;
|
|
24
|
+
headers: [string, string][];
|
|
25
|
+
body: BodyReaderABI;
|
|
26
|
+
}
|
|
27
|
+
/** An inbound HTTP request delivered to a NodeABI.serve handler. */
|
|
28
|
+
export interface RawRequest {
|
|
29
|
+
method: string;
|
|
30
|
+
url: string;
|
|
31
|
+
host: string;
|
|
32
|
+
headers: [string, string][];
|
|
33
|
+
remoteAddr: string;
|
|
34
|
+
body: BodyReaderABI;
|
|
35
|
+
}
|
|
36
|
+
/** A serve handler's response: headers as pairs or a plain object, body
|
|
37
|
+
* buffered or as a pull reader (streamed to the peer with per-chunk flush). */
|
|
38
|
+
export interface RawServeResponse {
|
|
39
|
+
status?: number;
|
|
40
|
+
headers?: [string, string][] | Record<string, string>;
|
|
41
|
+
body?: Uint8Array | string | BodyReaderABI;
|
|
42
|
+
}
|
|
43
|
+
/** A raw TCP connection / UDP flow as exposed by dial()/listen(). */
|
|
44
|
+
export interface SocketABI {
|
|
45
|
+
remoteAddr: string;
|
|
46
|
+
write(data: Uint8Array): void;
|
|
47
|
+
close(): void;
|
|
48
|
+
attach(onData: (chunk: Uint8Array) => void, onClose: () => void, onError: (msg: string) => void): void;
|
|
49
|
+
}
|
|
50
|
+
/** The object returned by globalThis.__tsedgeNewNode(config). */
|
|
51
|
+
export interface NodeABI {
|
|
52
|
+
start(onEvent: (type: string, data: unknown) => void): void;
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
up(): Promise<void>;
|
|
55
|
+
down(): Promise<void>;
|
|
56
|
+
fetch(req: unknown): Promise<RawResponse>;
|
|
57
|
+
dial(proto: string, addr: string): Promise<SocketABI>;
|
|
58
|
+
serve(port: number, handler: (req: RawRequest) => Promise<RawServeResponse>): Promise<Server>;
|
|
59
|
+
listen(proto: string, port: number, onConnection: (sock: SocketABI) => void): Promise<Server>;
|
|
60
|
+
getPeers(): Peer[];
|
|
61
|
+
}
|
package/dist/abi.js
ADDED
package/dist/abi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi.js","sourceRoot":"","sources":["../src/abi.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,wCAAwC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal typed event emitter — just enough for TailscaleNode and
|
|
3
|
+
* TailnetSocket, with no Node.js EventEmitter dependency so the package stays
|
|
4
|
+
* runtime-portable (Workers, browsers, Node).
|
|
5
|
+
*/
|
|
6
|
+
export declare class TypedEmitter<Events extends Record<string, (...args: any[]) => void>> {
|
|
7
|
+
#private;
|
|
8
|
+
on<K extends keyof Events>(event: K, cb: Events[K]): void;
|
|
9
|
+
off<K extends keyof Events>(event: K, cb: Events[K]): void;
|
|
10
|
+
protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void;
|
|
11
|
+
protected listenerCount(event: keyof Events): number;
|
|
12
|
+
}
|
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Copyright (c) 2026 Sam Jean
|
|
2
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
/**
|
|
4
|
+
* A minimal typed event emitter — just enough for TailscaleNode and
|
|
5
|
+
* TailnetSocket, with no Node.js EventEmitter dependency so the package stays
|
|
6
|
+
* runtime-portable (Workers, browsers, Node).
|
|
7
|
+
*/
|
|
8
|
+
export class TypedEmitter {
|
|
9
|
+
#listeners = new Map();
|
|
10
|
+
on(event, cb) {
|
|
11
|
+
let set = this.#listeners.get(event);
|
|
12
|
+
if (!set)
|
|
13
|
+
this.#listeners.set(event, (set = new Set()));
|
|
14
|
+
set.add(cb);
|
|
15
|
+
}
|
|
16
|
+
off(event, cb) {
|
|
17
|
+
this.#listeners.get(event)?.delete(cb);
|
|
18
|
+
}
|
|
19
|
+
emit(event, ...args) {
|
|
20
|
+
const set = this.#listeners.get(event);
|
|
21
|
+
if (!set)
|
|
22
|
+
return;
|
|
23
|
+
for (const cb of set)
|
|
24
|
+
cb(...args);
|
|
25
|
+
}
|
|
26
|
+
listenerCount(event) {
|
|
27
|
+
return this.#listeners.get(event)?.size ?? 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,wCAAwC;AAExC;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACvB,UAAU,GAAG,IAAI,GAAG,EAA2C,CAAC;IAEhE,EAAE,CAAyB,KAAQ,EAAE,EAAa;QAChD,IAAI,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QACxD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACd,CAAC;IAED,GAAG,CAAyB,KAAQ,EAAE,EAAa;QACjD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAES,IAAI,CAAyB,KAAQ,EAAE,GAAG,IAA2B;QAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,KAAK,MAAM,EAAE,IAAI,GAAG;YAAG,EAAgC,CAAC,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;IAES,aAAa,CAAC,KAAmB;QACzC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { NodeABI, Peer, Server, SocketABI } from "./abi";
|
|
2
|
+
import { TypedEmitter } from "./emitter";
|
|
3
|
+
export type { BodyReaderABI, NodeABI, Peer, RawRequest, RawResponse, RawServeResponse, Server, SocketABI, } from "./abi";
|
|
4
|
+
export type NodeState = "NoState" | "NeedsLogin" | "NeedsMachineAuth" | "InUseOtherUser" | "Starting" | "Running" | "Stopped";
|
|
5
|
+
export interface NodeConfig {
|
|
6
|
+
/**
|
|
7
|
+
* The compiled node module. In Cloudflare Workers pass a WebAssembly.Module
|
|
8
|
+
* (e.g. `import wasm from "@ts-edge/wasm/node.wasm"`); workerd forbids
|
|
9
|
+
* compiling from bytes. In browsers a BufferSource also works.
|
|
10
|
+
*/
|
|
11
|
+
wasm: WebAssembly.Module | BufferSource;
|
|
12
|
+
/** An ephemeral, reusable Tailscale auth key. */
|
|
13
|
+
authKey: string;
|
|
14
|
+
hostname?: string;
|
|
15
|
+
controlURL?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface FetchInit {
|
|
18
|
+
method?: string;
|
|
19
|
+
headers?: Record<string, string> | Headers | [string, string][];
|
|
20
|
+
body?: Uint8Array | string | ReadableStream<Uint8Array>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A fetch-style handler for TailscaleNode.serve(): pass a Hono (or any
|
|
24
|
+
* `(Request) => Response` framework) app's `fetch` directly. The second
|
|
25
|
+
* argument carries tailnet connection info; frameworks that accept an "env"
|
|
26
|
+
* second parameter (Hono) surface it there.
|
|
27
|
+
*/
|
|
28
|
+
export type ServeHandler = (req: Request, info: {
|
|
29
|
+
remoteAddr: string;
|
|
30
|
+
}) => Response | Promise<Response>;
|
|
31
|
+
type NodeEvents = {
|
|
32
|
+
state: (state: NodeState) => void;
|
|
33
|
+
netmap: (addrs: readonly string[]) => void;
|
|
34
|
+
/** Fires whenever the peer set changes — including instant online/offline
|
|
35
|
+
* flips pushed by the control plane. */
|
|
36
|
+
peers: (peers: readonly Peer[]) => void;
|
|
37
|
+
error: (err: Error) => void;
|
|
38
|
+
};
|
|
39
|
+
/** Create (but do not yet start) an ephemeral Tailscale node. */
|
|
40
|
+
export declare function createNode(config: NodeConfig): Promise<TailscaleNode>;
|
|
41
|
+
export declare class TailscaleNode extends TypedEmitter<NodeEvents> {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(abi: NodeABI);
|
|
44
|
+
get state(): NodeState;
|
|
45
|
+
get addrs(): readonly string[];
|
|
46
|
+
get ipv4(): string | undefined;
|
|
47
|
+
/** A snapshot of the current tailnet peers. */
|
|
48
|
+
getPeers(): Peer[];
|
|
49
|
+
/** Begin ephemeral login. Resolves once login has been kicked off. */
|
|
50
|
+
start(): Promise<void>;
|
|
51
|
+
waitUntilRunning({ timeoutMs }?: {
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
stop(): Promise<void>;
|
|
55
|
+
/** Bring the node back online after down() — `tailscale up`. The node stays
|
|
56
|
+
* registered with control across down/up, so no fresh login is needed. */
|
|
57
|
+
up(): Promise<void>;
|
|
58
|
+
/** Take the node offline without logging out — `tailscale down`. State
|
|
59
|
+
* becomes Stopped; peers see the node go offline. */
|
|
60
|
+
down(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* HTTP request over the tailnet, returning a standard web Response. The
|
|
63
|
+
* response resolves as soon as headers arrive; its body streams on demand.
|
|
64
|
+
* A ReadableStream body is sent as a streaming (chunked) upload.
|
|
65
|
+
*/
|
|
66
|
+
fetch(url: string, init?: FetchInit): Promise<Response>;
|
|
67
|
+
/** Raw connection to a tailnet host:port. Defaults to TCP; pass
|
|
68
|
+
* `{ proto: "udp" }` for UDP. */
|
|
69
|
+
dial(addr: string, opts?: {
|
|
70
|
+
proto?: "tcp" | "udp";
|
|
71
|
+
}): Promise<TailnetSocket>;
|
|
72
|
+
/**
|
|
73
|
+
* Serve a fetch-style handler (`(Request) => Response`) on a tailnet TCP
|
|
74
|
+
* port — pass a Hono app's `fetch` directly:
|
|
75
|
+
*
|
|
76
|
+
* await tailNode.serve({ port: 80 }, app.fetch);
|
|
77
|
+
*
|
|
78
|
+
* Request and response bodies stream; every response chunk is flushed to
|
|
79
|
+
* the peer as it is written, so SSE and long-lived responses work.
|
|
80
|
+
*/
|
|
81
|
+
serve(opts: {
|
|
82
|
+
port: number;
|
|
83
|
+
}, handler: ServeHandler): Promise<Server>;
|
|
84
|
+
/** Accept raw connections on a tailnet port. Defaults to TCP; pass
|
|
85
|
+
* `{ proto: "udp" }` for UDP. */
|
|
86
|
+
listen(opts: {
|
|
87
|
+
port: number;
|
|
88
|
+
proto?: "tcp" | "udp";
|
|
89
|
+
}, onConnection: (socket: TailnetSocket) => void): Promise<Server>;
|
|
90
|
+
}
|
|
91
|
+
type SocketEvents = {
|
|
92
|
+
data: (chunk: Uint8Array) => void;
|
|
93
|
+
close: () => void;
|
|
94
|
+
error: (err: Error) => void;
|
|
95
|
+
};
|
|
96
|
+
export declare class TailnetSocket extends TypedEmitter<SocketEvents> {
|
|
97
|
+
#private;
|
|
98
|
+
readonly remoteAddr: string;
|
|
99
|
+
constructor(sock: SocketABI);
|
|
100
|
+
on<K extends keyof SocketEvents>(event: K, cb: SocketEvents[K]): void;
|
|
101
|
+
write(data: Uint8Array | string): void;
|
|
102
|
+
close(): void;
|
|
103
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Copyright (c) 2026 Sam Jean
|
|
2
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
import { TypedEmitter } from "./emitter";
|
|
4
|
+
import { waitFor } from "./wait";
|
|
5
|
+
// ── loader ──────────────────────────────────────────────────────────────────
|
|
6
|
+
let goRuntimeStarted = false;
|
|
7
|
+
async function ensureGoRuntime(wasm) {
|
|
8
|
+
const g = globalThis;
|
|
9
|
+
if (typeof g.__tsedgeNewNode === "function")
|
|
10
|
+
return;
|
|
11
|
+
if (typeof Go !== "function" && typeof g.Go !== "function") {
|
|
12
|
+
throw new Error('globalThis.Go is missing — import "@ts-edge/wasm/wasm_exec.js" before createNode()');
|
|
13
|
+
}
|
|
14
|
+
if (goRuntimeStarted) {
|
|
15
|
+
await waitForGoRuntime(g);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
goRuntimeStarted = true;
|
|
19
|
+
const go = new Go();
|
|
20
|
+
let instance;
|
|
21
|
+
if (wasm instanceof WebAssembly.Module) {
|
|
22
|
+
instance = await WebAssembly.instantiate(wasm, go.importObject);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
({ instance } = await WebAssembly.instantiate(wasm, go.importObject));
|
|
26
|
+
}
|
|
27
|
+
// go.run resolves when the Go program exits — which happens when the last
|
|
28
|
+
// node is stopped. Reset the loader state so the next createNode() boots a
|
|
29
|
+
// fresh runtime instead of calling into the dead one.
|
|
30
|
+
const reset = () => {
|
|
31
|
+
goRuntimeStarted = false;
|
|
32
|
+
delete g.__tsedgeNewNode;
|
|
33
|
+
};
|
|
34
|
+
void go.run(instance).then(reset, (err) => {
|
|
35
|
+
console.error("Go runtime exited:", err);
|
|
36
|
+
reset();
|
|
37
|
+
});
|
|
38
|
+
await waitForGoRuntime(g);
|
|
39
|
+
}
|
|
40
|
+
function waitForGoRuntime(g) {
|
|
41
|
+
return waitFor(() => typeof g.__tsedgeNewNode === "function", "the Go runtime to start", {
|
|
42
|
+
timeoutMs: 5000,
|
|
43
|
+
intervalMs: 5,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Create (but do not yet start) an ephemeral Tailscale node. */
|
|
47
|
+
export async function createNode(config) {
|
|
48
|
+
await ensureGoRuntime(config.wasm);
|
|
49
|
+
const newNode = globalThis
|
|
50
|
+
.__tsedgeNewNode;
|
|
51
|
+
const abi = newNode({
|
|
52
|
+
authKey: config.authKey,
|
|
53
|
+
hostname: config.hostname,
|
|
54
|
+
controlURL: config.controlURL,
|
|
55
|
+
});
|
|
56
|
+
return new TailscaleNode(abi);
|
|
57
|
+
}
|
|
58
|
+
// ── node ────────────────────────────────────────────────────────────────────
|
|
59
|
+
export class TailscaleNode extends TypedEmitter {
|
|
60
|
+
#abi;
|
|
61
|
+
#state = "NoState";
|
|
62
|
+
#addrs = [];
|
|
63
|
+
#runningWaiters = [];
|
|
64
|
+
constructor(abi) {
|
|
65
|
+
super();
|
|
66
|
+
this.#abi = abi;
|
|
67
|
+
}
|
|
68
|
+
get state() {
|
|
69
|
+
return this.#state;
|
|
70
|
+
}
|
|
71
|
+
get addrs() {
|
|
72
|
+
return this.#addrs;
|
|
73
|
+
}
|
|
74
|
+
get ipv4() {
|
|
75
|
+
return this.#addrs.find((a) => a.includes("."));
|
|
76
|
+
}
|
|
77
|
+
/** A snapshot of the current tailnet peers. */
|
|
78
|
+
getPeers() {
|
|
79
|
+
return this.#abi.getPeers();
|
|
80
|
+
}
|
|
81
|
+
/** Begin ephemeral login. Resolves once login has been kicked off. */
|
|
82
|
+
async start() {
|
|
83
|
+
this.#abi.start((type, data) => {
|
|
84
|
+
if (type === "state") {
|
|
85
|
+
this.#state = data;
|
|
86
|
+
this.emit("state", this.#state);
|
|
87
|
+
if (this.#state === "Running") {
|
|
88
|
+
const waiters = this.#runningWaiters;
|
|
89
|
+
this.#runningWaiters = [];
|
|
90
|
+
for (const w of waiters)
|
|
91
|
+
w();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (type === "netmap") {
|
|
95
|
+
this.#addrs = data;
|
|
96
|
+
this.emit("netmap", this.#addrs);
|
|
97
|
+
}
|
|
98
|
+
else if (type === "peers") {
|
|
99
|
+
this.emit("peers", data);
|
|
100
|
+
}
|
|
101
|
+
else if (type === "error") {
|
|
102
|
+
this.emit("error", new Error(String(data)));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
waitUntilRunning({ timeoutMs = 60_000 } = {}) {
|
|
107
|
+
if (this.#state === "Running")
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const timer = setTimeout(() => reject(new Error(`node not Running after ${timeoutMs}ms (state=${this.#state})`)), timeoutMs);
|
|
111
|
+
this.#runningWaiters.push(() => {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async stop() {
|
|
118
|
+
await this.#abi.stop();
|
|
119
|
+
}
|
|
120
|
+
/** Bring the node back online after down() — `tailscale up`. The node stays
|
|
121
|
+
* registered with control across down/up, so no fresh login is needed. */
|
|
122
|
+
async up() {
|
|
123
|
+
await this.#abi.up();
|
|
124
|
+
}
|
|
125
|
+
/** Take the node offline without logging out — `tailscale down`. State
|
|
126
|
+
* becomes Stopped; peers see the node go offline. */
|
|
127
|
+
async down() {
|
|
128
|
+
await this.#abi.down();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* HTTP request over the tailnet, returning a standard web Response. The
|
|
132
|
+
* response resolves as soon as headers arrive; its body streams on demand.
|
|
133
|
+
* A ReadableStream body is sent as a streaming (chunked) upload.
|
|
134
|
+
*/
|
|
135
|
+
async fetch(url, init = {}) {
|
|
136
|
+
const raw = await this.#abi.fetch({
|
|
137
|
+
url,
|
|
138
|
+
method: init.method,
|
|
139
|
+
headers: headerPairs(init.headers),
|
|
140
|
+
body: init.body instanceof ReadableStream ? streamToReader(init.body) : init.body,
|
|
141
|
+
});
|
|
142
|
+
// Response forbids bodies on null-body statuses and HEAD responses.
|
|
143
|
+
const nullBody = (init.method ?? "GET").toUpperCase() === "HEAD" ||
|
|
144
|
+
[204, 205, 304].includes(raw.status);
|
|
145
|
+
if (nullBody)
|
|
146
|
+
raw.body.cancel();
|
|
147
|
+
return new Response(nullBody ? null : readerToStream(raw.body), {
|
|
148
|
+
status: raw.status,
|
|
149
|
+
statusText: raw.statusText,
|
|
150
|
+
headers: raw.headers,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** Raw connection to a tailnet host:port. Defaults to TCP; pass
|
|
154
|
+
* `{ proto: "udp" }` for UDP. */
|
|
155
|
+
async dial(addr, opts = {}) {
|
|
156
|
+
return new TailnetSocket(await this.#abi.dial(opts.proto ?? "tcp", addr));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Serve a fetch-style handler (`(Request) => Response`) on a tailnet TCP
|
|
160
|
+
* port — pass a Hono app's `fetch` directly:
|
|
161
|
+
*
|
|
162
|
+
* await tailNode.serve({ port: 80 }, app.fetch);
|
|
163
|
+
*
|
|
164
|
+
* Request and response bodies stream; every response chunk is flushed to
|
|
165
|
+
* the peer as it is written, so SSE and long-lived responses work.
|
|
166
|
+
*/
|
|
167
|
+
async serve(opts, handler) {
|
|
168
|
+
return this.#abi.serve(opts.port, async (raw) => {
|
|
169
|
+
// GET/HEAD must not carry a body (Request forbids it); drop the reader.
|
|
170
|
+
const hasBody = raw.method !== "GET" && raw.method !== "HEAD";
|
|
171
|
+
if (!hasBody)
|
|
172
|
+
raw.body.cancel();
|
|
173
|
+
const url = /^https?:\/\//.test(raw.url)
|
|
174
|
+
? raw.url
|
|
175
|
+
: `http://${raw.host || "tailnet"}${raw.url}`;
|
|
176
|
+
const req = new Request(url, {
|
|
177
|
+
method: raw.method,
|
|
178
|
+
headers: raw.headers,
|
|
179
|
+
body: hasBody ? readerToStream(raw.body) : undefined,
|
|
180
|
+
// Required (per spec) whenever a Request body is a stream.
|
|
181
|
+
duplex: "half",
|
|
182
|
+
});
|
|
183
|
+
const res = await handler(req, { remoteAddr: raw.remoteAddr });
|
|
184
|
+
return {
|
|
185
|
+
status: res.status,
|
|
186
|
+
// Pairs preserve repeated names (Set-Cookie); Headers iteration
|
|
187
|
+
// yields set-cookie uncombined.
|
|
188
|
+
headers: [...res.headers],
|
|
189
|
+
body: res.body ? streamToReader(res.body) : undefined,
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/** Accept raw connections on a tailnet port. Defaults to TCP; pass
|
|
194
|
+
* `{ proto: "udp" }` for UDP. */
|
|
195
|
+
async listen(opts, onConnection) {
|
|
196
|
+
return this.#abi.listen(opts.proto ?? "tcp", opts.port, (sock) => onConnection(new TailnetSocket(sock)));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export class TailnetSocket extends TypedEmitter {
|
|
200
|
+
remoteAddr;
|
|
201
|
+
#sock;
|
|
202
|
+
// Chunks that arrive before an on("data") listener is attached (e.g. the first
|
|
203
|
+
// UDP datagram, which is ready the instant the flow is created) are buffered
|
|
204
|
+
// and flushed when the first data listener registers.
|
|
205
|
+
#early = [];
|
|
206
|
+
constructor(sock) {
|
|
207
|
+
super();
|
|
208
|
+
this.#sock = sock;
|
|
209
|
+
this.remoteAddr = sock.remoteAddr;
|
|
210
|
+
sock.attach((chunk) => {
|
|
211
|
+
if (this.listenerCount("data") === 0)
|
|
212
|
+
this.#early.push(chunk);
|
|
213
|
+
else
|
|
214
|
+
this.emit("data", chunk);
|
|
215
|
+
}, () => this.emit("close"), (msg) => this.emit("error", new Error(msg)));
|
|
216
|
+
}
|
|
217
|
+
on(event, cb) {
|
|
218
|
+
super.on(event, cb);
|
|
219
|
+
if (event === "data" && this.#early.length > 0) {
|
|
220
|
+
const pending = this.#early;
|
|
221
|
+
this.#early = [];
|
|
222
|
+
for (const chunk of pending)
|
|
223
|
+
cb(chunk);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
write(data) {
|
|
227
|
+
this.#sock.write(typeof data === "string" ? new TextEncoder().encode(data) : data);
|
|
228
|
+
}
|
|
229
|
+
close() {
|
|
230
|
+
this.#sock.close();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ── body stream bridging ────────────────────────────────────────────────────
|
|
234
|
+
/** Wrap an ABI pull-reader as a web ReadableStream. */
|
|
235
|
+
function readerToStream(r) {
|
|
236
|
+
return new ReadableStream({
|
|
237
|
+
async pull(controller) {
|
|
238
|
+
const chunk = await r.read();
|
|
239
|
+
if (chunk == null)
|
|
240
|
+
controller.close();
|
|
241
|
+
else
|
|
242
|
+
controller.enqueue(chunk);
|
|
243
|
+
},
|
|
244
|
+
cancel() {
|
|
245
|
+
r.cancel();
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Expose a web ReadableStream as an ABI pull-reader. */
|
|
250
|
+
function streamToReader(s) {
|
|
251
|
+
const reader = s.getReader();
|
|
252
|
+
return {
|
|
253
|
+
async read() {
|
|
254
|
+
const { done, value } = await reader.read();
|
|
255
|
+
return done || value === undefined ? null : value;
|
|
256
|
+
},
|
|
257
|
+
cancel() {
|
|
258
|
+
void reader.cancel().catch(() => { });
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// ── header normalization ────────────────────────────────────────────────────
|
|
263
|
+
function headerPairs(h) {
|
|
264
|
+
// Headers accepts all three shapes and applies native fetch() semantics
|
|
265
|
+
// (lowercased names, duplicates combined — except set-cookie).
|
|
266
|
+
return h ? [...new Headers(h)] : [];
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,wCAAwC;AAgBxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AA+DjC,+EAA+E;AAE/E,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,KAAK,UAAU,eAAe,CAAC,IAAuC;IACpE,MAAM,CAAC,GAAG,UAAoE,CAAC;IAC/E,IAAI,OAAO,CAAC,CAAC,eAAe,KAAK,UAAU;QAAE,OAAO;IACpD,IAAI,OAAO,EAAE,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC;IACpB,IAAI,QAA8B,CAAC;IACnC,IAAI,IAAI,YAAY,WAAW,CAAC,MAAM,EAAE,CAAC;QACvC,QAAQ,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,CAAC,EAAE,QAAQ,EAAE,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,0EAA0E;IAC1E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,gBAAgB,GAAG,KAAK,CAAC;QACzB,OAAO,CAAC,CAAC,eAAe,CAAC;IAC3B,CAAC,CAAC;IACF,KAAK,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACxC,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QACzC,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,CAAC;IACH,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAgC;IACxD,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,eAAe,KAAK,UAAU,EAAE,yBAAyB,EAAE;QACvF,SAAS,EAAE,IAAI;QACf,UAAU,EAAE,CAAC;KACd,CAAC,CAAC;AACL,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAkB;IACjD,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAI,UAAsE;SACpF,eAAe,CAAC;IACnB,MAAM,GAAG,GAAG,OAAO,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;IACH,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,+EAA+E;AAE/E,MAAM,OAAO,aAAc,SAAQ,YAAwB;IACzD,IAAI,CAAU;IACd,MAAM,GAAc,SAAS,CAAC;IAC9B,MAAM,GAAsB,EAAE,CAAC;IAC/B,eAAe,GAAsB,EAAE,CAAC;IAExC,YAAY,GAAY;QACtB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,+CAA+C;IAC/C,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;YAC7B,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,IAAI,CAAC,MAAM,GAAG,IAAiB,CAAC;gBAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;gBAChC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC;oBACrC,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;oBAC1B,KAAK,MAAM,CAAC,IAAI,OAAO;wBAAE,CAAC,EAAE,CAAC;gBAC/B,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,IAAI,CAAC,MAAM,GAAG,IAAgB,CAAC;gBAC/B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACnC,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAc,CAAC,CAAC;YACrC,CAAC;iBAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,EAAE,SAAS,GAAG,MAAM,KAA6B,EAAE;QAClE,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QACxD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,SAAS,aAAa,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EACvF,SAAS,CACV,CAAC;YACF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC7B,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED;8EAC0E;IAC1E,KAAK,CAAC,EAAE;QACN,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACvB,CAAC;IAED;yDACqD;IACrD,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,OAAkB,EAAE;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAChC,GAAG;YACH,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;YAClC,IAAI,EAAE,IAAI,CAAC,IAAI,YAAY,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;SAClF,CAAC,CAAC;QACH,oEAAoE;QACpE,MAAM,QAAQ,GACZ,CAAC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM;YAC/C,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,QAAQ;YAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YAC9D,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;IACL,CAAC;IAED;qCACiC;IACjC,KAAK,CAAC,IAAI,CACR,IAAY,EACZ,OAAkC,EAAE;QAEpC,OAAO,IAAI,aAAa,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,KAAK,CAAC,IAAsB,EAAE,OAAqB;QACvD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC9C,wEAAwE;YACxE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC;YAC9D,IAAI,CAAC,OAAO;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBACtC,CAAC,CAAC,GAAG,CAAC,GAAG;gBACT,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,IAAI,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;YAChD,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;gBACpD,2DAA2D;gBAC3D,MAAM,EAAE,MAAM;aACA,CAAC,CAAC;YAClB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;YAC/D,OAAO;gBACL,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,gEAAgE;gBAChE,gCAAgC;gBAChC,OAAO,EAAE,CAAC,GAAG,GAAG,CAAC,OAAO,CAAuB;gBAC/C,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;aACtD,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;qCACiC;IACjC,KAAK,CAAC,MAAM,CACV,IAA6C,EAC7C,YAA6C;QAE7C,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAC/D,YAAY,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CACtC,CAAC;IACJ,CAAC;CACF;AAUD,MAAM,OAAO,aAAc,SAAQ,YAA0B;IAClD,UAAU,CAAS;IAC5B,KAAK,CAAY;IACjB,+EAA+E;IAC/E,6EAA6E;IAC7E,sDAAsD;IACtD,MAAM,GAAiB,EAAE,CAAC;IAE1B,YAAY,IAAe;QACzB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,CACT,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;gBACzD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EACxB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAC5C,CAAC;IACJ,CAAC;IAEQ,EAAE,CAA+B,KAAQ,EAAE,EAAmB;QACrE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpB,IAAI,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;YAC5B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,KAAK,MAAM,KAAK,IAAI,OAAO;gBAAG,EAA8B,CAAC,KAAK,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAyB;QAC7B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC;IACD,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AAED,+EAA+E;AAE/E,uDAAuD;AACvD,SAAS,cAAc,CAAC,CAAgB;IACtC,OAAO,IAAI,cAAc,CAAa;QACpC,KAAK,CAAC,IAAI,CAAC,UAAU;YACnB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,KAAK,IAAI,IAAI;gBAAE,UAAU,CAAC,KAAK,EAAE,CAAC;;gBACjC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,MAAM;YACJ,CAAC,CAAC,MAAM,EAAE,CAAC;QACb,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,yDAAyD;AACzD,SAAS,cAAc,CAAC,CAA6B;IACnD,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC;IAC7B,OAAO;QACL,KAAK,CAAC,IAAI;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,OAAO,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;QACpD,CAAC;QACD,MAAM;YACJ,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,SAAS,WAAW,CAClB,CAAoE;IAEpE,wEAAwE;IACxE,+DAA+D;IAC/D,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC"}
|
package/dist/wait.d.ts
ADDED
package/dist/wait.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) 2026 Sam Jean
|
|
2
|
+
// SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
/** Poll until pred() is true; rejects naming `what` after timeoutMs. */
|
|
4
|
+
export async function waitFor(pred, what, { timeoutMs = 15_000, intervalMs = 20 } = {}) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
while (!pred()) {
|
|
7
|
+
if (Date.now() - start > timeoutMs) {
|
|
8
|
+
throw new Error(`timeout waiting for ${what}`);
|
|
9
|
+
}
|
|
10
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=wait.js.map
|
package/dist/wait.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,wCAAwC;AAExC,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAmB,EACnB,IAAY,EACZ,EAAE,SAAS,GAAG,MAAM,EAAE,UAAU,GAAG,EAAE,KAAkD,EAAE;IAEzF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACf,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ts-edge/wasm",
|
|
3
|
+
"version": "1.100.0-1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "WASM ephemeral Tailscale node (Go js/wasm) with a typed JS/TS loader.",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"author": "Sam Jean",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/jseun/ts-edge.git",
|
|
11
|
+
"directory": "packages/wasm"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"tailscale",
|
|
15
|
+
"wasm",
|
|
16
|
+
"tailnet",
|
|
17
|
+
"cloudflare-workers",
|
|
18
|
+
"wireguard"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"./build/wasm_exec.js"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"build/node.wasm",
|
|
26
|
+
"build/wasm_exec.js"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./node.wasm": "./build/node.wasm",
|
|
34
|
+
"./wasm_exec.js": "./build/wasm_exec.js"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build:wasm": "./scripts/build-wasm.sh",
|
|
38
|
+
"build:ts": "tsc -p tsconfig.json",
|
|
39
|
+
"build": "pnpm build:ts",
|
|
40
|
+
"prepare": "pnpm build:ts",
|
|
41
|
+
"prepack": "pnpm build:wasm && pnpm build:ts",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json && tsc -p test/workerd/tsconfig.json",
|
|
43
|
+
"test": "vitest run --project unit",
|
|
44
|
+
"test:unit": "vitest run --project unit",
|
|
45
|
+
"test:functional": "vitest run --project functional",
|
|
46
|
+
"test:workerd": "pnpm build:ts && vitest run --project workerd",
|
|
47
|
+
"test:go": "./scripts/test-go.sh"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@cloudflare/vitest-pool-workers": "^0.18.0",
|
|
51
|
+
"@cloudflare/workers-types": "^4.20260702.1",
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"hono": "^4.12.28",
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"vitest": "^4.1.9"
|
|
56
|
+
}
|
|
57
|
+
}
|