Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing improvements for io and fixes for websocketclient, and a few others #1429

Open
wants to merge 21 commits into
base: public
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6bc4f6a
Start of io typings work
cmidgley Oct 17, 2024
f3c862b
Adjust namespace for 'config' to `websocketclient/config`
cmidgley Oct 19, 2024
e42b93c
Merge branch 'public' into io-typings
cmidgley Oct 19, 2024
2fe80d7
Revert config.js
cmidgley Oct 19, 2024
65d177e
Reverse webclient manfiest
cmidgley Oct 19, 2024
201906a
Revert TLS include
cmidgley Oct 19, 2024
9a3aa33
Define Device into global namespace to allow augmentation; have WebSo…
cmidgley Oct 19, 2024
afeb566
Removed unused import
cmidgley Oct 19, 2024
04a118f
Fixed module imports to be within declare
cmidgley Oct 19, 2024
b6e275e
More refinements/improvements to tcp/udp/dns/websocket types
cmidgley Oct 20, 2024
365854b
More io types, expanded webpage w/status and headers, fixed up 'this'…
cmidgley Oct 20, 2024
72b162a
Add status codes to http/1.1 response
cmidgley Oct 21, 2024
2d935cb
Fix bug with attach socket
cmidgley Oct 21, 2024
e002eab
Further type improvements for websocketclient
cmidgley Oct 21, 2024
a0c86df
Add onError error messages, don't use masks on server (attached socke…
cmidgley Oct 21, 2024
f88cff5
Fix bug with reversed client/server on mask
cmidgley Oct 22, 2024
b318fce
Switch builtin-global to builtin-device to be consistent with other t…
cmidgley Oct 23, 2024
629663f
Remove unnecessary import
cmidgley Oct 23, 2024
47168b1
Revert prior and apply new fix for attached sockets
cmidgley Oct 24, 2024
876f4e0
Fix type of buffer on onControl
cmidgley Oct 24, 2024
08e1cee
Small improvements on types
cmidgley Oct 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion examples/io/listener/httpserver/httpserver.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for filling this in. The change is good. I don't want even more copies of the HTTP status code map in the repo. I'll make a module for those so it can be shared. But that doesn't need to hold this up.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@

import Timer from "timer";

const statusReasons = Object.freeze({
200: "OK",
201: "Created",
204: "No Content",
301: "Moved Permanently",
302: "Found",
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
500: "Internal Server Error",
503: "Service Unavailable",
});

class Connection {
#server;
#socket;
Expand Down Expand Up @@ -288,7 +302,7 @@ class Connection {

switch (this.#state) {
case "sendResponse":
this.#pendingWrite = `HTTP/1.1 ${this.#options.status} FILL_THIS_IN\r\n`;
this.#pendingWrite = `HTTP/1.1 ${this.#options.status} ${statusReasons[this.#options.status] ?? ""}\r\n`;
this.#pendingWrite = ArrayBuffer.fromString(this.#pendingWrite);
this.#writePosition = 0;

Expand Down
3 changes: 2 additions & 1 deletion examples/io/listener/httpserver/options/webpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
export default {
onResponse(response) {
response.headers.set("content-length", this.route.msg.byteLength);
response.headers.set("content-type", "text/html");
response.headers.set("content-type", this.route.contentType ?? "text/html");
response.status = this.route.status ?? 200;
this.respond(response);
},
onWritable(count) {
Expand Down
2 changes: 1 addition & 1 deletion examples/io/tcp/websocket/WebSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class WebSocket {
this.#protocol = protocol;
options = { ...config, host, port, path, protocol, headers }
}
else
else if (!options.attach)
throw new URIError("no URL");
this.#client = new device.network.ws.io({
...options,
Expand Down
4 changes: 2 additions & 2 deletions examples/io/tcp/websocketclient/manifest_websocketclient.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
],
"modules": {
"*": [
"./config",
"$(MODULES)/data/logical/*"
],
"websocketclient/config": "./config",
"embedded:network/websocket/client": "./websocketclient"
},
"preload": [
"config",
"websocketclient/config",
"embedded:network/websocket/client",
"logical"
]
Expand Down
62 changes: 38 additions & 24 deletions examples/io/tcp/websocketclient/websocketclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class WebSocketClient {
from: attach,
onReadable: count => this.#onReadable(count),
onWritable: count => this.#onWritable(count),
onError: () => this.#onError()
onError: () => this.#onError('ws: unknown socket error')
});
this.#state = "connected";
return;
Expand All @@ -71,15 +71,15 @@ class WebSocketClient {
port: this.#options.port,
onReadable: count => this.#onReadable(count),
onWritable: count => this.#onWritable(count),
onError: () => this.#onError()
onError: () => this.#onError('ws: unknown socket error')
});
}
catch {
this.#onError?.();
this.#onError?.('ws: unknown socket connect error');
}
},
onError: () => {
this.#onError?.();
this.#onError?.('dns: unknown resolution error');
},
});
}
Expand Down Expand Up @@ -120,17 +120,28 @@ class WebSocketClient {
}

