# @device-portal/react [![NPM](https://img.shields.io/npm/v/@device-portal/react.svg)](https://www.npmjs.com/package/@device-portal/react) Simple WebRTC data channel for React. ## Install ```bash npm install @device-portal/react ``` ## How 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.cz` is 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 `Consumer`s. #### App A (Provider) The provider app sends a value. ```jsx 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 ( <>

App A

Value: {value}

) } ``` #### 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. ```jsx import { useDevicePortalConsumer } from '@device-portal/react' import { Suspense } from 'react' const AppB = () => { return ( Connecting…

}>
) } const ConsumerComponent = () => { const { value } = useDevicePortalConsumer('my-test-room', { webSocketSignalingServer: 'wss://device-portal.filipchalupa.cz', }) return ( <>

App B

Value from provider: {value}

) } ``` ### 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: ```jsx 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. ```jsx 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 ( <>

App A

Value: {value}

Last message from App B: {messageFromB}

) } ``` #### App B (Consumer with message sending) The consumer can now send messages to the provider. ```jsx import { useDevicePortalConsumer } from '@device-portal/react' import { Suspense } from 'react' const AppB = () => { return ( Connecting…

}>
) } const ConsumerComponent = () => { const { value, sendMessageToProvider } = useDevicePortalConsumer('my-test-room') return ( <>

App B

Value from provider: {value}

) } ``` ## 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: ```jsx const ConsumerComponent = () => { const { value, connectionStatus } = useDevicePortalConsumer('my-test-room') return ( <>

Value from provider: {value}

{connectionStatus === 'reconnecting' &&

Reconnecting to provider…

} ) } ``` 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 received `value` is 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`: ```jsx useDevicePortalProvider('my-room', { value: 'secret-data', webSocketSignalingServer: null, }) // ... useDevicePortalConsumer('my-room', { webSocketSignalingServer: null, }) ``` ## Development Run ```sh npm ci npm run dev ```