myde-unix-socket 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ import { EventEmitter } from "node:events";
2
+ export interface USocketOptions {
3
+ fd?: number;
4
+ path?: string;
5
+ }
6
+ export interface USocketWriteChunk {
7
+ data?: Buffer;
8
+ fds?: number[];
9
+ }
10
+ export interface ReadWithFdsResult {
11
+ data: Buffer | null;
12
+ fds: number[];
13
+ }
14
+ export declare class USocket {
15
+ fd?: number;
16
+ private _wrap;
17
+ constructor(opts?: USocketOptions | string);
18
+ connect(opts: USocketOptions | string): void;
19
+ write(data: Buffer, fds?: number[]): number;
20
+ read(size: number): Buffer | null;
21
+ readWithFds(size: number): ReadWithFdsResult | null;
22
+ shutdown(): void;
23
+ close(): void;
24
+ }
25
+ export declare class UServer extends EventEmitter {
26
+ fd?: number;
27
+ listening: boolean;
28
+ paused: boolean;
29
+ private _wrap;
30
+ constructor();
31
+ listen(path: string, backlog?: number, cb?: () => void): void;
32
+ listen(path: {
33
+ path: string;
34
+ backlog?: number;
35
+ }, cb?: () => void): void;
36
+ accept(): USocket | null;
37
+ pause(): void;
38
+ resume(): void;
39
+ close(): void;
40
+ }
41
+ declare const _default: {
42
+ USocket: typeof USocket;
43
+ UServer: typeof UServer;
44
+ };
45
+ export default _default;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAyB3C,MAAM,WAAW,cAAc;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;CACd;AAED,qBAAa,OAAO;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,KAAK,CAAa;gBAEd,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM;IAS1C,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI;IAiB5C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM;IAO3C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKjC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAKnD,QAAQ,IAAI,IAAI;IAKhB,KAAK,IAAI,IAAI;CAKb;AAED,qBAAa,OAAQ,SAAQ,YAAY;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAS;IAC3B,MAAM,EAAE,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAa;;IAM1B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAC7D,MAAM,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IA8BvE,MAAM,IAAI,OAAO,GAAG,IAAI;IAOxB,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAId,KAAK,IAAI,IAAI;CAKb;;;;;AAED,wBAGE"}
package/dist/index.mjs ADDED
@@ -0,0 +1,67 @@
1
+ import { EventEmitter as e } from "node:events";
2
+ //#endregion
3
+ //#region src/index.ts
4
+ var t = (/* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
5
+ if (typeof require < "u") return require.apply(this, arguments);
6
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
7
+ }))("../index.node"), n = class {
8
+ constructor(e) {
9
+ this._wrap = null, typeof e == "string" && (e = { path: e }), (e?.fd || e?.path) && this.connect(e);
10
+ }
11
+ connect(e) {
12
+ if (this._wrap) throw Error("connect on already connected USocket");
13
+ if (typeof e == "string" && (e = { path: e }), this._wrap = t.USocketWrap(), typeof e.fd == "number") {
14
+ let n = t.USocketWrap_adopt(this._wrap, e.fd);
15
+ this.fd = n >= 0 ? n : void 0;
16
+ } else if (typeof e.path == "string") {
17
+ let n = t.USocketWrap_connect(this._wrap, e.path);
18
+ this.fd = n >= 0 ? n : void 0;
19
+ }
20
+ }
21
+ write(e, n) {
22
+ if (!this._wrap) throw Error("USocket not connected");
23
+ return t.USocketWrap_write(this._wrap, e, n);
24
+ }
25
+ read(e) {
26
+ return this._wrap ? t.USocketWrap_read(this._wrap, e) : null;
27
+ }
28
+ readWithFds(e) {
29
+ return this._wrap ? t.USocketWrap_read_with_fds(this._wrap, e) : null;
30
+ }
31
+ shutdown() {
32
+ this._wrap && t.USocketWrap_shutdown(this._wrap);
33
+ }
34
+ close() {
35
+ this._wrap &&= (t.USocketWrap_close(this._wrap), null);
36
+ }
37
+ }, r = class extends e {
38
+ constructor() {
39
+ super(), this.listening = !1, this.paused = !1, this._wrap = null;
40
+ }
41
+ listen(e, n, r) {
42
+ if (this._wrap || this.listening) throw Error("listen on already listened UServer");
43
+ if (typeof e == "object" ? (n = e.backlog, e = e.path, r = n) : typeof n == "function" && (r = n, n = 0), n ||= 16, typeof e != "string") throw Error("UServer expects valid path");
44
+ typeof r == "function" && this.once("listening", r), this._wrap = t.UServerWrap();
45
+ let i = t.UServerWrap_listen(this._wrap, e, n);
46
+ this.fd = i >= 0 ? i : void 0, this.listening = !0, this.emit("listening"), this.paused || this.resume();
47
+ }
48
+ accept() {
49
+ if (!this._wrap) return null;
50
+ let e = t.UServerWrap_accept(this._wrap);
51
+ return e === null ? null : new n({ fd: e });
52
+ }
53
+ pause() {
54
+ this.paused = !0;
55
+ }
56
+ resume() {
57
+ this.paused = !1;
58
+ }
59
+ close() {
60
+ this._wrap &&= (t.UServerWrap_close(this._wrap), null);
61
+ }
62
+ }, i = {
63
+ USocket: n,
64
+ UServer: r
65
+ };
66
+ //#endregion
67
+ export { r as UServer, n as USocket, i as default };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=socket.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.test.d.ts","sourceRoot":"","sources":["../src/socket.test.ts"],"names":[],"mappings":""}
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "myde-unix-socket",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build:rust": "npm run cargo-build -- --release",
10
+ "build:ts": "vite build",
11
+ "build:types": "tsc --emitDeclarationOnly --outDir dist",
12
+ "build": "npm run build:rust && npm run build:ts && npm run build:types",
13
+ "test:rust": "cargo test",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "test:coverage": "vitest run --coverage",
17
+ "test:all": "npm run test:rust && npm run test",
18
+ "cargo-build": "cargo build --message-format=json-render-diagnostics > cargo.log",
19
+ "cross-build": "cross build --message-format=json-render-diagnostics > cross.log",
20
+ "postcargo-build": "neon dist < cargo.log",
21
+ "postcross-build": "neon dist -m /target < cross.log",
22
+ "debug": "npm run cargo-build --",
23
+ "cross": "npm run cross-build -- --release"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.mjs",
28
+ "require": "./dist/index.cjs"
29
+ },
30
+ "./native": "./index.node"
31
+ },
32
+ "author": "",
33
+ "license": "Apache-2.0",
34
+ "devDependencies": {
35
+ "@neon-rs/cli": "0.1.82",
36
+ "@types/node": "^25.5.0",
37
+ "typescript": "^5.9.3",
38
+ "vite": "^8.0.1",
39
+ "vitest": "^4.1.0"
40
+ }
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,166 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ // 加载原生模块 - 使用动态导入以兼容 CJS 和 ESM
4
+ const native = require("../index.node");
5
+
6
+ interface NativeModule {
7
+ USocketWrap: () => any;
8
+ USocketWrap_connect: (wrap: any, path: string) => number;
9
+ USocketWrap_adopt: (wrap: any, fd: number) => number;
10
+ USocketWrap_write: (wrap: any, data?: Buffer, fds?: number[]) => number;
11
+ USocketWrap_read: (wrap: any, size: number) => Buffer;
12
+ USocketWrap_read_with_fds: (
13
+ wrap: any,
14
+ size: number,
15
+ ) => { data: Buffer | null; fds: number[] };
16
+ USocketWrap_shutdown: (wrap: any) => void;
17
+ USocketWrap_close: (wrap: any) => void;
18
+ UServerWrap: () => any;
19
+ UServerWrap_listen: (wrap: any, path: string, backlog: number) => number;
20
+ UServerWrap_accept: (wrap: any) => number | null;
21
+ UServerWrap_close: (wrap: any) => void;
22
+ }
23
+
24
+ const nativeModule = native as NativeModule;
25
+
26
+ export interface USocketOptions {
27
+ fd?: number;
28
+ path?: string;
29
+ }
30
+
31
+ export interface USocketWriteChunk {
32
+ data?: Buffer;
33
+ fds?: number[];
34
+ }
35
+
36
+ export interface ReadWithFdsResult {
37
+ data: Buffer | null;
38
+ fds: number[];
39
+ }
40
+
41
+ export class USocket {
42
+ fd?: number;
43
+ private _wrap: any = null;
44
+
45
+ constructor(opts?: USocketOptions | string) {
46
+ if (typeof opts === "string") {
47
+ opts = { path: opts };
48
+ }
49
+ if (opts?.fd || opts?.path) {
50
+ this.connect(opts);
51
+ }
52
+ }
53
+
54
+ connect(opts: USocketOptions | string): void {
55
+ if (this._wrap) {
56
+ throw new Error("connect on already connected USocket");
57
+ }
58
+ if (typeof opts === "string") {
59
+ opts = { path: opts };
60
+ }
61
+ this._wrap = nativeModule.USocketWrap();
62
+ if (typeof opts.fd === "number") {
63
+ const result = nativeModule.USocketWrap_adopt(this._wrap, opts.fd);
64
+ this.fd = result >= 0 ? result : undefined;
65
+ } else if (typeof opts.path === "string") {
66
+ const result = nativeModule.USocketWrap_connect(this._wrap, opts.path);
67
+ this.fd = result >= 0 ? result : undefined;
68
+ }
69
+ }
70
+
71
+ write(data: Buffer, fds?: number[]): number {
72
+ if (!this._wrap) {
73
+ throw new Error("USocket not connected");
74
+ }
75
+ return nativeModule.USocketWrap_write(this._wrap, data, fds);
76
+ }
77
+
78
+ read(size: number): Buffer | null {
79
+ if (!this._wrap) return null;
80
+ return nativeModule.USocketWrap_read(this._wrap, size);
81
+ }
82
+
83
+ readWithFds(size: number): ReadWithFdsResult | null {
84
+ if (!this._wrap) return null;
85
+ return nativeModule.USocketWrap_read_with_fds(this._wrap, size);
86
+ }
87
+
88
+ shutdown(): void {
89
+ if (!this._wrap) return;
90
+ nativeModule.USocketWrap_shutdown(this._wrap);
91
+ }
92
+
93
+ close(): void {
94
+ if (!this._wrap) return;
95
+ nativeModule.USocketWrap_close(this._wrap);
96
+ this._wrap = null;
97
+ }
98
+ }
99
+
100
+ export class UServer extends EventEmitter {
101
+ fd?: number;
102
+ listening: boolean = false;
103
+ paused: boolean = false;
104
+ private _wrap: any = null;
105
+
106
+ constructor() {
107
+ super();
108
+ }
109
+
110
+ listen(path: string, backlog?: number, cb?: () => void): void;
111
+ listen(path: { path: string; backlog?: number }, cb?: () => void): void;
112
+ listen(path: any, backlog?: any, cb?: any): void {
113
+ if (this._wrap || this.listening) {
114
+ throw new Error("listen on already listened UServer");
115
+ }
116
+ if (typeof path === "object") {
117
+ backlog = path.backlog;
118
+ path = path.path;
119
+ cb = backlog;
120
+ } else if (typeof backlog === "function") {
121
+ cb = backlog;
122
+ backlog = 0;
123
+ }
124
+ backlog = backlog || 16;
125
+ if (typeof path !== "string") {
126
+ throw new Error("UServer expects valid path");
127
+ }
128
+ if (typeof cb === "function") {
129
+ this.once("listening", cb);
130
+ }
131
+ this._wrap = nativeModule.UServerWrap();
132
+ const result = nativeModule.UServerWrap_listen(this._wrap, path, backlog);
133
+ this.fd = result >= 0 ? result : undefined;
134
+ this.listening = true;
135
+ this.emit("listening");
136
+ if (!this.paused) {
137
+ this.resume();
138
+ }
139
+ }
140
+
141
+ accept(): USocket | null {
142
+ if (!this._wrap) return null;
143
+ const fd = nativeModule.UServerWrap_accept(this._wrap);
144
+ if (fd === null) return null;
145
+ return new USocket({ fd });
146
+ }
147
+
148
+ pause(): void {
149
+ this.paused = true;
150
+ }
151
+
152
+ resume(): void {
153
+ this.paused = false;
154
+ }
155
+
156
+ close(): void {
157
+ if (!this._wrap) return;
158
+ nativeModule.UServerWrap_close(this._wrap);
159
+ this._wrap = null;
160
+ }
161
+ }
162
+
163
+ export default {
164
+ USocket,
165
+ UServer,
166
+ };
package/src/lib.rs ADDED
@@ -0,0 +1 @@
1
+ mod socket;
package/src/socket.rs ADDED
@@ -0,0 +1,334 @@
1
+ use neon::prelude::*;
2
+ use neon::types::buffer::TypedArray;
3
+ use std::cell::RefCell;
4
+ use std::io::{self, IoSlice, IoSliceMut, Read};
5
+ use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
6
+ use std::os::unix::net::{UnixListener, UnixStream};
7
+
8
+ use nix::sys::socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, UnixAddr};
9
+
10
+ pub struct USocketWrap {
11
+ stream: RefCell<Option<UnixStream>>,
12
+ fd: RefCell<Option<RawFd>>,
13
+ }
14
+
15
+ impl Finalize for USocketWrap {}
16
+
17
+ impl USocketWrap {
18
+ pub fn new() -> Self {
19
+ Self {
20
+ stream: RefCell::new(None),
21
+ fd: RefCell::new(None),
22
+ }
23
+ }
24
+
25
+ pub fn connect(&self, path: &str) -> Result<(), io::Error> {
26
+ let stream = UnixStream::connect(path)?;
27
+ *self.fd.borrow_mut() = Some(stream.as_raw_fd());
28
+ *self.stream.borrow_mut() = Some(stream);
29
+ Ok(())
30
+ }
31
+
32
+ pub fn adopt(&self, fd: RawFd) -> Result<(), io::Error> {
33
+ let stream = unsafe { UnixStream::from_raw_fd(fd) };
34
+ *self.fd.borrow_mut() = Some(fd);
35
+ *self.stream.borrow_mut() = Some(stream);
36
+ Ok(())
37
+ }
38
+
39
+ pub fn write(&self, data: Option<&[u8]>, fds: Option<&[RawFd]>) -> Result<usize, io::Error> {
40
+ let stream = self.stream.borrow();
41
+ if let Some(stream) = stream.as_ref() {
42
+ let raw_fd = stream.as_raw_fd();
43
+
44
+ let data_buf = data.unwrap_or(&[]);
45
+ let iov = [IoSlice::new(data_buf)];
46
+
47
+ let written = if let Some(fds) = fds {
48
+ let cmsg = ControlMessage::ScmRights(fds);
49
+ sendmsg::<UnixAddr>(raw_fd, &iov, &[cmsg], MsgFlags::empty(), None)
50
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?
51
+ } else {
52
+ sendmsg::<UnixAddr>(raw_fd, &iov, &[], MsgFlags::empty(), None)
53
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?
54
+ };
55
+
56
+ Ok(written)
57
+ } else {
58
+ Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected"))
59
+ }
60
+ }
61
+
62
+ pub fn read(&self, buf: &mut [u8]) -> Result<usize, io::Error> {
63
+ let mut stream = self.stream.borrow_mut();
64
+ if let Some(stream) = stream.as_mut() {
65
+ stream.read(buf)
66
+ } else {
67
+ Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected"))
68
+ }
69
+ }
70
+
71
+ pub fn read_with_fds(&self, buf: &mut [u8]) -> Result<(usize, Vec<RawFd>), io::Error> {
72
+ let stream = self.stream.borrow();
73
+ if let Some(stream) = stream.as_ref() {
74
+ let raw_fd = stream.as_raw_fd();
75
+ let mut iov = [IoSliceMut::new(buf)];
76
+
77
+ // 分配控制消息缓冲区,足够接收 fd
78
+ let mut cmsg_buf = nix::cmsg_space!([RawFd; 3]);
79
+
80
+ let msg = recvmsg::<UnixAddr>(raw_fd, &mut iov, Some(&mut cmsg_buf), MsgFlags::empty())
81
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
82
+
83
+ let mut fds = Vec::new();
84
+ for cmsg in msg.cmsgs() {
85
+ if let ControlMessageOwned::ScmRights(received_fds) = cmsg {
86
+ fds.extend(received_fds);
87
+ }
88
+ }
89
+
90
+ Ok((msg.bytes, fds))
91
+ } else {
92
+ Err(io::Error::new(io::ErrorKind::NotConnected, "Not connected"))
93
+ }
94
+ }
95
+
96
+ pub fn shutdown(&self) -> Result<(), io::Error> {
97
+ let stream = self.stream.borrow();
98
+ if let Some(stream) = stream.as_ref() {
99
+ stream.shutdown(std::net::Shutdown::Both)?;
100
+ }
101
+ Ok(())
102
+ }
103
+
104
+ pub fn close(&self) {
105
+ *self.stream.borrow_mut() = None;
106
+ *self.fd.borrow_mut() = None;
107
+ }
108
+ }
109
+
110
+ pub struct UServerWrap {
111
+ listener: RefCell<Option<UnixListener>>,
112
+ fd: RefCell<Option<RawFd>>,
113
+ }
114
+
115
+ impl Finalize for UServerWrap {}
116
+
117
+ impl UServerWrap {
118
+ pub fn new() -> Self {
119
+ Self {
120
+ listener: RefCell::new(None),
121
+ fd: RefCell::new(None),
122
+ }
123
+ }
124
+
125
+ pub fn listen(&self, path: &str, backlog: i32) -> Result<(), io::Error> {
126
+ let _ = std::fs::remove_file(path);
127
+ let listener = UnixListener::bind(path)?;
128
+ listener.set_nonblocking(true)?;
129
+ *self.fd.borrow_mut() = Some(listener.as_raw_fd());
130
+ *self.listener.borrow_mut() = Some(listener);
131
+ let _ = backlog;
132
+ Ok(())
133
+ }
134
+
135
+ pub fn accept(&self) -> Result<Option<RawFd>, io::Error> {
136
+ let listener = self.listener.borrow();
137
+ if let Some(listener) = listener.as_ref() {
138
+ match listener.accept() {
139
+ Ok((stream, _)) => {
140
+ let fd = stream.as_raw_fd();
141
+ std::mem::forget(stream);
142
+ Ok(Some(fd))
143
+ }
144
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
145
+ Err(e) => Err(e),
146
+ }
147
+ } else {
148
+ Err(io::Error::new(io::ErrorKind::NotConnected, "Not listening"))
149
+ }
150
+ }
151
+
152
+ pub fn close(&self) {
153
+ *self.listener.borrow_mut() = None;
154
+ *self.fd.borrow_mut() = None;
155
+ }
156
+ }
157
+
158
+ fn usocket_wrap_new(mut cx: FunctionContext) -> JsResult<JsBox<USocketWrap>> {
159
+ Ok(cx.boxed(USocketWrap::new()))
160
+ }
161
+
162
+ fn usocket_wrap_connect(mut cx: FunctionContext) -> JsResult<JsNumber> {
163
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
164
+ let path = cx.argument::<JsString>(1)?.value(&mut cx);
165
+ match usocket_wrap.connect(&path) {
166
+ Ok(_) => {
167
+ let fd = usocket_wrap.fd.borrow();
168
+ Ok(cx.number(fd.unwrap_or(-1) as f64))
169
+ }
170
+ Err(e) => cx.throw_error(format!("Connect failed: {}", e)),
171
+ }
172
+ }
173
+
174
+ fn usocket_wrap_adopt(mut cx: FunctionContext) -> JsResult<JsNumber> {
175
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
176
+ let fd = cx.argument::<JsNumber>(1)?.value(&mut cx) as RawFd;
177
+ match usocket_wrap.adopt(fd) {
178
+ Ok(_) => Ok(cx.number(fd as f64)),
179
+ Err(e) => cx.throw_error(format!("Adopt failed: {}", e)),
180
+ }
181
+ }
182
+
183
+ fn usocket_wrap_write(mut cx: FunctionContext) -> JsResult<JsNumber> {
184
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
185
+ let data_opt = cx.argument_opt(1);
186
+ let fds_opt = cx.argument_opt(2);
187
+
188
+ let data_vec;
189
+ let data_bytes = if let Some(data) = data_opt {
190
+ if data.is_a::<JsBuffer, _>(&mut cx) {
191
+ let buffer = data.downcast_or_throw::<JsBuffer, _>(&mut cx)?;
192
+ data_vec = buffer.as_slice(&cx).to_vec();
193
+ Some(data_vec.as_slice())
194
+ } else {
195
+ None
196
+ }
197
+ } else {
198
+ None
199
+ };
200
+
201
+ let fds_vec;
202
+ let fds_slice = if let Some(fds) = fds_opt {
203
+ if fds.is_a::<JsArray, _>(&mut cx) {
204
+ let array = fds.downcast_or_throw::<JsArray, _>(&mut cx)?;
205
+ let mut vec = Vec::new();
206
+ for i in 0..array.len(&mut cx) {
207
+ let fd = array.get::<JsNumber, _, _>(&mut cx, i)?;
208
+ vec.push(fd.value(&mut cx) as RawFd);
209
+ }
210
+ fds_vec = vec;
211
+ Some(fds_vec.as_slice())
212
+ } else {
213
+ None
214
+ }
215
+ } else {
216
+ None
217
+ };
218
+
219
+ match usocket_wrap.write(data_bytes, fds_slice) {
220
+ Ok(n) => Ok(cx.number(n as f64)),
221
+ Err(e) => cx.throw_error(format!("Write failed: {}", e)),
222
+ }
223
+ }
224
+
225
+ fn usocket_wrap_read(mut cx: FunctionContext) -> JsResult<JsBuffer> {
226
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
227
+ let size = cx.argument::<JsNumber>(1)?.value(&mut cx) as usize;
228
+ let mut buf = vec![0u8; size];
229
+ match usocket_wrap.read(&mut buf) {
230
+ Ok(n) => {
231
+ let mut result = JsBuffer::new(&mut cx, n)?;
232
+ let slice = result.as_mut_slice(&mut cx);
233
+ slice.copy_from_slice(&buf[..n]);
234
+ Ok(result)
235
+ }
236
+ Err(e) => cx.throw_error(format!("Read failed: {}", e)),
237
+ }
238
+ }
239
+
240
+ fn usocket_wrap_read_with_fds(mut cx: FunctionContext) -> JsResult<JsObject> {
241
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
242
+ let size = cx.argument::<JsNumber>(1)?.value(&mut cx) as usize;
243
+ let mut buf = vec![0u8; size];
244
+
245
+ match usocket_wrap.read_with_fds(&mut buf) {
246
+ Ok((n, fds)) => {
247
+ let data = if n > 0 {
248
+ let mut result = JsBuffer::new(&mut cx, n)?;
249
+ let slice = result.as_mut_slice(&mut cx);
250
+ slice.copy_from_slice(&buf[..n]);
251
+ result.as_value(&mut cx)
252
+ } else {
253
+ cx.null().upcast()
254
+ };
255
+
256
+ let fds_array = JsArray::new(&mut cx, fds.len());
257
+ for (i, fd) in fds.iter().enumerate() {
258
+ let fd_js = cx.number(*fd as f64);
259
+ fds_array.set(&mut cx, i as u32, fd_js)?;
260
+ }
261
+
262
+ let obj = JsObject::new(&mut cx);
263
+ obj.set(&mut cx, "data", data)?;
264
+ obj.set(&mut cx, "fds", fds_array)?;
265
+ Ok(obj)
266
+ }
267
+ Err(e) => cx.throw_error(format!("Read with fds failed: {}", e)),
268
+ }
269
+ }
270
+
271
+ fn usocket_wrap_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
272
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
273
+ match usocket_wrap.shutdown() {
274
+ Ok(_) => Ok(cx.undefined()),
275
+ Err(e) => cx.throw_error(format!("Shutdown failed: {}", e)),
276
+ }
277
+ }
278
+
279
+ fn usocket_wrap_close(mut cx: FunctionContext) -> JsResult<JsUndefined> {
280
+ let usocket_wrap = cx.argument::<JsBox<USocketWrap>>(0)?;
281
+ usocket_wrap.close();
282
+ Ok(cx.undefined())
283
+ }
284
+
285
+ fn userver_wrap_new(mut cx: FunctionContext) -> JsResult<JsBox<UServerWrap>> {
286
+ Ok(cx.boxed(UServerWrap::new()))
287
+ }
288
+
289
+ fn userver_wrap_listen(mut cx: FunctionContext) -> JsResult<JsNumber> {
290
+ let userver_wrap = cx.argument::<JsBox<UServerWrap>>(0)?;
291
+ let path = cx.argument::<JsString>(1)?.value(&mut cx);
292
+ let backlog = cx.argument::<JsNumber>(2)?.value(&mut cx) as i32;
293
+ match userver_wrap.listen(&path, backlog) {
294
+ Ok(_) => {
295
+ let fd = userver_wrap.fd.borrow();
296
+ Ok(cx.number(fd.unwrap_or(-1) as f64))
297
+ }
298
+ Err(e) => cx.throw_error(format!("Listen failed: {}", e)),
299
+ }
300
+ }
301
+
302
+ fn userver_wrap_accept(mut cx: FunctionContext) -> JsResult<JsValue> {
303
+ let userver_wrap = cx.argument::<JsBox<UServerWrap>>(0)?;
304
+ match userver_wrap.accept() {
305
+ Ok(Some(fd)) => Ok(cx.number(fd as f64).upcast()),
306
+ Ok(None) => Ok(cx.null().upcast()),
307
+ Err(e) => cx.throw_error(format!("Accept failed: {}", e)),
308
+ }
309
+ }
310
+
311
+ fn userver_wrap_close(mut cx: FunctionContext) -> JsResult<JsUndefined> {
312
+ let userver_wrap = cx.argument::<JsBox<UServerWrap>>(0)?;
313
+ userver_wrap.close();
314
+ Ok(cx.undefined())
315
+ }
316
+
317
+ #[neon::main]
318
+ fn main(mut cx: ModuleContext) -> NeonResult<()> {
319
+ cx.export_function("USocketWrap", usocket_wrap_new)?;
320
+ cx.export_function("USocketWrap_connect", usocket_wrap_connect)?;
321
+ cx.export_function("USocketWrap_adopt", usocket_wrap_adopt)?;
322
+ cx.export_function("USocketWrap_write", usocket_wrap_write)?;
323
+ cx.export_function("USocketWrap_read", usocket_wrap_read)?;
324
+ cx.export_function("USocketWrap_read_with_fds", usocket_wrap_read_with_fds)?;
325
+ cx.export_function("USocketWrap_shutdown", usocket_wrap_shutdown)?;
326
+ cx.export_function("USocketWrap_close", usocket_wrap_close)?;
327
+
328
+ cx.export_function("UServerWrap", userver_wrap_new)?;
329
+ cx.export_function("UServerWrap_listen", userver_wrap_listen)?;
330
+ cx.export_function("UServerWrap_accept", userver_wrap_accept)?;
331
+ cx.export_function("UServerWrap_close", userver_wrap_close)?;
332
+
333
+ Ok(())
334
+ }