Skip to content

Commit f7b397d

Browse files
committed
[WIP] Turning Peer class into a generic that says what data type is exchanged
1 parent 339a673 commit f7b397d

File tree

9 files changed

+89
-35
lines changed

9 files changed

+89
-35
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ If `opts` is specified, then the default options (shown below) will be overridde
279279
trickle: true,
280280
allowHalfTrickle: false,
281281
wrtc: {}, // RTCPeerConnection/RTCSessionDescription/RTCIceCandidate
282-
objectMode: false
282+
binary: true
283283
}
284284
```
285285

@@ -299,8 +299,28 @@ The options do the following:
299299
- [`RTCPeerConnection`](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection)
300300
- [`RTCSessionDescription`](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescription)
301301
- [`RTCIceCandidate`](https://www.w3.org/TR/webrtc/#dom-rtcicecandidate)
302+
- `binary` - set to `false` to send/receive text as strings. Defaults to `true` (binary payloads as `Uint8Array`).
302303

303-
- `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.
304+
### Typing the peer
305+
306+
You can pick the data type via a generic parameter. By default data events are `Uint8Array` when `binary: true`.
307+
308+
```ts
309+
import Peer from 'simple-peer'
310+
311+
// default: Uint8Array
312+
const peer = new Peer()
313+
peer.on('data', (data: Uint8Array) => {})
314+
315+
// strings only
316+
const textPeer = new Peer<string>({ binary: false })
317+
textPeer.send('hello')
318+
319+
// custom struct (you must handle serialization)
320+
interface Message { type: 'ping'; id: string }
321+
const msgPeer = new Peer<Message>({ binary: false })
322+
msgPeer.send({ type: 'ping', id: '1' })
323+
```
304324

305325
### `peer.signal(data)`
306326

index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import Lite, { PeerOptions } from './lite.js'
33
import errCode from 'err-code'
44
import { MediaStream, MediaStreamTrack, RTCRtpSender, RTCRtpTransceiver } from 'webrtc-polyfill'
55