data = data.slice(0);
const mask = Uint8Array.of(Math.random() * 256, Math.random() * 256, Math.random() * 256, Math.random() * 256);
Logical.xor(data, mask.buffer);
const format = this.#socket.format;
this.#socket.format = "buffer";
if (byteLength < 126) {
this.#socket.write(Uint8Array.of(type, byteLength | 0x80, mask[0], mask[1], mask[2], mask[3]));
this.#writable -= (6 + byteLength);
}
else {
this.#socket.write(Uint8Array.of(type, 126 | 0x80, byteLength >> 8, byteLength, mask[0], mask[1], mask[2], mask[3]));
this.#writable -= (8 + byteLength);
if (this.#options.attach) {
if (byteLength < 126) {
this.#socket.write(Uint8Array.of(type, byteLength));
this.#writable -= (2 + byteLength);
}
else {
this.#socket.write(Uint8Array.of(type, 126, byteLength >> 8, byteLength));
this.#writable -= (4 + byteLength);
}
} else {
const mask = Uint8Array.of(Math.random() * 256, Math.random() * 256, Math.random() * 256, Math.random() * 256);
Logical.xor(data, mask.buffer);
if (byteLength < 126) {
this.#socket.write(Uint8Array.of(type, byteLength | 0x80, mask[0], mask[1], mask[2], mask[3]));
this.#writable -= (6 + byteLength);
}
else {
this.#socket.write(Uint8Array.of(type, 126 | 0x80, byteLength >> 8, byteLength, mask[0], mask[1], mask[2], mask[3]));
this.#writable -= (8 + byteLength);
}
}
this.#socket.write(data);
this.#socket.format = format;
Expand Down Expand Up @@ -161,7 +172,7 @@ class WebSocketClient {
if (this.#options.mask) {
const mask = this.#options.mask;
Logical.xor(data, mask.buffer);
if (this.#data) {
if (this.#options.length) {
switch (count & 3) {
case 1:
this.#options.mask = Uint8Array.of(mask[1], mask[2], mask[3], mask[0]);
Expand Down Expand Up @@ -203,16 +214,16 @@ class WebSocketClient {
if ("receiveStatus" === this.#state) {
let status = this.#line.split(" ");
if (status.length < 3)
return void this.#onError();
return void this.#onError('ws: http upgrade error (http status len < 3)');
status = parseInt(status[1]);
if (101 !== status)
return void this.#onError();
return void this.#onError(`ws: http upgrade error (expected status 101, got ${status})`);
this.#state = "receiveHeader";
}
else if ("\r\n" === this.#line) {
// done
if (7 !== this.#options.flags)
return void this.#onError();
return void this.#onError(`ws: http upgrade error (insufficient header received)`);
this.#state = "connected";
delete this.#options.flags;
this.#socket.format = "buffer";
Expand Down Expand Up @@ -257,17 +268,17 @@ class WebSocketClient {
count--;

if (tag & 0x70)
return void this.#onError();
return void this.#onError('ws: unsupported reserved bits');

tag &= 0x0F;
if (1 === tag & 0x0F)
if (1 === tag)
options.binary = false;
else if (2 === tag)
options.binary = true;
else if (8 & tag)
options.control = true;
else if (tag)
return void this.#onError();
return void this.#onError('ws: unknown opcode (${tag})');
continue;
}
if (undefined === options.length) {
Expand All @@ -290,7 +301,7 @@ class WebSocketClient {
continue;
}
if (options.mask && options.mask.length < 4) {
//@@ it is an error for client to receieve a mask. this code applies to future server. client should fail here.
//@@ it is an error for client to receieve a mask. client should fail here.
options.mask.push(this.#socket.read());
count--;
if (4 !== options.mask.length)
Expand All @@ -317,6 +328,9 @@ class WebSocketClient {
return;

const opcode = options.tag & 0x0F;
if (options.mask) {
Logical.xor(control, options.mask.buffer);
}
try {
this.#options.onControl?.call(this, opcode, control.buffer);
}
Expand Down Expand Up @@ -354,7 +368,7 @@ class WebSocketClient {
else if (10 === opcode) // pong
;
else
return void this.#onError();
return void this.#onError(`ws: unknown control opcode ${opcode}`);


delete options.tag;
Expand Down Expand Up @@ -464,12 +478,12 @@ class WebSocketClient {
break;
}
}
#onError() {
#onError(error) {
this.close();
if (this.#options.close)
this.#options.onClose?.call(this);
else
this.#options.onError?.call(this);
this.#options.onError?.call(this, error);
}

static text = 1;
Expand Down
1 change: 0 additions & 1 deletion examples/io/tcp/websocketsclient/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import "system" // system initializes globalThis.device. this ensures it runs before this module.

import TCP from "embedded:io/socket/tcp";
import UDP from "embedded:io/socket/udp";
import Resolver from "embedded:network/dns/resolver/udp";
import TLSSocket from "embedded:io/socket/tcp/tls";
Expand Down
10 changes: 8 additions & 2 deletions examples/manifest_typings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,23 @@
"embedded:io/socket/*": [
"$(TYPINGS)/embedded_io/socket/*"
],
"embedded:io/socket/tcp/*": [
"$(TYPINGS)/embedded_io/socket/tcp/*"
],
"embedded:network/http/*": [
"$(TYPINGS)/embedded_network/http/*"
],
"embedded:network/http/server/options/*": [
"$(TYPINGS)/embedded_network/http/server/options/*"
],
"embedded:network/mqtt/*": [
"$(TYPINGS)/embedded_network/mqtt/*"
],
"embedded:network/dns/resolver/*": [
"$(TYPINGS)/embedded_network/dns/resolver/*"
],
"embedded:network/*": [
"$(TYPINGS)/embedded_network/*"
"embedded:network/websocket/*": [
"$(TYPINGS)/embedded_network/websocket/*"
],
"mqtt/js": [
"$(TYPINGS)/mqtt/js"
Expand Down
1 change: 1 addition & 0 deletions test262
Submodule test262 added at 0add42
22 changes: 14 additions & 8 deletions typings/embedded_io/socket/listener.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@
*/

declare module "embedded:io/socket/listener" {
import TCP from "embedded:io/socket/tcp"
import type TCP from "embedded:io/socket/tcp";

export interface ListenerOptions {
port?: number;
address?: string;
onReadable?: (this: Listener, bytes: number) => void;
format?: "socket/tcp";
}

export type ListenerDevice = ListenerOptions & { io: typeof Listener };

class Listener {
constructor(options: {
port?: number;
address?: string;
onReadable?: (this: Listener, requests: number) => void;
format?: "socket/tcp";
target?: any;
})
constructor(options: ListenerOptions)
read(): TCP | undefined
get format(): "socket/tcp"
set format(value: "socket/tcp")

readonly port: number;
}

export default Listener;
}
98 changes: 50 additions & 48 deletions typings/embedded_io/socket/tcp.d.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
/*
* Copyright (c) 2022 Shinya Ishikawa
*
* This file is part of the Moddable SDK Tools.
*
* The Moddable SDK Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Moddable SDK Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Moddable SDK Tools. If not, see <http://www.gnu.org/licenses/>.
*
*/

* Copyright (c) 2022 Shinya Ishikawa
*
* This file is part of the Moddable SDK Tools.
*
* The Moddable SDK Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Moddable SDK Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Moddable SDK Tools. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare module "embedded:io/socket/tcp" {
import type { Buffer } from "embedded:io/_common";
export type Options = ((({
address: string;
} | {
host: string;
}) & {
port: number;
}) | {
from: TCP;
}) & {
nodelay?: boolean;
onReadable?: (this: TCP, bytes: number) => void;
onWritable?: (this: TCP, bytes: number) => void;
onError?: (this: TCP) => void;
format?: "number" | "buffer";
target?: any;
};

export default class TCP {
constructor(options: Options)
readonly remoteAddress: string | undefined;
readonly remotePort: number | undefined;
read(byteLength?: number): number | ArrayBuffer;
read(buffer: Buffer): void;
write(value: number | Buffer): void;
close(): void;
get format(): "number" | "buffer"
set format(value: "number" | "buffer")
}
import type { Buffer } from "embedded:io/_common";
import type UDP from "embedded:io/socket/udp";

export type TCPOptions = ((
{
address: string;
port: number;
} | {
from: TCP;
}) & {
nodelay?: boolean;
onReadable?: (this: TCP, bytes: number) => void;
onWritable?: (this: TCP, bytes: number) => void;
onError?: (this: TCP) => void;
format?: "number" | "buffer";
}
);

export type TCPDevice = TCPOptions & { io: typeof UDP };

export default class TCP {
constructor(options: TCPOptions);
readonly remoteAddress: string | undefined;
readonly remotePort: number | undefined;
read(): number;
read(byteLength: number): ArrayBuffer | undefined;
read(buffer: Buffer): void;
write(value: number | Buffer): void;
close(): void;
get format(): "number" | "buffer";
set format(value: "number" | "buffer");
}
}
Loading