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

  1. Ensure each protocol is a string matching /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/ (alphanumerics and a few symbols, no spaces).
  2. Deduplicate the array: [...new Set(protocols)].
  3. 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

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


AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03). Data as JSON: /data/errors/3ded9ed5c39a423c.json. Report an issue: GitHub.