@graphorin/protocol 0.6.0 → 0.7.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/CHANGELOG.md +24 -0
- package/README.md +2 -2
- package/dist/client-message.d.ts +24 -24
- package/dist/client-message.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/server-message.d.ts +32 -32
- package/dist/server-message.d.ts.map +1 -1
- package/dist/server-message.js +14 -2
- package/dist/server-message.js.map +1 -1
- package/package.json +8 -7
- package/src/client-message.ts +200 -0
- package/src/close-codes.ts +67 -0
- package/src/index.ts +25 -0
- package/src/server-message.ts +268 -0
- package/src/subprotocol.ts +106 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subprotocol identifier + negotiation helpers for the Graphorin
|
|
3
|
+
* WebSocket protocol.
|
|
4
|
+
*
|
|
5
|
+
* Clients announce supported subprotocols in the
|
|
6
|
+
* `Sec-WebSocket-Protocol` upgrade header. The server is expected to
|
|
7
|
+
* pick exactly one and echo it back; mismatches abort the handshake
|
|
8
|
+
* per RFC 6455 § 4. Browsers also accept additional comma-separated
|
|
9
|
+
* tokens - Graphorin uses this slot to attach a single-use ticket
|
|
10
|
+
* via the `ticket.<value>` form (the WebSocket browser API does not
|
|
11
|
+
* accept arbitrary headers, so the ticket has to ride the
|
|
12
|
+
* subprotocol channel).
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Canonical subprotocol identifier for the v1 wire format.
|
|
19
|
+
*
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
export const SUBPROTOCOL_NAME = 'graphorin.protocol.v1';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wire-format major version literal carried on every message body.
|
|
26
|
+
* The pair `(SUBPROTOCOL_NAME, PROTOCOL_VERSION)` is the binding
|
|
27
|
+
* contract a client commits to when it receives a successful upgrade.
|
|
28
|
+
*
|
|
29
|
+
* @stable
|
|
30
|
+
*/
|
|
31
|
+
export const PROTOCOL_VERSION = '1';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Prefix for the single-use ticket that browser clients attach to
|
|
35
|
+
* the `Sec-WebSocket-Protocol` header. The server's upgrade handler
|
|
36
|
+
* splits the comma-separated list, finds the first
|
|
37
|
+
* `ticket.<value>` token, and validates the value against the
|
|
38
|
+
* in-memory ticket store before granting the connection.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
export const TICKET_SUBPROTOCOL_PREFIX = 'ticket.';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format a ticket value as a `Sec-WebSocket-Protocol` token suitable
|
|
46
|
+
* for browser clients (which cannot attach an `Authorization`
|
|
47
|
+
* header on the WebSocket upgrade). The companion server helper is
|
|
48
|
+
* {@link parseTicketSubprotocol}.
|
|
49
|
+
*
|
|
50
|
+
* @stable
|
|
51
|
+
*/
|
|
52
|
+
export function formatTicketSubprotocol(ticket: string): string {
|
|
53
|
+
if (typeof ticket !== 'string' || ticket.length === 0) {
|
|
54
|
+
throw new TypeError('formatTicketSubprotocol: ticket must be a non-empty string.');
|
|
55
|
+
}
|
|
56
|
+
return `${TICKET_SUBPROTOCOL_PREFIX}${ticket}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract the ticket value from a single comma-separated client list
|
|
61
|
+
* (e.g. `'graphorin.protocol.v1, ticket.abc-123'`). Returns
|
|
62
|
+
* `undefined` if no `ticket.*` token is present. Whitespace around
|
|
63
|
+
* each comma-separated token is ignored.
|
|
64
|
+
*
|
|
65
|
+
* @stable
|
|
66
|
+
*/
|
|
67
|
+
export function parseTicketSubprotocol(
|
|
68
|
+
clientList: string | ReadonlyArray<string>,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
for (const token of normalizeTokens(clientList)) {
|
|
71
|
+
if (token.startsWith(TICKET_SUBPROTOCOL_PREFIX)) {
|
|
72
|
+
const value = token.slice(TICKET_SUBPROTOCOL_PREFIX.length);
|
|
73
|
+
if (value.length > 0) return value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Pick the single subprotocol the server should echo back. Returns
|
|
81
|
+
* `SUBPROTOCOL_NAME` when the client offered it, or `null` when no
|
|
82
|
+
* compatible variant was advertised. The function ignores `ticket.*`
|
|
83
|
+
* tokens - those are handled separately via {@link parseTicketSubprotocol}.
|
|
84
|
+
*
|
|
85
|
+
* @stable
|
|
86
|
+
*/
|
|
87
|
+
export function negotiateSubprotocol(clientList: string | ReadonlyArray<string>): string | null {
|
|
88
|
+
for (const token of normalizeTokens(clientList)) {
|
|
89
|
+
if (token === SUBPROTOCOL_NAME) return SUBPROTOCOL_NAME;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeTokens(input: string | ReadonlyArray<string>): ReadonlyArray<string> {
|
|
95
|
+
if (Array.isArray(input)) {
|
|
96
|
+
return input
|
|
97
|
+
.flatMap((entry) => entry.split(','))
|
|
98
|
+
.map((token) => token.trim())
|
|
99
|
+
.filter((token) => token.length > 0);
|
|
100
|
+
}
|
|
101
|
+
if (typeof input !== 'string') return [];
|
|
102
|
+
return input
|
|
103
|
+
.split(',')
|
|
104
|
+
.map((token) => token.trim())
|
|
105
|
+
.filter((token) => token.length > 0);
|
|
106
|
+
}
|