6+
type DataChunk = ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob
7+
68
/**
79
* WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods.
810
* Duplex stream.
911
*/
10-
class Peer extends Lite {
12+
class Peer<TData extends DataChunk = Uint8Array> extends Lite<TData> {
1113
streams: MediaStream[]
1214
_senderMap: WeakMap<MediaStreamTrack, WeakMap<MediaStream, RTCRtpSender>>
1315

lite.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
22
import debug from 'debug'
33
import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, RTCDataChannel, RTCDataChannelEvent, RTCPeerConnectionIceEvent, MediaStream, MediaStreamTrack } from 'webrtc-polyfill'
4-
import { EventEmitter } from 'eventemitter3'
4+
import { EventEmitter } from 'eventemitter3'
55
import errCode from 'err-code'
66
import { randomBytes, arr2hex, text2arr } from 'uint8-util'
77

88
type Callback = (err: Error | null) => void
9+
type DataChunk = ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob
910

1011
const Debug = debug('simple-peer')
1112

@@ -46,7 +47,7 @@ interface PeerOptions {
4647
trickle?: boolean
4748
allowHalfTrickle?: boolean
4849
iceCompleteTimeout?: number
49-
objectMode?: boolean
50+
binary?: boolean
5051
allowHalfOpen?: boolean
5152
}
5253

@@ -88,13 +89,13 @@ interface LegacyStatsResult {
8889
stat: (name: string) => unknown
8990
}
9091

91-
interface PeerEvents {
92+
interface PeerEvents<TData> {
9293
signal: (data: SignalData) => void
9394
connect: () => void
9495
disconnect: () => void
9596
close: () => void
9697
error: (err: Error) => void
97-
data: (data: Uint8Array | string) => void
98+
data: (data: TData) => void
9899
end: () => void
99100
finish: () => void
100101
iceStateChange: (iceConnectionState: RTCIceConnectionState, iceGatheringState: RTCIceGatheringState) => void
@@ -110,7 +111,7 @@ interface PeerEvents {
110111
* WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods.
111112
* Extends EventEmitter for event handling.
112113
*/
113-
class Peer extends EventEmitter<PeerEvents> {
114+
class Peer<TData extends DataChunk = Uint8Array> extends EventEmitter<PeerEvents<TData>> {
114115
_pc: RTCPeerConnection | null
115116
_id: string
116117
channelName: string | null
@@ -149,13 +150,13 @@ class Peer extends EventEmitter<PeerEvents> {
149150
_closingInterval: ReturnType<typeof setInterval> | null
150151
_remoteTracks: Array<{ track: MediaStreamTrack; stream: MediaStream }> | null
151152
_remoteStreams: MediaStream[] | null
152-
_chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob | null
153+
_chunk: TData | null
153154
_cb: Callback | null
154155
_interval: ReturnType<typeof setInterval> | null
155156
_isReactNativeWebrtc: boolean
156157
_connecting: boolean
157158
_onFinishBound: (() => void) | null
158-
__objectMode: boolean
159+
_binary: boolean
159160

160161
static WEBRTC_SUPPORT: boolean
161162
static config: RTCConfiguration
@@ -167,7 +168,6 @@ class Peer extends EventEmitter<PeerEvents> {
167168
this.destroyed = false
168169
this._readableEnded = false
169170
this._writableEnded = false
170-
this.__objectMode = !!opts.objectMode
171171

172172
this._id = arr2hex(randomBytes(4)).slice(0, 7)
173173
this._debug('new peer %o', opts)
@@ -225,7 +225,10 @@ class Peer extends EventEmitter<PeerEvents> {
225225
this._chunk = null
226226
this._cb = null
227227
this._interval = null
228+
this._isReactNativeWebrtc = typeof window !== 'undefined' && (window as typeof window & { ReactNativeWebRTCDebug?: unknown }).ReactNativeWebRTCDebug != null
228229
this._connecting = false
230+
this._onFinishBound = null
231+
this._binary = opts.binary !== undefined ? opts.binary : true
229232

230233
try {
231234
this._pc = new RTCPeerConnection(this.config)
@@ -366,10 +369,10 @@ class Peer extends EventEmitter<PeerEvents> {
366369
/**
367370
* Send text/binary data to the remote peer.
368371
*/
369-
send (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob): void {
372+
send (chunk: TData): void {
370373
if (this._destroying) return
371374
if (this.destroyed) throw errCode(new Error('cannot send after peer is destroyed'), 'ERR_DESTROYED')
372-
this._channel!.send(chunk as string | Blob | ArrayBuffer | ArrayBufferView)
375+
this._channel!.send(chunk as DataChunk)
373376
}
374377

375378
_needsNegotiation (): void {
@@ -420,7 +423,7 @@ class Peer extends EventEmitter<PeerEvents> {
420423
/**
421424
* Push data to the readable side. If data is null, signals end of stream.
422425
*/
423-
push (data: Uint8Array | string | null): void {
426+
push (data: TData | null): void {
424427
if (data === null) {
425428
this._readableEnded = true
426429
this.emit('end')
@@ -432,7 +435,7 @@ class Peer extends EventEmitter<PeerEvents> {
432435
/**
433436
* Write data to the peer (with optional callback).
434437
*/
435-
write (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob, cb?: Callback): void {
438+
write (chunk: TData, cb?: Callback): void {
436439
this._write(chunk, cb || (() => {}))
437440
}
438441

@@ -567,7 +570,7 @@ class Peer extends EventEmitter<PeerEvents> {
567570
}, CHANNEL_CLOSING_TIMEOUT)
568571
}
569572

570-
_write (chunk: ArrayBufferView | ArrayBuffer | Uint8Array | string | Blob, cb: Callback): void {
573+
_write (chunk: TData, cb: Callback): void {
571574
if (this.destroyed) return cb(errCode(new Error('cannot write after peer is destroyed'), 'ERR_DATA_CHANNEL'))
572575

573576
if (this._connected) {
@@ -983,13 +986,16 @@ class Peer extends EventEmitter<PeerEvents> {
983986

984987
_onChannelMessage (event: MessageEvent): void {
985988
if (this.destroyed) return
986-
let data: Uint8Array | string = event.data
989+
let data: unknown = event.data
987990
if (data instanceof ArrayBuffer) {
988991
data = new Uint8Array(data)
989-
} else if (this.__objectMode === false) {
992+
}
993+
if (ArrayBuffer.isView(data)) {
994+
// keep binary views as-is
995+
} else if (typeof data === 'string' && this._binary) {
990996
data = text2arr(data as string)
991997
}
992-
this.push(data as Uint8Array)
998+
this.push(data as TData)
993999
}
9941000

9951001
_onChannelBufferedAmountLow (): void {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@
8484
},
8585
"scripts": {
8686
"build": "tsc",
87+
"typecheck-tests": "tsc --project tsconfig.test.json --noEmit",
8788
"prebuild": "rm -rf dist",
8889
"prepublishOnly": "npm run build",
89-
"test": "npm run build && vitest run",
90+
"test": "npm run build && npm run typecheck-tests && vitest run",
9091
"test:watch": "vitest",
9192
"test-node": "vitest run",
9293
"test-browser": "npm run build && vitest run --config vitest.browser.config.ts",

test/binary.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ test('data send/receive Uint8Array', function () {
7777

7878
test('data send/receive ArrayBuffer', function () {
7979
return new Promise<void>((resolve) => {
80-
const peer1 = new Peer({ initiator: true })
81-
const peer2 = new Peer()
80+
const peer1 = new Peer<ArrayBuffer>({ initiator: true })
81+
const peer2 = new Peer<ArrayBuffer>()
8282
peer1.on('signal', function (data) {
8383
peer2.signal(data)
8484
})

test/object-mode.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import Peer from '../index.js'
22
import { test, expect } from 'vitest'
33

4-
test('data send/receive string {objectMode: true}', function () {
4+
test('data send/receive string {binary: false}', function () {
55
return new Promise<void>((resolve) => {
6-
const peer1 = new Peer({ initiator: true, objectMode: true })
7-
const peer2 = new Peer({ objectMode: true })
6+
const peer1 = new Peer<string>({ initiator: true, binary: false })
7+
const peer2 = new Peer<string>({ binary: false })
88
peer1.on('signal', function (data) {
99
peer2.signal(data)
1010
})
@@ -37,10 +37,10 @@ test('data send/receive string {objectMode: true}', function () {
3737
})
3838
})
3939

40-
test('data send/receive Buffer {objectMode: true}', function () {
40+
test('data send/receive Buffer {binary: false}', function () {
4141
return new Promise<void>((resolve) => {
42-
const peer1 = new Peer({ initiator: true, objectMode: true })
43-
const peer2 = new Peer({ objectMode: true })
42+
const peer1 = new Peer<string>({ initiator: true, binary: false })
43+
const peer2 = new Peer<string>({ binary: false })
4444
peer1.on('signal', function (data) {
4545
peer2.signal(data)
4646
})
@@ -73,10 +73,10 @@ test('data send/receive Buffer {objectMode: true}', function () {
7373
})
7474
})
7575

76-
test('data send/receive Uint8Array {objectMode: true}', function () {
76+
test('data send/receive Uint8Array {binary: false}', function () {
7777
return new Promise<void>((resolve) => {
78-
const peer1 = new Peer({ initiator: true, objectMode: true })
79-
const peer2 = new Peer({ objectMode: true })
78+
const peer1 = new Peer<string>({ initiator: true, binary: false })
79+
const peer2 = new Peer<string>({ binary: false })
8080
peer1.on('signal', function (data) {
8181
peer2.signal(data)
8282
})
@@ -111,10 +111,10 @@ test('data send/receive Uint8Array {objectMode: true}', function () {
111111
})
112112
})
113113

114-
test('data send/receive ArrayBuffer {objectMode: true}', function () {
114+
test('data send/receive ArrayBuffer {binary: false}', function () {
115115
return new Promise<void>((resolve) => {
116-
const peer1 = new Peer({ initiator: true, objectMode: true })
117-
const peer2 = new Peer({ objectMode: true })
116+
const peer1 = new Peer<string>({ initiator: true, binary: false })
117+
const peer2 = new Peer<string>({ binary: false })
118118
peer1.on('signal', function (data) {
119119
peer2.signal(data)
120120
})

test/types.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Type declaration for test environment
2+
import 'vitest'
3+
4+
declare global {
5+
namespace NodeJS {
6+
interface Process {
7+
browser?: boolean
8+
}
9+
}
10+
}
11+
12+
export {}
13+

tsconfig.test.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"strict": false,
5+
"noUnusedLocals": false,
6+
"noUnusedParameters": false
7+
},
8+
"include": [
9+
"test/**/*.ts"
10+
]
11+
}
12+

vitest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export default defineConfig({
77
testTimeout: 20000,
88
hookTimeout: 20000,
99
include: ['test/**/*.ts', 'test/**/*.js'],
10-
exclude: ['node_modules/**', 'test/common.ts', 'test/common.js', 'test/setup-browser.ts']
10+
exclude: ['node_modules/**', 'test/common.ts', 'test/common.js', 'test/setup-browser.ts', 'test/types.d.ts']
1111
}
1212
})

0 commit comments

Comments
 (0)