@amritk/nish-x86_64-linux 0.27.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +16 -8
- package/bin/nish +0 -0
- package/docs/AI.md +118 -26
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +176 -0
- package/runtime/nish.h +120 -6
- package/runtime/nish.mjs +24 -4
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +81 -5
- package/runtime/runtime-sandbox.c +479 -0
- package/runtime/runtime-sha256.c +239 -0
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +79 -28
- package/std/README.md +25 -13
- package/std/collections.ts +5 -5
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-typed.ts +34 -0
- package/std/json-value.ts +510 -17
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +301 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +127 -24
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres.ts +405 -0
- package/std/net/prefork.ts +344 -20
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-io.ts +35 -8
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- package/std/yaml-json.ts +841 -0
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/net/websocket.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nish/net/websocket` — RFC 6455 WebSocket framing and the
|
|
3
|
-
* handshake
|
|
2
|
+
* `nish/net/websocket` — RFC 6455 WebSocket framing and both sides of the
|
|
3
|
+
* opening handshake, sans-IO: bytes in, events and bytes out.
|
|
4
4
|
*
|
|
5
|
-
* **The handshake.** `nish/net/http1` reads the client's request;
|
|
5
|
+
* **The server's handshake.** `nish/net/http1` reads the client's request;
|
|
6
6
|
* `websocketRequestKey(parser)` answers its `Sec-WebSocket-Key` when the head
|
|
7
|
-
* is a valid opening handshake (§4.2.1),
|
|
8
|
-
*
|
|
7
|
+
* is a valid opening handshake (§4.2.1), `websocketOfferedProtocols(parser)`
|
|
8
|
+
* the subprotocols it offers, and `websocketUpgradeResponse(key, protocol)`
|
|
9
|
+
* writes the 101 that accepts it, with `Sec-WebSocket-Accept` from
|
|
9
10
|
* `websocketAcceptKey` (§4.2.2): the base64 of the SHA-1 of the key and a
|
|
10
11
|
* fixed GUID. What `HTTP1_UPGRADE`'s `upgradeBytes()` answers is the first
|
|
11
12
|
* input for the decoder.
|
|
12
13
|
*
|
|
14
|
+
* **The client's handshake** (§4.1). `websocketClientKey(nonce)` spells 16
|
|
15
|
+
* random bytes as the key, `websocketUpgradeRequest(host, target, key,
|
|
16
|
+
* protocols)` writes the GET that asks to upgrade, and
|
|
17
|
+
* `websocketCheckResponse(parser, key, offered)` reads the server's answer,
|
|
18
|
+
* parsed by `nish/net/http1-client` after `expectUpgrade()`: it answers the
|
|
19
|
+
* negotiated subprotocol, or which rule of §4.1 the response broke. What
|
|
20
|
+
* `HTTP1_UPGRADE`'s `tunnelBytes()` answers is the first input for
|
|
21
|
+
* `WsDecoder(false, …)`. This module draws no randomness: the nonce and every
|
|
22
|
+
* frame's mask key are the caller's, from `crypto.getRandomValues`.
|
|
23
|
+
*
|
|
13
24
|
* **The decoder** is a push-pull machine like `Http1Parser`: `feed` any slice,
|
|
14
25
|
* then call `next()` until it answers `WS_NEED_MORE`.
|
|
15
26
|
*
|
|
@@ -56,9 +67,18 @@
|
|
|
56
67
|
*
|
|
57
68
|
* Written from RFC 6455, not ported from another implementation.
|
|
58
69
|
*/
|
|
59
|
-
import {
|
|
70
|
+
import { base64StdPadEncode, base64urlDecode } from "nish/crypto/base64url"
|
|
60
71
|
import { sha1 } from "nish/crypto/sha1"
|
|
61
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
HTTP1_NO_BODY,
|
|
74
|
+
Http1Parser,
|
|
75
|
+
http1IsOws,
|
|
76
|
+
http1IsToken,
|
|
77
|
+
http1ResponseHead,
|
|
78
|
+
http1SameWord,
|
|
79
|
+
http1Text,
|
|
80
|
+
} from "nish/net/http1"
|
|
81
|
+
import { Http1ResponseParser, http1UpgradeRequestHead } from "nish/net/http1-client"
|
|
62
82
|
|
|
63
83
|
/** The GUID RFC 6455 §1.3 appends to the client's key before hashing it. */
|
|
64
84
|
export const WS_GUID: string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
@@ -753,14 +773,9 @@ const websocketSwapBytes = (text: string, from1: i32, to1: i32, from2: i32, to2:
|
|
|
753
773
|
/**
|
|
754
774
|
* `Sec-WebSocket-Accept` for the client's `Sec-WebSocket-Key` (§4.2.2 item
|
|
755
775
|
* 5.4): the SHA-1 of the key and `WS_GUID`, in padded standard base64.
|
|
756
|
-
* `nish/crypto/base64url` writes the base64; the URL alphabet's two last
|
|
757
|
-
* characters are swapped for the standard ones, and a 20-byte digest takes one
|
|
758
|
-
* `=` of padding.
|
|
759
776
|
*/
|
|
760
|
-
export const websocketAcceptKey = (key: string): string =>
|
|
761
|
-
|
|
762
|
-
return `${websocketSwapBytes(base64urlEncode(digest), 45, 43, 95, 47)}=`
|
|
763
|
-
}
|
|
777
|
+
export const websocketAcceptKey = (key: string): string =>
|
|
778
|
+
base64StdPadEncode(sha1(websocketBytesOf(`${key}${WS_GUID}`)))
|
|
764
779
|
|
|
765
780
|
/**
|
|
766
781
|
* Whether `key` is a valid `Sec-WebSocket-Key` (§4.2.1 item 5): padded
|
|
@@ -821,3 +836,269 @@ export const websocketUpgradeResponse = (key: string, protocol: string): u8[] |
|
|
|
821
836
|
const switching: i32 = 101
|
|
822
837
|
return http1ResponseHead(switching, "Switching Protocols", names, values, HTTP1_NO_BODY)
|
|
823
838
|
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* The subprotocols the opening handshake `p` has just read offers, in the
|
|
842
|
+
* order offered (§4.2.1 item 8, §11.3.4): every `Sec-WebSocket-Protocol`
|
|
843
|
+
* field's comma-separated list, joined, each element trimmed of OWS and the
|
|
844
|
+
* empty ones dropped (RFC 9110 §5.6.1). Answers `[]` when there is no such
|
|
845
|
+
* field, and `null` when an element is not a token: a server picks one of
|
|
846
|
+
* these, or none, for `websocketUpgradeResponse`.
|
|
847
|
+
*/
|
|
848
|
+
export const websocketOfferedProtocols = (p: Http1Parser): string[] | null => {
|
|
849
|
+
const out: string[] = []
|
|
850
|
+
const head: u8[] = p.head
|
|
851
|
+
const spans: i32[] = p.spans
|
|
852
|
+
for (let i: i32 = 0; i < p.headerCount && 4 * i + 3 < toI32(spans.length); i += 1) {
|
|
853
|
+
if (websocketSpanIs(head, spans[4 * i], spans[4 * i + 1], "sec-websocket-protocol")) {
|
|
854
|
+
websocketListInto(head, spans[4 * i + 2], spans[4 * i + 3], out)
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
for (let k: i32 = 0; k < toI32(out.length); k += 1) {
|
|
858
|
+
if (!http1IsToken(out[k])) {
|
|
859
|
+
return null
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return out
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/** Whether `bytes[a .. b)` is `word`, which is lower case, in any ASCII case. Allocates nothing. */
|
|
866
|
+
const websocketSpanIs = (bytes: u8[], a: i32, b: i32, word: string): boolean => {
|
|
867
|
+
const n: i32 = toI32(word.length)
|
|
868
|
+
if (a < 0 || b - a !== n || b > toI32(bytes.length)) {
|
|
869
|
+
return false
|
|
870
|
+
}
|
|
871
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
872
|
+
const at: i32 = a + k
|
|
873
|
+
if (at < 0 || at >= toI32(bytes.length) || k >= toI32(word.length)) {
|
|
874
|
+
return false
|
|
875
|
+
}
|
|
876
|
+
let c: i32 = toI32(bytes[at])
|
|
877
|
+
if (c >= 65 && c <= 90) {
|
|
878
|
+
c += 32
|
|
879
|
+
}
|
|
880
|
+
if (c !== toI32(word.charCodeAt(k))) {
|
|
881
|
+
return false
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return true
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Appends the elements of the comma-separated list in `bytes[from .. to)`
|
|
889
|
+
* (RFC 9110 §5.6.1) to `out`, each trimmed of OWS, the empty ones dropped.
|
|
890
|
+
*/
|
|
891
|
+
const websocketListInto = (bytes: u8[], from: i32, to: i32, out: string[]): void => {
|
|
892
|
+
if (from < 0 || to > toI32(bytes.length)) {
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
let start: i32 = from
|
|
896
|
+
for (let k: i32 = from; k <= to; k += 1) {
|
|
897
|
+
if (k === to || (k >= 0 && k < toI32(bytes.length) && toI32(bytes[k]) === 44)) {
|
|
898
|
+
let a: i32 = start
|
|
899
|
+
let b: i32 = k
|
|
900
|
+
while (a < b && a >= 0 && a < toI32(bytes.length) && http1IsOws(toI32(bytes[a]))) {
|
|
901
|
+
a += 1
|
|
902
|
+
}
|
|
903
|
+
while (b > a && b - 1 >= 0 && b - 1 < toI32(bytes.length) && http1IsOws(toI32(bytes[b - 1]))) {
|
|
904
|
+
b -= 1
|
|
905
|
+
}
|
|
906
|
+
if (b > a) {
|
|
907
|
+
out.push(http1Text(bytes, a, b, false))
|
|
908
|
+
}
|
|
909
|
+
start = k + 1
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** Whether the comma-separated list `value` (RFC 9110 §5.6.1) has an element that is `word`, which is lower case, in any ASCII case. */
|
|
915
|
+
const websocketListHas = (value: string, word: string): boolean => {
|
|
916
|
+
const n: i32 = toI32(value.length)
|
|
917
|
+
let start: i32 = 0
|
|
918
|
+
for (let k: i32 = 0; k <= n; k += 1) {
|
|
919
|
+
if (k === n || (k >= 0 && k < n && toI32(value.charCodeAt(k)) === 44)) {
|
|
920
|
+
let a: i32 = start
|
|
921
|
+
let b: i32 = k
|
|
922
|
+
while (a < b && a >= 0 && a < n && http1IsOws(toI32(value.charCodeAt(a)))) {
|
|
923
|
+
a += 1
|
|
924
|
+
}
|
|
925
|
+
while (b > a && b - 1 >= 0 && b - 1 < n && http1IsOws(toI32(value.charCodeAt(b - 1)))) {
|
|
926
|
+
b -= 1
|
|
927
|
+
}
|
|
928
|
+
if (http1SameWord(value, a, b, word)) {
|
|
929
|
+
return true
|
|
930
|
+
}
|
|
931
|
+
start = k + 1
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return false
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* The `Sec-WebSocket-Key` for a client's opening handshake (§4.1 item 7): the
|
|
939
|
+
* 16-byte `nonce` in padded standard base64, 24 characters. Answers `null` for
|
|
940
|
+
* a nonce of any other length. The nonce is the caller's, drawn from
|
|
941
|
+
* `crypto.getRandomValues` and fresh for every connection: a key that repeats
|
|
942
|
+
* or can be guessed lets a cache or an intermediary replay an old handshake's
|
|
943
|
+
* answer. This module draws no randomness itself.
|
|
944
|
+
*/
|
|
945
|
+
export const websocketClientKey = (nonce: u8[]): string | null => {
|
|
946
|
+
if (toI32(nonce.length) !== 16) {
|
|
947
|
+
return null
|
|
948
|
+
}
|
|
949
|
+
return base64StdPadEncode(nonce)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* A client's opening handshake (§4.1 items 1 to 10): `GET target HTTP/1.1`
|
|
954
|
+
* with `Host`, `Sec-WebSocket-Key`, `Sec-WebSocket-Version: 13`, the offered
|
|
955
|
+
* `protocols` in one `Sec-WebSocket-Protocol` field when there are any, and
|
|
956
|
+
* `Upgrade: websocket` and `Connection: Upgrade`, written by
|
|
957
|
+
* `nish/net/http1-client`'s `http1UpgradeRequestHead`. Answers `null` for a
|
|
958
|
+
* key `websocketKeyIsValid` refuses, a protocol that is not a token (an empty
|
|
959
|
+
* one, or one holding a comma, a space, CR or LF), a protocol offered twice, an
|
|
960
|
+
* empty host, and everything the request writer refuses (CR, LF or another
|
|
961
|
+
* control byte in `host`, a `target` that is not a request target).
|
|
962
|
+
*/
|
|
963
|
+
export const websocketUpgradeRequest = (
|
|
964
|
+
host: string,
|
|
965
|
+
target: string,
|
|
966
|
+
key: string,
|
|
967
|
+
protocols: string[]
|
|
968
|
+
): u8[] | null => {
|
|
969
|
+
if (toI32(host.length) === 0 || !websocketKeyIsValid(key)) {
|
|
970
|
+
return null
|
|
971
|
+
}
|
|
972
|
+
const count: i32 = toI32(protocols.length)
|
|
973
|
+
for (let i: i32 = 0; i < count; i += 1) {
|
|
974
|
+
if (!http1IsToken(protocols[i])) {
|
|
975
|
+
return null
|
|
976
|
+
}
|
|
977
|
+
for (let j: i32 = 0; j < i; j += 1) {
|
|
978
|
+
if (protocols[j] === protocols[i]) {
|
|
979
|
+
return null
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
const names: string[] = ["Host", "Sec-WebSocket-Key", "Sec-WebSocket-Version"]
|
|
984
|
+
const values: string[] = [host, key, "13"]
|
|
985
|
+
if (count > 0) {
|
|
986
|
+
names.push("Sec-WebSocket-Protocol")
|
|
987
|
+
values.push(protocols.join(", "))
|
|
988
|
+
}
|
|
989
|
+
return http1UpgradeRequestHead("GET", target, names, values, "websocket")
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// What `websocketCheckResponse` found (`WsHandshake.error`): the handshake
|
|
993
|
+
// succeeded, or the first rule of §4.1 the server's response broke.
|
|
994
|
+
/** The server accepted the handshake; `protocol` is the subprotocol it chose. */
|
|
995
|
+
export const WS_HS_OK: i32 = 0
|
|
996
|
+
/** The status is not 101; the parser's `status` says what it is. No redirect is followed. */
|
|
997
|
+
export const WS_HS_STATUS: i32 = 1
|
|
998
|
+
/** The response is not HTTP/1.1. */
|
|
999
|
+
export const WS_HS_VERSION: i32 = 2
|
|
1000
|
+
/** `Upgrade` is absent, repeated, or not `websocket` in any case. */
|
|
1001
|
+
export const WS_HS_UPGRADE: i32 = 3
|
|
1002
|
+
/** No `Connection` field lists `upgrade`. */
|
|
1003
|
+
export const WS_HS_CONNECTION: i32 = 4
|
|
1004
|
+
/** `Sec-WebSocket-Accept` is absent, repeated, or not the key's accept value. */
|
|
1005
|
+
export const WS_HS_ACCEPT: i32 = 5
|
|
1006
|
+
/** A `Sec-WebSocket-Extensions` field, when the client offered none. */
|
|
1007
|
+
export const WS_HS_EXTENSION: i32 = 6
|
|
1008
|
+
/** A `Sec-WebSocket-Protocol` that is repeated, a list, or not one the client offered. */
|
|
1009
|
+
export const WS_HS_PROTOCOL: i32 = 7
|
|
1010
|
+
|
|
1011
|
+
/** `websocketCheckResponse`'s answer: `error` is a `WS_HS_*` code; `protocol` is set when it is `WS_HS_OK`. */
|
|
1012
|
+
export class WsHandshake {
|
|
1013
|
+
/** The subprotocol the server chose, or `""` when it chose none. */
|
|
1014
|
+
protocol: string = ""
|
|
1015
|
+
error: i32 = 0
|
|
1016
|
+
|
|
1017
|
+
constructor(error: i32, protocol: string) {
|
|
1018
|
+
this.error = error
|
|
1019
|
+
this.protocol = protocol
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Reads the server's answer to `websocketUpgradeRequest(…, key, offered)`
|
|
1025
|
+
* (§4.1, the client's checks of the response): call it at the parser's
|
|
1026
|
+
* `HTTP1_HEAD`. It fails closed, and the connection is closed on any failure:
|
|
1027
|
+
* a status other than 101 (`WS_HS_STATUS`; a redirect is not followed), a
|
|
1028
|
+
* version other than HTTP/1.1, `Upgrade` absent, repeated or not `websocket`,
|
|
1029
|
+
* no `Connection` listing `upgrade`, `Sec-WebSocket-Accept` absent, repeated
|
|
1030
|
+
* or not `websocketAcceptKey(key)` (a forged or replayed answer), any
|
|
1031
|
+
* `Sec-WebSocket-Extensions` (this client offers none, so none may be in use),
|
|
1032
|
+
* and a `Sec-WebSocket-Protocol` that is present when nothing was offered,
|
|
1033
|
+
* repeated, a list, or not one of `offered`. On `WS_HS_OK` the parser's next
|
|
1034
|
+
* events are `HTTP1_END` and `HTTP1_UPGRADE`; its `tunnelBytes()` go to
|
|
1035
|
+
* `WsDecoder(false, …)`, and every frame the client sends is masked with a
|
|
1036
|
+
* fresh mask key from `crypto.getRandomValues` (§5.3).
|
|
1037
|
+
*/
|
|
1038
|
+
export const websocketCheckResponse = (
|
|
1039
|
+
response: Http1ResponseParser,
|
|
1040
|
+
key: string,
|
|
1041
|
+
offered: string[]
|
|
1042
|
+
): WsHandshake => {
|
|
1043
|
+
if (response.status !== 101) {
|
|
1044
|
+
return new WsHandshake(WS_HS_STATUS, "")
|
|
1045
|
+
}
|
|
1046
|
+
if (response.minor !== 1) {
|
|
1047
|
+
return new WsHandshake(WS_HS_VERSION, "")
|
|
1048
|
+
}
|
|
1049
|
+
const expected: string = websocketKeyIsValid(key) ? websocketAcceptKey(key) : ""
|
|
1050
|
+
let upgrades: i32 = 0
|
|
1051
|
+
let upgradeOk: boolean = false
|
|
1052
|
+
let connection: boolean = false
|
|
1053
|
+
let accepts: i32 = 0
|
|
1054
|
+
let acceptOk: boolean = false
|
|
1055
|
+
let extensions: boolean = false
|
|
1056
|
+
let protocols: i32 = 0
|
|
1057
|
+
let protocol: string = ""
|
|
1058
|
+
const names: string[] = response.names
|
|
1059
|
+
const values: string[] = response.values
|
|
1060
|
+
for (let i: i32 = 0; i < toI32(names.length) && i < toI32(values.length); i += 1) {
|
|
1061
|
+
const name: string = names[i]
|
|
1062
|
+
const value: string = values[i]
|
|
1063
|
+
if (name === "upgrade") {
|
|
1064
|
+
upgrades += 1
|
|
1065
|
+
upgradeOk = http1SameWord(value, 0, toI32(value.length), "websocket")
|
|
1066
|
+
} else if (name === "connection") {
|
|
1067
|
+
if (websocketListHas(value, "upgrade")) {
|
|
1068
|
+
connection = true
|
|
1069
|
+
}
|
|
1070
|
+
} else if (name === "sec-websocket-accept") {
|
|
1071
|
+
accepts += 1
|
|
1072
|
+
acceptOk = toI32(expected.length) > 0 && value === expected
|
|
1073
|
+
} else if (name === "sec-websocket-extensions") {
|
|
1074
|
+
extensions = true
|
|
1075
|
+
} else if (name === "sec-websocket-protocol") {
|
|
1076
|
+
protocols += 1
|
|
1077
|
+
protocol = value
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (upgrades !== 1 || !upgradeOk) {
|
|
1081
|
+
return new WsHandshake(WS_HS_UPGRADE, "")
|
|
1082
|
+
}
|
|
1083
|
+
if (!connection) {
|
|
1084
|
+
return new WsHandshake(WS_HS_CONNECTION, "")
|
|
1085
|
+
}
|
|
1086
|
+
if (accepts !== 1 || !acceptOk) {
|
|
1087
|
+
return new WsHandshake(WS_HS_ACCEPT, "")
|
|
1088
|
+
}
|
|
1089
|
+
if (extensions) {
|
|
1090
|
+
return new WsHandshake(WS_HS_EXTENSION, "")
|
|
1091
|
+
}
|
|
1092
|
+
if (protocols === 0) {
|
|
1093
|
+
return new WsHandshake(WS_HS_OK, "")
|
|
1094
|
+
}
|
|
1095
|
+
if (protocols > 1 || !http1IsToken(protocol)) {
|
|
1096
|
+
return new WsHandshake(WS_HS_PROTOCOL, "")
|
|
1097
|
+
}
|
|
1098
|
+
for (let i: i32 = 0; i < toI32(offered.length); i += 1) {
|
|
1099
|
+
if (offered[i] === protocol) {
|
|
1100
|
+
return new WsHandshake(WS_HS_OK, protocol)
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return new WsHandshake(WS_HS_PROTOCOL, "")
|
|
1104
|
+
}
|