Skip to content
Merged
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
31 changes: 20 additions & 11 deletions docs/explanation/moq-with-fishjam.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,42 +98,51 @@ Access to the relay is controlled by **MoQ tokens** — short-lived JWTs that ar
| Publisher token | Write access to a specific path | Streamer |
| Subscriber token | Read access to a path or namespace | Viewer |

A token is attached to the relay URL as a query parameter (`?jwt=<token>`). The relay validates the token and enforces its scope before allowing any media to flow.
A token is attached to the relay URL as a query parameter (`?jwt=<token>`). The relay validates the token and enforces its scope before allowing any media to flow. Both the Sandbox API and the Server SDK hand you the authenticated **connection URL** (the relay URL with the token already embedded), so the client can just take it as is and connect.

Keeping publisher and subscriber tokens separate ensures that a viewer can never accidentally publish to the stream, and a publisher cannot subscribe to paths it does not own.

## Getting Tokens
## Where to get the Connection URL from?

There are two ways to obtain MoQ tokens, depending on where you are in the development lifecycle.
There are two ways to obtain a MoQ connection URL, depending on where you are in the development lifecycle.

### Sandbox API (prototyping)

The **Sandbox API** is a ready-made backend provided by Fishjam for development and prototyping. It issues tokens without requiring you to build your own server, so you can start streaming immediately.
The **Sandbox API** is a ready-made backend provided by Fishjam for development and prototyping. It issues connection URLs without requiring you to build your own server, so you can start streaming immediately.

To get a publisher token, call:
To get a publisher connection URL, call:

```
GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{PUBLISHER-PATH}/publisher
```

To get a subscriber token, call:
To get a subscriber connection URL, call:

```
GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{SUBSCRIBER-PATH}/subscriber
```

To get a full-access connection URL — one that can both publish to and subscribe on the path — call:

```
GET https://fishjam.io/api/v1/connect/{FISHJAM_ID}/room-manager/moq/{PATH}/full-access
```

Each returns a JSON object with a `connection_url` field — the relay URL with the JWT embedded as `?jwt=` — alongside the raw `token`.

Comment on lines +131 to +132
The Sandbox API is **not intended for production** — it has no authentication and is only available in the Sandbox environment. See [What is the Sandbox API?](./sandbox-api-concept) for more context.

### Fishjam Server SDK (production)

In production, your backend generates tokens using the **Fishjam Server SDK**. This gives you full control over who can publish and who can subscribe.
In production, your backend generates connection URLs using the **Fishjam Server SDK**. This gives you full control over who can publish and who can subscribe.

The SDK's `createMoqToken` method accepts either a `publishPath` or a `subscribePath`:
The SDK's `createMoqAccess` method accepts a `publishPath`, a `subscribePath`, or both, and returns the MoQ access details — a `connection_url` (the relay URL with the JWT embedded) alongside the raw `token`:

- `publishPath` — issues a publisher token scoped to that path.
- `subscribePath` — issues a subscriber token scoped to that path or namespace prefix.
- `publishPath` — returns a publisher connection URL scoped to that path.
- `subscribePath` — returns a subscriber connection URL scoped to that path or namespace prefix.
- both `publishPath` and `subscribePath` — returns a full-access connection URL that can publish and subscribe on those paths.

Your backend then delivers each token to the appropriate client (publisher or viewer), which uses it to connect to the relay.
Your backend then delivers each connection URL to the appropriate client (publisher or viewer), which uses it to connect to the relay.
Comment thread
Karolk99 marked this conversation as resolved.

See the [Web Publishing](../tutorials/moq/web-publishing) and [Web Subscribing](../tutorials/moq/web-subscribing) tutorials (or their [React Native](../tutorials/moq/react-native-publishing) [counterparts](../tutorials/moq/react-native-subscribing)) for working code examples.

Expand Down
38 changes: 16 additions & 22 deletions docs/tutorials/moq/react-native-publishing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ MoQ is a protocol with a well-defined negotiation, so a publisher and a subscrib

If you don't have a backend server set up, you can prototype publishing using the [Sandbox API](../../explanation/sandbox-api-concept).

### Obtaining a publisher token
### Obtaining a publisher connection URL

For more on what the Sandbox API is and its limitations, see [What is the Sandbox API?](../../explanation/sandbox-api-concept).

