diff --git a/client/build.sh b/client/build.sh
index be013ab..c77d2e7 100755
--- a/client/build.sh
+++ b/client/build.sh
@@ -80,11 +80,14 @@ VERSION=$(cat package.json | jq -r '.version')
sed -i "s/__library_version__/$VERSION/" $OUT_FILE
#add extra libraries
+sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/copyright.js" $OUT_FILE
sed -i "/__extra_libraries__/r $WISP_CLIENT/polyfill.js" $OUT_FILE
sed -i "/__extra_libraries__/r $WISP_CLIENT/wisp.js" $OUT_FILE
sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/messages.js" $OUT_FILE
+sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/tls_socket.js" $OUT_FILE
sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/websocket.js" $OUT_FILE
-sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/copyright.js" $OUT_FILE
+sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/custom_websocket.js" $OUT_FILE
+sed -i "/__extra_libraries__/r $JAVSCRIPT_DIR/util.js" $OUT_FILE
#apply patches
python3 tools/patch_js.py $FRAGMENTS_DIR $OUT_FILE
diff --git a/client/exported_funcs.txt b/client/exported_funcs.txt
index f437e28..ccd56dc 100644
--- a/client/exported_funcs.txt
+++ b/client/exported_funcs.txt
@@ -8,7 +8,7 @@ get_version
recv_from_websocket
send_to_websocket
close_websocket
-cleanup_websocket
+cleanup_handle
get_result_size
get_result_buffer
get_result_code
@@ -16,4 +16,7 @@ get_result_closed
get_result_bytes_left
get_result_is_text
+recv_from_socket
+send_to_socket
+
free
\ No newline at end of file
diff --git a/client/index.html b/client/index.html
index 9760e4c..8555276 100644
--- a/client/index.html
+++ b/client/index.html
@@ -6,7 +6,7 @@
diff --git a/client/javascript/custom_websocket.js b/client/javascript/custom_websocket.js
new file mode 100644
index 0000000..eb91f46
--- /dev/null
+++ b/client/javascript/custom_websocket.js
@@ -0,0 +1,112 @@
+//class for custom websocket
+//multiple classes attempt to replicate the websocket API
+//so this prevents code duplication
+
+class CustomWebSocket extends EventTarget {
+ constructor(url, protocols=[]) {
+ super();
+
+ this.url = url;
+ this.protocols = protocols;
+ this.binaryType = "blob";
+
+ //legacy event handlers
+ this.onopen = () => {};
+ this.onerror = () => {};
+ this.onmessage = () => {};
+ this.onclose = () => {};
+
+ this.CONNECTING = 0;
+ this.OPEN = 1;
+ this.CLOSING = 2;
+ this.CLOSED = 3;
+
+ this.status = this.CONNECTING;
+ }
+
+ custom_recv() {}
+ recv() {
+ let {success, data, is_text} = this.custom_recv();
+ if (!success) return;
+
+ let converted;
+ if (is_text) {
+ converted = new TextDecoder().decode(data);
+ }
+ else {
+ if (this.binaryType == "blob") {
+ converted = new Blob(data);
+ }
+ else if (this.binaryType == "arraybuffer") {
+ converted = data.buffer;
+ }
+ else {
+ throw "invalid binaryType string";
+ }
+ }
+
+ let msg_event = new MessageEvent("message", {data: converted});
+ this.onmessage(msg_event);
+ this.dispatchEvent(msg_event);
+ }
+
+ recv_loop() {
+ this.event_loop = setInterval(() => {
+ this.recv();
+ }, 0);
+ }
+
+ close_callback(error) {
+ if (this.status == this.CLOSED) return;
+ this.status = this.CLOSED;
+
+ if (error) {
+ let error_event = new Event("error");
+ this.dispatchEvent(error_event);
+ this.onerror(error_event);
+ }
+ else {
+ let close_event = new CloseEvent("close");
+ this.dispatchEvent(close_event);
+ this.onclose(close_event);
+ }
+ }
+
+ open_callback() {
+ this.status = this.OPEN;
+ let open_event = new Event("open");
+ this.onopen(open_event);
+ this.dispatchEvent(open_event);
+ }
+
+ send(data) {
+ let is_text = typeof data === "string";
+ if (this.status === this.CONNECTING) {
+ throw new DOMException("ws not ready yet");
+ }
+ if (this.status === this.CLOSED) {
+ return;
+ }
+
+ let data_array = any_to_array(data);
+ this.custom_send(data_array, is_text);
+ }
+
+ close() {
+ this.status = this.CLOSING;
+ this.custom_close();
+ }
+
+ get readyState() {
+ return this.status;
+ }
+ get bufferedAmount() {
+ return 0;
+ }
+ get protocol() {
+ return this.protocols;
+ }
+ get extensions() {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/client/javascript/main.js b/client/javascript/main.js
index f42596b..3110e53 100644
--- a/client/javascript/main.js
+++ b/client/javascript/main.js
@@ -45,42 +45,6 @@ function check_loaded(check_websocket) {
}
}
-//a case insensitive dictionary for request headers
-class HeadersDict {
- constructor(obj) {
- for (let key in obj) {
- this[key] = obj[key];
- }
- return new Proxy(this, this);
- }
- get(target, prop) {
- let keys = Object.keys(this);
- for (let key of keys) {
- if (key.toLowerCase() === prop.toLowerCase()) {
- return this[key];
- }
- }
- }
- set(target, prop, value) {
- let keys = Object.keys(this);
- for (let key of keys) {
- if (key.toLowerCase() === prop.toLowerCase()) {
- this[key] = value;
- }
- }
- this[prop] = value;
- return true;
- }
-}
-
-function allocate_str(str) {
- return allocate(intArrayFromString(str), ALLOC_NORMAL);
-}
-
-function allocate_array(array) {
- return allocate(array, ALLOC_NORMAL);
-}
-
//low level interface with c code
function perform_request(url, params, js_data_callback, js_end_callback, body=null) {
let params_str = JSON.stringify(params);
@@ -313,15 +277,17 @@ return {
load_wasm: load_wasm,
wisp: _wisp_connections,
WebSocket: CurlWebSocket,
+ TLSSocket: TLSSocket,
get copyright() {return copyright_notice},
get version() {return get_version()},
get ready() {return wasm_ready},
+ get websocket_url() {return websocket_url},
get stdout() {return out},
set stdout(callback) {out = callback},
get stderr() {return err},
- set stderr(callback) {err = callback}
+ set stderr(callback) {err = callback},
}
})()
\ No newline at end of file
diff --git a/client/javascript/tls_socket.js b/client/javascript/tls_socket.js
new file mode 100644
index 0000000..36eca50
--- /dev/null
+++ b/client/javascript/tls_socket.js
@@ -0,0 +1,73 @@
+class TLSSocket extends CustomWebSocket {
+ constructor(hostname, port, debug) {
+ super();
+ this.hostname = hostname;
+ this.port = port;
+ this.url = `https://${hostname}:${port}`;
+ this.debug = debug;
+
+ this.connect();
+ }
+
+ connect() {
+ let data_callback = () => {};
+ let finish_callback = (error, response_info) => {
+ this.status = this.OPEN;
+ if (error === 0) {
+ this.open_callback();
+ this.recv_loop();
+ }
+ else {
+ this.cleanup(error);
+ }
+ }
+ let options = {
+ _connect_only: 1,
+ }
+ if (this.debug) options._libcurl_verbose = 1;
+
+ this.http_handle = perform_request(this.url, options, data_callback, finish_callback, null);
+ }
+
+ custom_recv() {
+ let buffer_size = 64*1024;
+ let result_ptr = _recv_from_socket(this.http_handle, buffer_size);
+ let data_ptr = _get_result_buffer(result_ptr);
+ let result_code = _get_result_code(result_ptr);
+
+ if (result_code == 0) { //CURLE_OK - data received
+ if (_get_result_closed(result_ptr)) {
+ this.close_callback();
+ return;
+ }
+
+ let data_size = _get_result_size(result_ptr);
+ let data_heap = Module.HEAPU8.subarray(data_ptr, data_ptr + data_size);
+ let data = new Uint8Array(data_heap);
+
+ let message_event = new MessageEvent("message", {data: data});
+ this.dispatchEvent(message_event);
+ }
+
+ _free(data_ptr);
+ _free(result_ptr);
+ }
+
+ custom_send(data_array) {
+ let data_ptr = allocate_array(data_array);
+ let data_len = data_array.length;
+ _send_to_socket(this.http_handle, data_ptr, data_len);
+ _free(data_ptr);
+ }
+
+ cleanup(error=false) {
+ if (this.http_handle) _cleanup_handle(this.http_handle);
+ clearInterval(this.event_loop);
+ this.close_callback(error);
+ }
+
+ custom_close() {
+ this.cleanup();
+ this.status = this.CLOSED;
+ }
+}
\ No newline at end of file
diff --git a/client/javascript/util.js b/client/javascript/util.js
new file mode 100644
index 0000000..989d1b8
--- /dev/null
+++ b/client/javascript/util.js
@@ -0,0 +1,70 @@
+//a case insensitive dictionary for request headers
+class HeadersDict {
+ constructor(obj) {
+ for (let key in obj) {
+ this[key] = obj[key];
+ }
+ return new Proxy(this, this);
+ }
+ get(target, prop) {
+ let keys = Object.keys(this);
+ for (let key of keys) {
+ if (key.toLowerCase() === prop.toLowerCase()) {
+ return this[key];
+ }
+ }
+ }
+ set(target, prop, value) {
+ let keys = Object.keys(this);
+ for (let key of keys) {
+ if (key.toLowerCase() === prop.toLowerCase()) {
+ this[key] = value;
+ }
+ }
+ this[prop] = value;
+ return true;
+ }
+}
+
+function allocate_str(str) {
+ return allocate(intArrayFromString(str), ALLOC_NORMAL);
+}
+
+function allocate_array(array) {
+ return allocate(array, ALLOC_NORMAL);
+}
+
+//convert any data to a uint8array
+function any_to_array(data) {
+ let data_array;
+ if (typeof data === "string") {
+ data_array = new TextEncoder().encode(data);
+ }
+ else if (data instanceof Blob) {
+ data.arrayBuffer().then(array_buffer => {
+ data_array = new Uint8Array(array_buffer);
+ this.send(data_array);
+ });
+ return;
+ }
+ //any typedarray
+ else if (data instanceof ArrayBuffer) {
+ //dataview objects
+ if (ArrayBuffer.isView(data) && data instanceof DataView) {
+ data_array = new Uint8Array(data.buffer);
+ }
+ //regular arraybuffers
+ else {
+ data_array = new Uint8Array(data);
+ }
+ }
+ //regular typed arrays
+ else if (ArrayBuffer.isView(data)) {
+ data_array = Uint8Array.from(data);
+ }
+ else {
+ throw "invalid data type";
+ }
+
+ return data_array;
+}
\ No newline at end of file
diff --git a/client/javascript/websocket.js b/client/javascript/websocket.js
index 23af368..3330e1f 100644
--- a/client/javascript/websocket.js
+++ b/client/javascript/websocket.js
@@ -1,38 +1,30 @@
-//class for custom websocket
-
-class CurlWebSocket extends EventTarget {
- constructor(url, protocols=[], websocket_debug=false) {
- super();
+class CurlWebSocket extends CustomWebSocket {
+ constructor(url, protocols=[], debug=false) {
+ super(url, protocols);
check_loaded(true);
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
throw new SyntaxError("invalid url");
}
-
- this.url = url;
+
this.protocols = protocols;
- this.binaryType = "blob";
+ this.debug = debug;
this.recv_buffer = [];
- this.websocket_debug = websocket_debug;
-
- //legacy event handlers
- this.onopen = () => {};
- this.onerror = () => {};
- this.onmessage = () => {};
- this.onclose = () => {};
-
- this.CONNECTING = 0;
- this.OPEN = 1;
- this.CLOSING = 2;
- this.CLOSED = 3;
this.connect();
}
connect() {
- this.status = this.CONNECTING;
let data_callback = () => {};
let finish_callback = (error, response_info) => {
- this.finish_callback(error, response_info);
+ if (error === 0) {
+ this.status = this.OPEN;
+ this.open_callback();
+ this.recv_loop();
+ }
+ else {
+ this.status = this.CLOSED;
+ this.cleanup(error);
+ }
}
let options = {};
if (this.protocols) {
@@ -40,23 +32,23 @@ class CurlWebSocket extends EventTarget {
"Sec-Websocket-Protocol": this.protocols.join(", "),
};
}
- if (this.websocket_debug) {
+ if (this.debug) {
options._libcurl_verbose = 1;
}
this.http_handle = perform_request(this.url, options, data_callback, finish_callback, null);
- this.recv_loop();
}
- recv() {
+ custom_recv() {
let buffer_size = 64*1024;
let result_ptr = _recv_from_websocket(this.http_handle, buffer_size);
let data_ptr = _get_result_buffer(result_ptr);
let result_code = _get_result_code(result_ptr);
- if (result_code == 0) { //CURLE_OK - data recieved
+ if (result_code == 0) { //CURLE_OK - data received
if (_get_result_closed(result_ptr)) {
- //this.pass_buffer();
- this.close_callback();
+ _free(data_ptr);
+ _free(result_ptr);
+ this.cleanup();
return;
}
@@ -69,133 +61,43 @@ class CurlWebSocket extends EventTarget {
let full_data = merge_arrays(this.recv_buffer);
let is_text = _get_result_is_text(result_ptr)
this.recv_buffer = [];
- this.recv_callback(full_data, is_text);
+ return {
+ success: true,
+ data: full_data,
+ is_text: is_text
+ }
}
}
if (result_code == 52) { //CURLE_GOT_NOTHING - socket closed
- this.close_callback();
+ this.cleanup();
}
_free(data_ptr);
_free(result_ptr);
- }
- recv_loop() {
- this.event_loop = setInterval(() => {
- this.recv();
- }, 1);
- }
-
- recv_callback(data, is_text=false) {
- let converted;
- if (is_text) {
- converted = new TextDecoder().decode(data);
+ return {
+ success: false,
+ data: null,
+ is_text: false
}
- else {
- if (this.binaryType == "blob") {
- converted = new Blob(data);
- }
- else if (this.binaryType == "arraybuffer") {
- converted = data.buffer;
- }
- else {
- throw "invalid binaryType string";
- }
- }
-
- let msg_event = new MessageEvent("message", {data: converted});
- this.onmessage(msg_event);
- this.dispatchEvent(msg_event);
}
- close_callback(error=false) {
- if (this.status == this.CLOSED) return;
- this.status = this.CLOSED;
-
+ cleanup(error=false) {
+ if (this.http_handle) _cleanup_handle(this.http_handle);
clearInterval(this.event_loop);
- _cleanup_websocket();
-
- if (error) {
- let error_event = new Event("error");
- this.dispatchEvent(error_event);
- this.onerror(error_event);
- }
- else {
- let close_event = new CloseEvent("close");
- this.dispatchEvent(close_event);
- this.onclose(close_event);
- }
+ this.close_callback(error);
}
- finish_callback(error, response_info) {
- this.status = this.OPEN;
- if (error != 0) this.close_callback(true);
- let open_event = new Event("open");
- this.onopen(open_event);
- this.dispatchEvent(open_event);
- }
-
- send(data) {
- let is_text = false;
- if (this.status === this.CONNECTING) {
- throw new DOMException("ws not ready yet");
- }
- if (this.status === this.CLOSED) {
- return;
- }
-
- let data_array;
- if (typeof data === "string") {
- data_array = new TextEncoder().encode(data);
- is_text = true;
- }
- else if (data instanceof Blob) {
- data.arrayBuffer().then(array_buffer => {
- data_array = new Uint8Array(array_buffer);
- this.send(data_array);
- });
- return;
- }
- //any typedarray
- else if (data instanceof ArrayBuffer) {
- //dataview objects
- if (ArrayBuffer.isView(data) && data instanceof DataView) {
- data_array = new Uint8Array(data.buffer);
- }
- //regular arraybuffers
- else {
- data_array = new Uint8Array(data);
- }
- }
- //regular typed arrays
- else if (ArrayBuffer.isView(data)) {
- data_array = Uint8Array.from(data);
- }
- else {
- throw "invalid data type to be sent";
- }
-
+ custom_send(data_array, is_text) {
let data_ptr = allocate_array(data_array);
- let data_len = data.length;
+ let data_len = data_array.length;
_send_to_websocket(this.http_handle, data_ptr, data_len, is_text);
_free(data_ptr);
}
- close() {
- _close_websocket(this.http_handle);
- }
-
- get readyState() {
- return this.status;
- }
- get bufferedAmount() {
- return 0;
- }
- get protocol() {
- return "";
- }
- get extensions() {
- return "";
+ custom_close() {
+ this.cleanup();
+ this.status = this.CLOSED;
}
}
\ No newline at end of file
diff --git a/client/libcurl/main.c b/client/libcurl/main.c
index f085d7a..ebb04f4 100644
--- a/client/libcurl/main.c
+++ b/client/libcurl/main.c
@@ -83,6 +83,13 @@ CURL* start_request(const char* url, const char* json_params, DataCallback data_
curl_easy_setopt(http_handle, CURLOPT_VERBOSE, 1L);
}
+ if (strcmp(key, "_connect_only") == 0) {
+ curl_easy_setopt(http_handle, CURLOPT_CONNECT_ONLY, 1L);
+ curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_easy_setopt(http_handle, CURLOPT_SSL_ENABLE_ALPN, 0L);
+ prevent_cleanup = 1;
+ }
+
if (strcmp(key, "method") == 0 && cJSON_IsString(item)) {
curl_easy_setopt(http_handle, CURLOPT_CUSTOMREQUEST, item->valuestring);
}
diff --git a/client/libcurl/tls_socket.c b/client/libcurl/tls_socket.c
new file mode 100644
index 0000000..c890769
--- /dev/null
+++ b/client/libcurl/tls_socket.c
@@ -0,0 +1,24 @@
+#include
+
+#include "curl/curl.h"
+#include "curl/easy.h"
+#include "types.h"
+
+struct WSResult* recv_from_socket(CURL* http_handle, int buffer_size) {
+ size_t nread;
+ char* buffer = malloc(buffer_size);
+ CURLcode res = curl_easy_recv(http_handle, buffer, buffer_size, &nread);
+
+ struct WSResult* result = malloc(sizeof(struct WSResult));
+ result->buffer_size = nread;
+ result->buffer = buffer;
+ result->res = (int) res;
+ result->closed = (nread == 0);
+ return result;
+}
+
+int send_to_socket(CURL* http_handle, const char* data, int data_len) {
+ size_t sent;
+ CURLcode res = curl_easy_send(http_handle, data, data_len, &sent);
+ return (int) res;
+}
\ No newline at end of file
diff --git a/client/libcurl/websocket.c b/client/libcurl/websocket.c
index 4c7e5fd..49d2ddf 100644
--- a/client/libcurl/websocket.c
+++ b/client/libcurl/websocket.c
@@ -37,7 +37,7 @@ void close_websocket(CURL* http_handle) {
}
//clean up the http handle associated with the websocket, since the main loop can't do this automatically
-void cleanup_websocket(CURL* http_handle) {
+void cleanup_handle(CURL* http_handle) {
struct RequestInfo *request_info;
curl_easy_getinfo(http_handle, CURLINFO_PRIVATE, &request_info);
diff --git a/client/package.json b/client/package.json
index 47caa58..0981d4e 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "libcurl.js",
- "version": "0.3.3",
+ "version": "0.4.0",
"description": "An experimental port of libcurl to WebAssembly for use in the browser.",
"main": "libcurl.mjs",
"scripts": {
diff --git a/client/wisp_client b/client/wisp_client
index 51ad95a..84b71a2 160000
--- a/client/wisp_client
+++ b/client/wisp_client
@@ -1 +1 @@
-Subproject commit 51ad95a6d912ec404c20284f0cded40c0b5c4e62
+Subproject commit 84b71a2882378dcb48d6d421fdb19290ebe0df23
diff --git a/server/wisp_server b/server/wisp_server
index 65d3124..3b0d432 160000
--- a/server/wisp_server
+++ b/server/wisp_server
@@ -1 +1 @@
-Subproject commit 65d312414aa896d8f9e18f48e8fe37b72d75ee5c
+Subproject commit 3b0d432e89cff7eaa850ba8605b180189e237f1b