mirror of
https://dev.iopsys.eu/feed/iopsys.git
synced 2025-12-09 23:34:51 +01:00
quickjs-websocket: 1.1
This commit is contained in:
parent
0d6980e0ec
commit
aaffffdba2
5 changed files with 1489 additions and 0 deletions
55
quickjs-websocket/Makefile
Normal file
55
quickjs-websocket/Makefile
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#
|
||||
# Copyright (c) 2020 Genexis B.V.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation
|
||||
# files (the "Software"), to deal in the Software without
|
||||
# restriction, including without limitation the rights to use, copy,
|
||||
# modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
# of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=quickjs-websocket
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/quickjs-websocket
|
||||
SECTION:=libs
|
||||
CATEGORY:=Libraries
|
||||
TITLE:=WebSocket API for QuickJS
|
||||
MAINTAINER:=Erik Karlsson <erik.karlsson@genexis.eu>
|
||||
DEPENDS:=+quickjs +libwebsockets
|
||||
endef
|
||||
|
||||
define Package/quickjs-websocket/description
|
||||
Implementation of the W3C WebSocket API in QuickJS on top of the
|
||||
libwebsockets C library.
|
||||
endef
|
||||
|
||||
define Package/quickjs-websocket/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/quickjs
|
||||
$(CP) $(PKG_BUILD_DIR)/lws-client.so $(1)/usr/lib/quickjs/
|
||||
$(CP) $(PKG_BUILD_DIR)/websocket.js $(1)/usr/lib/quickjs/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,quickjs-websocket))
|
||||
23
quickjs-websocket/README
Executable file
23
quickjs-websocket/README
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
WebSocket API for QuickJS
|
||||
===============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
This is an implementation of the W3C WebSocket API for the QuickJS
|
||||
JavaScript engine on top of the libwebsockets C library.
|
||||
|
||||
Usage
|
||||
------------
|
||||
import { WebSocket } from '/usr/lib/quickjs/websocket.js'
|
||||
|
||||
const w = new WebSocket('wss://example.com/', ['protocol1', 'protocol2'])
|
||||
|
||||
globalThis.WebSocket = WebSocket // To make the API available globally
|
||||
|
||||
Limitations
|
||||
------------
|
||||
Events emitted by WebSocket objects do not implement the full DOM
|
||||
event specification. Only a subset of properties is available. The
|
||||
EventTarget interface, i.e. addEventListener/removeEventListener, is
|
||||
unimplemented. The onopen/onerror/onclose/onmesseage handlers have to
|
||||
be used instead.
|
||||
38
quickjs-websocket/src/Makefile
Normal file
38
quickjs-websocket/src/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
# Copyright (c) 2020 Genexis B.V.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation
|
||||
# files (the "Software"), to deal in the Software without
|
||||
# restriction, including without limitation the rights to use, copy,
|
||||
# modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
# of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
|
||||
TARGETS = lws-client.so
|
||||
|
||||
CFLAGS += -Os -Wall -Werror
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
%.pic.o: %.c
|
||||
$(CC) $(CFLAGS) -fPIC -c -o $@ $<
|
||||
|
||||
lws-client.so: lws-client.pic.o
|
||||
$(CC) $(LDFLAGS) -shared -o $@ $^ -lwebsockets
|
||||
|
||||
clean:
|
||||
rm -f *.o $(TARGETS)
|
||||
958
quickjs-websocket/src/lws-client.c
Normal file
958
quickjs-websocket/src/lws-client.c
Normal file
|
|
@ -0,0 +1,958 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Genexis B.V.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy,
|
||||
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <quickjs/quickjs.h>
|
||||
#include <quickjs/quickjs-libc.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#define countof(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
#define CDEF(name) JS_PROP_INT32_DEF(#name, name, JS_PROP_CONFIGURABLE)
|
||||
|
||||
#if LWS_LIBRARY_VERSION_NUMBER < 3002000
|
||||
#define MAX_WAIT 1000
|
||||
#else
|
||||
#define MAX_WAIT INT32_MAX
|
||||
#ifndef LWS_WITH_EXTERNAL_POLL
|
||||
#error "LWS_WITH_EXTERNAL_POLL is needed for LWS versions >= 3.2.0"
|
||||
#endif
|
||||
#if LWS_LIBRARY_VERSION_NUMBER < 4001002
|
||||
#error "External poll is broken for 3.2.0 <= LWS version < 4.1.2"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define WSI_DATA_USE_OBJECT (1 << 0)
|
||||
#define WSI_DATA_USE_LINKED (1 << 1)
|
||||
|
||||
typedef struct js_lws_wsi_data {
|
||||
struct js_lws_wsi_data *next;
|
||||
struct lws *wsi;
|
||||
JSValue object;
|
||||
JSValue context;
|
||||
JSValue user;
|
||||
uint8_t in_use;
|
||||
} js_lws_wsi_data_t;
|
||||
|
||||
typedef struct {
|
||||
struct lws_context *context;
|
||||
JSContext *ctx;
|
||||
JSValue callback;
|
||||
js_lws_wsi_data_t *wsi_list;
|
||||
} js_lws_context_data_t;
|
||||
|
||||
static JSClassID js_lws_context_class_id;
|
||||
static JSClassID js_lws_wsi_class_id;
|
||||
|
||||
static void free_wsi_data_rt(JSRuntime *rt, js_lws_wsi_data_t *data)
|
||||
{
|
||||
JS_FreeValueRT(rt, data->object);
|
||||
JS_FreeValueRT(rt, data->context);
|
||||
JS_FreeValueRT(rt, data->user);
|
||||
js_free_rt(rt, data);
|
||||
}
|
||||
|
||||
static void unlink_wsi_rt(JSRuntime *rt, js_lws_context_data_t *data,
|
||||
js_lws_wsi_data_t *wsi_data)
|
||||
{
|
||||
js_lws_wsi_data_t **p;
|
||||
for (p = &data->wsi_list; *p; p = &(*p)->next) {
|
||||
if (*p == wsi_data) {
|
||||
*p = (*p)->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
wsi_data->next = NULL;
|
||||
wsi_data->wsi = NULL;
|
||||
JS_FreeValueRT(rt, wsi_data->object);
|
||||
wsi_data->object = JS_UNDEFINED;
|
||||
wsi_data->in_use &= ~WSI_DATA_USE_LINKED;
|
||||
if (wsi_data->in_use == 0)
|
||||
free_wsi_data_rt(rt, wsi_data);
|
||||
}
|
||||
|
||||
static void unlink_wsi(JSContext *ctx, js_lws_context_data_t *data,
|
||||
js_lws_wsi_data_t *wsi_data)
|
||||
{
|
||||
unlink_wsi_rt(JS_GetRuntime(ctx), data, wsi_data);
|
||||
}
|
||||
|
||||
static JSValue convert_pollargs(JSContext *ctx, const struct lws_pollargs *pa)
|
||||
{
|
||||
JSValue obj;
|
||||
|
||||
if (pa == NULL)
|
||||
return JS_NULL;
|
||||
|
||||
obj = JS_NewObject(ctx);
|
||||
if (JS_IsException(obj))
|
||||
return obj;
|
||||
|
||||
if (JS_SetPropertyStr(ctx, obj, "fd", JS_NewInt32(ctx, pa->fd)) < 0
|
||||
|| JS_SetPropertyStr(ctx, obj, "events",
|
||||
JS_NewInt32(ctx, pa->events)) < 0
|
||||
|| JS_SetPropertyStr(ctx, obj, "prev_events",
|
||||
JS_NewInt32(ctx, pa->prev_events)) < 0) {
|
||||
JS_FreeValue(ctx, obj);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue convert_close_payload(JSContext *ctx,
|
||||
uint8_t *payload, size_t len)
|
||||
{
|
||||
JSValue array;
|
||||
uint16_t status;
|
||||
JSValue reason;
|
||||
|
||||
if (payload == NULL || len < 2)
|
||||
return JS_NULL;
|
||||
|
||||
array = JS_NewArray(ctx);
|
||||
if (JS_IsException(array))
|
||||
return array;
|
||||
|
||||
status = (payload[0] << 8) | payload[1];
|
||||
if (JS_SetPropertyUint32(ctx, array, 0, JS_NewInt32(ctx, status)) < 0) {
|
||||
JS_FreeValue(ctx, array);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
reason = JS_NewStringLen(ctx, (const char *)(payload + 2), len - 2);
|
||||
if (JS_IsException(reason)
|
||||
|| JS_SetPropertyUint32(ctx, array, 1, reason) < 0) {
|
||||
JS_FreeValue(ctx, array);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static int client_callback(struct lws *wsi, enum lws_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
js_lws_context_data_t *data = lws_context_user(lws_get_context(wsi));
|
||||
js_lws_wsi_data_t *wsi_data = lws_wsi_user(wsi);
|
||||
JSContext *ctx;
|
||||
JSValue args[3];
|
||||
JSValue ret;
|
||||
int32_t ret_int;
|
||||
int i;
|
||||
|
||||
if (data == NULL || data->ctx == NULL || JS_IsUndefined(data->callback))
|
||||
return 0;
|
||||
|
||||
ctx = data->ctx;
|
||||
args[0] = wsi_data ? JS_DupValue(ctx, wsi_data->object) : JS_NULL;
|
||||
args[1] = JS_NewInt32(ctx, reason);
|
||||
args[2] = JS_NULL;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ADD_POLL_FD:
|
||||
case LWS_CALLBACK_DEL_POLL_FD:
|
||||
case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
|
||||
args[2] = convert_pollargs(ctx, in);
|
||||
break;
|
||||
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
if (in)
|
||||
args[2] = JS_NewStringLen(ctx, in, len);
|
||||
break;
|
||||
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
|
||||
args[2] = convert_close_payload(ctx, in, len);
|
||||
break;
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
if (in)
|
||||
args[2] = JS_NewArrayBufferCopy(ctx, in, len);
|
||||
break;
|
||||
case LWS_CALLBACK_WSI_DESTROY:
|
||||
if (wsi_data)
|
||||
unlink_wsi(ctx, data, wsi_data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (JS_IsException(args[2]))
|
||||
ret = JS_EXCEPTION;
|
||||
else
|
||||
ret = JS_Call(ctx, data->callback, JS_UNDEFINED, countof(args), args);
|
||||
|
||||
if (JS_IsException(ret) || JS_ToInt32(ctx, &ret_int, ret) < 0) {
|
||||
js_std_dump_error(ctx);
|
||||
ret_int = -1;
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx, ret);
|
||||
for (i = 0; i < countof(args); i++) {
|
||||
JS_FreeValue(ctx, args[i]);
|
||||
}
|
||||
|
||||
return ret_int;
|
||||
}
|
||||
|
||||
static const struct lws_protocols client_protocols[] = {
|
||||
{ "lws-client", client_callback, 0, 0, 0, NULL, 0 },
|
||||
{ NULL, NULL, 0, 0, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static JSValue js_decode_utf8(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
size_t size;
|
||||
uint8_t *ptr = JS_GetArrayBuffer(ctx, &size, argv[0]);
|
||||
if (ptr == NULL)
|
||||
return JS_EXCEPTION;
|
||||
return JS_NewStringLen(ctx, (const char *)ptr, size);
|
||||
}
|
||||
|
||||
static JSValue js_lws_set_log_level(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
int32_t level;
|
||||
if (JS_ToInt32(ctx, &level, argv[0]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
lws_set_log_level(level, NULL);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue js_lws_create_context(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
int secure;
|
||||
JSValue obj;
|
||||
js_lws_context_data_t *data;
|
||||
struct lws_context_creation_info info;
|
||||
struct lws_context *context;
|
||||
|
||||
if (!JS_IsFunction(ctx, argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "not a function");
|
||||
|
||||
secure = JS_ToBool(ctx, argv[1]);
|
||||
if (secure < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
obj = JS_NewObjectClass(ctx, js_lws_context_class_id);
|
||||
if (JS_IsException(obj))
|
||||
return obj;
|
||||
|
||||
data = js_mallocz(ctx, sizeof(js_lws_context_data_t));
|
||||
if (data == NULL) {
|
||||
JS_FreeValue(ctx, obj);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
data->callback = JS_UNDEFINED;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.port = CONTEXT_PORT_NO_LISTEN;
|
||||
info.protocols = client_protocols;
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
info.options = secure ? LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT : 0;
|
||||
info.user = data;
|
||||
context = lws_create_context(&info);
|
||||
if (context == NULL) {
|
||||
JS_FreeValue(ctx, obj);
|
||||
js_free(ctx, data);
|
||||
return JS_ThrowOutOfMemory(ctx);
|
||||
}
|
||||
|
||||
data->context = context;
|
||||
data->ctx = JS_DupContext(ctx);
|
||||
data->callback = JS_DupValue(ctx, argv[0]);
|
||||
JS_SetOpaque(obj, data);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void js_lws_context_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque(val, js_lws_context_class_id);
|
||||
if (data) {
|
||||
JS_FreeContext(data->ctx);
|
||||
data->ctx = NULL;
|
||||
JS_FreeValueRT(rt, data->callback);
|
||||
data->callback = JS_UNDEFINED;
|
||||
|
||||
lws_context_destroy(data->context);
|
||||
|
||||
while (data->wsi_list) {
|
||||
unlink_wsi_rt(rt, data, data->wsi_list);
|
||||
}
|
||||
|
||||
js_free_rt(rt, data);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_lws_context_mark(JSRuntime *rt, JSValue val,
|
||||
JS_MarkFunc *mark_func)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque(val, js_lws_context_class_id);
|
||||
if (data) {
|
||||
js_lws_wsi_data_t *wd;
|
||||
mark_func(rt, (JSGCObjectHeader *)data->ctx);
|
||||
JS_MarkValue(rt, data->callback, mark_func);
|
||||
for (wd = data->wsi_list; wd; wd = wd->next) {
|
||||
JS_MarkValue(rt, wd->object, mark_func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_lws_context_get_connections(JSContext *ctx,
|
||||
JSValueConst this_val)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
||||
js_lws_context_class_id);
|
||||
int32_t connections = 0;
|
||||
js_lws_wsi_data_t *wd;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
for (wd = data->wsi_list; wd; wd = wd->next) {
|
||||
connections++;
|
||||
}
|
||||
|
||||
return JS_NewInt32(ctx, connections);
|
||||
}
|
||||
|
||||
static JSValue js_lws_client_connect(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
||||
js_lws_context_class_id);
|
||||
const char *address = NULL;
|
||||
int32_t port;
|
||||
int secure;
|
||||
const char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL;
|
||||
JSValue obj = JS_UNDEFINED, ret = JS_EXCEPTION;
|
||||
js_lws_wsi_data_t *wsi_data;
|
||||
struct lws_client_connect_info info;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
address = JS_ToCString(ctx, argv[0]);
|
||||
if (address == NULL)
|
||||
goto exception;
|
||||
|
||||
if (JS_ToInt32(ctx, &port, argv[1]) < 0)
|
||||
goto exception;
|
||||
|
||||
if (port < 1 || port > 65535) {
|
||||
JS_ThrowRangeError(ctx, "port must be between 1 and 65535");
|
||||
goto exception;
|
||||
}
|
||||
|
||||
secure = JS_ToBool(ctx, argv[2]);
|
||||
if (secure < 0)
|
||||
goto exception;
|
||||
|
||||
path = JS_ToCString(ctx, argv[3]);
|
||||
if (path == NULL)
|
||||
goto exception;
|
||||
|
||||
host = JS_ToCString(ctx, argv[4]);
|
||||
if (host == NULL)
|
||||
goto exception;
|
||||
|
||||
if (!JS_IsUndefined(argv[5]) && !JS_IsNull(argv[5])) {
|
||||
origin = JS_ToCString(ctx, argv[5]);
|
||||
if (origin == NULL)
|
||||
goto exception;
|
||||
}
|
||||
|
||||
if (!JS_IsUndefined(argv[6]) && !JS_IsNull(argv[6])) {
|
||||
protocol = JS_ToCString(ctx, argv[6]);
|
||||
if (protocol == NULL)
|
||||
goto exception;
|
||||
}
|
||||
|
||||
obj = JS_NewObjectClass(ctx, js_lws_wsi_class_id);
|
||||
if (JS_IsException(obj))
|
||||
goto exception;
|
||||
|
||||
wsi_data = js_mallocz(ctx, sizeof(js_lws_wsi_data_t));
|
||||
if (wsi_data == NULL)
|
||||
goto exception;
|
||||
wsi_data->next = data->wsi_list;
|
||||
wsi_data->object = JS_DupValue(ctx, obj);
|
||||
wsi_data->context = JS_DupValue(ctx, this_val);
|
||||
wsi_data->user = JS_DupValue(ctx, argv[7]);
|
||||
wsi_data->in_use = WSI_DATA_USE_OBJECT | WSI_DATA_USE_LINKED;
|
||||
data->wsi_list = wsi_data;
|
||||
JS_SetOpaque(obj, wsi_data);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.context = data->context;
|
||||
info.address = address;
|
||||
info.port = port;
|
||||
info.ssl_connection = secure ? LCCSCF_USE_SSL : 0;
|
||||
info.local_protocol_name = "lws-client";
|
||||
info.path = path;
|
||||
info.host = host;
|
||||
info.origin = origin;
|
||||
info.protocol = protocol;
|
||||
info.ietf_version_or_minus_one = -1;
|
||||
info.userdata = wsi_data;
|
||||
info.pwsi = &wsi_data->wsi;
|
||||
lws_client_connect_via_info(&info);
|
||||
|
||||
if (wsi_data->wsi) {
|
||||
ret = JS_DupValue(ctx, obj);
|
||||
} else {
|
||||
unlink_wsi(ctx, data, wsi_data);
|
||||
JS_ThrowReferenceError(ctx, "cannot connect to [%s]:%d (%s)",
|
||||
address, port, secure ? "wss" : "ws");
|
||||
}
|
||||
|
||||
exception:
|
||||
JS_FreeCString(ctx, address);
|
||||
JS_FreeCString(ctx, path);
|
||||
JS_FreeCString(ctx, host);
|
||||
JS_FreeCString(ctx, origin);
|
||||
JS_FreeCString(ctx, protocol);
|
||||
JS_FreeValue(ctx, obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JSValue js_lws_service_fd(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
||||
js_lws_context_class_id);
|
||||
int32_t fd, events, revents;
|
||||
struct lws_pollfd pfd;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (JS_ToInt32(ctx, &fd, argv[0]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (JS_ToInt32(ctx, &events, argv[1]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (JS_ToInt32(ctx, &revents, argv[2]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = events;
|
||||
pfd.revents = revents;
|
||||
lws_service_fd(data->context, &pfd);
|
||||
|
||||
return JS_NewInt32(ctx,
|
||||
lws_service_adjust_timeout(data->context, MAX_WAIT, 0));
|
||||
}
|
||||
|
||||
static JSValue js_lws_service_periodic(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
||||
js_lws_context_class_id);
|
||||
int timeout;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
#if LWS_LIBRARY_VERSION_NUMBER < 3002000
|
||||
lws_service_fd(data->context, NULL);
|
||||
#endif
|
||||
|
||||
timeout = lws_service_adjust_timeout(data->context, MAX_WAIT, 0);
|
||||
if (timeout == 0) {
|
||||
lws_service(data->context, -1);
|
||||
timeout = lws_service_adjust_timeout(data->context, MAX_WAIT, 0);
|
||||
}
|
||||
|
||||
return JS_NewInt32(ctx, timeout);
|
||||
}
|
||||
|
||||
static void js_lws_wsi_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque(val, js_lws_wsi_class_id);
|
||||
if (data) {
|
||||
JS_FreeValueRT(rt, data->context);
|
||||
data->context = JS_UNDEFINED;
|
||||
JS_FreeValueRT(rt, data->user);
|
||||
data->user = JS_UNDEFINED;
|
||||
|
||||
data->in_use &= ~WSI_DATA_USE_OBJECT;
|
||||
if (data->in_use == 0)
|
||||
free_wsi_data_rt(rt, data);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_lws_wsi_mark(JSRuntime *rt, JSValue val,
|
||||
JS_MarkFunc *mark_func)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque(val, js_lws_wsi_class_id);
|
||||
if (data) {
|
||||
JS_MarkValue(rt, data->context, mark_func);
|
||||
JS_MarkValue(rt, data->user, mark_func);
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_lws_wsi_get_context(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
return JS_DupValue(ctx, data->context);
|
||||
}
|
||||
|
||||
static JSValue js_lws_wsi_get_user(JSContext *ctx, JSValueConst this_val)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
return JS_DupValue(ctx, data->user);
|
||||
}
|
||||
|
||||
static JSValue js_lws_wsi_get_hdr(JSContext *ctx, JSValueConst this_val,
|
||||
int magic)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
int len;
|
||||
char *str;
|
||||
JSValue ret;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
|
||||
len = lws_hdr_total_length(data->wsi, magic);
|
||||
if (len < 0)
|
||||
return JS_ThrowReferenceError(ctx, "HTTP headers unavailable");
|
||||
|
||||
len++;
|
||||
str = js_mallocz(ctx, len);
|
||||
if (str == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
len = lws_hdr_copy(data->wsi, str, len, magic);
|
||||
if (len < 0)
|
||||
ret = JS_ThrowReferenceError(ctx, "HTTP headers unavailable");
|
||||
else
|
||||
ret = JS_NewStringLen(ctx, str, len);
|
||||
|
||||
js_free(ctx, str);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static JSValue js_lws_is_final_fragment(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
return JS_NewBool(ctx, lws_is_final_fragment(data->wsi));
|
||||
}
|
||||
|
||||
static JSValue js_lws_is_first_fragment(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
return JS_NewBool(ctx, lws_is_first_fragment(data->wsi));
|
||||
}
|
||||
|
||||
static JSValue js_lws_frame_is_binary(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
return JS_NewBool(ctx, lws_frame_is_binary(data->wsi));
|
||||
}
|
||||
|
||||
static JSValue js_lws_callback_on_writable(JSContext *ctx,
|
||||
JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
lws_callback_on_writable(data->wsi);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue js_lws_write(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
const char *str = NULL;
|
||||
const uint8_t *ptr;
|
||||
uint8_t *buf;
|
||||
size_t size;
|
||||
enum lws_write_protocol protocol;
|
||||
int ret;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
str = JS_ToCStringLen(ctx, &size, argv[0]);
|
||||
if (str == NULL)
|
||||
return JS_EXCEPTION;
|
||||
ptr = (const uint8_t *)str;
|
||||
protocol = LWS_WRITE_TEXT;
|
||||
} else {
|
||||
ptr = JS_GetArrayBuffer(ctx, &size, argv[0]);
|
||||
if (ptr == NULL)
|
||||
return JS_EXCEPTION;
|
||||
protocol = LWS_WRITE_BINARY;
|
||||
}
|
||||
|
||||
buf = js_malloc(ctx, LWS_PRE + size);
|
||||
if (buf)
|
||||
memcpy(buf + LWS_PRE, ptr, size);
|
||||
if (str)
|
||||
JS_FreeCString(ctx, str);
|
||||
if (buf == NULL)
|
||||
return JS_EXCEPTION;
|
||||
ret = lws_write(data->wsi, buf + LWS_PRE, size, protocol);
|
||||
js_free(ctx, buf);
|
||||
|
||||
if (ret < 0)
|
||||
return JS_ThrowTypeError(ctx, "WSI not writable");
|
||||
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue js_lws_close_reason(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
||||
int32_t status;
|
||||
const char *reason = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
if (data == NULL)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (data->wsi == NULL)
|
||||
return JS_ThrowTypeError(ctx, "defunct WSI");
|
||||
|
||||
if (JS_ToInt32(ctx, &status, argv[0]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (status < 0 || status > 65535)
|
||||
return JS_ThrowRangeError(ctx, "status must be between 0 and 65535");
|
||||
|
||||
if (!JS_IsUndefined(argv[1])) {
|
||||
reason = JS_ToCStringLen(ctx, &len, argv[1]);
|
||||
if (reason == NULL)
|
||||
return JS_EXCEPTION;
|
||||
if (len > 123) {
|
||||
JS_FreeCString(ctx, reason);
|
||||
return JS_ThrowTypeError(ctx, "reason too long (%zu > 123)", len);
|
||||
}
|
||||
}
|
||||
|
||||
lws_close_reason(data->wsi, status, (uint8_t *)reason, len);
|
||||
|
||||
if (reason)
|
||||
JS_FreeCString(ctx, reason);
|
||||
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_lws_funcs[] = {
|
||||
CDEF(LLL_ERR),
|
||||
CDEF(LLL_WARN),
|
||||
CDEF(LLL_NOTICE),
|
||||
CDEF(LLL_INFO),
|
||||
CDEF(LLL_DEBUG),
|
||||
CDEF(LLL_PARSER),
|
||||
CDEF(LLL_HEADER),
|
||||
CDEF(LLL_EXT),
|
||||
CDEF(LLL_CLIENT),
|
||||
CDEF(LLL_LATENCY),
|
||||
CDEF(LLL_USER),
|
||||
CDEF(LLL_THREAD),
|
||||
CDEF(LLL_COUNT),
|
||||
CDEF(LWS_CALLBACK_PROTOCOL_INIT),
|
||||
CDEF(LWS_CALLBACK_PROTOCOL_DESTROY),
|
||||
CDEF(LWS_CALLBACK_WSI_CREATE),
|
||||
CDEF(LWS_CALLBACK_WSI_DESTROY),
|
||||
CDEF(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS),
|
||||
CDEF(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS),
|
||||
CDEF(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION),
|
||||
CDEF(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY),
|
||||
CDEF(LWS_CALLBACK_SSL_INFO),
|
||||
CDEF(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
|
||||
CDEF(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED),
|
||||
CDEF(LWS_CALLBACK_HTTP),
|
||||
CDEF(LWS_CALLBACK_HTTP_BODY),
|
||||
CDEF(LWS_CALLBACK_HTTP_BODY_COMPLETION),
|
||||
CDEF(LWS_CALLBACK_HTTP_FILE_COMPLETION),
|
||||
CDEF(LWS_CALLBACK_HTTP_WRITEABLE),
|
||||
CDEF(LWS_CALLBACK_CLOSED_HTTP),
|
||||
CDEF(LWS_CALLBACK_FILTER_HTTP_CONNECTION),
|
||||
CDEF(LWS_CALLBACK_ADD_HEADERS),
|
||||
CDEF(LWS_CALLBACK_CHECK_ACCESS_RIGHTS),
|
||||
CDEF(LWS_CALLBACK_PROCESS_HTML),
|
||||
CDEF(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_HTTP_CONFIRM_UPGRADE),
|
||||
CDEF(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP),
|
||||
CDEF(LWS_CALLBACK_CLOSED_CLIENT_HTTP),
|
||||
CDEF(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
|
||||
CDEF(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
|
||||
CDEF(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
|
||||
CDEF(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
|
||||
CDEF(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_ESTABLISHED),
|
||||
CDEF(LWS_CALLBACK_CLOSED),
|
||||
CDEF(LWS_CALLBACK_SERVER_WRITEABLE),
|
||||
CDEF(LWS_CALLBACK_RECEIVE),
|
||||
CDEF(LWS_CALLBACK_RECEIVE_PONG),
|
||||
CDEF(LWS_CALLBACK_WS_PEER_INITIATED_CLOSE),
|
||||
CDEF(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION),
|
||||
CDEF(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY),
|
||||
CDEF(LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_CLIENT_CONNECTION_ERROR),
|
||||
CDEF(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH),
|
||||
CDEF(LWS_CALLBACK_CLIENT_ESTABLISHED),
|
||||
CDEF(LWS_CALLBACK_CLIENT_CLOSED),
|
||||
CDEF(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER),
|
||||
CDEF(LWS_CALLBACK_CLIENT_RECEIVE),
|
||||
CDEF(LWS_CALLBACK_CLIENT_RECEIVE_PONG),
|
||||
CDEF(LWS_CALLBACK_CLIENT_WRITEABLE),
|
||||
CDEF(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED),
|
||||
CDEF(LWS_CALLBACK_WS_EXT_DEFAULTS),
|
||||
CDEF(LWS_CALLBACK_FILTER_NETWORK_CONNECTION),
|
||||
CDEF(LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_GET_THREAD_ID),
|
||||
CDEF(LWS_CALLBACK_ADD_POLL_FD),
|
||||
CDEF(LWS_CALLBACK_DEL_POLL_FD),
|
||||
CDEF(LWS_CALLBACK_CHANGE_MODE_POLL_FD),
|
||||
CDEF(LWS_CALLBACK_LOCK_POLL),
|
||||
CDEF(LWS_CALLBACK_UNLOCK_POLL),
|
||||
CDEF(LWS_CALLBACK_CGI),
|
||||
CDEF(LWS_CALLBACK_CGI_TERMINATED),
|
||||
CDEF(LWS_CALLBACK_CGI_STDIN_DATA),
|
||||
CDEF(LWS_CALLBACK_CGI_STDIN_COMPLETED),
|
||||
CDEF(LWS_CALLBACK_CGI_PROCESS_ATTACH),
|
||||
CDEF(LWS_CALLBACK_SESSION_INFO),
|
||||
CDEF(LWS_CALLBACK_GS_EVENT),
|
||||
CDEF(LWS_CALLBACK_HTTP_PMO),
|
||||
CDEF(LWS_CALLBACK_RAW_RX),
|
||||
CDEF(LWS_CALLBACK_RAW_CLOSE),
|
||||
CDEF(LWS_CALLBACK_RAW_WRITEABLE),
|
||||
CDEF(LWS_CALLBACK_RAW_ADOPT),
|
||||
CDEF(LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_RAW_ADOPT_FILE),
|
||||
CDEF(LWS_CALLBACK_RAW_RX_FILE),
|
||||
CDEF(LWS_CALLBACK_RAW_WRITEABLE_FILE),
|
||||
CDEF(LWS_CALLBACK_RAW_CLOSE_FILE),
|
||||
CDEF(LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL),
|
||||
CDEF(LWS_CALLBACK_TIMER),
|
||||
CDEF(LWS_CALLBACK_EVENT_WAIT_CANCELLED),
|
||||
CDEF(LWS_CALLBACK_CHILD_CLOSING),
|
||||
CDEF(LWS_CALLBACK_VHOST_CERT_AGING),
|
||||
CDEF(LWS_CALLBACK_VHOST_CERT_UPDATE),
|
||||
CDEF(LWS_CALLBACK_USER),
|
||||
CDEF(LWS_POLLHUP),
|
||||
CDEF(LWS_POLLIN),
|
||||
CDEF(LWS_POLLOUT),
|
||||
JS_CFUNC_DEF("decode_utf8", 1, js_decode_utf8),
|
||||
JS_CFUNC_DEF("set_log_level", 1, js_lws_set_log_level),
|
||||
JS_CFUNC_DEF("create_context", 2, js_lws_create_context),
|
||||
};
|
||||
|
||||
static const JSClassDef js_lws_context_class = {
|
||||
"Context",
|
||||
.finalizer = js_lws_context_finalizer,
|
||||
.gc_mark = js_lws_context_mark,
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_lws_context_proto_funcs[] = {
|
||||
JS_CGETSET_DEF("connections", js_lws_context_get_connections, NULL),
|
||||
JS_CFUNC_DEF("client_connect", 8, js_lws_client_connect),
|
||||
JS_CFUNC_DEF("service_fd", 3, js_lws_service_fd),
|
||||
JS_CFUNC_DEF("service_periodic", 0, js_lws_service_periodic),
|
||||
};
|
||||
|
||||
static const JSClassDef js_lws_wsi_class = {
|
||||
"WSI",
|
||||
.finalizer = js_lws_wsi_finalizer,
|
||||
.gc_mark = js_lws_wsi_mark,
|
||||
};
|
||||
|
||||
#define HDRGET(name) JS_CGETSET_MAGIC_DEF(#name, js_lws_wsi_get_hdr, NULL, name)
|
||||
|
||||
static const JSCFunctionListEntry js_lws_wsi_proto_funcs[] = {
|
||||
JS_CGETSET_DEF("context", js_lws_wsi_get_context, NULL),
|
||||
JS_CGETSET_DEF("user", js_lws_wsi_get_user, NULL),
|
||||
HDRGET(WSI_TOKEN_GET_URI),
|
||||
HDRGET(WSI_TOKEN_POST_URI),
|
||||
HDRGET(WSI_TOKEN_OPTIONS_URI),
|
||||
HDRGET(WSI_TOKEN_HOST),
|
||||
HDRGET(WSI_TOKEN_CONNECTION),
|
||||
HDRGET(WSI_TOKEN_UPGRADE),
|
||||
HDRGET(WSI_TOKEN_ORIGIN),
|
||||
HDRGET(WSI_TOKEN_DRAFT),
|
||||
HDRGET(WSI_TOKEN_CHALLENGE),
|
||||
HDRGET(WSI_TOKEN_EXTENSIONS),
|
||||
HDRGET(WSI_TOKEN_KEY1),
|
||||
HDRGET(WSI_TOKEN_KEY2),
|
||||
HDRGET(WSI_TOKEN_PROTOCOL),
|
||||
HDRGET(WSI_TOKEN_ACCEPT),
|
||||
HDRGET(WSI_TOKEN_NONCE),
|
||||
HDRGET(WSI_TOKEN_HTTP),
|
||||
HDRGET(WSI_TOKEN_HTTP2_SETTINGS),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCEPT),
|
||||
HDRGET(WSI_TOKEN_HTTP_AC_REQUEST_HEADERS),
|
||||
HDRGET(WSI_TOKEN_HTTP_IF_MODIFIED_SINCE),
|
||||
HDRGET(WSI_TOKEN_HTTP_IF_NONE_MATCH),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCEPT_ENCODING),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCEPT_LANGUAGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_PRAGMA),
|
||||
HDRGET(WSI_TOKEN_HTTP_CACHE_CONTROL),
|
||||
HDRGET(WSI_TOKEN_HTTP_AUTHORIZATION),
|
||||
HDRGET(WSI_TOKEN_HTTP_COOKIE),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_LENGTH),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_TYPE),
|
||||
HDRGET(WSI_TOKEN_HTTP_DATE),
|
||||
HDRGET(WSI_TOKEN_HTTP_RANGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_REFERER),
|
||||
HDRGET(WSI_TOKEN_KEY),
|
||||
HDRGET(WSI_TOKEN_VERSION),
|
||||
HDRGET(WSI_TOKEN_SWORIGIN),
|
||||
HDRGET(WSI_TOKEN_HTTP_COLON_AUTHORITY),
|
||||
HDRGET(WSI_TOKEN_HTTP_COLON_METHOD),
|
||||
HDRGET(WSI_TOKEN_HTTP_COLON_PATH),
|
||||
HDRGET(WSI_TOKEN_HTTP_COLON_SCHEME),
|
||||
HDRGET(WSI_TOKEN_HTTP_COLON_STATUS),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCEPT_CHARSET),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCEPT_RANGES),
|
||||
HDRGET(WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN),
|
||||
HDRGET(WSI_TOKEN_HTTP_AGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_ALLOW),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_DISPOSITION),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_ENCODING),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_LANGUAGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_LOCATION),
|
||||
HDRGET(WSI_TOKEN_HTTP_CONTENT_RANGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_ETAG),
|
||||
HDRGET(WSI_TOKEN_HTTP_EXPECT),
|
||||
HDRGET(WSI_TOKEN_HTTP_EXPIRES),
|
||||
HDRGET(WSI_TOKEN_HTTP_FROM),
|
||||
HDRGET(WSI_TOKEN_HTTP_IF_MATCH),
|
||||
HDRGET(WSI_TOKEN_HTTP_IF_RANGE),
|
||||
HDRGET(WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE),
|
||||
HDRGET(WSI_TOKEN_HTTP_LAST_MODIFIED),
|
||||
HDRGET(WSI_TOKEN_HTTP_LINK),
|
||||
HDRGET(WSI_TOKEN_HTTP_LOCATION),
|
||||
HDRGET(WSI_TOKEN_HTTP_MAX_FORWARDS),
|
||||
HDRGET(WSI_TOKEN_HTTP_PROXY_AUTHENTICATE),
|
||||
HDRGET(WSI_TOKEN_HTTP_PROXY_AUTHORIZATION),
|
||||
HDRGET(WSI_TOKEN_HTTP_REFRESH),
|
||||
HDRGET(WSI_TOKEN_HTTP_RETRY_AFTER),
|
||||
HDRGET(WSI_TOKEN_HTTP_SERVER),
|
||||
HDRGET(WSI_TOKEN_HTTP_SET_COOKIE),
|
||||
HDRGET(WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY),
|
||||
HDRGET(WSI_TOKEN_HTTP_TRANSFER_ENCODING),
|
||||
HDRGET(WSI_TOKEN_HTTP_USER_AGENT),
|
||||
HDRGET(WSI_TOKEN_HTTP_VARY),
|
||||
HDRGET(WSI_TOKEN_HTTP_VIA),
|
||||
HDRGET(WSI_TOKEN_HTTP_WWW_AUTHENTICATE),
|
||||
HDRGET(WSI_TOKEN_PATCH_URI),
|
||||
HDRGET(WSI_TOKEN_PUT_URI),
|
||||
HDRGET(WSI_TOKEN_DELETE_URI),
|
||||
HDRGET(WSI_TOKEN_HTTP_URI_ARGS),
|
||||
HDRGET(WSI_TOKEN_PROXY),
|
||||
HDRGET(WSI_TOKEN_HTTP_X_REAL_IP),
|
||||
HDRGET(WSI_TOKEN_HTTP1_0),
|
||||
HDRGET(WSI_TOKEN_X_FORWARDED_FOR),
|
||||
HDRGET(WSI_TOKEN_CONNECT),
|
||||
HDRGET(WSI_TOKEN_HEAD_URI),
|
||||
HDRGET(WSI_TOKEN_TE),
|
||||
HDRGET(WSI_TOKEN_REPLAY_NONCE),
|
||||
HDRGET(WSI_TOKEN_COLON_PROTOCOL),
|
||||
HDRGET(WSI_TOKEN_X_AUTH_TOKEN),
|
||||
JS_CFUNC_DEF("is_final_fragment", 0, js_lws_is_final_fragment),
|
||||
JS_CFUNC_DEF("is_first_fragment", 0, js_lws_is_first_fragment),
|
||||
JS_CFUNC_DEF("frame_is_binary", 0, js_lws_frame_is_binary),
|
||||
JS_CFUNC_DEF("callback_on_writable", 0, js_lws_callback_on_writable),
|
||||
JS_CFUNC_DEF("write", 1, js_lws_write),
|
||||
JS_CFUNC_DEF("close_reason", 2, js_lws_close_reason),
|
||||
};
|
||||
|
||||
static int js_lws_init(JSContext *ctx, JSModuleDef *m)
|
||||
{
|
||||
JSValue proto;
|
||||
|
||||
JS_NewClassID(&js_lws_context_class_id);
|
||||
JS_NewClass(JS_GetRuntime(ctx), js_lws_context_class_id,
|
||||
&js_lws_context_class);
|
||||
proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, proto, js_lws_context_proto_funcs,
|
||||
countof(js_lws_context_proto_funcs));
|
||||
JS_SetClassProto(ctx, js_lws_context_class_id, proto);
|
||||
|
||||
JS_NewClassID(&js_lws_wsi_class_id);
|
||||
JS_NewClass(JS_GetRuntime(ctx), js_lws_wsi_class_id, &js_lws_wsi_class);
|
||||
proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, proto, js_lws_wsi_proto_funcs,
|
||||
countof(js_lws_wsi_proto_funcs));
|
||||
JS_SetClassProto(ctx, js_lws_wsi_class_id, proto);
|
||||
|
||||
return JS_SetModuleExportList(ctx, m, js_lws_funcs, countof(js_lws_funcs));
|
||||
}
|
||||
|
||||
JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
|
||||
{
|
||||
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_lws_init);
|
||||
if (m == NULL)
|
||||
return NULL;
|
||||
JS_AddModuleExportList(ctx, m, js_lws_funcs, countof(js_lws_funcs));
|
||||
return m;
|
||||
}
|
||||
415
quickjs-websocket/src/websocket.js
Normal file
415
quickjs-websocket/src/websocket.js
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Genexis B.V.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy,
|
||||
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import * as os from 'os'
|
||||
import * as lws from './lws-client.so'
|
||||
|
||||
const CONNECTING = 0
|
||||
const OPEN = 1
|
||||
const CLOSING = 2
|
||||
const CLOSED = 3
|
||||
|
||||
const CLOSING1 = 0x10 | CLOSING
|
||||
const CLOSING2 = 0x20 | CLOSING
|
||||
|
||||
function serviceScheduler (context) {
|
||||
let running = false
|
||||
let timeout = null
|
||||
|
||||
function schedule (time) {
|
||||
if (timeout) os.clearTimeout(timeout)
|
||||
timeout = running ? os.setTimeout(callback, time) : null
|
||||
}
|
||||
|
||||
function callback () {
|
||||
schedule(context.service_periodic())
|
||||
}
|
||||
|
||||
return {
|
||||
start: function () {
|
||||
running = true
|
||||
schedule(0)
|
||||
},
|
||||
stop: function () {
|
||||
running = false
|
||||
schedule(0)
|
||||
},
|
||||
reschedule: schedule
|
||||
}
|
||||
}
|
||||
|
||||
function fdHandler (fd, events, revents) {
|
||||
return function () {
|
||||
service.reschedule(context.service_fd(fd, events, revents))
|
||||
}
|
||||
}
|
||||
|
||||
function contextCallback (wsi, reason, arg) {
|
||||
switch (reason) {
|
||||
case lws.LWS_CALLBACK_ADD_POLL_FD:
|
||||
service.start()
|
||||
// fallthrough
|
||||
case lws.LWS_CALLBACK_CHANGE_MODE_POLL_FD:
|
||||
os.setReadHandler(
|
||||
arg.fd,
|
||||
(arg.events & lws.LWS_POLLIN)
|
||||
? fdHandler(arg.fd, arg.events, lws.LWS_POLLIN)
|
||||
: null
|
||||
)
|
||||
os.setWriteHandler(
|
||||
arg.fd,
|
||||
(arg.events & lws.LWS_POLLOUT)
|
||||
? fdHandler(arg.fd, arg.events, lws.LWS_POLLOUT)
|
||||
: null
|
||||
)
|
||||
break
|
||||
case lws.LWS_CALLBACK_DEL_POLL_FD:
|
||||
os.setReadHandler(arg.fd, null)
|
||||
os.setWriteHandler(arg.fd, null)
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
wsi.user.error(typeof arg === 'string' ? arg : '')
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH:
|
||||
if (wsi.user.readyState !== CONNECTING) {
|
||||
return -1
|
||||
}
|
||||
wsi.user.protocol = wsi.WSI_TOKEN_PROTOCOL
|
||||
wsi.user.extensions = wsi.WSI_TOKEN_EXTENSIONS
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
if (wsi.user.readyState !== CONNECTING) {
|
||||
return -1
|
||||
}
|
||||
wsi.user.wsi = wsi
|
||||
wsi.user.open()
|
||||
break
|
||||
case lws.LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
|
||||
if (wsi.user.readyState === CLOSED) {
|
||||
return -1
|
||||
}
|
||||
if (arg instanceof Array) {
|
||||
wsi.user.closeEvent.code = arg[0]
|
||||
wsi.user.closeEvent.reason = arg[1]
|
||||
} else {
|
||||
wsi.user.closeEvent.code = 1005
|
||||
wsi.user.closeEvent.reason = ''
|
||||
}
|
||||
wsi.user.readyState = CLOSING2
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_CLOSED:
|
||||
wsi.user.close()
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
if (!(arg instanceof ArrayBuffer) ||
|
||||
wsi.user.readyState === CONNECTING ||
|
||||
wsi.user.readyState === CLOSED) {
|
||||
return -1
|
||||
}
|
||||
if (wsi.is_first_fragment()) {
|
||||
wsi.user.inbuf = []
|
||||
}
|
||||
wsi.user.inbuf.push(arg)
|
||||
if (wsi.is_final_fragment()) {
|
||||
wsi.user.message(wsi.frame_is_binary())
|
||||
}
|
||||
break
|
||||
case lws.LWS_CALLBACK_CLIENT_WRITEABLE:
|
||||
if ((wsi.user.readyState === OPEN || wsi.user.readyState === CLOSING1) &&
|
||||
wsi.user.outbuf.length > 0) {
|
||||
const msg = wsi.user.outbuf.shift()
|
||||
if (msg === null) {
|
||||
wsi.user.readyState = CLOSING2
|
||||
return -1
|
||||
}
|
||||
wsi.write(msg)
|
||||
if (wsi.user.outbuf.length > 0) {
|
||||
wsi.callback_on_writable()
|
||||
}
|
||||
}
|
||||
break
|
||||
case lws.LWS_CALLBACK_WSI_DESTROY:
|
||||
if (wsi.context.connections === 0) service.stop()
|
||||
break
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
lws.set_log_level(lws.LLL_ERR | lws.LLL_WARN)
|
||||
const context = lws.create_context(contextCallback, true)
|
||||
const service = serviceScheduler(context)
|
||||
|
||||
function arrayBufferJoin (bufs) {
|
||||
if (!(bufs instanceof Array)) {
|
||||
throw new TypeError('Array expected')
|
||||
}
|
||||
|
||||
if (!bufs.every(function (val) { return val instanceof ArrayBuffer })) {
|
||||
throw new TypeError('ArrayBuffer expected')
|
||||
}
|
||||
|
||||
const len = bufs.reduce(function (acc, val) {
|
||||
return acc + val.byteLength
|
||||
}, 0)
|
||||
const array = new Uint8Array(len)
|
||||
|
||||
let offset = 0
|
||||
for (const b of bufs) {
|
||||
array.set(new Uint8Array(b), offset)
|
||||
offset += b.byteLength
|
||||
}
|
||||
|
||||
return array.buffer
|
||||
}
|
||||
|
||||
export function WebSocket (url, protocols) {
|
||||
const pattern = /^(ws|wss):\/\/([^/?#]*)([^#]*)$/i
|
||||
const match = pattern.exec(url)
|
||||
if (match === null) {
|
||||
throw new TypeError('invalid WebSocket URL')
|
||||
}
|
||||
const secure = match[1].toLowerCase() === 'wss'
|
||||
const host = match[2]
|
||||
const path = match[3].startsWith('/') ? match[3] : '/' + match[3]
|
||||
|
||||
const hostPattern = /^(?:([a-z\d.-]+)|\[([\da-f:]+:[\da-f.]*)\])(?::(\d*))?$/i
|
||||
const hostMatch = hostPattern.exec(host)
|
||||
if (hostMatch === null) {
|
||||
throw new TypeError('invalid WebSocket URL')
|
||||
}
|
||||
const address = hostMatch[1] || hostMatch[2]
|
||||
const port = hostMatch[3] ? parseInt(hostMatch[3]) : (secure ? 443 : 80)
|
||||
|
||||
const validPath = /^\/[A-Za-z0-9_.!~*'()%:@&=+$,;/?-]*$/
|
||||
if (!validPath.test(path)) {
|
||||
throw new TypeError('invalid WebSocket URL')
|
||||
}
|
||||
if (!(port >= 1 && port <= 65535)) {
|
||||
throw new RangeError('port must be between 1 and 65535')
|
||||
}
|
||||
|
||||
if (protocols === undefined) {
|
||||
protocols = []
|
||||
} else if (!(protocols instanceof Array)) {
|
||||
protocols = [protocols]
|
||||
}
|
||||
const validProto = /^[A-Za-z0-9!#$%&'*+.^_|~-]+$/
|
||||
if (!protocols.every(function (val) { return validProto.test(val) })) {
|
||||
throw new TypeError('invalid WebSocket subprotocol name')
|
||||
}
|
||||
const proto = protocols.length > 0 ? protocols.join(', ') : null
|
||||
|
||||
const self = this
|
||||
const state = {
|
||||
url: url,
|
||||
readyState: CONNECTING,
|
||||
extensions: '',
|
||||
protocol: '',
|
||||
onopen: null,
|
||||
onerror: null,
|
||||
onclose: null,
|
||||
onmessage: null,
|
||||
wsi: null,
|
||||
inbuf: [],
|
||||
outbuf: [],
|
||||
closeEvent: {
|
||||
type: 'close',
|
||||
code: 1005,
|
||||
reason: '',
|
||||
wasClean: false
|
||||
},
|
||||
open: function () {
|
||||
if (state.readyState === CONNECTING) {
|
||||
state.readyState = OPEN
|
||||
if (state.onopen) {
|
||||
state.onopen.call(self, { type: 'open' })
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
if (state.readyState !== CLOSED) {
|
||||
state.closeEvent.code = 1006
|
||||
state.closeEvent.reason = String(e)
|
||||
state.readyState = CLOSED
|
||||
try {
|
||||
if (state.onerror) {
|
||||
state.onerror.call(self, { type: 'error' })
|
||||
}
|
||||
} finally {
|
||||
if (state.onclose) {
|
||||
state.onclose.call(self, Object.assign({}, state.closeEvent))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
if (state.readyState !== CLOSED) {
|
||||
state.closeEvent.wasClean = true
|
||||
state.readyState = CLOSED
|
||||
if (state.onclose) {
|
||||
state.onclose.call(self, Object.assign({}, state.closeEvent))
|
||||
}
|
||||
}
|
||||
},
|
||||
message: function (binary) {
|
||||
if (state.inbuf.length > 0) {
|
||||
const msg = state.inbuf.length === 1
|
||||
? state.inbuf[0]
|
||||
: arrayBufferJoin(state.inbuf)
|
||||
state.inbuf = []
|
||||
if (state.readyState === OPEN && state.onmessage) {
|
||||
state.onmessage.call(self, {
|
||||
type: 'messasge',
|
||||
data: binary ? msg : lws.decode_utf8(msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._wsState = state
|
||||
|
||||
os.setTimeout(function () {
|
||||
try {
|
||||
context.client_connect(
|
||||
address, port, secure, path, host, null, proto, state
|
||||
)
|
||||
} catch (e) {
|
||||
state.error(e)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const readyStateConstants = {
|
||||
CONNECTING: { value: CONNECTING },
|
||||
OPEN: { value: OPEN },
|
||||
CLOSING: { value: CLOSING },
|
||||
CLOSED: { value: CLOSED }
|
||||
}
|
||||
|
||||
Object.defineProperties(WebSocket, readyStateConstants)
|
||||
Object.defineProperties(WebSocket.prototype, readyStateConstants)
|
||||
|
||||
function checkNullOrFunction (val) {
|
||||
if (val !== null && typeof val !== 'function') {
|
||||
throw new TypeError('null or Function expected')
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(WebSocket.prototype, {
|
||||
url: { get: function () { return this._wsState.url } },
|
||||
readyState: { get: function () { return this._wsState.readyState & 0xf } },
|
||||
extensions: { get: function () { return this._wsState.extensions } },
|
||||
protocol: { get: function () { return this._wsState.protocol } },
|
||||
bufferedAmount: {
|
||||
get: function () {
|
||||
return this._wsState.outbuf.reduce(function (acc, val) {
|
||||
if (val instanceof ArrayBuffer) {
|
||||
acc += val.byteLength
|
||||
} else if (typeof val === 'string') {
|
||||
acc += val.length
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
}
|
||||
},
|
||||
binaryType: {
|
||||
get: function () { return 'arraybuffer' },
|
||||
set: function (val) {
|
||||
if (val !== 'arraybuffer') {
|
||||
throw new TypeError('only "arraybuffer" allowed for "binaryType"')
|
||||
}
|
||||
}
|
||||
},
|
||||
onopen: {
|
||||
get: function () { return this._wsState.onopen },
|
||||
set: function (val) {
|
||||
checkNullOrFunction(val)
|
||||
this._wsState.onopen = val
|
||||
}
|
||||
},
|
||||
onerror: {
|
||||
get: function () { return this._wsState.onerror },
|
||||
set: function (val) {
|
||||
checkNullOrFunction(val)
|
||||
this._wsState.onerror = val
|
||||
}
|
||||
},
|
||||
onclose: {
|
||||
get: function () { return this._wsState.onclose },
|
||||
set: function (val) {
|
||||
checkNullOrFunction(val)
|
||||
this._wsState.onclose = val
|
||||
}
|
||||
},
|
||||
onmessage: {
|
||||
get: function () { return this._wsState.onmessage },
|
||||
set: function (val) {
|
||||
checkNullOrFunction(val)
|
||||
this._wsState.onmessage = val
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
WebSocket.prototype.close = function (code, reason) {
|
||||
if (code !== undefined) {
|
||||
code = Math.trunc(code)
|
||||
reason = reason === undefined ? '' : String(reason)
|
||||
if (code !== 1000 && !(code >= 3000 && code <= 4999)) {
|
||||
throw new RangeError('code must be 1000 or between 3000 and 4999')
|
||||
}
|
||||
}
|
||||
const state = this._wsState
|
||||
if (state.readyState === OPEN) {
|
||||
if (code !== undefined) {
|
||||
state.wsi.close_reason(code, reason)
|
||||
state.closeEvent.code = code
|
||||
state.closeEvent.reason = reason
|
||||
}
|
||||
state.readyState = CLOSING1
|
||||
state.outbuf.push(null)
|
||||
state.wsi.callback_on_writable()
|
||||
} else if (state.readyState === CONNECTING) {
|
||||
state.readyState = CLOSING2
|
||||
}
|
||||
}
|
||||
|
||||
WebSocket.prototype.send = function (msg) {
|
||||
const state = this._wsState
|
||||
if (state.readyState === CONNECTING) {
|
||||
throw new TypeError('send() not allowed in CONNECTING state')
|
||||
}
|
||||
if (msg instanceof ArrayBuffer) {
|
||||
state.outbuf.push(msg.slice(0))
|
||||
} else if (ArrayBuffer.isView(msg)) {
|
||||
state.outbuf.push(
|
||||
msg.buffer.slice(msg.byteOffset, msg.byteOffset + msg.byteLength)
|
||||
)
|
||||
} else {
|
||||
state.outbuf.push(String(msg))
|
||||
}
|
||||
if (state.readyState === OPEN) {
|
||||
state.wsi.callback_on_writable()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue