diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 834cec28..97e66d37 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -166,16 +166,6 @@ jobs: run: rm -rf ./tmp shell: bash - - name: Test Compatibility - if: ${{ !matrix.dockerfile }} - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 2 - command: | - pnpm run test.unit.compat - continue-on-error: true - - name: Clean Tmp run: rm -rf ./tmp shell: bash diff --git a/.mocharc.js b/.mocharc.js index 8bb67cfa..b3b2e22d 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -6,7 +6,7 @@ const config = { "expose-gc": true, "v8-expose-gc": true, exit: true, - parallel: false, + parallel: true, timeout: 6000, retries: 3, fullTrace: true, diff --git a/package.json b/package.json index 785bbeb8..563cadb7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "devDependencies": { "@types/benchmark": "~2.1.5", "@types/chai": "^4", - "@types/deasync": "~0.1.5", "@types/eslint": "~9.6.1", "@types/fs-extra": "^11.0.4", "@types/gh-pages": "~6.1.0", @@ -38,7 +37,6 @@ "benchmark": "^2.1.4", "chai": "^4", "cross-env": "^7.0.3", - "deasync": "^0.1.30", "downlevel-dts": "^0.11.0", "electron": "^33.2.1", "electron-mocha": "^13.0.1", @@ -106,8 +104,7 @@ "test": "run-s test.unit", "test.debug": "run-s test.unit.debug", "test.unit": "run-s clean.temp build && mocha ./test/unit/*-test.ts", - "test.unit.debug": "run-s clean.temp build.debug && mocha ./test/unit/*-test.ts", - "test.unit.compat": "run-s clean.temp build && cross-env INCLUDE_COMPAT_TESTS=true mocha ./test/unit/compat/*-test.ts", + "test.unit.debug": "run-s clean.temp build.debug && cross-env INCLUDE_COMPAT_TESTS=true mocha ./test/unit/*-test.ts ./test/unit/**/*-test.ts", "test.unit.nogc": "run-s clean.temp build && cross-env SKIP_GC_TESTS=true mocha", "test.electron.main": "run-s clean.temp build && cross-env SKIP_GC_TESTS=true electron-mocha ./test/unit/*-test.ts", "test.electron.renderer": "run-s build && electron-mocha --renderer ./test/unit/*-test.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 340f7c59..ef159ef5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,6 @@ importers: '@types/chai': specifier: ^4 version: 4.3.20 - '@types/deasync': - specifier: ~0.1.5 - version: 0.1.5 '@types/eslint': specifier: ~9.6.1 version: 9.6.1 @@ -69,9 +66,6 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 - deasync: - specifier: ^0.1.30 - version: 0.1.30 downlevel-dts: specifier: ^0.11.0 version: 0.11.0 @@ -403,9 +397,6 @@ packages: '@types/chai@4.3.20': resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} - '@types/deasync@0.1.5': - resolution: {integrity: sha512-mLov/tw+fOX4ZsrT9xuHOJv8xToOpNsp6W4gp8VDHy2qniJ58izyOzHlisnz5r8HdZ+WItDHtANWZy/W0JEJwg==} - '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -800,9 +791,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1093,10 +1081,6 @@ packages: date-now@0.1.4: resolution: {integrity: sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==} - deasync@0.1.30: - resolution: {integrity: sha512-OaAjvEQuQ9tJsKG4oHO9nV1UHTwb2Qc2+fadB0VeVtD0Z9wiG1XPGLJ4W3aLhAoQSYTaLROFRbd5X20Dkzf7MQ==} - engines: {node: '>=0.11.0'} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1616,9 +1600,6 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filename-reserved-regex@2.0.0: resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} engines: {node: '>=4'} @@ -2414,9 +2395,6 @@ packages: resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==} engines: {node: '>=10'} - node-addon-api@1.7.2: - resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} - node-addon-api@8.3.0: resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} engines: {node: ^18 || ^20 || >= 21} @@ -3939,8 +3917,6 @@ snapshots: '@types/chai@4.3.20': {} - '@types/deasync@0.1.5': {} - '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.6 @@ -4428,10 +4404,6 @@ snapshots: binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -4779,11 +4751,6 @@ snapshots: date-now@0.1.4: {} - deasync@0.1.30: - dependencies: - bindings: 1.5.0 - node-addon-api: 1.7.2 - debug@2.6.9: dependencies: ms: 2.0.0 @@ -5552,8 +5519,6 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-uri-to-path@1.0.0: {} - filename-reserved-regex@2.0.0: {} filenamify@4.3.0: @@ -6409,8 +6374,6 @@ snapshots: dependencies: semver: 7.6.3 - node-addon-api@1.7.2: {} - node-addon-api@8.3.0: {} node-int64@0.4.0: {} diff --git a/src/compat.ts b/src/compat.ts index 7314439d..7d6ce92c 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -327,34 +327,12 @@ class Socket extends EventEmitter { ) } - bindSync(...args: Parameters) { - try { - Object.defineProperty(this, "bindSync", { - value: require("deasync")(this.bind), - }) - } catch (err) { - throw new Error( - "bindSync() has been removed from compatibility mode; " + - "use bind() instead, or add 'deasync' to your project dependencies", - ) - } - - this.bindSync(...args) + bindSync(address: string) { + this._socket.bindSync(address) } - unbindSync(...args: Parameters) { - try { - Object.defineProperty(this, "unbindSync", { - value: require("deasync")(this.unbind), - }) - } catch (err) { - throw new Error( - "unbindSync() has been removed from compatibility mode; " + - "use unbind() instead, or add 'deasync' to your project dependencies", - ) - } - - this.unbindSync(...args) + unbindSync(address: string) { + this._socket.unbindSync(address) } pause() { diff --git a/src/native.ts b/src/native.ts index 8f9c1f0b..83410733 100644 --- a/src/native.ts +++ b/src/native.ts @@ -539,6 +539,26 @@ export declare abstract class Socket { */ bind(address: string): Promise + /** + * Binds the socket to the given address. During {@link bind}() the socket + * cannot be used. Do not call any other methods until the returned promise + * resolves. Make sure to use `await`. + * + * You can use `*` in place of a hostname to bind on all interfaces/addresses, + * and you can use `*` in place of a port to bind to a random port (which can + * be retrieved with {@link lastEndpoint} later). + * + * ```typescript + * socket.bindSync("tcp://127.0.0.1:3456") + * socket.bindSync("tcp://*:3456") // binds on all interfaces + * socket.bindSync("tcp://127.0.0.1:*") // binds on random port + * ``` + * + * @param address Address to bind this socket to. + * @returns Resolved when the socket was successfully bound. + */ + bindSync(address: string): void + /** * Unbinds the socket to the given address. During {@link unbind}() the socket * cannot be used. Do not call any other methods until the returned promise @@ -549,6 +569,16 @@ export declare abstract class Socket { */ unbind(address: string): Promise + /** + * Unbinds the socket to the given address. During {@link unbind}() the socket + * cannot be used. Do not call any other methods until the returned promise + * resolves. Make sure to use `await`. + * + * @param address Address to unbind this socket from. + * @returns Resolved when the socket was successfully unbound. + * */ + unbindSync(address: string): void + /** * Connects to the socket at the given remote address and returns immediately. * The connection will be made asynchronously in the background. diff --git a/src/socket.cc b/src/socket.cc index df8bf45f..e66768e8 100644 --- a/src/socket.cc +++ b/src/socket.cc @@ -407,6 +407,44 @@ Napi::Value Socket::Bind(const Napi::CallbackInfo& info) { return res.Promise(); } +Napi::Value Socket::BindSync(const Napi::CallbackInfo& info) { + Arg::Validator const args{ + Arg::Required("Address must be a string"), + }; + + if (args.ThrowIfInvalid(info)) { + return Env().Undefined(); + } + + if (!ValidateOpen()) { + return Env().Undefined(); + } + + state = Socket::State::Blocked; + auto run_ctx = std::make_shared(info[0].As()); + + while (zmq_bind(socket, run_ctx->address.c_str()) < 0) { + if (zmq_errno() != EINTR) { + run_ctx->error = static_cast(zmq_errno()); + break; + } + } + + state = Socket::State::Open; + endpoints++; + + if (request_close) { + Close(); + } + + if (run_ctx->error != 0) { + ErrnoException(Env(), static_cast(run_ctx->error), run_ctx->address) + .ThrowAsJavaScriptException(); + } + + return Env().Undefined(); +} + Napi::Value Socket::Unbind(const Napi::CallbackInfo& info) { Arg::Validator const args{ Arg::Required("Address must be a string"), @@ -463,6 +501,44 @@ Napi::Value Socket::Unbind(const Napi::CallbackInfo& info) { return res.Promise(); } +Napi::Value Socket::UnbindSync(const Napi::CallbackInfo& info) { + Arg::Validator const args{ + Arg::Required("Address must be a string"), + }; + + if (args.ThrowIfInvalid(info)) { + return Env().Undefined(); + } + + if (!ValidateOpen()) { + return Env().Undefined(); + } + + state = Socket::State::Blocked; + auto run_ctx = std::make_shared(info[0].As()); + + while (zmq_unbind(socket, run_ctx->address.c_str()) < 0) { + if (zmq_errno() != EINTR) { + run_ctx->error = static_cast(zmq_errno()); + break; + } + } + + state = Socket::State::Open; + --endpoints; + + if (request_close) { + Close(); + } + + if (run_ctx->error != 0) { + ErrnoException(Env(), static_cast(run_ctx->error), run_ctx->address) + .ThrowAsJavaScriptException(); + } + + return Env().Undefined(); +} + void Socket::Connect(const Napi::CallbackInfo& info) { Arg::Validator const args{ Arg::Required("Address must be a string"), @@ -919,6 +995,10 @@ void Socket::Initialize(Module& module, Napi::Object& exports) { auto proto = { InstanceMethod<&Socket::Bind>("bind"), InstanceMethod<&Socket::Unbind>("unbind"), + + InstanceMethod<&Socket::BindSync>("bindSync"), + InstanceMethod<&Socket::BindSync>("unbindSync"), + InstanceMethod<&Socket::Connect>("connect"), InstanceMethod<&Socket::Disconnect>("disconnect"), InstanceMethod<&Socket::Close>("close"), diff --git a/src/socket.h b/src/socket.h index 601cb3f2..ed1e0cc9 100644 --- a/src/socket.h +++ b/src/socket.h @@ -35,7 +35,10 @@ class Socket : public Napi::ObjectWrap, public Closable { inline void Close(const Napi::CallbackInfo& info); inline Napi::Value Bind(const Napi::CallbackInfo& info); + inline Napi::Value BindSync(const Napi::CallbackInfo& info); + inline Napi::Value Unbind(const Napi::CallbackInfo& info); + inline Napi::Value UnbindSync(const Napi::CallbackInfo& info); inline void Connect(const Napi::CallbackInfo& info); inline void Disconnect(const Napi::CallbackInfo& info);