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

  1. Coerce the reason to a string: ws.close(1000, String(reason)) or ws.close(1000, JSON.stringify(obj)).
  2. If you have an ArrayBuffer, wrap it: ws.close(1000, new Uint8Array(arrayBuffer)).
  3. 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

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


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)