@device-portal/react
v0.1.0
Published
Simple WebRTC data channel for React.
Readme
@device-portal/react
Simple WebRTC data channel for React.
Install
npm install @device-portal/reactHow to use
It is expected that the package will be used on two different devices. Create for them two separate pages or apps. Let's call them App A and App B. Both apps will be linked by same room (e.g. 'my-test-room').
Note: The example signaling server
wss://device-portal.filipchalupa.czis running on a free instance of render.com, so expect slower startup times if it has been inactive.
One-way Data Flow
This is the simplest use case where a Provider sends data to one or more Consumers.
App A (Provider)
The provider app sends a value.
import { useDevicePortalProvider } from '@device-portal/react'
import { useState } from 'react'
const AppA = () => {
const [value, setValue] = useState(0)
useDevicePortalProvider('my-test-room', {
value: value.toString(),
webSocketSignalingServer: 'wss://device-portal.filipchalupa.cz',
})
return (
<>
<h1>App A</h1>
<p>Value: {value}</p>
<button
onClick={() => {
setValue(value + 1)
}}
>
Increment
</button>
</>
)
}App B (Consumer)
The consumer app receives the value from the provider. Every time the provider's value changes, the consumer will be automatically updated.
import { useDevicePortalConsumer } from '@device-portal/react'
import { Suspense } from 'react'
const AppB = () => {
return (
<Suspense fallback={<p>Connecting…</p>}>
<ConsumerComponent />
</Suspense>
)
}
const ConsumerComponent = () => {
const { value } = useDevicePortalConsumer('my-test-room', {
webSocketSignalingServer: 'wss://device-portal.filipchalupa.cz',
})
return (
<>
<h1>App B</h1>
<p>Value from provider: {value}</p>
</>
)
}Per-peer Values
Instead of broadcasting the same value to all consumers, you can pass a function that returns a different value for each peer:
useDevicePortalProvider('my-test-room', {
value: (peerId) => JSON.stringify({ peerId, timestamp: Date.now() }),
webSocketSignalingServer: 'wss://device-portal.filipchalupa.cz',
})Two-way Communication
You can also send messages from the Consumer back to the Provider.
App A (Provider with message handling)
The provider now also listens for messages from the consumer.
import { useDevicePortalProvider } from '@device-portal/react'
import { useState } from 'react'
const AppA = () => {
const [value, setValue] = useState(0)
const [messageFromB, setMessageFromB] = useState('')
useDevicePortalProvider('my-test-room', {
value: value.toString(),
onMessageFromConsumer: (message, peerId) => {
setMessageFromB(message)
},
})
return (
<>
<h1>App A</h1>
<p>Value: {value}</p>
<button
onClick={() => {
setValue(value + 1)
}}
>
Increment
</button>
<p>Last message from App B: {messageFromB}</p>
</>
)
}App B (Consumer with message sending)
The consumer can now send messages to the provider.
import { useDevicePortalConsumer } from '@device-portal/react'
import { Suspense } from 'react'
const AppB = () => {
return (
<Suspense fallback={<p>Connecting…</p>}>
<ConsumerComponent />
</Suspense>
)
}
const ConsumerComponent = () => {
const { value, sendMessageToProvider } =
useDevicePortalConsumer('my-test-room')
return (
<>
<h1>App B</h1>
<p>Value from provider: {value}</p>
<button onClick={() => sendMessageToProvider('Hello from B!')}>
Send Message to A
</button>
</>
)
}Resilience
The WebRTC connection is designed to be resilient. If the connection to the signaling server is temporarily lost, any established peer-to-peer connections will remain active. The client will attempt to reconnect to the signaling server in the background to handle any future connection negotiations.
Connection Status
useDevicePortalConsumer exposes a connectionStatus field so the UI can react when the peer link drops:
const ConsumerComponent = () => {
const { value, connectionStatus } = useDevicePortalConsumer('my-test-room')
return (
<>
<p>Value from provider: {value}</p>
{connectionStatus === 'reconnecting' && <p>Reconnecting to provider…</p>}
</>
)
}The status is one of:
'connected'— the peer link is up.'reconnecting'— the peer link dropped (peer left, ICE failed); the consumer is retrying in the background. The last receivedvalueis kept.
'connecting' is intentionally not exposed: the hook suspends until the first value arrives, so by the time your component renders the consumer was already connected at least once.
Browser Direct Communication
When peers are in the same browser (different tabs or same tab), the library automatically uses direct browser APIs for communication instead of WebRTC. This results in a near-instant connection and works offline.
By default, browserDirect is true.
Browser Direct Only Mode
For privacy or offline-only applications where you know all peers are on the same browser, you can disable external signaling entirely by passing webSocketSignalingServer: null:
useDevicePortalProvider('my-room', {
value: 'secret-data',
webSocketSignalingServer: null,
})
// ...
useDevicePortalConsumer('my-room', {
webSocketSignalingServer: null,
})Development
Run
npm ci
npm run dev