@noita-ts/pollnet 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.
package/index.d.ts ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * DISCLAIMER:
3
+ * Similar to nxml, this typedef was semi-vibecoded, this will likely be tested
4
+ * more thoroughly though.
5
+ */
6
+
7
+ /** @noSelfInFile */
8
+
9
+ declare namespace pollnet {
10
+ const VERSION: string;
11
+
12
+ type SocketStatus =
13
+ | "unpolled"
14
+ | "invalid"
15
+ | "invalid_handle"
16
+ | "open"
17
+ | "opening"
18
+ | "error"
19
+ | "closed";
20
+
21
+ /** Initializes the pollnet context */
22
+ function init(): void;
23
+
24
+ /** Initializes the pollnet context using the static hack */
25
+ function init_hack_static(): void;
26
+
27
+ /** Shuts down the pollnet context */
28
+ function shutdown(): void;
29
+
30
+ /** Sleeps (blocks) for the given milliseconds */
31
+ function sleep_ms(ms: number): void;
32
+
33
+ /** Generates a nanoid string */
34
+ function nanoid(): string;
35
+
36
+ /** Formats headers as a string */
37
+ function format_headers(
38
+ headers: Record<string, string | string[]> | string,
39
+ ): string;
40
+
41
+ /** Parses headers from a string */
42
+ function parse_headers(headers: string): Record<string, string>;
43
+
44
+ /** Parses HTTP method line */
45
+ function parse_method(
46
+ line: string,
47
+ ): LuaMultiReturn<
48
+ [method: string, path: string, query: Record<string, string>]
49
+ >;
50
+
51
+ /** Wraps a request handler for HTTP server */
52
+ function wrap_req_handler(
53
+ handler: (req: {
54
+ addr: string;
55
+ method: string;
56
+ path: string;
57
+ query: Record<string, string>;
58
+ headers: Record<string, string>;
59
+ body: string;
60
+ raw: string[];
61
+ }) => {
62
+ status?: string;
63
+ headers?: Record<string, string | string[]>;
64
+ body?: string;
65
+ },
66
+ expose_errors?: boolean,
67
+ ): (req_sock: Socket, addr: string) => void;
68
+
69
+ /** Opens a websocket client */
70
+ function open_ws(url: string): Socket;
71
+
72
+ /** Listens for websocket connections */
73
+ function listen_ws(
74
+ addr: string,
75
+ callback?: (sock: Socket, addr: string) => void,
76
+ ): Socket;
77
+
78
+ /** Opens a TCP client */
79
+ function open_tcp(addr: string): Socket;
80
+
81
+ /** Listens for TCP connections */
82
+ function listen_tcp(
83
+ addr: string,
84
+ callback?: (sock: Socket, addr: string) => void,
85
+ ): Socket;
86
+
87
+ /** Serves HTTP (static or dynamic) */
88
+ function serve_http(addr: string, dir?: string): Socket;
89
+
90
+ /** Serves dynamic HTTP */
91
+ function serve_dynamic_http(
92
+ addr: string,
93
+ keep_alive?: boolean,
94
+ callback?: (sock: Socket, addr: string) => void,
95
+ ): Socket;
96
+
97
+ /** Performs an HTTP GET */
98
+ function http_get(
99
+ url: string,
100
+ headers?: Record<string, string | string[]> | string,
101
+ ret_body_only?: boolean,
102
+ ): Socket;
103
+
104
+ /** Performs an HTTP POST */
105
+ function http_post(
106
+ url: string,
107
+ headers?: Record<string, string | string[]> | string,
108
+ body?: string,
109
+ ret_body_only?: boolean,
110
+ ): Socket;
111
+
112
+ function Socket(): Socket;
113
+
114
+ interface Socket {
115
+ /** Opens a websocket client */
116
+ open_ws(url: string): this;
117
+
118
+ /** Opens a TCP client */
119
+ open_tcp(addr: string): this;
120
+
121
+ /** Serves HTTP (static or dynamic) */
122
+ serve_http(addr: string, dir?: string): this;
123
+
124
+ /** Adds a virtual file to HTTP server */
125
+ add_virtual_file(filename: string, filedata: string): void;
126
+
127
+ /** Removes a virtual file from HTTP server */
128
+ remove_virtual_file(filename: string): void;
129
+
130
+ /** Listens for websocket connections */
131
+ listen_ws(
132
+ addr: string,
133
+ callback?: (sock: Socket, addr: string) => void,
134
+ ): this;
135
+
136
+ /** Listens for TCP connections */
137
+ listen_tcp(
138
+ addr: string,
139
+ callback?: (sock: Socket, addr: string) => void,
140
+ ): this;
141
+
142
+ /** Serves dynamic HTTP */
143
+ serve_dynamic_http(
144
+ addr: string,
145
+ keep_alive?: boolean,
146
+ callback?: (sock: Socket, addr: string) => void,
147
+ ): this;
148
+
149
+ /** Sets a connection handler */
150
+ on_connection(callback: (sock: Socket, addr: string) => void): this;
151
+
152
+ /** Polls for socket events */
153
+ poll(): LuaMultiReturn<[true, undefined]> | LuaMultiReturn<[false, string]>;
154
+
155
+ /** Awaits a message (coroutine.yield) */
156
+ await():
157
+ | LuaMultiReturn<[string, undefined]>
158
+ | LuaMultiReturn<[false, string]>;
159
+
160
+ /** Awaits N messages */
161
+ await_n(
162
+ count: number,
163
+ ): LuaMultiReturn<[string[], undefined]> | LuaMultiReturn<[false, string]>;
164
+
165
+ /** Gets the last message */
166
+ last_message(): string | undefined;
167
+
168
+ /** Gets the status */
169
+ status(): SocketStatus | undefined;
170
+
171
+ /** Sends a string message */
172
+ send(msg: string): void;
173
+
174
+ /** Sends a binary message */
175
+ send_binary(msg: string): void;
176
+
177
+ /** Closes the socket */
178
+ close(): void;
179
+
180
+ /** Is HTTP server */
181
+ is_http_server?: boolean;
182
+
183
+ /** Parent socket (for server connections) */
184
+ parent?: Socket;
185
+
186
+ /** Remote address (for server connections) */
187
+ remote_addr?: string;
188
+
189
+ /** Timeout for await */
190
+ timeout?: number;
191
+ }
192
+
193
+ function Reactor(): Reactor;
194
+
195
+ interface Reactor {
196
+ /** Initializes a reactor */
197
+ init(): void;
198
+
199
+ /** Runs a coroutine thread */
200
+ run(thread_body: (self: Reactor) => Promise<void> | void): void;
201
+
202
+ /** Runs a server with client handler */
203
+ run_server(
204
+ server_sock: Socket,
205
+ client_body: (sock: Socket, addr: string) => Promise<void> | void,
206
+ ): void;
207
+
208
+ /** Logs messages */
209
+ log(...args: any[]): void;
210
+
211
+ /** Updates reactor (runs threads) */
212
+ update(): number;
213
+ }
214
+ }
215
+
216
+ export default pollnet;
package/index.lua ADDED
@@ -0,0 +1,490 @@
1
+ -- From https://github.com/probable-basilisk/pollnet/commit/b9361aabd82e568d7e0c6b00bbe991e3dc8c56bf
2
+ -- with minimal adjustment to use NoitaTS-specific path for ffi.load
3
+
4
+ -- pollnet bindings for luajit+ffi
5
+
6
+ local API_VERSION = "1.0.0"
7
+
8
+ local ffi = require("ffi")
9
+ ffi.cdef[[
10
+ typedef struct pollnet_ctx pollnet_ctx;
11
+ typedef uint64_t sockethandle_t;
12
+ typedef uint32_t socketstatus_t;
13
+
14
+ const char* pollnet_version();
15
+ bool pollnet_handle_is_valid(sockethandle_t handle);
16
+ sockethandle_t pollnet_invalid_handle();
17
+
18
+ pollnet_ctx* pollnet_init();
19
+ pollnet_ctx* pollnet_get_or_init_static();
20
+ void pollnet_shutdown(pollnet_ctx* ctx);
21
+ sockethandle_t pollnet_open_tcp(pollnet_ctx* ctx, const char* addr);
22
+ sockethandle_t pollnet_listen_tcp(pollnet_ctx* ctx, const char* addr);
23
+ sockethandle_t pollnet_open_ws(pollnet_ctx* ctx, const char* url);
24
+ sockethandle_t pollnet_simple_http_get(pollnet_ctx* ctx, const char* url, const char* headers, bool ret_body_only);
25
+ sockethandle_t pollnet_simple_http_post(pollnet_ctx* ctx, const char* url, const char* headers, const char* data, uint32_t datasize, bool ret_body_only);
26
+ void pollnet_close(pollnet_ctx* ctx, sockethandle_t handle);
27
+ void pollnet_close_all(pollnet_ctx* ctx);
28
+ void pollnet_send(pollnet_ctx* ctx, sockethandle_t handle, const char* msg);
29
+ void pollnet_send_binary(pollnet_ctx* ctx, sockethandle_t handle, const unsigned char* msg, uint32_t msgsize);
30
+ socketstatus_t pollnet_update(pollnet_ctx* ctx, sockethandle_t handle);
31
+ socketstatus_t pollnet_update_blocking(pollnet_ctx* ctx, sockethandle_t handle);
32
+ uint32_t pollnet_get_data_size(pollnet_ctx* ctx, sockethandle_t handle);
33
+ uint32_t pollnet_get_data(pollnet_ctx* ctx, sockethandle_t handle, char* dest, uint32_t dest_size);
34
+ const uint8_t* pollnet_unsafe_get_data_ptr(pollnet_ctx* ctx, sockethandle_t handle);
35
+ void pollnet_clear_data(pollnet_ctx* ctx, sockethandle_t handle);
36
+ uint32_t pollnet_get_error(pollnet_ctx* ctx, sockethandle_t handle, char* dest, uint32_t dest_size);
37
+ sockethandle_t pollnet_get_connected_client_handle(pollnet_ctx* ctx, sockethandle_t handle);
38
+ sockethandle_t pollnet_listen_ws(pollnet_ctx* ctx, const char* addr);
39
+ sockethandle_t pollnet_serve_static_http(pollnet_ctx* ctx, const char* addr, const char* serve_dir);
40
+ sockethandle_t pollnet_serve_dynamic_http(pollnet_ctx* ctx, const char* addr, bool keep_alive);
41
+ sockethandle_t pollnet_serve_http(pollnet_ctx* ctx, const char* addr);
42
+ void pollnet_add_virtual_file(pollnet_ctx* ctx, sockethandle_t handle, const char* filename, const char* filedata, uint32_t filesize);
43
+ void pollnet_remove_virtual_file(pollnet_ctx* ctx, sockethandle_t handle, const char* filename);
44
+ uint32_t pollnet_get_nanoid(char* dest, uint32_t dest_size);
45
+ void pollnet_sleep_ms(uint32_t milliseconds);
46
+ ]]
47
+
48
+ --- @noita-ts-include ./pollnet.dll
49
+ local pollnet = ffi.load("mods/" .. require("$mod").MOD_ID .. "/lua_modules/@noita-ts/pollnet/pollnet.dll")
50
+
51
+ local POLLNET_VERSION = ffi.string(pollnet.pollnet_version())
52
+
53
+ do
54
+ local function split_version(v)
55
+ local major, minor, patch = v:match("(%d+)%.(%d+)%.(%d+)")
56
+ return tonumber(major), tonumber(minor), tonumber(patch)
57
+ end
58
+
59
+ local maj_req, min_req, pat_req = split_version(API_VERSION)
60
+ local maj_dll, min_dll, pat_dll = split_version(POLLNET_VERSION)
61
+ if maj_dll ~= maj_req then
62
+ error("Incompatible Pollnet binary: expected " .. API_VERSION
63
+ .. " got " .. POLLNET_VERSION)
64
+ end
65
+ if (min_dll < min_req) or (min_dll == min_req and pat_dll < pat_req) then
66
+ error("Incompatible Pollnet binary: expected " .. API_VERSION
67
+ .. " got " .. POLLNET_VERSION)
68
+ end
69
+ end
70
+
71
+ local POLLNET_RESULT_CODES = {
72
+ [0] = "invalid_handle",
73
+ [1] = "error",
74
+ [2] = "closed",
75
+ [3] = "opening",
76
+ [4] = "nodata",
77
+ [5] = "hasdata",
78
+ [6] = "newclient"
79
+ }
80
+
81
+ local _ctx = nil
82
+
83
+ local function init_ctx()
84
+ if _ctx then return end
85
+ _ctx = ffi.gc(pollnet.pollnet_init(), pollnet.pollnet_shutdown)
86
+ assert(_ctx ~= nil)
87
+ end
88
+
89
+ local function init_ctx_hack_static()
90
+ if _ctx then return end
91
+ _ctx = pollnet.pollnet_get_or_init_static()
92
+ assert(_ctx ~= nil)
93
+ pollnet.pollnet_close_all(_ctx)
94
+ end
95
+
96
+ local function shutdown_ctx()
97
+ if not _ctx then return end
98
+ pollnet.pollnet_shutdown(ffi.gc(_ctx, nil))
99
+ _ctx = nil
100
+ end
101
+
102
+ local socket_mt = {}
103
+ local function Socket()
104
+ return setmetatable({}, {__index = socket_mt})
105
+ end
106
+
107
+ function socket_mt:_from_handle(handle)
108
+ init_ctx()
109
+ if self._socket then self:close() end
110
+ self._socket = handle
111
+ self._status = "unpolled"
112
+ return self
113
+ end
114
+
115
+ function socket_mt:_open(opener, ...)
116
+ init_ctx()
117
+ if self._socket then self:close() end
118
+ self._socket = opener(_ctx, ...)
119
+ self._status = "unpolled"
120
+ return self
121
+ end
122
+
123
+ local function format_headers(headers)
124
+ if type(headers) == 'string' then return headers end
125
+ if type(headers) ~= 'table' then
126
+ error("HTTP headers must be table|string, got: " .. tostring(headers))
127
+ end
128
+ local keys = {}
129
+ for name, _ in pairs(headers) do
130
+ table.insert(keys, name)
131
+ end
132
+ table.sort(keys)
133
+ local frags = {}
134
+ for idx, name in ipairs(keys) do
135
+ local val = headers[name]
136
+ if type(val) == 'string' then
137
+ table.insert(frags, ("%s:%s"):format(name, val))
138
+ else -- assume table representing a duplicated header
139
+ for _, subval in ipairs(val) do
140
+ table.insert(frags, ("%s:%s"):format(name, subval))
141
+ end
142
+ end
143
+ end
144
+ return table.concat(frags, "\n")
145
+ end
146
+
147
+ local function parse_headers(headers_str)
148
+ local headers = {}
149
+ for line in headers_str:gmatch("[^\n]+") do
150
+ local key, val = line:match("^([^:]*):(.*)$")
151
+ if key then headers[key:lower()] = val end
152
+ end
153
+ return headers
154
+ end
155
+
156
+ local function parse_query(s)
157
+ local queries = {[1]=s}
158
+ for k, v in s:gmatch("([^=&]+)=([^&]+)") do
159
+ queries[k] = v
160
+ end
161
+ return queries
162
+ end
163
+
164
+ local function parse_method(s)
165
+ local method, path, query = s:match("^(%w+) ([^?]+)%??(.*)$")
166
+ local queries = parse_query(query)
167
+ return method, path, queries
168
+ end
169
+
170
+ function socket_mt:http_get(url, headers, ret_body_only)
171
+ headers = format_headers(headers or "")
172
+ ret_body_only = not not ret_body_only
173
+ return self:_open(
174
+ pollnet.pollnet_simple_http_get,
175
+ url,
176
+ headers,
177
+ ret_body_only
178
+ )
179
+ end
180
+
181
+ function socket_mt:http_post(url, headers, body, ret_body_only)
182
+ body = body or ""
183
+ headers = format_headers(headers or {
184
+ ["content-type"] = "application/x-www-form-urlencoded"
185
+ })
186
+ ret_body_only = not not ret_body_only
187
+ return self:_open(
188
+ pollnet.pollnet_simple_http_post,
189
+ url,
190
+ headers,
191
+ body,
192
+ #body,
193
+ ret_body_only
194
+ )
195
+ end
196
+
197
+ function socket_mt:open_ws(url)
198
+ return self:_open(pollnet.pollnet_open_ws, url)
199
+ end
200
+
201
+ function socket_mt:open_tcp(addr)
202
+ return self:_open(pollnet.pollnet_open_tcp, addr)
203
+ end
204
+
205
+ function socket_mt:serve_http(addr, dir)
206
+ self.is_http_server = true
207
+ if dir and dir ~= "" then
208
+ return self:_open(pollnet.pollnet_serve_static_http, addr, dir)
209
+ else
210
+ return self:_open(pollnet.pollnet_serve_http, addr)
211
+ end
212
+ end
213
+
214
+ function socket_mt:add_virtual_file(filename, filedata)
215
+ assert(filedata and type(filedata) == 'string', "filedata must be provided as string!")
216
+ if filename:sub(1,1) ~= "/" then
217
+ -- url paths start from root at "/"
218
+ filename = "/" .. filename
219
+ end
220
+ local dsize = #filedata
221
+ pollnet.pollnet_add_virtual_file(_ctx, self._socket, filename, filedata, dsize)
222
+ end
223
+
224
+ function socket_mt:remove_virtual_file(filename)
225
+ pollnet.pollnet_remove_virtual_file(_ctx, self._socket, filename)
226
+ end
227
+
228
+ function socket_mt:listen_ws(addr, callback)
229
+ if callback then self:on_connection(callback) end
230
+ return self:_open(pollnet.pollnet_listen_ws, addr)
231
+ end
232
+
233
+ function socket_mt:listen_tcp(addr, callback)
234
+ if callback then self:on_connection(callback) end
235
+ return self:_open(pollnet.pollnet_listen_tcp, addr)
236
+ end
237
+
238
+ function socket_mt:serve_dynamic_http(addr, keep_alive, callback)
239
+ if callback then self:on_connection(callback) end
240
+ return self:_open(pollnet.pollnet_serve_dynamic_http, addr, keep_alive or false)
241
+ end
242
+
243
+ function socket_mt:on_connection(f)
244
+ self._on_connection = f
245
+ return self
246
+ end
247
+
248
+ function socket_mt:_get_message()
249
+ local msg_size = pollnet.pollnet_get_data_size(_ctx, self._socket)
250
+ if msg_size > 0 then
251
+ -- Note: unsafe_get_data_ptr requires careful consideration to use safely!
252
+ -- Here we are OK because ffi.string copies the data to a new Lua string,
253
+ -- so we only hang on to the pointer long enough for the copy.
254
+ local raw_pointer = pollnet.pollnet_unsafe_get_data_ptr(_ctx, self._socket)
255
+ if raw_pointer == nil then
256
+ error("Impossible situation: msg_size > 0 but null data pointer")
257
+ end
258
+ return ffi.string(raw_pointer, msg_size)
259
+ else
260
+ return ""
261
+ end
262
+ end
263
+
264
+ function socket_mt:poll()
265
+ self._last_message = nil
266
+ if not self._socket then
267
+ self._status = "invalid"
268
+ return false, "invalid"
269
+ end
270
+ local res = POLLNET_RESULT_CODES[pollnet.pollnet_update(_ctx, self._socket)] or "error"
271
+ self._status = res
272
+ if res == "hasdata" then
273
+ self._status = "open"
274
+ self._last_message = self:_get_message()
275
+ return true, self._last_message
276
+ elseif res == "nodata" then
277
+ self._status = "open"
278
+ return true
279
+ elseif res == "opening" then
280
+ self._status = "opening"
281
+ return true
282
+ elseif res == "error" then
283
+ self._status = "error"
284
+ self._last_message = self:_get_message()
285
+ return false, self._last_message
286
+ elseif res == "closed" then
287
+ self._status = "closed"
288
+ self._last_message = "closed"
289
+ return false, "closed"
290
+ elseif res == "newclient" then
291
+ self._status = "open"
292
+ local client_addr = self:_get_message()
293
+ local client_handle = pollnet.pollnet_get_connected_client_handle(_ctx, self._socket)
294
+ assert(client_handle > 0)
295
+ local client_sock = Socket():_from_handle(client_handle)
296
+ client_sock.parent = self
297
+ client_sock.remote_addr = client_addr
298
+ if self._on_connection then
299
+ self._on_connection(client_sock, client_addr)
300
+ else
301
+ print("Incoming connection but no :on_connection handler! Just closing it!")
302
+ client_sock:close()
303
+ end
304
+ return true
305
+ end
306
+ end
307
+
308
+ function socket_mt:await()
309
+ local yield_count = 0
310
+ while true do
311
+ if self.timeout and (yield_count > self.timeout) then
312
+ return false, "timeout"
313
+ end
314
+ local happy, msg = self:poll()
315
+ if not happy then
316
+ self:close()
317
+ return false, "error: " .. tostring(msg)
318
+ end
319
+ if msg then return msg end
320
+ yield_count = yield_count + 1
321
+ coroutine.yield()
322
+ end
323
+ end
324
+
325
+ function socket_mt:await_n(count)
326
+ local parts = {}
327
+ for idx = 1, count do
328
+ local part, err = self:await()
329
+ if not part then return false, err end
330
+ parts[idx] = part
331
+ end
332
+ return parts
333
+ end
334
+
335
+ function socket_mt:last_message()
336
+ return self._last_message
337
+ end
338
+
339
+ function socket_mt:status()
340
+ return self._status
341
+ end
342
+
343
+ function socket_mt:send(msg)
344
+ assert(self._socket)
345
+ assert(type(msg) == 'string', "Argument to send must be a string")
346
+ pollnet.pollnet_send(_ctx, self._socket, msg)
347
+ end
348
+
349
+ function socket_mt:send_binary(msg)
350
+ assert(self._socket)
351
+ assert(type(msg) == 'string', "Argument to send must be a string")
352
+ pollnet.pollnet_send_binary(_ctx, self._socket, msg, #msg)
353
+ end
354
+
355
+ function socket_mt:close()
356
+ if not self._socket then return end
357
+ pollnet.pollnet_close(_ctx, self._socket)
358
+ self._socket = nil
359
+ end
360
+
361
+ local function get_nanoid()
362
+ local _id_scratch = ffi.new("int8_t[?]", 128)
363
+ local msg_size = pollnet.pollnet_get_nanoid(_id_scratch, 128)
364
+ return ffi.string(_id_scratch, msg_size)
365
+ end
366
+
367
+ local function sleep_ms(ms)
368
+ pollnet.pollnet_sleep_ms(ms)
369
+ end
370
+
371
+ local reactor_mt = {}
372
+ local function Reactor()
373
+ local ret = setmetatable({}, {__index = reactor_mt})
374
+ ret:init()
375
+ return ret
376
+ end
377
+
378
+ function reactor_mt:init()
379
+ self.threads = {}
380
+ end
381
+
382
+ function reactor_mt:run(thread_body)
383
+ local thread = coroutine.create(function()
384
+ thread_body(self)
385
+ end)
386
+ self.threads[thread] = true
387
+ end
388
+
389
+ function reactor_mt:run_server(server_sock, client_body)
390
+ server_sock:on_connection(function(client_sock, addr)
391
+ self:run(function()
392
+ client_body(client_sock, addr)
393
+ end)
394
+ end)
395
+ self:run(function()
396
+ while true do server_sock:await() end
397
+ end)
398
+ end
399
+
400
+ function reactor_mt:log(...)
401
+ print(...)
402
+ end
403
+
404
+ function reactor_mt:update()
405
+ local live_count = 0
406
+ local cur_threads = self.threads
407
+ self.threads = {}
408
+ for thread, _ in pairs(cur_threads) do
409
+ if coroutine.status(thread) == "dead" then
410
+ cur_threads[thread] = nil
411
+ else
412
+ live_count = live_count + 1
413
+ local happy, err = coroutine.resume(thread)
414
+ if not happy then self:log("Error", err) end
415
+ end
416
+ end
417
+ for thread, _ in pairs(self.threads) do
418
+ live_count = live_count + 1
419
+ cur_threads[thread] = true
420
+ end
421
+ self.threads = cur_threads
422
+ return live_count
423
+ end
424
+
425
+ local function invoke_handler(handler, req, expose_errors)
426
+ local happy, res = pcall(handler, req)
427
+ if happy then
428
+ return res
429
+ else
430
+ return {
431
+ status = "500",
432
+ body = (expose_errors and tostring(res)) or "Internal Error"
433
+ }
434
+ end
435
+ end
436
+
437
+ local function wrap_req_handler(handler, expose_errors)
438
+ return function(req_sock, addr)
439
+ while true do
440
+ local raw_req = req_sock:await_n(3)
441
+ if not raw_req then break end
442
+ local method, path, query = parse_method(raw_req[1])
443
+ local headers = parse_headers(raw_req[2])
444
+ local reply = invoke_handler(handler, {
445
+ addr = addr,
446
+ method = method,
447
+ path = path,
448
+ query = query,
449
+ headers = headers,
450
+ body = raw_req[3],
451
+ raw = raw_req
452
+ }, expose_errors)
453
+ req_sock:send(reply.status or "404")
454
+ req_sock:send(format_headers(reply.headers or {}))
455
+ req_sock:send_binary(reply.body or "")
456
+ end
457
+ req_sock:close()
458
+ end
459
+ end
460
+
461
+ local exports = {
462
+ VERSION = POLLNET_VERSION,
463
+ init = init_ctx,
464
+ init_hack_static = init_ctx_hack_static,
465
+ shutdown = shutdown_ctx,
466
+ Socket = Socket,
467
+ Reactor = Reactor,
468
+ pollnet = pollnet,
469
+ nanoid = get_nanoid,
470
+ sleep_ms = sleep_ms,
471
+ format_headers = format_headers,
472
+ parse_headers = parse_headers,
473
+ parse_method = parse_method,
474
+ wrap_req_handler = wrap_req_handler
475
+ }
476
+
477
+ local fnames = {
478
+ "open_ws", "listen_ws", "open_tcp", "listen_tcp",
479
+ "serve_http", "serve_dynamic_http", "http_get", "http_post"
480
+ }
481
+ for _, name in ipairs(fnames) do
482
+ exports[name] = function(...)
483
+ local sock = Socket()
484
+ return sock[name](sock, ...)
485
+ end
486
+ end
487
+
488
+ exports.default = exports
489
+
490
+ return exports
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@noita-ts/pollnet",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "index.lua",
6
+ "index.d.ts",
7
+ "pollnet.dll",
8
+ "pollnet-license.txt"
9
+ ],
10
+ "main": "index",
11
+ "type": "module",
12
+ "types": "index.d.ts",
13
+ "scripts": {
14
+ "prepublishOnly": "tsc -p ."
15
+ },
16
+ "dependencies": {
17
+ "@typescript-to-lua/language-extensions": "^1.19.0"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.9.3"
21
+ }
22
+ }
package/pollnet.dll ADDED
Binary file
package/readme.md ADDED
@@ -0,0 +1,9 @@
1
+ # @noita-ts/pollnet
2
+ A [noita-ts](https://github.com/necauqua/noita-ts) package and type definitions for the
3
+ [pollnet](https://github.com/probable-basilisk/pollnet) library.
4
+
5
+ Note that this does not work with generic TypeScriptToLua!
6
+
7
+ It only contains the 32-bit `pollent.dll` (windows) binary, uses a
8
+ noita-specific way of loading it and noita-ts itself bundles it into the final
9
+ mod, the vanilla tstl bundler ignores non-lua files.