{"record":{"id":"3ded9ed5c39a423c","repo":"websockets/ws","slug":"an-invalid-or-duplicated-subprotocol-was-specified","errorCode":null,"errorMessage":"An invalid or duplicated subprotocol was specified","messagePattern":"An invalid or duplicated subprotocol was specified","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"lib/websocket.js","lineNumber":786,"sourceCode":"\n  if (opts.perMessageDeflate) {\n    perMessageDeflate = new PerMessageDeflate({\n      ...opts.perMessageDeflate,\n      isServer: false,\n      maxPayload: opts.maxPayload\n    });\n    opts.headers['Sec-WebSocket-Extensions'] = format({\n      [PerMessageDeflate.extensionName]: perMessageDeflate.offer()\n    });\n  }\n  if (protocols.length) {\n    for (const protocol of protocols) {\n      if (\n        typeof protocol !== 'string' ||\n        !subprotocolRegex.test(protocol) ||\n        protocolSet.has(protocol)\n      ) {\n        throw new SyntaxError(\n          'An invalid or duplicated subprotocol was specified'\n        );\n      }\n\n      protocolSet.add(protocol);\n    }\n\n    opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');\n  }\n  if (opts.origin) {\n    if (opts.protocolVersion < 13) {\n      opts.headers['Sec-WebSocket-Origin'] = opts.origin;\n    } else {\n      opts.headers.Origin = opts.origin;\n    }\n  }\n  if (parsedUrl.username || parsedUrl.password) {\n    opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;","sourceCodeStart":768,"sourceCodeEnd":804,"githubUrl":"https://github.com/websockets/ws/blob/c791e707eab3c13dd9a261d2479c3cc4a49a6fed/lib/websocket.js#L768-L804","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst ws = new WebSocket(url, ['chat v2', 'chat v2']);\n\n// after\nconst ws = new WebSocket(url, [...new Set(['chat.v2'])]);","handlingStrategy":"validation","validationCode":"const subprotocolRegex = /^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/;\nfunction sanitizeClientProtocols(protocols) {\n  if (!Array.isArray(protocols)) protocols = [protocols];\n  const seen = new Set();\n  const out = [];\n  for (const p of protocols) {\n    if (typeof p === 'string' && subprotocolRegex.test(p) && !seen.has(p)) {\n      seen.add(p);\n      out.push(p);\n    }\n  }\n  return out;\n}\n// usage: new WebSocket(url, sanitizeClientProtocols(list));","typeGuard":"const subprotocolRegex = /^[!#$%&'*+\\-.0-9A-Z^_`|a-z~]+$/;\nfunction areValidClientProtocols(protocols) {\n  if (!Array.isArray(protocols)) return false;\n  const seen = new Set();\n  return protocols.every(p =>\n    typeof p === 'string' && subprotocolRegex.test(p) && !seen.has(p) && seen.add(p)\n  );\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["websocket","client","subprotocol","validation","regex"],"backgroundTag":null,"analyzedSha":"c791e707eab3c13dd9a261d2479c3cc4a49a6fed","analyzedAt":"2026-08-06T19:07:51.047Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}