diff --git a/README.md b/README.md index 4951bfb6..7b35e8d9 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ If `opts` is specified, then the default options (shown below) will be overridde trickle: true, allowHalfTrickle: false, wrtc: {}, // RTCPeerConnection/RTCSessionDescription/RTCIceCandidate - objectMode: false + binary: true } ``` @@ -299,8 +299,28 @@ The options do the following: - [`RTCPeerConnection`](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection) - [`RTCSessionDescription`](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescription) - [`RTCIceCandidate`](https://www.w3.org/TR/webrtc/#dom-rtcicecandidate) +- `binary` - set to `false` to send/receive text as strings. Defaults to `true` (binary payloads as `Uint8Array`). -- `objectMode` - set to `true` to create the stream in [Object Mode](https://nodejs.org/api/stream.html#stream_object_mode). In this mode, incoming string data is not automatically converted to `Buffer` objects. +### Typing the peer + +You can pick the data type via a generic parameter. By default data events are `Uint8Array` when `binary: true`. + +```ts +import Peer from 'simple-peer' + +// default: Uint8Array +const peer = new Peer() +peer.on('data', (data: Uint8Array) => {}) + +// strings only +const textPeer = new Peer({ binary: false }) +textPeer.send('hello') + +// custom struct (you must handle serialization) +interface Message { type: 'ping'; id: string } +const msgPeer = new Peer({ binary: false }) +msgPeer.send({ type: 'ping', id: '1' }) +``` ### `peer.signal(data)` diff --git a/index.ts b/index.ts index fdd1a457..73192efe 100644 --- a/index.ts +++ b/index.ts @@ -3,11 +3,13 @@ import Lite, { PeerOptions } from './lite.js' import errCode from 'err-code' import { MediaStream, MediaStreamTrack, RTCRtpSender, RTCRtpTransceiver } from 'webrtc-polyfill' +type DataChunk = ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob + /** * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods. * Duplex stream. */ -class Peer extends Lite { +class Peer extends Lite { streams: MediaStream[] _senderMap: WeakMap> diff --git a/lite.ts b/lite.ts index 43fc26bc..4ecc0c0f 100644 --- a/lite.ts +++ b/lite.ts @@ -1,11 +1,12 @@ /*! simple-peer. MIT License. Feross Aboukhadijeh */ import debug from 'debug' import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, RTCDataChannel, RTCDataChannelEvent, RTCPeerConnectionIceEvent, MediaStream, MediaStreamTrack } from 'webrtc-polyfill' - import { EventEmitter } from 'eventemitter3' +import { EventEmitter } from 'eventemitter3' import errCode from 'err-code' import { randomBytes, arr2hex, text2arr } from 'uint8-util' type Callback = (err: Error | null) => void +type DataChunk = ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob const Debug = debug('simple-peer') @@ -46,7 +47,7 @@ interface PeerOptions { trickle?: boolean allowHalfTrickle?: boolean iceCompleteTimeout?: number - objectMode?: boolean + binary?: boolean allowHalfOpen?: boolean } @@ -88,13 +89,13 @@ interface LegacyStatsResult { stat: (name: string) => unknown } -interface PeerEvents { +interface PeerEvents { signal: (data: SignalData) => void connect: () => void disconnect: () => void close: () => void error: (err: Error) => void - data: (data: Uint8Array | string) => void + data: (data: TData) => void end: () => void finish: () => void iceStateChange: (iceConnectionState: RTCIceConnectionState, iceGatheringState: RTCIceGatheringState) => void @@ -110,7 +111,7 @@ interface PeerEvents { * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods. * Extends EventEmitter for event handling. */ -class Peer extends EventEmitter { +class Peer extends EventEmitter> { _pc: RTCPeerConnection | null _id: string channelName: string | null @@ -149,13 +150,13 @@ class Peer extends EventEmitter { _closingInterval: ReturnType | null _remoteTracks: Array<{ track: MediaStreamTrack; stream: MediaStream }> | null _remoteStreams: MediaStream[] | null - _chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob | null + _chunk: TData | null _cb: Callback | null _interval: ReturnType | null _isReactNativeWebrtc: boolean _connecting: boolean _onFinishBound: (() => void) | null - __objectMode: boolean + _binary: boolean static WEBRTC_SUPPORT: boolean static config: RTCConfiguration @@ -167,7 +168,6 @@ class Peer extends EventEmitter { this.destroyed = false this._readableEnded = false this._writableEnded = false - this.__objectMode = !!opts.objectMode this._id = arr2hex(randomBytes(4)).slice(0, 7) this._debug('new peer %o', opts) @@ -225,7 +225,10 @@ class Peer extends EventEmitter { this._chunk = null this._cb = null this._interval = null + this._isReactNativeWebrtc = typeof window !== 'undefined' && (window as typeof window & { ReactNativeWebRTCDebug?: unknown }).ReactNativeWebRTCDebug != null this._connecting = false + this._onFinishBound = null + this._binary = opts.binary !== undefined ? opts.binary : true try { this._pc = new RTCPeerConnection(this.config) @@ -366,10 +369,10 @@ class Peer extends EventEmitter { /** * Send text/binary data to the remote peer. */ - send (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob): void { + send (chunk: TData): void { if (this._destroying) return if (this.destroyed) throw errCode(new Error('cannot send after peer is destroyed'), 'ERR_DESTROYED') - this._channel!.send(chunk as string | Blob | ArrayBuffer | ArrayBufferView) + this._channel!.send(chunk as DataChunk) } _needsNegotiation (): void { @@ -420,7 +423,7 @@ class Peer extends EventEmitter { /** * Push data to the readable side. If data is null, signals end of stream. */ - push (data: Uint8Array | string | null): void { + push (data: TData | null): void { if (data === null) { this._readableEnded = true this.emit('end') @@ -432,7 +435,7 @@ class Peer extends EventEmitter { /** * Write data to the peer (with optional callback). */ - write (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob, cb?: Callback): void { + write (chunk: TData, cb?: Callback): void { this._write(chunk, cb || (() => {})) } @@ -567,7 +570,7 @@ class Peer extends EventEmitter { }, CHANNEL_CLOSING_TIMEOUT) } - _write (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob, cb: Callback): void { + _write (chunk: TData, cb: Callback): void { if (this.destroyed) return cb(errCode(new Error('cannot write after peer is destroyed'), 'ERR_DATA_CHANNEL')) if (this._connected) { @@ -983,13 +986,16 @@ class Peer extends EventEmitter { _onChannelMessage (event: MessageEvent): void { if (this.destroyed) return - let data: Uint8Array | string = event.data + let data: unknown = event.data if (data instanceof ArrayBuffer) { data = new Uint8Array(data) - } else if (this.__objectMode === false) { + } + if (ArrayBuffer.isView(data)) { + // keep binary views as-is + } else if (typeof data === 'string' && this._binary) { data = text2arr(data as string) } - this.push(data as Uint8Array) + this.push(data as TData) } _onChannelBufferedAmountLow (): void { diff --git a/package.json b/package.json index ff1ecf6e..83bc176e 100644 --- a/package.json +++ b/package.json @@ -84,9 +84,10 @@ }, "scripts": { "build": "tsc", + "typecheck-tests": "tsc --project tsconfig.test.json --noEmit", "prebuild": "rm -rf dist", "prepublishOnly": "npm run build", - "test": "npm run build && vitest run", + "test": "npm run build && npm run typecheck-tests && vitest run", "test:watch": "vitest", "test-node": "vitest run", "test-browser": "npm run build && vitest run --config vitest.browser.config.ts", diff --git a/test/binary.ts b/test/binary.ts index e6d2a50c..9b9b00bf 100644 --- a/test/binary.ts +++ b/test/binary.ts @@ -77,8 +77,8 @@ test('data send/receive Uint8Array', function () { test('data send/receive ArrayBuffer', function () { return new Promise((resolve) => { - const peer1 = new Peer({ initiator: true }) - const peer2 = new Peer() + const peer1 = new Peer({ initiator: true }) + const peer2 = new Peer() peer1.on('signal', function (data) { peer2.signal(data) }) diff --git a/test/object-mode.ts b/test/object-mode.ts index dc5b4b9e..c8f53324 100644 --- a/test/object-mode.ts +++ b/test/object-mode.ts @@ -1,10 +1,10 @@ import Peer from '../index.js' import { test, expect } from 'vitest' -test('data send/receive string {objectMode: true}', function () { +test('data send/receive string {binary: false}', function () { return new Promise((resolve) => { - const peer1 = new Peer({ initiator: true, objectMode: true }) - const peer2 = new Peer({ objectMode: true }) + const peer1 = new Peer({ initiator: true, binary: false }) + const peer2 = new Peer({ binary: false }) peer1.on('signal', function (data) { peer2.signal(data) }) @@ -37,10 +37,10 @@ test('data send/receive string {objectMode: true}', function () { }) }) -test('data send/receive Buffer {objectMode: true}', function () { +test('data send/receive Buffer {binary: false}', function () { return new Promise((resolve) => { - const peer1 = new Peer({ initiator: true, objectMode: true }) - const peer2 = new Peer({ objectMode: true }) + const peer1 = new Peer({ initiator: true, binary: false }) + const peer2 = new Peer({ binary: false }) peer1.on('signal', function (data) { peer2.signal(data) }) @@ -73,10 +73,10 @@ test('data send/receive Buffer {objectMode: true}', function () { }) }) -test('data send/receive Uint8Array {objectMode: true}', function () { +test('data send/receive Uint8Array {binary: false}', function () { return new Promise((resolve) => { - const peer1 = new Peer({ initiator: true, objectMode: true }) - const peer2 = new Peer({ objectMode: true }) + const peer1 = new Peer({ initiator: true, binary: false }) + const peer2 = new Peer({ binary: false }) peer1.on('signal', function (data) { peer2.signal(data) }) @@ -111,10 +111,10 @@ test('data send/receive Uint8Array {objectMode: true}', function () { }) }) -test('data send/receive ArrayBuffer {objectMode: true}', function () { +test('data send/receive ArrayBuffer {binary: false}', function () { return new Promise((resolve) => { - const peer1 = new Peer({ initiator: true, objectMode: true }) - const peer2 = new Peer({ objectMode: true }) + const peer1 = new Peer({ initiator: true, binary: false }) + const peer2 = new Peer({ binary: false }) peer1.on('signal', function (data) { peer2.signal(data) }) diff --git a/test/types.d.ts b/test/types.d.ts new file mode 100644 index 00000000..8096ac0e --- /dev/null +++ b/test/types.d.ts @@ -0,0 +1,13 @@ +// Type declaration for test environment +import 'vitest' + +declare global { + namespace NodeJS { + interface Process { + browser?: boolean + } + } +} + +export {} + diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..241f7aca --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": [ + "test/**/*.ts" + ] +} + diff --git a/vitest.config.ts b/vitest.config.ts index 254fa278..e5a8b58a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,6 @@ export default defineConfig({ testTimeout: 20000, hookTimeout: 20000, include: ['test/**/*.ts', 'test/**/*.js'], - exclude: ['node_modules/**', 'test/common.ts', 'test/common.js', 'test/setup-browser.ts'] + exclude: ['node_modules/**', 'test/common.ts', 'test/common.js', 'test/setup-browser.ts', 'test/types.d.ts'] } })