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() (lib/websocket.js:779-789) when iterating the requested subprotocols list: each entry must be a string, must match the subprotocolRegex (/^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/), and must not already have been seen (tracked via a Set). Any failure of those three checks throws SyntaxError. This validates the protocols array the caller passes to the WebSocket constructor.
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 ae1de54330)
Solutions
- Ensure each protocol is a string matching /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/ (alphanumerics and a few symbols, no spaces).
- Deduplicate the array: [...new Set(protocols)].
- Sanitize/validate the protocols array before constructing the client.
Example fix
// before new WebSocket(url, ['chat', 'chat', 'my protocol']); // after new WebSocket(url, [...new Set(['chat', 'my-protocol'])]);
Defensive patterns
Strategy: validation
Validate before calling
const TOKEN_RE = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
function normalizeProtocols(protocols) {
const arr = Array.isArray(protocols) ? protocols : protocols != null ? [protocols] : [];
const seen = new Set();
const out = [];
for (const p of arr) {
if (typeof p !== 'string' || !TOKEN_RE.test(p) || seen.has(p)) continue;
seen.add(p);
out.push(p);
}
return out;
}
// usage: new WebSocket(url, normalizeProtocols(input)) Type guard
const TOKEN_RE = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
function isValidProtocolList(protocols) {
const seen = new Set();
for (const p of protocols) {
if (typeof p !== 'string' || !TOKEN_RE.test(p) || seen.has(p)) return false;
seen.add(p);
}
return true;
} Try / catch
try {
new WebSocket(url, protocols);
} catch (err) {
if (/invalid or duplicated subprotocol/.test(err.message)) {
new WebSocket(url, normalizeProtocols(protocols));
} else {
throw err;
}
} Prevention
- Validate each protocol is a string matching the token regex before constructing.
- Deduplicate the list to avoid the duplicate-subprotocol branch.
- Sanitize protocol identifiers from config/user input at the boundary.
When it happens
Trigger: Constructing new WebSocket(url, protocols) where protocols is an array containing a non-string (e.g. [123]), a string with invalid characters (e.g. ['my protocol'] with a space), or a duplicate (e.g. ['chat', 'chat']). Also triggered by the single-string form new WebSocket(url, 'bad protocol').
Common situations: Passing numeric or object protocol identifiers; allowing spaces or slashes in protocol names; concatenating user protocols with library defaults without dedup; reading protocols from config that contains whitespace.
Related errors
- Invalid URL: ${address}
- The message must not be greater than 123 bytes
- Second argument must be a string or a Uint8Array
- The data size must not be greater than 125 bytes
- Unexpected character at index ${i}
AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03).
Data as JSON: /data/errors/3ded9ed5c39a423c.json.
Report an issue: GitHub.