Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```

Expand All @@ -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<string>({ binary: false })
textPeer.send('hello')

// custom struct (you must handle serialization)
interface Message { type: 'ping'; id: string }
const msgPeer = new Peer<Message>({ binary: false })
msgPeer.send({ type: 'ping', id: '1' })
```

### `peer.signal(data)`

Expand Down
4 changes: 3 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TData extends DataChunk = Uint8Array> extends Lite<TData> {
streams: MediaStream[]
_senderMap: WeakMap<MediaStreamTrack, WeakMap<MediaStream, RTCRtpSender>>

Expand Down
38 changes: 22 additions & 16 deletions lite.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
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')

Expand Down Expand Up @@ -46,7 +47,7 @@ interface PeerOptions {
trickle?: boolean
allowHalfTrickle?: boolean
iceCompleteTimeout?: number
objectMode?: boolean
binary?: boolean
allowHalfOpen?: boolean
}

Expand Down Expand Up @@ -88,13 +89,13 @@ interface LegacyStatsResult {
stat: (name: string) => unknown
}

interface PeerEvents {
interface PeerEvents<TData> {
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
Expand All @@ -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<PeerEvents> {
class Peer<TData extends DataChunk = Uint8Array> extends EventEmitter<PeerEvents<TData>> {
_pc: RTCPeerConnection | null
_id: string
channelName: string | null
Expand Down Expand Up @@ -149,13 +150,13 @@ class Peer extends EventEmitter<PeerEvents> {
_closingInterval: ReturnType<typeof setInterval> | 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<typeof setInterval> | null
_isReactNativeWebrtc: boolean
_connecting: boolean
_onFinishBound: (() => void) | null
__objectMode: boolean
_binary: boolean

static WEBRTC_SUPPORT: boolean
static config: RTCConfiguration
Expand All @@ -167,7 +168,6 @@ class Peer extends EventEmitter<PeerEvents> {
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)
Expand Down Expand Up @@ -225,7 +225,10 @@ class Peer extends EventEmitter<PeerEvents> {
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)
Expand Down Expand Up @@ -366,10 +369,10 @@ class Peer extends EventEmitter<PeerEvents> {
/**
* 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 {
Expand Down Expand Up @@ -420,7 +423,7 @@ class Peer extends EventEmitter<PeerEvents> {
/**
* 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')
Expand All @@ -432,7 +435,7 @@ class Peer extends EventEmitter<PeerEvents> {
/**
* 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 || (() => {}))
}

Expand Down Expand Up @@ -567,7 +570,7 @@ class Peer extends EventEmitter<PeerEvents> {
}, 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) {
Expand Down Expand Up @@ -983,13 +986,16 @@ class Peer extends EventEmitter<PeerEvents> {

_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 {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions test/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ test('data send/receive Uint8Array', function () {

test('data send/receive ArrayBuffer', function () {
return new Promise<void>((resolve) => {
const peer1 = new Peer({ initiator: true })
const peer2 = new Peer()
const peer1 = new Peer<ArrayBuffer>({ initiator: true })
const peer2 = new Peer<ArrayBuffer>()
peer1.on('signal', function (data) {
peer2.signal(data)
})
Expand Down
24 changes: 12 additions & 12 deletions test/object-mode.ts
Original file line number Diff line number Diff line change
@@ -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<void>((resolve) => {
const peer1 = new Peer({ initiator: true, objectMode: true })
const peer2 = new Peer({ objectMode: true })
const peer1 = new Peer<string>({ initiator: true, binary: false })
const peer2 = new Peer<string>({ binary: false })
peer1.on('signal', function (data) {
peer2.signal(data)
})
Expand Down Expand Up @@ -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<void>((resolve) => {
const peer1 = new Peer({ initiator: true, objectMode: true })
const peer2 = new Peer({ objectMode: true })
const peer1 = new Peer<string>({ initiator: true, binary: false })
const peer2 = new Peer<string>({ binary: false })
peer1.on('signal', function (data) {
peer2.signal(data)
})
Expand Down Expand Up @@ -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<void>((resolve) => {
const peer1 = new Peer({ initiator: true, objectMode: true })
const peer2 = new Peer({ objectMode: true })
const peer1 = new Peer<string>({ initiator: true, binary: false })
const peer2 = new Peer<string>({ binary: false })
peer1.on('signal', function (data) {
peer2.signal(data)
})
Expand Down Expand Up @@ -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<void>((resolve) => {
const peer1 = new Peer({ initiator: true, objectMode: true })
const peer2 = new Peer({ objectMode: true })
const peer1 = new Peer<string>({ initiator: true, binary: false })
const peer2 = new Peer<string>({ binary: false })
peer1.on('signal', function (data) {
peer2.signal(data)
})
Expand Down
13 changes: 13 additions & 0 deletions test/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Type declaration for test environment
import 'vitest'

declare global {
namespace NodeJS {
interface Process {
browser?: boolean
}
}
}

export {}

12 changes: 12 additions & 0 deletions tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"strict": false,
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": [
"test/**/*.ts"
]
}

2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}
})
Loading