websockets/ws · error · SyntaxError
An invalid or duplicated subprotocol was specified
Error message
An invalid or duplicated subprotocol was specified
What it means
Thrown by initAsClient() at websocket.js:781-788 when validating the protocols array passed to the WebSocket client constructor. Each entry must be (a) a string, (b) match the subprotocolRegex /^[!#$%&'*+-.0-9A-Z^_`|a-z~]+$/ (websocket.js:39), and (c) not already seen (duplicates rejected via protocolSet). Any failure throws a SyntaxError synchronously during construction.
Solutions
- Validate each protocol against the token regex and dedupe before connecting: protocols.filter(p => typeof p === 'string' && /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/.test(p)).
- Use simple alphanumeric/dot names for subprotocols (e.g. 'chat.v2', 'json') that always satisfy the regex.
- Dedupe: [...new Set(protocols)] to avoid duplicate-triggered failures.
Example fix
// before const ws = new WebSocket(url, ['chat v2', 'chat v2']); // after const ws = new WebSocket(url, [...new Set(['chat.v2'])]);
Defensive patterns
Strategy: validation
Validate before calling
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
function sanitizeClientProtocols(protocols) {
if (!Array.isArray(protocols)) protocols = [protocols];
const seen = new Set();
const out = [];
for (const p of protocols) {
if (typeof p === 'string' && subprotocolRegex.test(p) && !seen.has(p)) {
seen.add(p);
out.push(p);
}
}
return out;
}
// usage: new WebSocket(url, sanitizeClientProtocols(list)); Type guard
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
function areValidClientProtocols(protocols) {
if (!Array.isArray(protocols)) return false;
const seen = new Set();
return protocols.every(p =>
typeof p === 'string' && subprotocolRegex.test(p) && !seen.has(p) && seen.add(p)
);
} Prevention
- Validate each protocol against the token regex and dedupe before passing to the constructor.
- Prefer simple alphanumeric/dot protocol names that always satisfy the regex.
- Sanitize any user-supplied protocol strings before use.
When it happens
Trigger: Passing protocols containing non-token characters (e.g. 'chat v2' with a space, 'a:b'); passing a non-string element (a number, object, or null in the array); passing duplicates like ['chat', 'chat']; passing an empty string ''. The loop at websocket.js:780-785 checks all three conditions per element.
Common situations: User-supplied protocol names that include spaces, colons, or slashes; a protocol list built from unvalidated input; passing a single non-string value where the constructor wraps it in an array (websocket.js:84) and then the loop rejects it; accidental duplicates from merging config sources.
Related errors
- Invalid URL
- Unexpected end of input
- Invalid value for parameter
- Second argument must be a string or a Uint8Array
- The data size must not be greater than 125 bytes
AI-assisted analysis of websockets/ws@c791e707ea (2026-08-06).
Data as JSON: /api/errors/3ded9ed5c39a423c.
Report an issue: GitHub.
Appendix: source
Thrown at lib/websocket.js:786
if (opts.perMessageDeflate) {
perMessageDeflate = new PerMessageDeflate({
...opts.perMessageDeflate,
isServer: false,
maxPayload: opts.maxPayload
});
opts.headers['Sec-WebSocket-Extensions'] = format({
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
});
}
if (protocols.length) {
for (const protocol of protocols) {
if (
typeof protocol !== 'string' ||
!subprotocolRegex.test(protocol) ||
protocolSet.has(protocol)
) {
throw new SyntaxError(
'An invalid or duplicated subprotocol was specified'
);
}
protocolSet.add(protocol);
}
opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');
}
if (opts.origin) {
if (opts.protocolVersion < 13) {
opts.headers['Sec-WebSocket-Origin'] = opts.origin;
} else {
opts.headers.Origin = opts.origin;
}
}
if (parsedUrl.username || parsedUrl.password) {
opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;View on GitHub (pinned to c791e707ea)