Skip to content

Commit 97ce2db

Browse files
clean up docs, prepend clientName to eventClass if not included
1 parent 12fde6f commit 97ce2db

File tree

4 files changed

+149
-37
lines changed

4 files changed

+149
-37
lines changed

README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
This is a React Javascript library for two or more React apps to connect to MUPPET data channels (WebRTC/websockets under the hood), then use simple, React-like interfaces to forward each other serialized UI events from their app (like user clicks, selections, or input) via the [MUPPETs protocol](https://docs.google.com/document/d/1TSvRtfzQGdclHGys9e0dLXKNnvWAmRnizH-biQW066o/view?usp=sharing).
88

99
## Table of Contents
10+
1011
- [IDSSe MUPPET Library](#idsse-muppet-library)
1112
- [Table of Contents](#table-of-contents)
1213
- [Usage](#usage)
@@ -49,7 +50,72 @@ npm install @noaa-gsl/idsse-muppet
4950
Now you can use the MUPPET library to create new connections to a MUPPET channel and send/receive events over it in your React application.
5051

5152
### Developer Guide
52-
For React apps, see the [Developer Guide - React](docs/react.md).
53+
54+
To use MUPPET channels in your React app, first need to wrap your top-level React component in a `MuppetProvider`:
55+
56+
```javascript
57+
// main.jsx
58+
import ReactDOM from "react-dom/client";
59+
import { MuppetProvider } from "@noaa-gsl/idsse-muppet";
60+
import App from "./App";
61+
62+
ReactDOM.createRoot(document.getElementById("root")).render(
63+
<MuppetProvider
64+
clientName="MY_APP"
65+
serverUrl="http://example.com" // URL of WebRTC signaling server
66+
serverPath="/"
67+
channelNames={["my-channel"]}
68+
>
69+
<App>
70+
</MuppetProvider>
71+
);
72+
```
73+
74+
- `channelNames`: 1 or more WebRTC rooms on the WebRTC server that you wish to connect to. This will need to line up with the room(s) used by the app with which you want to send/receive messages.
75+
- `clientName`: how your app identifies itself to other apps using MUPPET. This will be prepended to the `eventClass` of every event you send. For example, other apps may subscribe to events from you with eventClass `MY_APP.SOME_BUTTON_CLICKED`, or if they want to send an RPC type message to your app specifically, they would use this clientName string to address the message.
76+
77+
With that Provider in place, now any components in your React component tree have access to shared, persistent `MuppetChannel` instances. You can fetch this channel and use it directly in your React component:
78+
79+
```javascript
80+
// MyComponent.jsx
81+
import { useState } from 'react';
82+
import { useMuppetChannel, useMuppetCallback } from '@noaa-gsl/idsse-muppet';
83+
84+
function MyComponent() {
85+
// track user's favorite color from other app
86+
const [currentColor, setCurrentColor] = useState('');
87+
88+
const channel = useMuppetChannel('my-channel');
89+
90+
useMuppetCallback(
91+
'my-channel',
92+
(channel, evt) => {
93+
console.log('Received new color selection from OTHER_APP:', evt);
94+
setCurrentColor(event.color);
95+
},
96+
['OTHER_APP.COLOR_SELECTED'],
97+
);
98+
99+
const onButtonClick = () => {
100+
channel?.sendEvent({
101+
eventClass: 'BUTTON_CLICKED',
102+
event: { value: 123 },
103+
});
104+
};
105+
106+
return (
107+
<div>
108+
<button onClick={onButtonClick}>Hello world</button>
109+
<p>Favorite color:</p>
110+
<p>{currentColor}</p>
111+
</div>
112+
);
113+
}
114+
115+
export default MyComponent;
116+
```
117+
118+
For more details, see the [Developer Guide - React](docs/react.md).
53119
54120
Although it's not recommended because it can be much harder to manage app state, if you want to use the underlying JS without the niceties of React Context or custom Hooks, see [Developer Guide - Vanilla JS](docs/vanilla-js.md)
55121

docs/react.md

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44

55
- [Developer Guide - React](#developer-guide---react)
66
- [Table of Contents](#table-of-contents)
7-
- [Initialize a new MUPPET channel](#initialize-a-new-muppet-channel)
8-
- [Broadcast an event](#broadcast-an-event)
9-
- [Receive events](#receive-events)
10-
- [Get all channels](#get-all-channels)
11-
- [Send an RPC request](#send-an-rpc-request)
7+
- [`MuppetProvider`](#muppetprovider)
8+
- [Hooks](#hooks)
9+
- [`useMuppetChannel`](#usemuppetchannel)
10+
- [`useMuppetChannels`](#usemuppetchannels)
11+
- [`useMuppetCallback`](#usemuppetcallback)
12+
- [`MuppetChannel`](#muppetchannel)
13+
- [`sendEvent()`](#sendevent)
14+
- [`sendRequest()`](#sendrequest)
15+
- [`sendRawEvent()`](#sendrawevent)
1216

13-
## Initialize a new MUPPET channel
17+
## `MuppetProvider`
1418

15-
First, in your `main.jsx` file, add a `<MuppetProvider>` component wrapping your main React app component. Pass the Provider a string array `channelNames`, which is a list of WebRTC rooms on the server you wish to connect to.
19+
In the `main.jsx` file of your React app, add a `<MuppetProvider>` component wrapping your top-level React app component (typically called "App"). Pass the Provider a string array `channelNames`, which is a list of WebRTC rooms on the server you wish to connect to.
1620

1721
```javascript
1822
// main.jsx
@@ -34,7 +38,7 @@ ReactDOM.createRoot(document.getElementById("root")).render(
3438

3539
These channel name(s) and clientName should be coordinated beforehand with the other web apps you wish to communicate via MUPPET; your app and the other app must connect to the same channel on the server for the apps to "find each other" (negotiate a peer-to-peer websocket) and start sending messages. The `clientName` is important because the other app may wish to subscribe to events published by you (which will start with your `clientName`). You can pass any number of `channelNames` to the Provider, which will attempt to create individual connections to each channel listed.
3640

37-
This Provider now stores an app-wide [React Context](https://react.dev/learn/passing-data-deeply-with-context) for you, so any components in your component tree can reuse the same persistant `MuppetChannel` by calling the `useMuppetChannel()` React hook with one of the channel names. See the [Broadcast an event](#broadcast-an-event) section for more details.
41+
This Provider stores an app-wide [React Context](https://react.dev/learn/passing-data-deeply-with-context) for you, so any components in your component tree can reuse the same persistant `MuppetChannel` by calling the [useMuppetChannel()](#usemuppetchannel) React hook with one of the channel names.
3842

3943
> For reliability, it's recommended that this room is unique to your session/browser ("my-room-abc123", for example, instead of just "my-room"). Consider establishing with the app with which you're integrating some shared nonce or algorithm to generate a new room for each new user session, so user A using your app will not have problems with user B's click actions taking effect in their session.
4044
>
@@ -58,37 +62,51 @@ if (channel.isOpen()) {
5862
>
5963
> As soon as both apps have connected to the same MuppetChannel, all of these pending events will be "replayed"--sent over the channel--so the receiver can receive them.
6064
61-
## Broadcast an event
65+
## Hooks
6266

63-
To broadcast a MUPPET event over your new MuppetChannel, simply pass your MUPPET eventClass and event to `MuppetChannel.sendEvent()`.
67+
### `useMuppetChannel`
6468

65-
This can (and generally should) be invoked right in the Javascript component where the user took action. For example, the `onClick` callback of some button, or an `onSelect` of an HTML select element:
69+
Assuming you created a React `MuppetProvider` from above and passed `channelNames` to it, the Provider stores and exposes to any component in your React app tree the `MuppetChannel` instances for those channel names.
6670

6771
```javascript
6872
// MyComponent.jsx
73+
import { useEffect } from 'react';
6974
import { useMuppetChannel } from '@noaa-gsl/idsse-muppet';
7075

7176
function MyComponent() {
7277
const channel = useMuppetChannel('my-channel');
7378

74-
const onButtonClick = () => {
75-
channel?.sendEvent({
76-
eventClass: 'MY_APP.SOME_EVENT',
77-
event: { value: 123 },
78-
});
79-
};
80-
81-
return <button onClick={onButtonClick}>Hello world</button>;
79+
useEffect(() => {
80+
console.log('Got MUPPET channel, current status:', channel?.state);
81+
}, [channel]);
8282
}
83-
84-
export default MyComponent;
8583
```
8684
8785
Note the component must know the "channel name" string given to `MuppetProvider`, so it can reference the `MuppetChannel` object it wants to use, as multiple channels can be stored in MuppetProvider Context. In the example above, the exact channel name is `my-channel`, but if a non-existent channel was requested by the component, the `useMuppetChannel()` hook would return `undefined`.
8886
89-
## Receive events
87+
### `useMuppetChannels`
88+
89+
You can also get the mapping of all channel names to their `MuppetChannel` instances by calling `useMuppetChannels()`. Remember, these channel names will match exactly the strings passed to `MuppetProvider`, and may be null if they haven't attempted to connect yet.
9090
91-
Assuming you created a React `MuppetProvider` from above and passed `channelNames` to it, the Provider stores and exposes to any component in your React app tree the `MuppetChannel` instances for those channel names. Use the Hook `useMuppetCallback` similar to how you might wire up a callback function to a given React state change using [useEffect()](https://react.dev/reference/react/hooks#effect-hooks).
91+
For example, if you passed `channelNames={["some-channel", "another-channel"]}` to the overall `MuppetProvider`:
92+
93+
```javascript
94+
const channelMap = useMuppetChannels();
95+
const { 'some-channel': someChannel, 'another-channel': anotherChannel } = channelMap;
96+
97+
console.log('Is some-channel open?', someChannel.isOpen());
98+
99+
anotherChannel?.sendEvent({
100+
eventClass: 'MY_APP.COLOR_SELECTED',
101+
event: { color: 'blue' },
102+
});
103+
```
104+
105+
### `useMuppetCallback`
106+
107+
Assuming you created a React `MuppetProvider` from above and passed `channelNames` to it, the Provider stores and exposes to any component in your React app tree the `MuppetChannel` instances for those channel names.
108+
109+
Use the Hook `useMuppetCallback` similar to how you might wire up a callback function to a given React state change using [useEffect()](https://react.dev/reference/react/hooks#effect-hooks).
92110
93111
```javascript
94112
// MyComponent.jsx
@@ -117,25 +135,35 @@ Since WebRTC itself has no rules or conventions on how data sent over channels s
117135
118136
> Note: you will need to coordinate with the app you're integrating with to determine the eventClass constants that it plans to send you. According to MUPPET conventions, it should be declared in JSON Schema the body of a special `SCHEMAS` eventClass message that the other app sends you immediately after you both connect.
119137
120-
### Get all channels
138+
## `MuppetChannel`
121139
122-
You can also get the mapping of all channel names to their `MuppetChannel` instances by calling `useMuppetChannels()`. Remember, these channel names will match exactly the strings passed to `MuppetProvider`, and may be null if they haven't attempted to connect yet.
140+
### `sendEvent()`
123141
124-
For example, if you passed `channelNames={["some-channel", "another-channel"]}` to the overall `MuppetProvider`:
142+
To broadcast a MUPPET event over your new MuppetChannel, simply pass your MUPPET eventClass and event to `MuppetChannel.sendEvent()`.
143+
144+
This can (and generally should) be invoked right in the Javascript component where the user took action. For example, the `onClick` callback of some button, or an `onSelect` of an HTML select element:
125145
126146
```javascript
127-
const channelMap = useMuppetChannels();
128-
const { 'some-channel': someChannel, 'another-channel': anotherChannel } = channelMap;
147+
// MyComponent.jsx
148+
import { useMuppetChannel } from '@noaa-gsl/idsse-muppet';
129149

130-
console.log('Is some-channel open?', someChannel.isOpen());
150+
function MyComponent() {
151+
const channel = useMuppetChannel('my-channel');
131152

132-
anotherChannel?.sendEvent({
133-
eventClass: 'MY_APP.COLOR_SELECTED',
134-
event: { color: 'blue' },
135-
});
153+
const onButtonClick = () => {
154+
channel?.sendEvent({
155+
eventClass: 'SOME_EVENT',
156+
event: { value: 123 },
157+
});
158+
};
159+
160+
return <button onClick={onButtonClick}>Hello world</button>;
161+
}
162+
163+
export default MyComponent;
136164
```
137165
138-
## Send an RPC request
166+
### `sendRequest()`
139167
140168
To send a MUPPET event that is expected to receive some response from the receiver, call `MuppetChannel.sendRequest()`, passing the eventClass, event body, and the destination (the name of the app that should respond to the event).
141169
@@ -177,3 +205,19 @@ function MyComponent() {
177205
```
178206
179207
Under the hood, this is sending a MUPPET event to the app "THEIR_APP" with an event class "MY_APP.GET_USER_PHONE", which the receiver should respond to by sending a MUPPET event back with class "THEIR_APP.GET_USER_PHONE" and destination: "MY_APP". The MUPPET library then resolves your awaited promise with the response payload.
208+
209+
### `sendRawEvent()`
210+
211+
This function should be avoided as much as possible, using `sendEvent()` instead, which will take the `event` you pass it and, using helpful defaults, transform it to a MUPPET protocol-compliant format that is understood by receiving apps (provide a UUID, build a lower-level eventClass that combines your clientName, eventClass, and the intended destination app, etc.)
212+
213+
If you need to bypass these MUPPET conventions, you can send a custom JSON object down the MUPPET channel. Be aware that it's not guaranteed to be parsed, interpreted, and acted upon as expected by the receiving app.
214+
215+
```javascript
216+
const channel = useMuppetChannel('my-channel');
217+
channel?.sendRawEvent({
218+
id: '123456',
219+
destination: 'OTHER_APP',
220+
eventClass: 'CUSTOM_SENDER_APP.BUTTON_CLICKED',
221+
event: { foo: 'bar' },
222+
});
223+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@noaa-gsl/idsse-muppet",
3-
"version": "0.0.6",
3+
"version": "0.0.7",
44
"description": "Connect to, send, and receive data over MUPPET data channels in convenient React interfaces using the MUPPETs protocol",
55
"main": "index.js",
66
"scripts": {

src/MuppetChannel.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ class MuppetChannel {
330330
id: crypto.randomUUID(),
331331
destination,
332332
requestId: requestId || undefined,
333-
eventClass: `${this.#clientName}.${eventClass}`,
333+
eventClass: eventClass.startsWith(`${this.#clientName}.`)
334+
? eventClass
335+
: `${this.#clientName}.${eventClass}`,
334336
event,
335337
});
336338

0 commit comments

Comments
 (0)