From: Matt Corallo <649246+TheBlueMatt@users.noreply.github.com> Date: Sat, 23 Jul 2022 03:51:17 +0000 (+0000) Subject: Merge pull request #107 from TheBlueMatt/main X-Git-Tag: v0.0.110.0~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=8a515be62568ee4327e636a977e32204006d6493;hp=711bc9cabbf2a733bd86133f86128348f338bf34;p=ldk-java Merge pull request #107 from TheBlueMatt/main [TS] Properly export unitary enums such that they're in *.d.mjs --- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72d97454..638bab96 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,6 +96,7 @@ jobs: - name: Check latest TS files are in git run: | git checkout ts/package.json + git checkout node-net/package.json git diff --exit-code java_bindings: diff --git a/README.md b/README.md index 4c937681..7affd530 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ The TypeScript bindings require modern web standards, including support for `Fin and `WeakRef` (Chrome 84, Firefox 79, Safari 14.1/iOS 14.5 and Node 14.6) and WASM BigInt support (Chrome 85, Firefox 78, Safari 14.1/iOS 14.5, and Node 15.0). +For users of Node.JS environments you may wish to use the `lightningdevkit-node-net` package as +well to implement the required network handling to bridge the `lightningdevkit` package's +`SocketDescriptor` interface to Node.JS TCP Sockets. For those wishing to run a lightning node in +the browser you will need to provide your own bridge from `SocketDescriptor` to a WebSocket proxy. + ## General The only known issue resulting in a use-after-free bug requires custom a custom ChannelKeys instance diff --git a/genbindings.sh b/genbindings.sh index 4102884d..8034337d 100755 --- a/genbindings.sh +++ b/genbindings.sh @@ -159,7 +159,7 @@ if [ "$2" != "wasm" ]; then popd LDK_LIB="tmp/libldk.bc tmp/libldk.a" fi - $COMPILE -o liblightningjni_release$LDK_TARGET_SUFFIX.so -flto -O3 -I"$1"/lightning-c-bindings/include/ $2 src/main/jni/bindings.c $LDK_LIB -lm + $COMPILE -o liblightningjni_release$LDK_TARGET_SUFFIX.so -s -flto -O3 -I"$1"/lightning-c-bindings/include/ $2 src/main/jni/bindings.c $LDK_LIB -lm if [ "$IS_MAC" = "false" -a "$4" = "false" ]; then GLIBC_SYMBS="$(objdump -T liblightningjni_release$LDK_TARGET_SUFFIX.so | grep GLIBC_ | grep -v "GLIBC_2\.2\." | grep -v "GLIBC_2\.3\(\.\| \)" | grep -v "GLIBC_2.\(14\|17\) " || echo)" if [ "$GLIBC_SYMBS" != "" ]; then @@ -190,6 +190,8 @@ else fi rm -f ts/bindings.c sed -i 's/^ "version": .*/ "version": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'",/g' ts/package.json + sed -i 's/^ "version": .*/ "version": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'",/g' node-net/package.json + sed -i 's/^ "lightningdevkit": .*/ "lightningdevkit": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'"/g' node-net/package.json if [ "$3" = "true" ]; then echo "#define LDK_DEBUG_BUILD" > ts/bindings.c elif [ "$3" = "leaks" ]; then @@ -224,11 +226,16 @@ else tsc --types node --typeRoots . cp ../$WASM_FILE liblightningjs.wasm cp ../README.md README.md + cd ../node-net + tsc --types node --typeRoots . echo Ready to publish! if [ -x "$(which node)" ]; then NODE_V="$(node --version)" if [ "${NODE_V:1:2}" -gt 14 ]; then + cd ../ts node test/node.mjs + cd ../node-net + node test/test.mjs fi fi fi diff --git a/node-net/README.md b/node-net/README.md new file mode 100644 index 00000000..28c45f8c --- /dev/null +++ b/node-net/README.md @@ -0,0 +1,5 @@ +LDK Node.JS TypeScript Network Implementation +============================================= + +This module bridges the LDK `SocketDescriptor` and `PeerManager` interfaces to Node.JS's `net` +TCP sockets. See the `lightningdevkit` module for more info. diff --git a/node-net/net.mts b/node-net/net.mts new file mode 100644 index 00000000..58da387d --- /dev/null +++ b/node-net/net.mts @@ -0,0 +1,177 @@ +import * as ldk from "lightningdevkit"; +import * as net from "net"; + +/** + * Handles TCP connections using Node.JS's 'net' module given an `ldk.PeerManager`. + */ +export class NodeLDKNet { + private ping_timer; + private servers: net.Server[]; + public constructor(public peer_manager: ldk.PeerManager) { + this.ping_timer = setInterval(function() { + peer_manager.timer_tick_occurred(); + peer_manager.process_events(); + }, 10_000); + this.servers = []; + } + + /** + * Disconnects all connections and releases all resources for this net handler. + */ + public stop() { + clearInterval(this.ping_timer); + for (const server of this.servers) { + server.close(); + } + this.peer_manager.disconnect_all_peers(); + } + + /** + * Processes any pending events for the PeerManager, sending queued messages. + * You should call this (or peer_manager.process_events()) any time you take an action which + * is likely to generate messages to send (eg send a payment, processing payment forwards, + * etc). + */ + public process_events() { this.peer_manager.process_events(); } + + private descriptor_count = BigInt(0); + private get_descriptor(socket: net.Socket): ldk.SocketDescriptor { + const this_index = this.descriptor_count; + this.descriptor_count += BigInt(1); + + socket.setNoDelay(true); + + const this_pm = this.peer_manager; + var sock_write_waiting = false; + + let descriptor = ldk.SocketDescriptor.new_impl ({ + send_data(data: Uint8Array, resume_read: boolean): number { + if (resume_read) socket.resume(); + + if (sock_write_waiting) return 0; + const written = socket.write(data); + if (!written) sock_write_waiting = true; + return data.length; + }, + disconnect_socket(): void { + socket.destroy(); + }, + eq(other: ldk.SocketDescriptor): boolean { + return other.hash() == this.hash(); + }, + hash(): bigint { + return this_index; + } + } as ldk.SocketDescriptorInterface); + + socket.on("drain", function() { + if (sock_write_waiting) { + if (!this_pm.write_buffer_space_avail(descriptor).is_ok()) { + descriptor.disconnect_socket(); + } + } + }); + + socket.on("data", function(data) { + const res = this_pm.read_event(descriptor, data); + if (!res.is_ok()) descriptor.disconnect_socket(); + else if ((res as ldk.Result_boolPeerHandleErrorZ_OK).res) socket.pause(); + this_pm.process_events(); + }); + + socket.on("close", function() { + this_pm.socket_disconnected(descriptor); + }); + + return descriptor; + } + + private static v4_addr_from_ip(ip: string, port: number): ldk.NetAddress { + const sockaddr = ip.split(".").map(parseFloat); + return ldk.NetAddress.constructor_ipv4(new Uint8Array(sockaddr), port); + } + private static v6_addr_from_ip(ip: string, port: number): ldk.NetAddress { + const sockaddr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const halves = ip.split("::"); // either one or two elements + const first_half = halves[0].split(":"); + for (var idx = 0; idx < first_half.length; idx++) { + const v = parseInt(first_half[idx], 16); + sockaddr[idx*2] = v >> 8; + sockaddr[idx*2 + 1] = v & 0xff; + } + if (halves.length == 2) { + const second_half = halves[1].split(":"); + for (var idx = 0; idx < second_half.length; idx++) { + const v = parseInt(second_half[second_half.length - idx - 1], 16); + sockaddr[14 - idx*2] = v >> 8; + sockaddr[15 - idx*2] = v & 0xff; + } + } + return ldk.NetAddress.constructor_ipv6(new Uint8Array(sockaddr), port); + } + + private static get_addr_from_socket(socket: net.Socket): ldk.Option_NetAddressZ { + const addr = socket.remoteAddress; + if (addr === undefined) + return ldk.Option_NetAddressZ.constructor_none(); + if (net.isIPv4(addr)) { + return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v4_addr_from_ip(addr, socket.remotePort)); + } + if (net.isIPv6(addr)) { + return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v6_addr_from_ip(addr, socket.remotePort)); + } + return ldk.Option_NetAddressZ.constructor_none(); + } + + /** + * Binds a listener on the given host and port, accepting incoming connections. + */ + public async bind_listener(host: string, port: number) { + const this_handler = this; + const server = net.createServer(function(incoming_sock: net.Socket) { + const descriptor = this_handler.get_descriptor(incoming_sock); + const res = this_handler.peer_manager + .new_inbound_connection(descriptor, NodeLDKNet.get_addr_from_socket(incoming_sock)); + if (!res.is_ok()) descriptor.disconnect_socket(); + }); + const servers_list = this.servers; + return new Promise((resolve, reject) => { + server.on("error", function() { + reject(); + server.close(); + }); + server.on("listening", function() { + servers_list.push(server); + resolve(); + }); + server.listen(port, host); + }); + } + + /** + * Establishes an outgoing connection to the given peer at the given host and port. + * + * Note that the peer will not appear in the PeerManager peers list until the socket has + * connected and the initial handshake completes. + */ + public async connect_peer(host: string, port: number, peer_node_id: Uint8Array) { + const this_handler = this; + const sock = new net.Socket(); + const res = new Promise((resolve, reject) => { + sock.on("connect", function() { resolve(); }); + sock.on("error", function() { reject(); }); + }); + sock.connect(port, host, function() { + const descriptor = this_handler.get_descriptor(sock); + const res = this_handler.peer_manager + .new_outbound_connection(peer_node_id, descriptor, NodeLDKNet.get_addr_from_socket(sock)); + if (!res.is_ok()) descriptor.disconnect_socket(); + else { + const bytes = (res as ldk.Result_CVec_u8ZPeerHandleErrorZ_OK).res; + const send_res = descriptor.send_data(bytes, true); + console.assert(send_res == bytes.length); + } + }); + return res; + } +} diff --git a/node-net/node_modules/@types/node b/node-net/node_modules/@types/node new file mode 120000 index 00000000..d80bd63a --- /dev/null +++ b/node-net/node_modules/@types/node @@ -0,0 +1 @@ +../../../ts/node/ \ No newline at end of file diff --git a/node-net/node_modules/lightningdevkit b/node-net/node_modules/lightningdevkit new file mode 120000 index 00000000..2351a940 --- /dev/null +++ b/node-net/node_modules/lightningdevkit @@ -0,0 +1 @@ +../../ts \ No newline at end of file diff --git a/node-net/package.json b/node-net/package.json new file mode 100644 index 00000000..e0b8a0e0 --- /dev/null +++ b/node-net/package.json @@ -0,0 +1,34 @@ +{ + "name": "lightningdevkit-node-net", + "version": "Set in genbindings.sh automagically", + "description": "Lightning Development Kit Net Implementation for Node.JS", + "main": "net.mjs", + "types": "net.d.mts", + "type": "module", + "files": [ + "net.mjs", + "net.d.mts", + "tsconfig.json", + "README.md" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/lightningdevkit/ldk-garbagecollected.git" + }, + "keywords": [ + "lightning", + "bitcoin" + ], + "author": "LDK Developers", + "license": "MIT OR Apache-2.0", + "bugs": { + "url": "https://github.com/lightningdevkit/ldk-garbagecollected/issues" + }, + "homepage": "https://github.com/lightningdevkit/ldk-garbagecollected#readme", + "dependencies": { + "lightningdevkit": "Set in genbindings.sh automagically" + }, + "devDependencies": { + "@types/node": "^17.0.8" + } +} diff --git a/node-net/test/test.mts b/node-net/test/test.mts new file mode 100644 index 00000000..8d493e4d --- /dev/null +++ b/node-net/test/test.mts @@ -0,0 +1,96 @@ +import * as ldk from "../../ts/index.mjs"; +import * as node_net from '../net.mjs'; + +import * as fs from 'fs'; + +const wasm_file = fs.readFileSync('../ts/liblightningjs.wasm'); +await ldk.initializeWasmFromBinary(wasm_file); + +const logger_a = ldk.Logger.new_impl({ + log(record: ldk.Record): void { + console.log(record.get_module_path() + ": " + record.get_args()); + } +} as ldk.LoggerInterface); +const logger_b = logger_a; + +const node_a_secret = new Uint8Array(32); +for (var i = 0; i < 32; i++) node_a_secret[i] = 42; +// The public key for a secret key of all 42s: +const node_a_pk = new Uint8Array([3, 91, 229, 233, 71, 130, 9, 103, 74, 150, 230, 15, 31, 3, 127, 97, 118, 84, 15, 208, 1, 250, 29, 100, 105, 71, 112, 197, 106, 119, 9, 196, 44]); + +const node_b_secret = new Uint8Array(32); +for (var i = 0; i < 32; i++) node_b_secret[i] = 43; + +const rng_seed = new Uint8Array(32); +const routing_handler = ldk.IgnoringMessageHandler.constructor_new().as_RoutingMessageHandler(); +const chan_handler = ldk.ErroringMessageHandler.constructor_new().as_ChannelMessageHandler(); +const cust_handler = ldk.IgnoringMessageHandler.constructor_new().as_CustomMessageHandler(); + +const a_pm = ldk.PeerManager.constructor_new(chan_handler, routing_handler, node_a_secret, rng_seed, logger_a, cust_handler); +const a_net_handler = new node_net.NodeLDKNet(a_pm); +var port = 10000; +for (; port < 11000; port++) { + try { + // Try ports until we find one we can bind to. + await a_net_handler.bind_listener("127.0.0.1", port); + break; + } catch(_) {} +} + +const b_pm = ldk.PeerManager.constructor_new(chan_handler, routing_handler, node_b_secret, rng_seed, logger_b, cust_handler); +const b_net_handler = new node_net.NodeLDKNet(b_pm); +await b_net_handler.connect_peer("127.0.0.1", port, node_a_pk); + +try { + // Ensure we get an error if we try to bind the same port twice. + await a_net_handler.bind_listener("127.0.0.1", port); + console.assert(false); +} catch(_) {} + +await new Promise(resolve => { + // Wait until the peers are connected and have exchanged the initial handshake + var timer: ReturnType; + timer = setInterval(function() { + if (a_pm.get_peer_node_ids().length == 1 && b_pm.get_peer_node_ids().length == 1) { + resolve(); + clearInterval(timer); + } + }, 500); +}); + +b_pm.disconnect_by_node_id(node_a_pk, false); +await new Promise(resolve => { + // Wait until A learns the connection is closed from the socket closure + var timer: ReturnType; + timer = setInterval(function() { + if (a_pm.get_peer_node_ids().length == 0 && b_pm.get_peer_node_ids().length == 0) { + resolve(); + clearInterval(timer); + } + }, 500); +}); + +a_net_handler.stop(); +b_net_handler.stop(); + +function arr_eq(a: number[]|Uint8Array, b: number[]|Uint8Array): boolean { + return a.length == b.length && a.every((val, idx) => val == b[idx]); +} + +const v4_parse = node_net.NodeLDKNet["v4_addr_from_ip"]; +console.assert((v4_parse("127.0.0.1", 4242) as ldk.NetAddress_IPv4).port == 4242); +console.assert(arr_eq((v4_parse("127.0.0.1", 4242) as ldk.NetAddress_IPv4).addr, [127,0,0,1])); +console.assert(arr_eq((v4_parse("0.0.0.0", 4242) as ldk.NetAddress_IPv4).addr, [0,0,0,0])); + +const v6_parse = node_net.NodeLDKNet["v6_addr_from_ip"]; +console.assert((v6_parse("::", 4242) as ldk.NetAddress_IPv4).port == 4242); +console.assert(arr_eq((v6_parse("::", 4242) as ldk.NetAddress_IPv6).addr, + [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])); +console.assert(arr_eq((v6_parse("fe80::", 4242) as ldk.NetAddress_IPv6).addr, + [0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0])); +console.assert(arr_eq((v6_parse("fe80::42", 4242) as ldk.NetAddress_IPv6).addr, + [0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0x42])); +console.assert(arr_eq((v6_parse("fe80:A:b::", 4242) as ldk.NetAddress_IPv6).addr, + [0xfe,0x80,0,0xa,0,0xb,0,0,0,0,0,0,0,0,0,0])); +console.assert(arr_eq((v6_parse("2001:1:bad::beef:cafe", 4242) as ldk.NetAddress_IPv6).addr, + [0x20, 0x01, 0, 1, 0xb, 0xad, 0, 0, 0, 0, 0, 0, 0xbe, 0xef, 0xca, 0xfe])); diff --git a/node-net/tsconfig.json b/node-net/tsconfig.json new file mode 100644 index 00000000..4eb9eea8 --- /dev/null +++ b/node-net/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "es2022", + "sourceMap": true, + "esModuleInterop": false, + "stripInternal": true, + "moduleResolution": "node", + + "paths": { + "lightningdevkit": [ "../ts/index.mts" ] + }, + + "allowSyntheticDefaultImports": false, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": false, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": false, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "exactOptionalPropertyTypes": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false + } +} diff --git a/ts/bindings.mts b/ts/bindings.mts index b339eab6..f2e599e5 100644 --- a/ts/bindings.mts +++ b/ts/bindings.mts @@ -153,7 +153,7 @@ export function encodeUint64Array (inputArray: BigUint64Array|Array): nu /* @internal */ export function check_arr_len(arr: Uint8Array, len: number): Uint8Array { - if (arr.length != len) { throw new Error("Expected array of length " + len + "got " + arr.length); } + if (arr.length != len) { throw new Error("Expected array of length " + len + " got " + arr.length); } return arr; } @@ -229,7 +229,9 @@ export function decodeString(stringPointer: number, free = true): string { /* @internal */ export function getRemainingAllocationCount(): number { return 0; } /* @internal */ export function debugPrintRemainingAllocs() { } -/* @internal */ +/** + * An error when accessing the chain via [`Access`]. + */ export enum AccessError { /** * The requested chain is unknown. @@ -242,7 +244,9 @@ export enum AccessError { } -/* @internal */ +/** + * An enum which can either contain a or not + */ export enum COption_NoneZ { /** * When we're in this state, this COption_NoneZ contains a @@ -255,7 +259,9 @@ export enum COption_NoneZ { } -/* @internal */ +/** + * An error enum representing a failure to persist a channel monitor update. + */ export enum ChannelMonitorUpdateErr { /** * Used to indicate a temporary failure (eg connection to a watchtower or remote backup of @@ -324,7 +330,10 @@ export enum ChannelMonitorUpdateErr { } -/* @internal */ +/** + * An enum that represents the speed at which we want a transaction to confirm used for feerate + * estimation. + */ export enum ConfirmationTarget { /** * We are happy with this transaction confirming slowly when feerate drops some. @@ -341,7 +350,9 @@ export enum ConfirmationTarget { } -/* @internal */ +/** + * Errors that may occur when constructing a new `RawInvoice` or `Invoice` + */ export enum CreationError { /** * The supplied description string was longer than 639 __bytes__ (see [`Description::new(...)`](./struct.Description.html#method.new)) @@ -369,7 +380,9 @@ export enum CreationError { } -/* @internal */ +/** + * Enum representing the crypto currencies (or networks) supported by this library + */ export enum Currency { /** * Bitcoin mainnet @@ -394,7 +407,9 @@ export enum Currency { } -/* @internal */ +/** + * Represents an IO Error. Note that some information is lost in the conversion from Rust. + */ export enum IOError { LDKIOError_NotFound, LDKIOError_PermissionDenied, @@ -417,7 +432,9 @@ export enum IOError { } -/* @internal */ +/** + * An enum representing the available verbosity levels of the logger. + */ export enum Level { /** * Designates extremely verbose information, including gossip-induced messages @@ -446,7 +463,9 @@ export enum Level { } -/* @internal */ +/** + * An enum representing the possible Bitcoin or test networks which we can run on + */ export enum Network { /** * The main Bitcoin blockchain. @@ -467,7 +486,10 @@ export enum Network { } -/* @internal */ +/** + * Specifies the recipient of an invoice, to indicate to [`KeysInterface::sign_invoice`] what node + * secret key should be used to sign the invoice. + */ export enum Recipient { /** * The invoice should be signed with the local node secret key. @@ -483,7 +505,9 @@ export enum Recipient { } -/* @internal */ +/** + * Represents an error returned from libsecp256k1 during validation of some secp256k1 data + */ export enum Secp256k1Error { /** * Signature failed verification @@ -532,7 +556,10 @@ export enum Secp256k1Error { } -/* @internal */ +/** + * Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the + * requirements sections in BOLT #11 + */ export enum SemanticError { /** * The invoice is missing the mandatory payment hash @@ -578,7 +605,9 @@ export enum SemanticError { } -/* @internal */ +/** + * SI prefixes for the human readable part + */ export enum SiPrefix { /** * 10^-3 diff --git a/ts/node/buffer.d.ts b/ts/node/buffer.d.ts new file mode 100755 index 00000000..eaba0379 --- /dev/null +++ b/ts/node/buffer.d.ts @@ -0,0 +1,10 @@ +// Minimal part of the Node API which we depend on. +// May be (c) Microsoft licensed under the MIT license, however API's are not generally copyrightable per recent precedent. +declare module 'buffer' { + global { + interface Buffer extends Uint8Array {} + } +} +declare module 'node:buffer' { + export * from 'buffer'; +} diff --git a/ts/node/crypto.d.ts b/ts/node/crypto.d.ts index f7020d4d..5d2a7163 100755 --- a/ts/node/crypto.d.ts +++ b/ts/node/crypto.d.ts @@ -2,7 +2,7 @@ // May be (c) Microsoft licensed under the MIT license, however API's are not generally copyrightable per recent precedent. declare module 'crypto' { namespace webcrypto { - function getRandomValues(TypedArray): void; + function getRandomValues(out: Uint8Array): void; } } declare module 'node:crypto' { diff --git a/ts/node/fs.d.ts b/ts/node/fs.d.ts index 76792ddd..95a17e0b 100755 --- a/ts/node/fs.d.ts +++ b/ts/node/fs.d.ts @@ -1,7 +1,7 @@ // Minimal part of the Node fs API which we depend on. // May be (c) Microsoft licensed under the MIT license, however API's are not generally copyrightable per recent precedent. declare module 'fs' { - export type PathLike = string | Buffer | URL; + export type PathLike = string | URL; export type PathOrFileDescriptor = PathLike | number; export function readFileSync( path: PathOrFileDescriptor, diff --git a/ts/node/index.d.ts b/ts/node/index.d.ts index 3f2f5c77..24931b1b 100755 --- a/ts/node/index.d.ts +++ b/ts/node/index.d.ts @@ -1,4 +1,7 @@ // Minimal part of the Node API which we depend on. // May be (c) Microsoft licensed under the MIT license, however API's are not generally copyrightable per recent precedent. +/// /// /// +/// +/// diff --git a/ts/node/net.d.ts b/ts/node/net.d.ts new file mode 100755 index 00000000..07350ff9 --- /dev/null +++ b/ts/node/net.d.ts @@ -0,0 +1,32 @@ +// Minimal part of the Node fs API which we depend on. +// May be (c) Microsoft licensed under the MIT license, however API's are not generally copyrightable per recent precedent. +declare module 'net' { + import * as stream from 'node:stream'; + class Socket extends stream.Duplex { + constructor(); + write(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean; + connect(port: number, host: string, connectionListener?: () => void): this; + pause(): this; + resume(): this; + setNoDelay(noDelay?: boolean): this; + readonly remoteAddress?: string | undefined; + readonly remotePort?: number | undefined; + on(event: 'close', listener: (hadError: boolean) => void): this; + on(event: 'connect', listener: () => void): this; + on(event: 'data', listener: (data: Buffer) => void): this; + on(event: 'drain', listener: () => void): this; + on(event: 'error', listener: (err: Error) => void): this; + } + class Server { + listen(port?: number, hostname?: string, listeningListener?: () => void): this; + close(callback?: (err?: Error) => void): this; + on(event: 'error', listener: (err: Error) => void): this; + on(event: 'listening', listener: () => void): this; + } + function createServer(connectionListener?: (socket: Socket) => void): Server; + function isIPv4(input: string): boolean; + function isIPv6(input: string): boolean; +} +declare module 'node:net' { + export * from 'net'; +} diff --git a/ts/node/stream.d.ts b/ts/node/stream.d.ts new file mode 100755 index 00000000..5a202619 --- /dev/null +++ b/ts/node/stream.d.ts @@ -0,0 +1,35 @@ +/** + * A stream is an abstract interface for working with streaming data in Node.js. + * The `stream` module provides an API for implementing the stream interface. + * + * There are many stream objects provided by Node.js. For instance, a `request to an HTTP server` and `process.stdout` are both stream instances. + * + * Streams can be readable, writable, or both. All streams are instances of `EventEmitter`. + * + * To access the `stream` module: + * + * ```js + * const stream = require('stream'); + * ``` + * + * The `stream` module is useful for creating new types of stream instances. It is + * usually not necessary to use the `stream` module to consume streams. + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/stream.js) + */ +declare module 'stream' { + namespace internal { + class Stream {} + class Readable extends Stream { + destroy(error?: Error): this; + } + class Writable extends Stream { + destroy(error?: Error): this; + } + class Duplex extends Readable implements Writable {} + } + export = internal; +} +declare module 'node:stream' { + import stream = require('stream'); + export = stream; +} diff --git a/typescript_strings.py b/typescript_strings.py index c8fcfc93..0d303642 100644 --- a/typescript_strings.py +++ b/typescript_strings.py @@ -191,7 +191,7 @@ export function encodeUint64Array (inputArray: BigUint64Array|Array): nu /* @internal */ export function check_arr_len(arr: Uint8Array, len: number): Uint8Array { - if (arr.length != len) { throw new Error("Expected array of length " + len + "got " + arr.length); } + if (arr.length != len) { throw new Error("Expected array of length " + len + " got " + arr.length); } return arr; } @@ -779,8 +779,12 @@ import * as bindings from '../bindings.mjs' out_c = out_c + "\t}\n" out_c = out_c + "}\n" + # Note that this is *not* marked /* @internal */ as we re-expose it directly in enums/ + enum_comment_formatted = enum_doc_comment.replace("\n", "\n * ") out_typescript = f""" -/* @internal */ +/** + * {enum_comment_formatted} + */ export enum {struct_name} {{ {out_typescript_enum_fields} }}