websockets/ws · error · TypeError
Second argument must be a string or a Uint8Array
Error message
Second argument must be a string or a Uint8Array
What it means
Thrown by Sender.close() at sender.js:206-210 when the reason argument (the second parameter, `data`) is neither a string nor a Uint8Array (which includes Node Buffers). The library only knows how to write these two representations into the close frame body, so any other type is rejected. The check is reached only when a valid numeric code is present and data is non-empty.
Solutions
- Coerce the reason to a string: ws.close(1000, String(reason)) or ws.close(1000, JSON.stringify(obj)).
- If you have an ArrayBuffer, wrap it: ws.close(1000, new Uint8Array(arrayBuffer)).
- Verify the argument order: ws.close(statusCode, reasonString) — code first, reason second.
Example fix
// before
ws.close(1000, { detail: 'shutting down' });
// after
ws.close(1000, JSON.stringify({ detail: 'shutting down' })); Defensive patterns
Strategy: validation
Validate before calling
const { isUint8Array } = require('util');
function coerceCloseReason(data) {
if (data == null) return undefined;
if (typeof data === 'string') return data;
if (isUint8Array(data)) return data;
return String(data); // or JSON.stringify(data) for objects
} Type guard
const { isUint8Array } = require('util');
function isCloseReasonAcceptable(data) {
return typeof data === 'string' || isUint8Array(data);
} Try / catch
try {
ws.close(code, data);
} catch (err) {
if (err instanceof TypeError && /string or a Uint8Array/.test(err.message)) {
ws.close(code, String(data));
} else {
throw err;
}
} Prevention
- Always coerce non-string reasons to string or Buffer before calling close().
- Wrap ArrayBuffers in new Uint8Array(ab) before passing.
- Double-check argument order: (statusCode, reason).
When it happens
Trigger: Calling ws.close(code, data) / sender.close(code, data, mask, cb) where data is a number, plain object, array, null-wrapped value, or any non-Uint8Array typed array. The isUint8Array check from util.types at sender.js:206 returns false, triggering the TypeError.
Common situations: Passing a number as reason (e.g. ws.close(1000, 42)); passing an object like {message: '...'} from a JSON error serializer; passing an ArrayBuffer (which is NOT a Uint8Array) instead of a Uint8Array/Buffer view; accidentally swapping the code and data arguments.
Related errors
- The message must not be greater than 123 bytes
- An invalid or duplicated subprotocol was specified
- Invalid URL
- Invalid value for parameter
- 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/e127a1f6a1af731a.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sender.js:209
} else if (data === undefined || !data.length) {
buf = Buffer.allocUnsafe(2);
buf.writeUInt16BE(code, 0);
} else {
const length = Buffer.byteLength(data);
if (length > 123) {
throw new RangeError('The message must not be greater than 123 bytes');
}
buf = Buffer.allocUnsafe(2 + length);
buf.writeUInt16BE(code, 0);
if (typeof data === 'string') {
buf.write(data, 2);
} else if (isUint8Array(data)) {
buf.set(data, 2);
} else {
throw new TypeError('Second argument must be a string or a Uint8Array');
}
}
const options = {
[kByteLength]: buf.length,
fin: true,
generateMask: this._generateMask,
mask,
maskBuffer: this._maskBuffer,
opcode: 0x08,
readOnly: false,
rsv1: false
};
if (this._state !== DEFAULT) {
this.enqueue([this.dispatch, buf, false, options, cb]);
} else {
this.sendFrame(Sender.frame(buf, options), cb);View on GitHub (pinned to c791e707ea)