Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <stddef.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <tlsuv/websocket.h>#include <uv.h>#include <zlib.h>
#include "ant.h"#include "cfunc.h"#include "ptr.h"#include "list.h"#include "loop.h"#include "common.h"#include "errors.h"#include "inspector.h"#include "internal.h"#include "modules/timer.h"#include "descriptors.h"
#include "http/websocket.h"#include "modules/blob.h"#include "modules/buffer.h"#include "modules/events.h"#include "modules/websocket.h"#include "modules/url.h"#include "net/listener.h"
struct websocket_state_s { ant_value_t obj; ant_value_t request_obj; tlsuv_websocket_t client; uv_connect_t connect_req; ant_conn_t *server_conn; uint64_t inspector_request_id; websocket_state_t *next; websocket_state_t **pprev; uint8_t *fragment_buf; size_t fragment_len; size_t fragment_cap; size_t max_payload_len; z_stream deflate_strm; z_stream inflate_strm; ant_ws_opcode_t fragment_opcode; uint16_t client_close_code; uint8_t ready_state; bool is_client: 1; bool close_emitted: 1; bool client_close_started: 1; bool client_close_was_clean: 1; bool fragmenting: 1; bool fragment_compressed: 1; bool per_message_deflate: 1; bool deflate_ready: 1; bool inflate_ready: 1; bool binary_type_blob: 1;};
enum { WS_HANDLER_OPEN, WS_HANDLER_MESSAGE, WS_HANDLER_CLOSE, WS_HANDLER_ERROR, WS_HANDLER_COUNT };static const char *const ws_handler_types[WS_HANDLER_COUNT] = { "open", "message", "close", "error" };
enum { WS_CONNECTING = 0, WS_OPEN = 1, WS_CLOSING = 2, WS_CLOSED = 3,};
static constexpr uint32_t WS_NATIVE_TAG = Ant_NativeTag("WSOB");static constexpr uint32_t WS_DEFAULT_MAX_PAYLOAD_LEN = 16u * 1024u * 1024u;
static websocket_state_t *websocket_data(ant_value_t value) { return (websocket_state_t *)js_get_native(value, WS_NATIVE_TAG);}
static websocket_state_t *websocket_from_client(tlsuv_websocket_t *client) { return client ? (websocket_state_t *)((char *)client - offsetof(websocket_state_t, client)) : NULL;}
static void websocket_fragment_reset(websocket_state_t *ws) { if (!ws) return; ws->fragment_len = 0; ws->fragment_opcode = 0; ws->fragmenting = false; ws->fragment_compressed = false;}
static void websocket_fragment_clear(websocket_state_t *ws) { if (!ws) return; websocket_fragment_reset(ws); free(ws->fragment_buf); ws->fragment_buf = NULL; ws->fragment_cap = 0;}
static bool websocket_fragment_append(websocket_state_t *ws, const uint8_t *data, size_t len) { if (!ws) return false; if (len > SIZE_MAX - ws->fragment_len) return false; size_t need = ws->fragment_len + len; if (need > ws->fragment_cap) { size_t cap = ws->fragment_cap ? ws->fragment_cap : 1024; while (cap < need) { if (cap > SIZE_MAX / 2) { cap = need; break; } cap *= 2; } uint8_t *next = realloc(ws->fragment_buf, cap); if (!next) return false; ws->fragment_buf = next; ws->fragment_cap = cap; } if (len > 0) memcpy(ws->fragment_buf + ws->fragment_len, data, len); ws->fragment_len = need; return true;}
static void websocket_free_state(websocket_state_t *ws) { if (!ws) return; Ant_List_Remove(ws); websocket_fragment_clear(ws); if (ws->deflate_ready) deflateEnd(&ws->deflate_strm); if (ws->inflate_ready) inflateEnd(&ws->inflate_strm); free(ws);}
static void websocket_sync_state(ant_t *js, websocket_state_t *ws) { if (!ws || !is_object_type(ws->obj)) return; js_set(js, ws->obj, "readyState", js_mknum(ws->ready_state));}
static const uint8_t ws_deflate_flush_tail[] = { 0x00, 0x00, 0xff, 0xff };
static bool websocket_deflate_message(websocket_state_t *ws, const uint8_t *data, size_t len, uint8_t **out, size_t *out_len) { z_stream *strm = &ws->deflate_strm; uint8_t *buf = NULL; uLong bound = 0; int rc = 0;
if (out) *out = NULL; if (out_len) *out_len = 0; if (!out || !out_len) return false;
if (!ws->deflate_ready) { memset(strm, 0, sizeof(*strm)); if (deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) return false; ws->deflate_ready = true; } else if (deflateReset(strm) != Z_OK) return false;
bound = deflateBound(strm, (uLong)len) + sizeof(ws_deflate_flush_tail); buf = malloc((size_t)bound); if (!buf) return false;
strm->next_in = (Bytef *)(uintptr_t)data; strm->avail_in = (uInt)len; strm->next_out = buf; strm->avail_out = (uInt)bound;
rc = deflate(strm, Z_SYNC_FLUSH); if (rc != Z_OK || strm->avail_in != 0) { free(buf); return false; }
*out_len = strm->total_out; if (*out_len >= sizeof(ws_deflate_flush_tail) && memcmp(buf + *out_len - sizeof(ws_deflate_flush_tail), ws_deflate_flush_tail, sizeof(ws_deflate_flush_tail)) == 0) { *out_len -= sizeof(ws_deflate_flush_tail); }
*out = buf; return true;}
static bool websocket_inflate_chunk( z_stream *strm, const uint8_t *in, size_t in_len, size_t max_len, uint8_t **buf, size_t *cap, bool *done) { strm->next_in = (Bytef *)(uintptr_t)in; strm->avail_in = (uInt)in_len;
while (strm->avail_in > 0) { if (strm->total_out == *cap) { size_t next_cap = *cap * 2; if (max_len > 0 && *cap >= max_len) return false; if (next_cap < *cap) return false; if (max_len > 0 && next_cap > max_len) next_cap = max_len; uint8_t *next = realloc(*buf, next_cap); if (!next) return false; *buf = next; *cap = next_cap; }
strm->next_out = *buf + strm->total_out; strm->avail_out = (uInt)(*cap - strm->total_out); int rc = inflate(strm, Z_SYNC_FLUSH); if (rc == Z_BUF_ERROR && strm->avail_in == 0) break; if (rc != Z_OK && rc != Z_STREAM_END) return false; if (rc == Z_STREAM_END) { *done = true; break; } if (strm->avail_out != 0 && strm->avail_in == 0) break; } return true;}
static bool websocket_inflate_message( websocket_state_t *ws, const uint8_t *data, size_t len, size_t max_len, uint8_t **out, size_t *out_len) { z_stream *strm = &ws->inflate_strm; uint8_t *buf = NULL; size_t cap = len > 1024 ? len * 2 : 1024; bool done = false;
if (out) *out = NULL; if (out_len) *out_len = 0; if (!out || !out_len) return false; if (max_len > 0 && cap > max_len) cap = max_len; if (cap == 0) cap = 1;
if (!ws->inflate_ready) { memset(strm, 0, sizeof(*strm)); if (inflateInit2(strm, -15) != Z_OK) return false; ws->inflate_ready = true; } else if (inflateReset(strm) != Z_OK) return false;
buf = malloc(cap); if (!buf) return false;
if (!websocket_inflate_chunk(strm, data, len, max_len, &buf, &cap, &done)) goto fail; if (!done && !websocket_inflate_chunk(strm, ws_deflate_flush_tail, sizeof(ws_deflate_flush_tail), max_len, &buf, &cap, &done)) goto fail;
if (max_len > 0 && strm->total_out > max_len) goto fail; *out_len = strm->total_out; *out = buf; return true;
fail: free(buf); return false;}
static void websocket_emit_simple(ant_t *js, websocket_state_t *ws, int idx) { ant_value_t event = Ant_EventTarget_CreateEvent(js, ws->obj, js->builtins.event_proto, ANT_EVENT_BASE, ws_handler_types[idx]); if (!is_object_type(event)) return; Ant_EventTarget_Dispatch(js, ws->obj, event); js_maybe_drain_microtasks(js);}
static ant_value_t websocket_invalid_this(ant_t *js, ant_value_t this_obj) { return websocket_data(this_obj) ? 0 : js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket");}
ANT_EVENT_HANDLER_ACCESSORS(js_websocket, open, websocket_invalid_this)ANT_EVENT_HANDLER_ACCESSORS(js_websocket, message, websocket_invalid_this)ANT_EVENT_HANDLER_ACCESSORS(js_websocket, close, websocket_invalid_this)ANT_EVENT_HANDLER_ACCESSORS(js_websocket, error, websocket_invalid_this)
static ant_value_t websocket_on_impl(ant_t *js, ant_value_t *args, int nargs, bool once, bool remove) { ant_value_t self = js_getthis(js); if (!websocket_data(self)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket"); if (nargs < 2 || !is_callable(args[1])) return js_mkerr_typed(js, JS_ERR_TYPE, "listener must be a function"); ant_value_t key = js_tostring_val(js, args[0]); if (is_err(key)) return key; if (remove) eventemitter_remove_listener_val(js, self, key, args[1]); else eventemitter_add_listener_val(js, self, key, args[1], once); return self;}
static ant_value_t js_websocket_on(ant_params_t) { return websocket_on_impl(js, args, nargs, false, false); }static ant_value_t js_websocket_once(ant_params_t) { return websocket_on_impl(js, args, nargs, true, false); }static ant_value_t js_websocket_off(ant_params_t) { return websocket_on_impl(js, args, nargs, false, true); }
static void websocket_emit_message(ant_t *js, websocket_state_t *ws, const uint8_t *data, size_t len, bool binary) { ant_value_t event = Ant_EventTarget_CreateEvent(js, ws->obj, js->builtins.message_event_proto, ANT_EVENT_MESSAGE, "message"); if (!is_object_type(event)) return;
GC_ROOT_SAVE(mark, js); GC_ROOT_PIN(js, event);
ant_value_t data_val; if (!binary) data_val = js_mkstr(js, data, len); else if (ws->binary_type_blob) data_val = blob_create(js, data, len, ""); else data_val = create_arraybuffer_copy(js, data, len);
if (!is_err(data_val)) { ant_value_t empty = js_mkstr(js, "", 0); Ant_Event_SetField(js, event, ANT_EVENT_FIELD_MESSAGE_data, data_val); Ant_Event_SetField(js, event, ANT_EVENT_FIELD_MESSAGE_origin, empty); Ant_Event_SetField(js, event, ANT_EVENT_FIELD_MESSAGE_lastEventId, empty); Ant_EventTarget_Dispatch(js, ws->obj, event); } js_maybe_drain_microtasks(js); GC_ROOT_RESTORE(js, mark);}
static void websocket_emit_close(ant_t *js, websocket_state_t *ws, uint16_t code, const char *reason, bool was_clean) { if (!ws || ws->close_emitted) return; ant_inspector_websocket_closed(ws->inspector_request_id); ws->close_emitted = true; ws->ready_state = WS_CLOSED; websocket_fragment_clear(ws); websocket_sync_state(js, ws);
ant_value_t event = Ant_EventTarget_CreateEvent(js, ws->obj, js->builtins.close_event_proto, ANT_EVENT_CLOSE, "close"); if (!is_object_type(event)) return; Ant_Event_SetField(js, event, ANT_EVENT_FIELD_CLOSE_code, js_mknum(code)); Ant_Event_SetField(js, event, ANT_EVENT_FIELD_CLOSE_reason, js_mkstr(js, reason ? reason : "", reason ? strlen(reason) : 0)); Ant_Event_SetField(js, event, ANT_EVENT_FIELD_CLOSE_wasClean, js_bool(was_clean)); Ant_EventTarget_Dispatch(js, ws->obj, event); js_maybe_drain_microtasks(js);}
static void websocket_client_close_cb(uv_handle_t *handle) { ant_t *js = Ant_Loop_Isolate(handle->loop); websocket_state_t *ws = websocket_from_client((tlsuv_websocket_t *)handle); websocket_emit_close(js, ws, ws->client_close_code, "", ws->client_close_was_clean); Ant_List_Remove(ws);}
static void websocket_close_client(websocket_state_t *ws, uint16_t code, bool was_clean) { if (!ws || ws->client_close_started) return; ws->client_close_started = true; ws->client_close_code = code; ws->client_close_was_clean = was_clean; tlsuv_websocket_close(&ws->client, websocket_client_close_cb);}
static void websocket_client_connect_cb(uv_connect_t *req, int status) { ant_t *js = Ant_Loop_Isolate(req->handle->loop); websocket_state_t *ws = websocket_from_client((tlsuv_websocket_t *)req->handle); if (!ws) return;
if (status == 0) { ant_http_header_t upgrade = { "Upgrade", "websocket", NULL }; ant_http_header_t connection = { "Connection", "Upgrade", &upgrade }; ant_inspector_websocket_response(ws->inspector_request_id, 101, "Switching Protocols", &connection); ws->ready_state = WS_OPEN; websocket_sync_state(js, ws); websocket_emit_simple(js, ws, WS_HANDLER_OPEN); return; }
ws->ready_state = WS_CLOSED; ant_inspector_websocket_error(ws->inspector_request_id, uv_strerror(status)); websocket_sync_state(js, ws); websocket_emit_simple(js, ws, WS_HANDLER_ERROR); websocket_close_client(ws, 1006, false);}
static void websocket_client_read_cb(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) { ant_t *js = Ant_Loop_Isolate(handle->loop); websocket_state_t *ws = websocket_from_client((tlsuv_websocket_t *)handle); if (!ws) return;
if (nread > 0) { bool binary = ws->client.last_frame_binary; ant_inspector_websocket_frame_received(ws->inspector_request_id, (const uint8_t *)buf->base, (size_t)nread, binary); websocket_emit_message(js, ws, (const uint8_t *)buf->base, (size_t)nread, binary); return; }
if (nread < 0) { if (nread != UV_EOF) { ant_inspector_websocket_error(ws->inspector_request_id, uv_strerror((int)nread)); websocket_emit_simple(js, ws, WS_HANDLER_ERROR); } ws->ready_state = WS_CLOSING; websocket_sync_state(js, ws); websocket_close_client(ws, nread == UV_EOF ? 1000 : 1006, nread == UV_EOF); }}
static void websocket_write_cb(uv_write_t *req, int status) { free(req->data); free(req);}
static int websocket_client_write_frame(websocket_state_t *ws, ant_ws_opcode_t opcode, const uint8_t *bytes, size_t len) { if (!ws || !ws->client.tr || !ws->client.tr_write) return UV_ENOTCONN;
size_t frame_len = 0; uint8_t *frame = ant_ws_encode_frame(opcode, bytes, len, true, false, &frame_len); if (!frame) return UV_ENOMEM;
uv_write_t *wr = calloc(1, sizeof(*wr)); if (!wr) { free(frame); return UV_ENOMEM; }
wr->data = frame; uv_buf_t buf = uv_buf_init((char *)frame, (unsigned int)frame_len); int rc = ws->client.tr_write(wr, ws->client.tr, &buf, 1, websocket_write_cb); if (rc != 0) { free(frame); free(wr); } return rc;}
static void websocket_server_close_with_code(websocket_state_t *ws, ant_conn_t *conn, uint16_t code, const char *reason) { size_t frame_len = 0; uint8_t *frame = ant_ws_encode_close_frame(code, reason, false, &frame_len); if (frame && conn && !ant_conn_is_closing(conn)) ant_conn_write(conn, (char *)frame, frame_len, NULL, NULL); if (conn) ant_conn_shutdown(conn);}
static void websocket_finalize(ant_t *js, ant_object_t *obj) { (void)js; ant_value_t value = js_obj_from_ptr(obj); websocket_state_t *ws = websocket_data(value); if (!ws) return; js_clear_native(value, WS_NATIVE_TAG); websocket_free_state(ws);}
static websocket_state_t *websocket_state_new(ant_t *js, ant_value_t obj) { websocket_state_t *ws = calloc(1, sizeof(*ws)); if (!ws) return NULL; ws->obj = obj; ws->ready_state = WS_CONNECTING; ws->max_payload_len = WS_DEFAULT_MAX_PAYLOAD_LEN; Ant_List_Insert(&js->active_websockets, ws); return ws;}
static ant_value_t websocket_create_object(ant_t *js) { ant_value_t obj = Ant_Object_CreateBare(js); if (is_object_type(js->builtins.websocket_proto)) js_set_proto_init(obj, js->builtins.websocket_proto); js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_EVENTTARGET)); js_set(js, obj, "bufferedAmount", js_mknum(0)); js_set(js, obj, "extensions", js_mkstr(js, "", 0)); js_set(js, obj, "protocol", js_mkstr(js, "", 0)); js_set_finalizer(obj, websocket_finalize); return obj;}
static ant_value_t websocket_resolve_url(ant_t *js, ant_value_t input) { const char *text = js_getstr(js, input, NULL); url_state_t parsed = {0}; if (!text || Ant_URL_ParseDocumentRelative(js, text, &parsed) != 0) { url_state_clear(&parsed); return js_mkerr_typed(js, JS_ERR_SYNTAX, "Failed to construct 'WebSocket': The URL '%s' is invalid.", text ? text : ""); }
const char *protocol = parsed.protocol ? parsed.protocol : ""; const char *scheme = NULL; size_t skip = 0; if (strcmp(protocol, "ws:") == 0 || strcmp(protocol, "wss:") == 0) scheme = ""; else if (strcmp(protocol, "http:") == 0) { scheme = "ws"; skip = 4; } else if (strcmp(protocol, "https:") == 0) { scheme = "wss"; skip = 5; }
ant_value_t result; if (!scheme) result = js_mkerr_typed( js, JS_ERR_SYNTAX, "Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '%s' is not allowed.", protocol ); else if (parsed.hash && parsed.hash[0]) result = js_mkerr_typed( js, JS_ERR_SYNTAX, "Failed to construct 'WebSocket': The URL contains a fragment identifier ('%s'). Fragment identifiers are not allowed in WebSocket URLs.", parsed.hash ); else { char *href = build_href(&parsed); if (!href) result = js_mkerr(js, "oom"); else { size_t scheme_len = strlen(scheme), rest_len = strlen(href) - skip; char *out = malloc(scheme_len + rest_len + 1); if (!out) result = js_mkerr(js, "oom"); else { memcpy(out, scheme, scheme_len); memcpy(out + scheme_len, href + skip, rest_len + 1); result = js_mkstr(js, out, scheme_len + rest_len); free(out); } free(href); } }
url_state_clear(&parsed); return result;}
static ant_value_t js_websocket_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "WebSocket constructor requires 'new'"); if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "WebSocket URL is required");
ant_value_t url_val = js_tostring_val(js, args[0]); if (is_err(url_val)) return url_val; ant_value_t resolved = websocket_resolve_url(js, url_val); if (is_err(resolved)) return resolved; url_val = resolved; const char *url = js_getstr(js, url_val, NULL);
ant_value_t obj = websocket_create_object(js); websocket_state_t *ws = websocket_state_new(js, obj); if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); ws->is_client = true; js_set_native(obj, ws, WS_NATIVE_TAG); js_set(js, obj, "url", url_val); websocket_sync_state(js, ws); ws->inspector_request_id = ant_inspector_websocket_created(url); ant_inspector_websocket_request(ws->inspector_request_id, NULL);
if (tlsuv_websocket_init(js->loop, &ws->client) != 0) { ws->ready_state = WS_CLOSED; ant_inspector_websocket_error(ws->inspector_request_id, "WebSocket initialization failed"); websocket_sync_state(js, ws); Ant_List_Remove(ws); return obj; }
int rc = tlsuv_websocket_connect( &ws->connect_req, &ws->client, url, websocket_client_connect_cb, websocket_client_read_cb ); if (rc != 0) { ws->ready_state = WS_CLOSED; ant_inspector_websocket_error(ws->inspector_request_id, uv_strerror(rc)); websocket_sync_state(js, ws); websocket_emit_simple(js, ws, WS_HANDLER_ERROR); websocket_close_client(ws, 1006, false); }
return obj;}
static ant_value_t js_websocket_get_binary_type(ant_params_t) { websocket_state_t *ws = websocket_data(js_getthis(js)); if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket"); return ws->binary_type_blob ? js_mkstr(js, "blob", 4) : js_mkstr(js, "arraybuffer", 11);}
static ant_value_t js_websocket_set_binary_type(ant_params_t) { websocket_state_t *ws = websocket_data(js_getthis(js)); if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket"); if (nargs < 1) return js_mkundef(); ant_value_t str = js_tostring_val(js, args[0]); if (is_err(str)) return str; const char *value = js_getstr(js, str, NULL); if (value && strcmp(value, "blob") == 0) ws->binary_type_blob = true; else if (value && strcmp(value, "arraybuffer") == 0) ws->binary_type_blob = false; return js_mkundef();}
static bool websocket_bytes_from_value(ant_t *js, ant_value_t value, const uint8_t **bytes, size_t *len, ant_value_t *owned_str) { if (buffer_source_get_bytes(js, value, bytes, len)) { if (owned_str) *owned_str = js_mkundef(); return true; }
ant_value_t str = js_tostring_val(js, value); if (is_err(str)) return false; if (owned_str) *owned_str = str; *bytes = (const uint8_t *)js_getstr(js, str, len); return *bytes != NULL;}
static ant_value_t js_websocket_send(ant_params_t) { websocket_state_t *ws = websocket_data(js_getthis(js)); const uint8_t *bytes = NULL; size_t len = 0; ant_value_t keepalive = js_mkundef(); bool binary = false;
if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket"); if (ws->ready_state != WS_OPEN) return js_mkerr_typed(js, JS_ERR_TYPE, "WebSocket is not open"); if (nargs < 1) return js_mkundef(); binary = buffer_source_get_bytes(js, args[0], &bytes, &len); if (!binary && !websocket_bytes_from_value(js, args[0], &bytes, &len, &keepalive)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket message");
if (ws->is_client) { int rc = websocket_client_write_frame(ws, binary ? ANT_WS_OPCODE_BINARY : ANT_WS_OPCODE_TEXT, bytes, len); if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); ant_inspector_websocket_frame_sent(ws->inspector_request_id, bytes, len, binary); return js_mkundef(); }
if (ws->server_conn) { size_t frame_len = 0; uint8_t *frame = NULL; uint8_t *compressed = NULL; size_t compressed_len = 0; bool compressed_frame = false; if (ws->per_message_deflate) { if (!websocket_deflate_message(ws, bytes, len, &compressed, &compressed_len)) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); bytes = compressed; len = compressed_len; compressed_frame = true; } frame = ant_ws_encode_frame( binary ? ANT_WS_OPCODE_BINARY : ANT_WS_OPCODE_TEXT, bytes, len, false, compressed_frame, &frame_len ); free(compressed); if (!frame) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); int rc = ant_conn_write(ws->server_conn, (char *)frame, frame_len, NULL, NULL); if (rc != 0) { free(frame); return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc)); } } return js_mkundef();}
static ant_value_t js_websocket_close(ant_params_t) { websocket_state_t *ws = websocket_data(js_getthis(js)); uint16_t code = nargs > 0 && vtype(args[0]) == kTypeNumber ? (uint16_t)js_getnum(args[0]) : 1000; const char *reason = NULL;
if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid WebSocket"); if (ws->ready_state == WS_CLOSED || ws->ready_state == WS_CLOSING) return js_mkundef(); if (nargs > 1 && vtype(args[1]) != kTypeUndefined && vtype(args[1]) != kTypeNull) { ant_value_t reason_val = js_tostring_val(js, args[1]); if (is_err(reason_val)) return reason_val; reason = js_getstr(js, reason_val, NULL); }
ws->ready_state = WS_CLOSING; websocket_sync_state(js, ws); if (ws->is_client) { websocket_close_client(ws, 1000, true); } else if (ws->server_conn) { size_t frame_len = 0; uint8_t *frame = ant_ws_encode_close_frame(code, reason, false, &frame_len); if (frame) ant_conn_write(ws->server_conn, (char *)frame, frame_len, NULL, NULL); ant_conn_shutdown(ws->server_conn); } return js_mkundef();}
static ant_value_t websocket_event_ctor_init( ant_t *js, ant_value_t *args, int nargs, ant_value_t new_target, ant_value_t fallback_proto, ant_event_kind_t kind, const char *default_type) { ant_value_t type = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, default_type, strlen(default_type)); if (is_err(type)) return type; ant_value_t proto = js_instance_proto_from_new_target(js, fallback_proto, new_target); return Ant_Event_Create(js, proto, kind, type, 0);}
static ant_value_t websocket_event_init_field( ant_t *js, ant_value_t event, ant_value_t init, const char *key, unsigned int index, ant_value_t fallback, ant_value_t (*convert)(ant_t *, ant_value_t)) { ant_value_t value = is_object_type(init) ? js_get(js, init, key) : js_mkundef(); if (is_err(value)) return value; if (vtype(value) == kTypeUndefined) value = fallback; else if (convert) value = convert(js, value); if (is_err(value)) return value; Ant_Event_SetField(js, event, index, value); return js_mkundef();}
static ant_value_t websocket_to_bool(ant_t *js, ant_value_t value) { return js_bool(js_truthy(js, value));}
static ant_value_t websocket_to_number(ant_t *js, ant_value_t value) { double number = js_to_number(js, value); return Ant_Exception_Pending(js) ? Ant_Exception_Current(js) : js_mknum(number);}
static ant_value_t js_message_event_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "MessageEvent constructor requires 'new'"); ant_value_t event = websocket_event_ctor_init(js, args, nargs, call_new_target, js->builtins.message_event_proto, ANT_EVENT_MESSAGE, "message"); if (is_err(event)) return event; ant_value_t init = nargs > 1 ? args[1] : js_mkundef(); ant_value_t empty = js_mkstr(js, "", 0); ant_value_t r; if (is_err(r = websocket_event_init_field(js, event, init, "data", ANT_EVENT_FIELD_MESSAGE_data, js_mknull(), NULL))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "origin", ANT_EVENT_FIELD_MESSAGE_origin, empty, js_tostring_val))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "lastEventId", ANT_EVENT_FIELD_MESSAGE_lastEventId, empty, js_tostring_val))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "source", ANT_EVENT_FIELD_MESSAGE_source, js_mknull(), NULL))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "ports", ANT_EVENT_FIELD_MESSAGE_ports, js_mkarr(js), NULL))) return r; return event;}
static ant_value_t js_close_event_ctor(ant_params_t) { if (vtype(call_new_target) == kTypeUndefined) return js_mkerr_typed(js, JS_ERR_TYPE, "CloseEvent constructor requires 'new'");
ant_value_t event = websocket_event_ctor_init(js, args, nargs, call_new_target, js->builtins.close_event_proto, ANT_EVENT_CLOSE, "close"); if (is_err(event)) return event; ant_value_t init = nargs > 1 ? args[1] : js_mkundef(); ant_value_t r; if (is_err(r = websocket_event_init_field(js, event, init, "wasClean", ANT_EVENT_FIELD_CLOSE_wasClean, js_false, websocket_to_bool))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "code", ANT_EVENT_FIELD_CLOSE_code, js_mknum(0), websocket_to_number))) return r; if (is_err(r = websocket_event_init_field(js, event, init, "reason", ANT_EVENT_FIELD_CLOSE_reason, js_mkstr(js, "", 0), js_tostring_val))) return r; return event;}
ant_value_t ant_websocket_accept_server( ant_t *js, ant_conn_t *conn, ant_value_t request_obj, const char *protocol, const ant_websocket_server_options_t *options) { ant_value_t obj = websocket_create_object(js); websocket_state_t *ws = websocket_state_new(js, obj); if (!ws) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory"); ws->server_conn = conn; ws->request_obj = request_obj; ws->ready_state = WS_CONNECTING; if (options) { ws->max_payload_len = options->max_payload_len; ws->per_message_deflate = options->per_message_deflate; } js_set_native(obj, ws, WS_NATIVE_TAG); js_set(js, obj, "url", js_mkstr(js, "", 0)); if (protocol) js_set(js, obj, "protocol", js_mkstr(js, protocol, strlen(protocol))); websocket_sync_state(js, ws); return obj;}
void ant_websocket_server_open(ant_t *js, ant_value_t socket_obj) { websocket_state_t *ws = websocket_data(socket_obj); if (!ws) return; ws->ready_state = WS_OPEN; websocket_sync_state(js, ws); websocket_emit_simple(js, ws, WS_HANDLER_OPEN);}
void ant_websocket_server_on_read(ant_t *js, ant_value_t socket_obj, ant_conn_t *conn) { static const void *dispatch[] = { [ANT_WS_OPCODE_CONTINUATION] = &&l_continuation, [ANT_WS_OPCODE_TEXT] = &&l_message, [ANT_WS_OPCODE_BINARY] = &&l_message, [ANT_WS_OPCODE_CLOSE] = &&l_close, [ANT_WS_OPCODE_PING] = &&l_ping, };
websocket_state_t *ws = websocket_data(socket_obj); if (!ws || !conn) return;
while (ant_conn_buffer_len(conn) > 0) { ant_ws_frame_t frame = {0}; ant_ws_frame_result_t result = ant_ws_parse_frame( (uint8_t *)(uintptr_t)ant_conn_buffer(conn), ant_conn_buffer_len(conn), true, ws->per_message_deflate, true, &frame ); if (result == ANT_WS_FRAME_INCOMPLETE) return; if (result == ANT_WS_FRAME_PROTOCOL_ERROR) { websocket_emit_simple(js, ws, WS_HANDLER_ERROR); ant_conn_close(conn); return; }
ant_conn_consume(conn, frame.consumed_len); if (frame.opcode < sizeof(dispatch) / sizeof(*dispatch) && dispatch[frame.opcode]) goto *dispatch[frame.opcode]; goto l_done;
l_message: if (ws->fragmenting) goto l_protocol_error; if (frame.payload_len > ws->max_payload_len) goto l_too_big; if (frame.fin) { if (frame.rsv1) { uint8_t *inflated = NULL; size_t inflated_len = 0; if (!websocket_inflate_message(ws, frame.payload, frame.payload_len, ws->max_payload_len, &inflated, &inflated_len)) goto l_protocol_error; websocket_emit_message(js, ws, inflated, inflated_len, frame.opcode == ANT_WS_OPCODE_BINARY); free(inflated); } else websocket_emit_message(js, ws, frame.payload, frame.payload_len, frame.opcode == ANT_WS_OPCODE_BINARY); } else { ws->fragmenting = true; ws->fragment_opcode = frame.opcode; ws->fragment_compressed = frame.rsv1; if (!websocket_fragment_append(ws, frame.payload, frame.payload_len)) goto l_protocol_error; } goto l_done;
l_continuation: if (!ws->fragmenting) goto l_protocol_error; if (frame.rsv1) goto l_protocol_error; if (!websocket_fragment_append(ws, frame.payload, frame.payload_len)) goto l_protocol_error; if (ws->fragment_len > ws->max_payload_len) goto l_too_big; if (frame.fin) { if (ws->fragment_compressed) { uint8_t *inflated = NULL; size_t inflated_len = 0; if (!websocket_inflate_message(ws, ws->fragment_buf, ws->fragment_len, ws->max_payload_len, &inflated, &inflated_len)) goto l_protocol_error; websocket_emit_message(js, ws, inflated, inflated_len, ws->fragment_opcode == ANT_WS_OPCODE_BINARY); free(inflated); } else websocket_emit_message(js, ws, ws->fragment_buf, ws->fragment_len, ws->fragment_opcode == ANT_WS_OPCODE_BINARY); websocket_fragment_reset(ws); } goto l_done;
l_ping: { size_t out_len = 0; uint8_t *out = ant_ws_encode_frame(ANT_WS_OPCODE_PONG, frame.payload, frame.payload_len, false, false, &out_len); if (out) ant_conn_write(conn, (char *)out, out_len, NULL, NULL); goto l_done; }
l_close: { uint16_t code = 1000; const char *reason = ""; char reason_buf[124]; if (frame.payload_len >= 2) { code = (uint16_t)(((uint16_t)frame.payload[0] << 8) | frame.payload[1]); size_t reason_len = frame.payload_len - 2; if (reason_len > sizeof(reason_buf) - 1) reason_len = sizeof(reason_buf) - 1; if (reason_len > 0) memcpy(reason_buf, frame.payload + 2, reason_len); reason_buf[reason_len] = '\0'; reason = reason_buf; } size_t out_len = 0; uint8_t *out = ant_ws_encode_frame(ANT_WS_OPCODE_CLOSE, frame.payload, frame.payload_len, false, false, &out_len); if (out) ant_conn_write(conn, (char *)out, out_len, NULL, NULL); websocket_emit_close(js, ws, code, reason, true); ant_conn_shutdown(conn); goto l_done; }
l_protocol_error: websocket_emit_simple(js, ws, WS_HANDLER_ERROR); ant_ws_frame_clear(&frame); ant_conn_close(conn); return;
l_too_big: websocket_fragment_clear(ws); ant_ws_frame_clear(&frame); websocket_server_close_with_code(ws, conn, 1009, "Message Too Big"); return;
l_done: ant_ws_frame_clear(&frame); }}
void ant_websocket_server_on_close(ant_t *js, ant_value_t socket_obj) { websocket_state_t *ws = websocket_data(socket_obj); if (!ws) return; ws->server_conn = NULL; websocket_emit_close(js, ws, 1000, "", true); Ant_List_Remove(ws);}
void init_websocket_module(ant_t *js) { ant_value_t event_proto = js->builtins.event_proto; ant_value_t eventtarget_proto = js->builtins.eventtarget_proto;
js->builtins.websocket_proto = Ant_Object_CreateBare(js); if (is_object_type(eventtarget_proto)) js_set_proto_init(js->builtins.websocket_proto, eventtarget_proto); js_set(js, js->builtins.websocket_proto, "send", Ant_Function_Create(js_websocket_send)); js_set(js, js->builtins.websocket_proto, "close", Ant_Function_Create(js_websocket_close)); js_set(js, js->builtins.websocket_proto, "on", Ant_Function_Create(js_websocket_on)); js_set(js, js->builtins.websocket_proto, "once", Ant_Function_Create(js_websocket_once)); js_set(js, js->builtins.websocket_proto, "off", Ant_Function_Create(js_websocket_off)); js_set_accessor_desc( js, js->builtins.websocket_proto, "binaryType", 10, Ant_Function_Create(js_websocket_get_binary_type), Ant_Function_Create(js_websocket_set_binary_type), JS_DESC_E | JS_DESC_C );
ANT_EVENT_HANDLER_DEFINE(js->builtins.websocket_proto, js_websocket, open); ANT_EVENT_HANDLER_DEFINE(js->builtins.websocket_proto, js_websocket, message); ANT_EVENT_HANDLER_DEFINE(js->builtins.websocket_proto, js_websocket, close); ANT_EVENT_HANDLER_DEFINE(js->builtins.websocket_proto, js_websocket, error); js_set(js, js->builtins.websocket_proto, "CONNECTING", js_mknum(WS_CONNECTING)); js_set(js, js->builtins.websocket_proto, "OPEN", js_mknum(WS_OPEN)); js_set(js, js->builtins.websocket_proto, "CLOSING", js_mknum(WS_CLOSING)); js_set(js, js->builtins.websocket_proto, "CLOSED", js_mknum(WS_CLOSED)); js_set_tostringtag(js, js->builtins.websocket_proto, js_mkstr(js, "WebSocket", 9)); js->builtins.websocket_ctor = Ant_Function_CreateIntrinsicConstructor(js, js_websocket_ctor, js->builtins.websocket_proto, "WebSocket", 1); js_set(js, js->builtins.websocket_ctor, "CONNECTING", js_mknum(WS_CONNECTING)); js_set(js, js->builtins.websocket_ctor, "OPEN", js_mknum(WS_OPEN)); js_set(js, js->builtins.websocket_ctor, "CLOSING", js_mknum(WS_CLOSING)); js_set(js, js->builtins.websocket_ctor, "CLOSED", js_mknum(WS_CLOSED)); js_set_global_builtin(js, "WebSocket", js->builtins.websocket_ctor);
js->builtins.message_event_proto = Ant_Object_CreateBare(js); if (is_object_type(event_proto)) js_set_proto_init(js->builtins.message_event_proto, event_proto); js_set_tostringtag(js, js->builtins.message_event_proto, js_mkstr(js, "MessageEvent", 12)); Ant_Event_DefineFields(js, js->builtins.message_event_proto, ANT_EVENT_MESSAGE); ant_value_t message_ctor = Ant_Function_CreateIntrinsicConstructor(js, js_message_event_ctor, js->builtins.message_event_proto, "MessageEvent", 1); js_set_global_builtin(js, "MessageEvent", message_ctor);
js->builtins.close_event_proto = Ant_Object_CreateBare(js); if (is_object_type(event_proto)) js_set_proto_init(js->builtins.close_event_proto, event_proto); js_set_tostringtag(js, js->builtins.close_event_proto, js_mkstr(js, "CloseEvent", 10)); Ant_Event_DefineFields(js, js->builtins.close_event_proto, ANT_EVENT_CLOSE); ant_value_t close_ctor = Ant_Function_CreateIntrinsicConstructor(js, js_close_event_ctor, js->builtins.close_event_proto, "CloseEvent", 1); js_set_global_builtin(js, "CloseEvent", close_ctor);}
void cleanup_websocket_module(ant_t *js) { for (websocket_state_t *ws = js->active_websockets, *next; ws; ws = next) { next = ws->next; if (!ws->is_client) continue; ws->close_emitted = true; websocket_close_client(ws, 1001, false); }}
void gc_mark_websocket(ant_t *js, gc_mark_fn mark) { for (websocket_state_t *ws = js->active_websockets; ws; ws = ws->next) { mark(js, ws->obj); if (is_object_type(ws->request_obj)) mark(js, ws->request_obj); }}