:::info
To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you don't have them already, see [Sandbox API URL and Fishjam ID](../../explanation/sandbox-api-concept#sandbox-api-url-and-fishjam-id).
To obtain a MoQ connection URL you'll need your Sandbox API URL. If you don't have it already, see [Sandbox API URL](../../explanation/sandbox-api-concept#sandbox-api-url-and-fishjam-id).
:::

<Tabs groupId="sandbox-token">
Expand All @@ -64,17 +64,16 @@ To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you do
// @noErrors
import { useSandbox } from "@fishjam-cloud/react-native-client";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

// Inside a React component:
const { getSandboxMoqPublisherToken } = useSandbox({
const { getSandboxMoqPublisherAccess } = useSandbox({
sandboxApiUrl: SANDBOX_API_URL,
});

// Request a publisher token scoped to the publisher path
const publishToken = await getSandboxMoqPublisherToken(PUBLISHER_PATH);
// Request a publisher connection URL scoped to the publisher path
const { connection_url: publishUrl } = await getSandboxMoqPublisherAccess(PUBLISHER_PATH);
```
Comment on lines +71 to 77

</TabItem>
Expand All @@ -85,14 +84,13 @@ To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you do

```ts
// @noErrors
const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

const response = await fetch(
`${SANDBOX_API_URL}/moq/${PUBLISHER_PATH}/publisher`,
);
const { token: publishToken } = await response.json();
const { connection_url: publishUrl } = await response.json();
```

</TabItem>
Expand All @@ -102,7 +100,7 @@ To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you do

`react-native-moq` is hooks-based. You open a session against the relay, capture the camera and microphone, and hand them to a publisher.

Build the relay URL using the publisher token, open the session, and publish the camera + microphone tracks:
Open the session with the publisher connection URL, capture the camera and microphone, and publish the tracks:

```tsx
// @noErrors
Expand All @@ -116,16 +114,12 @@ import {
useSession,
} from "react-native-moq";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const PUBLISHER_PATH = "stream-alice";
const publishToken = ""; // from the step above

// Build the relay URL using the publisher token
const relayUrl = `https://relay.fishjam.io/${FISHJAM_ID}?jwt=${publishToken}`;
const publishUrl = ""; // from the step above

function PublishScreen() {
// Open the MoQ session against the Fishjam relay
const session = useSession(relayUrl, (s) => s.connect());
// Open the MoQ session against the Fishjam relay using the publisher connection URL
const session = useSession(publishUrl, (s) => s.connect());
const camera = useCamera({ position: "front" });
const microphone = useMicrophone();
const publisher = usePublisher(session);
Expand Down Expand Up @@ -174,7 +168,7 @@ The session owns the connection to the relay; the publisher reuses it. Because p

The [Quickstart](#quickstart-with-the-sandbox-api) gets you publishing quickly. In production, your backend generates tokens with proper authorization, so you control who can publish.

A **publisher token** grants write access to a specific path. Generate one on your backend and deliver it to the broadcasting client:
A **publisher connection URL** grants write access to a specific path. Generate one on your backend and deliver it to the broadcasting client:

<Tabs groupId="language">
<TabItem value="ts" label="TypeScript">
Expand All @@ -193,8 +187,8 @@ A **publisher token** grants write access to a specific path. Generate one on yo

const streamPath = 'stream-alice';

// Generate a token that allows publishing to 'stream-alice'
const { token: publishToken } = await fishjamClient.createMoqToken({
// Generate a connection URL that allows publishing to 'stream-alice'
const { connection_url: publishUrl } = await fishjamClient.createMoqAccess({
publishPath: streamPath,
});
Comment on lines +190 to 193
```
Expand All @@ -213,14 +207,14 @@ A **publisher token** grants write access to a specific path. Generate one on yo

stream_path = 'stream-alice'

# Generate a token that allows publishing to 'stream-alice'
publish_token = fishjam_client.create_moq_token(publish_path=stream_path)
# Generate a connection URL that allows publishing to 'stream-alice'
publish_url = fishjam_client.create_moq_access(publish_path=stream_path).connection_url
```

</TabItem>
</Tabs>

Deliver this token to the mobile client, then use it to build the relay URL and connect as described in [Connecting and publishing](#connecting-and-publishing).
Deliver this connection URL to the mobile client, then use it to connect as described in [Connecting and publishing](#connecting-and-publishing).

## See also

Expand Down
63 changes: 27 additions & 36 deletions docs/tutorials/moq/react-native-subscribing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ If you don't have a backend server set up, you can prototype subscribing using t
MoQ is a protocol with a well-defined negotiation, so a publisher and a subscriber don't need to use the same client library. A stream published from the browser with [`@moq/publish`](./web-publishing) can be watched with `react-native-moq`, and vice versa.
:::

### Obtaining a subscriber token
### Obtaining a subscriber connection URL

For more on what the Sandbox API is and its limitations, see [What is the Sandbox API?](../../explanation/sandbox-api-concept).

:::info
To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you don't have them already, see [Sandbox API URL and Fishjam ID](../../explanation/sandbox-api-concept#sandbox-api-url-and-fishjam-id).
To obtain a MoQ connection URL you'll need your Sandbox API URL. If you don't have it already, see [Sandbox API URL](../../explanation/sandbox-api-concept#sandbox-api-url-and-fishjam-id).
:::

<Tabs groupId="sandbox-token">
Expand All @@ -72,17 +72,16 @@ To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you do
// @noErrors
import { useSandbox } from "@fishjam-cloud/react-native-client";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const SUBSCRIBER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

// Inside a React component:
const { getSandboxMoqSubscriberToken } = useSandbox({
const { getSandboxMoqSubscriberAccess } = useSandbox({
sandboxApiUrl: SANDBOX_API_URL,
});

// Request a subscriber token scoped to the subscriber path
const subscribeToken = await getSandboxMoqSubscriberToken(SUBSCRIBER_PATH);
// Request a subscriber connection URL scoped to the subscriber path
const { connection_url: subscribeUrl } = await getSandboxMoqSubscriberAccess(SUBSCRIBER_PATH);
```
Comment on lines +79 to 85

</TabItem>
Expand All @@ -93,14 +92,13 @@ To obtain a MoQ token you'll need your Fishjam ID and Sandbox API URL. If you do

```ts
// @noErrors
const FISHJAM_ID = "YOUR_FISHJAM_ID";
const SUBSCRIBER_PATH = "stream-alice";
const SANDBOX_API_URL = "YOUR_SANDBOX_API_URL";

const response = await fetch(
`${SANDBOX_API_URL}/moq/${SUBSCRIBER_PATH}/subscriber`,
);
const { token: subscribeToken } = await response.json();
const { connection_url: subscribeUrl } = await response.json();
```

</TabItem>
Expand All @@ -121,16 +119,12 @@ import {
} from "react-native-moq";
import type { BroadcastInfo } from "react-native-moq";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const SUBSCRIBER_PATH = "stream-alice";
const subscribeToken = ""; // from the step above

// Build the relay URL using the subscriber token
const relayUrl = `https://relay.fishjam.io/${FISHJAM_ID}?jwt=${subscribeToken}`;
const subscribeUrl = ""; // from the step above

function WatchScreen() {
// Connect to the Fishjam MoQ relay on mount
const session = useSession(relayUrl, (s) => s.connect());
// Connect to the Fishjam MoQ relay on mount using the subscriber connection URL
const session = useSession(subscribeUrl, (s) => s.connect());

// Discover broadcasts under the subscriber path
const broadcasts = useBroadcasts(session, SUBSCRIBER_PATH);
Expand Down Expand Up @@ -198,7 +192,7 @@ It exposes imperative `enterFullscreen()` / `exitFullscreen()` methods on its re

The [Quickstart](#quickstart-with-the-sandbox-api) gets you watching quickly. In production, your backend generates tokens with proper authorization, so you control who can subscribe.

A **subscriber token** grants read access to a specific path. Generate one on your backend and deliver it to the viewing client:
A **subscriber connection URL** grants read access to a specific path. Generate one on your backend and deliver it to the viewing client:

<Tabs groupId="language">
<TabItem value="ts" label="TypeScript">
Expand All @@ -217,8 +211,8 @@ A **subscriber token** grants read access to a specific path. Generate one on yo

const streamPath = 'stream-alice';

// Generate a token that allows subscribing to 'stream-alice'
const { token: subscribeToken } = await fishjamClient.createMoqToken({
// Generate a connection URL that allows subscribing to 'stream-alice'
const { connection_url: subscribeUrl } = await fishjamClient.createMoqAccess({
subscribePath: streamPath,
});
Comment on lines +214 to 217
```
Expand All @@ -237,21 +231,21 @@ A **subscriber token** grants read access to a specific path. Generate one on yo

stream_path = 'stream-alice'

# Generate a token that allows subscribing to 'stream-alice'
subscribe_token = fishjam_client.create_moq_token(subscribe_path=stream_path)
# Generate a connection URL that allows subscribing to 'stream-alice'
subscribe_url = fishjam_client.create_moq_access(subscribe_path=stream_path).connection_url
```

</TabItem>
</Tabs>

Deliver this token to the mobile client, then use it to build the relay URL and connect as described in [Connecting and subscribing](#connecting-and-subscribing).
Deliver this connection URL to the mobile client, then use it to connect as described in [Connecting and subscribing](#connecting-and-subscribing).

### Subscribe to a namespace

When multiple publishers join a room, you won't know their exact paths in advance.
Instead of consuming a single path, you can **discover** all broadcasts published under a namespace prefix and subscribe to each one as they appear.

To do this, generate a subscriber token scoped to the room namespace instead of a single stream path.
To do this, generate a subscriber connection URL scoped to the room namespace instead of a single stream path.

<Tabs groupId="language">
<TabItem value="ts" label="TypeScript">
Expand All @@ -267,16 +261,16 @@ To do this, generate a subscriber token scoped to the room namespace instead of

const roomName = 'my-room';

const { token: alicePublisherToken } = await fishjamClient.createMoqToken({
const { connection_url: alicePublisherUrl } = await fishjamClient.createMoqAccess({
publishPath: roomName + "/alice",
});

const { token: bobPublisherToken } = await fishjamClient.createMoqToken({
const { connection_url: bobPublisherUrl } = await fishjamClient.createMoqAccess({
publishPath: roomName + "/bob",
});


const { token: namespaceToken } = await fishjamClient.createMoqToken({
const { connection_url: namespaceUrl } = await fishjamClient.createMoqAccess({
subscribePath: roomName,
});
```
Expand All @@ -295,21 +289,21 @@ To do this, generate a subscriber token scoped to the room namespace instead of

room_name = 'my-room'

alice_publisher_token = fishjam_client.create_moq_token(
alice_publisher_url = fishjam_client.create_moq_access(
publish_path=room_name + "/alice",
)
).connection_url

bob_publisher_token = fishjam_client.create_moq_token(
bob_publisher_url = fishjam_client.create_moq_access(
publish_path=room_name + "/bob",
)
).connection_url

namespace_token = fishjam_client.create_moq_token(subscribe_path=room_name)
namespace_url = fishjam_client.create_moq_access(subscribe_path=room_name).connection_url
```

</TabItem>
</Tabs>

On the client, pass the namespace token's relay URL to `useSession`, then call `useBroadcasts(session, roomName)` with the room prefix. It returns a reactive `BroadcastInfo[]` that re-renders every time a publisher joins or leaves — no manual diffing of an announce set. Map over it to mount a player per broadcast:
On the client, pass the namespace connection URL to `useSession`, then call `useBroadcasts(session, roomName)` with the room prefix. It returns a reactive `BroadcastInfo[]` that re-renders every time a publisher joins or leaves — no manual diffing of an announce set. Map over it to mount a player per broadcast:

```tsx
// @noErrors
Expand All @@ -321,14 +315,11 @@ import {
} from "react-native-moq";
import type { BroadcastInfo } from "react-native-moq";

const FISHJAM_ID = "YOUR_FISHJAM_ID";
const ROOM_NAME = "my-room";
const namespaceToken = "token";

const relayUrl = `https://relay.fishjam.io/${FISHJAM_ID}?jwt=${namespaceToken}`;
const namespaceUrl = "";

function RoomGrid() {
const session = useSession(relayUrl, (s) => s.connect());
const session = useSession(namespaceUrl, (s) => s.connect());

// Every broadcast published under the 'my-room' prefix, kept live
const broadcasts = useBroadcasts(session, ROOM_NAME);
Expand Down
Loading
Loading