Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions .changeset/moody-snails-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@nordeck/matrix-neoboard-widget': minor
'@nordeck/matrix-neoboard-react-sdk': minor
---

Update MatrixRTC implementation
194 changes: 137 additions & 57 deletions docs/model/matrix-rtc-events.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
# MatrixRTC Events Data Model

Having the option to use MatrixRTC as an alternative to WebRTC introduces some
changes to the data model.
changes:

Specifically, we replace the `net.nordeck.whiteboard.sessions`events with the
`m.rtc.member` MatrixRTC membership state events (or the unstable `org.matrix.msc3401.call.member`)
and no longer need to use To Device Messages for establishing peer connections,
as all signaling and connection logic is now handled by the LiveKit Client SDK
and LiveKit Server backend.
We replace the `net.nordeck.whiteboard` state event with the MatrixRTC `m.rtc.slot` state event

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Aren't these two separate concepts?

One is indicating this room has whiteboard data in it which can be used by the standalone app or the widget, and the other is enabling realtime collaboration (which can be disabled, if the slot is closed).

I haven't checked yet the full PR but this has data migration implications.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very good question.

I somehow thought that would be useful and migration shouldn't be an issue and I addressed the PR to handle that. However I got other issues in standalone because of this decision.

I agree, that should be separated, that makes sense and makes everything much easier and consistent 👍

I have update PR to revert whiteboard to slot changes and add slot sending separately.

(or the unstable `org.matrix.msc4143.rtc.slot`).

## Room Messages
We replace the `net.nordeck.whiteboard.sessions` state event with the
MatrixRTC `m.rtc.member` membership **sticky** event (or the unstable `org.matrix.msc4143.rtc.member`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A link to the sticky events MSC would be helpful here, no?


The whiteboard state and RTC session membership is stored using the following events in a Matrix room:
We no longer need to use To Device Messages for establishing peer connections.
All signaling and connection logic is now handled by the LiveKit Client SDK and LiveKit Server backend.

## Room Events

The whiteboard state and RTC membership is stored using the following events in a Matrix room:

```
┌────────────────────────────────┐ ┌────────────────────────────────────┐
│ │ │ │
│ net.nordeck.whiteboard │◄─────────────────┤ org.matrix.msc3401.call.member │
│ (state_key: <whiteboard-id>) │ (whiteboard-id) │ (state_key: _<user_id>_<device_id>)│
│ │ │ │
└──┬─────────────────────────────┘ └────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ │
│ org.matrix.msc4143.rtc.slot │ ┌───────────────────────────────┐
│ state_key: net.nordeck.whiteboard#<whiteboard-id> │ content.slot_id │ │
│ content.status: open │◄───────────────────┤ org.matrix.msc4143.rtc.member │
│ content.application.type: net.nordeck.whiteboard │ │ │
│ │ └───────────────────────────────┘
└──┬────────────────────────────────────────────────┘
│ content.documentId
Expand All @@ -34,65 +39,140 @@ The whiteboard state and RTC session membership is stored using the following ev

All other events and relations remain as described in [Matrix Events](matrix-events.md)

### `org.matrix.msc3401.call.member` (State Event)
### `org.matrix.msc4143.rtc.slot` (State event)

This is a renamed `net.nordeck.whiteboard` event with [MSC4143: MatrixRTC][MSC4143] slot specific fields
added to the content: `status`, `applicaiton.type`.
Comment thread
maheichyk marked this conversation as resolved.
Outdated

#### Fields

| Field | Type | Description |
| ------------------------ | ------------------------ | -------------------------------------------------------- |
| `status` | `'open'` | A slot's status. |
| `application.type` | `net.nordeck.whiteboard` | Nordeck whiteboard application identifier. |
Comment on lines +43 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are mixing type and values here. For example, status can be open or closed and the application type is a string following the CNIG.

Suggested change
| `status` | `'open'` | A slot's status. |
| `application.type` | `net.nordeck.whiteboard` | Nordeck whiteboard application identifier. |
| `status` | `string` | A slot's status, either 'open' or 'closed'. |
| `application.type` | `string` | Nordeck whiteboard application identifier: `net.nordeck.whiteboard` |

| `application.documentId` | `string` | A `net.nordeck.whiteboard.document.create` room event id |

#### Example

```json
{
"type": "org.matrix.msc4143.rtc.slot",
"sender": "@user-id:example.com",
"state_key": "<whiteboard-id>",
Comment thread
maheichyk marked this conversation as resolved.
Outdated
"content": {
"status": "open",
"application": {
"type": "net.nordeck.whiteboard",
"documentId": "$H1-nssrxUGbrMdKSDJcACCpmc4PrClb2WDSOrGUv6bs"
}
},
"event_id": "$event-id",
"origin_server_ts": 1665134498391,
"room_id": "!room-id:example.com"
}
```

According to [MSC4143: MatrixRTC][MSC4143], a RTC application must keep it's session state in
a state event of type `m.rtc.member` (or the unstable `org.matrix.msc3401.call.member`),
with a state key composed of the user's Matrix ID and the user's Device ID.
### `org.matrix.msc4143.rtc.member` (Sticky event) with `join` membership

This state event will keep RTC and app-specific medatada in it's `content` field.
According to [MSC4143: MatrixRTC][MSC4143], a RTC application must keep it's membership data in
the sticky event of type `m.rtc.member` with `member.membership` to be `join`.

The termination of a RTC session is signaled by clearing the state event's `content`. This is done using delayed events.
Sending a `m.rtc.member` event with `membership` equal to `join` for an opened slot opens a session.

#### Content
#### Fields

| Field | Type | Description |
| -------------------------------------- | -------- | -------------------------------------------------------------------------- |
| `application` | `string` | The NeoBoard application identifier, which is `net.nordeck.whiteboard`. |
| `call_id` | `string` | The ID of the Whiteboard for this session, which matches the Widget ID. |
| `device_id` | `string` | The Device ID of the user's client. |
| `focus_active` | `object` | The currently active backend focus type and focus selection strategy. |
| `focus_active.type` | `string` | The type of the focus, `livekit` for LiveKit. |
| `focus_active.focus_selection` | `string` | The focus selection strategy. Currently only supports `oldest_membership`. |
| `foci_preferred[]` | `array` | A list of possible foci this user knows about. |
| `foci_preferred[].type` | `string` | The type of the focus, `livekit` for LiveKit. |
| `foci_preferred[].livekit_service_url` | `string` | The URL of the LiveKit MatrixRTC backend to use for the session. |
| `scope` | `string` | The scope of the RTC session. Only supported value is 'm.room'. |
| `expires` | `number` | The expiration timestamp for this session membership. |
| Field | Type | Description |
| --------------------------- | -------------------------- | ------------------------------------------------------------------------------- |
| `slot_id` | `string` | A MatrixRTC slot ID, example: `net.nordeck.whiteboard#whiteboard-id` |
| `member.id` | `string` | A unique user identifier for each join, even for the same user and device. |
| `member.membership` | `'join'` | Identifies membership event as `join`. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also mixing types and values here and I understand why you are doing it - still, I think this is not correct as is.

| `member.deviceId` | `string` | The Device ID of the user's client. |
| `application.type` | `'net.nordeck.whiteboard'` | Nordeck whiteboard application identifier. |
| `application.whiteboard_id` | `string` | A whiteboard id. |
| `transports.published[]` | `array` | Array of transports used by member to publis media, see MatrixRTC transports. |
Comment thread
maheichyk marked this conversation as resolved.
Outdated
| `transports.can_subscribe` | `array` | Array of transports member can subscribe to. At the moment: `['livekit']` only. |
| `msc4354_sticky_key` | `string` | The sticky key. Must be the same as `member.id`. |

#### Example

```json
{
"type": "org.matrix.msc3401.call.member",
"sender": "@alice:matrix.internal",
"type": "org.matrix.msc4143.rtc.member",
"sender": "@user-id:example.com",
"content": {
"application": "net.nordeck.whiteboard",
"call_id": "whiteboard-id",
"device_id": "SDXDZRNDJA",
"focus_active": {
"type": "livekit",
"focus_selection": "oldest_membership"
"slot_id": "net.nordeck.whiteboard#whiteboard-id",
"member": {
"id": "$member-id-0",
"membership": "join",
"device_id": "$device-id-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the device_id property coming from? I can't find it in the spec proposal.

},
"application": {
"type": "net.nordeck.whiteboard",
"whiteboard_id": "whiteboard-id"
},
"transports": {
"published": [
{
"type": "livekit",
"livekit_service_url": "https://livekit-jwt.example.com"
}
],
"can_subscribe": ["livekit"]
},
"foci_preferred": [
{
"type": "livekit",
"livekit_service_url": "https//livekit-jwt.matrix.internal"
}
],
"scope": "m.room",
"expires": 1743778636001
"msc4354_sticky_key": "$member-id-0"
},
"state_key": "_@alice:matrix.internal_SDXDZRNDJA",
"origin_server_ts": 1743764236021,
"unsigned": {
"membership": "join",
"age": 68
"origin_server_ts": 0,
"event_id": "$event-id",
"room_id": "!room-id:example.com"
}
```

### `org.matrix.msc4143.rtc.member` (Sticky event) with `leave` membership

A session is terminated when a `m.rtc.member` event with a `leave` `membership` is sent.

The `leave_reason.code` is set to `leave` when users leaves intentionally.

The `leave_reason.code` is set to `delayed_leave` when user's [MSC4140 delayed event][MSC4140] to leave is sent.

#### Fields

| Field | Type | Description |
| -------------------- | ---------------------------- | -------------------------------------------------------------------------- |
| `slot_id` | `string` | A MatrixRTC slot ID, example: `net.nordeck.whiteboard#whiteboard-id` |
| `member.id` | `string` | A unique user identifier for each join, even for the same user and device. |
| `member.membership` | `'leave'` | Idnentifies membership event as `leave`. |
Comment thread
maheichyk marked this conversation as resolved.
Outdated
| `member.deviceId` | `string` | The Device ID of the user's client. |
| `leave_reason.code` | `'leave' \| 'delayed_leave'` | Leave code. |
| `msc4354_sticky_key` | `string` | The sticky key. Must be the same as `member.id`. |

#### Example

```json
{
"type": "org.matrix.msc4143.rtc.member",
"sender": "@user-id:example.com",
"content": {
"slot_id": "net.nordeck.whiteboard#whiteboard-id",
"member": {
"id": "$member-id-0",
"membership": "leave",
"device_id": "$device-id-0"
},
"leave_reason": {
"code": "leave"
},
"msc4354_sticky_key": "$member-id-0"
},
"event_id": "$bFsA4Obl-sneiJlq4SAM2WGMLe00ie3f-Mod7VQfF_c",
"room_id": "!BWCjlIjHYWgJyZySxE:matrix.internal"
"origin_server_ts": 0,
"event_id": "$event-id",
"room_id": "!room-id:example.com",
"msc4354_sticky": {
"duration_ms": 3600000
}
}
```

[matrix-events]: ./matrix-events.md
[MSC4143]: https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md
[MSC4140]: https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/expiring-events-keep-alive/proposals/4140-delayed-events-futures.md
4 changes: 2 additions & 2 deletions matrix-neoboard-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"i18next-http-backend": "^3.0.6",
"loglevel": "^1.9.2",
"loglevel-plugin-prefix": "^0.8.4",
"matrix-widget-api": "^1.16.1",
"matrix-widget-api": "1.18.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.7.4",
Expand Down Expand Up @@ -52,7 +52,7 @@
"docker:build": "docker build --build-context root=../ -t nordeck/matrix-neoboard-widget .",
"docker:run": "dotenv -c -- docker run -it --rm -e REACT_APP_HOME_SERVER_URL -p 3000:8080 --name matrix-neoboard-widget nordeck/matrix-neoboard-widget",
"dev": "vite",
"dev:https": "VITE_DEV_SSL=true vite",
"dev:https": "VITE_DEV_SSL=true vite --force",
"preview": "vite preview",
"preview:https": "VITE_DEV_SSL=true vite preview",
"build": "tsc && vite build",
Expand Down
6 changes: 5 additions & 1 deletion matrix-neoboard-widget/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ const widgetApiPromise = WidgetApiImpl.create({
});

const store = createStore({ widgetApi: widgetApiPromise });
const whiteboardManager = createWhiteboardManager(store, widgetApiPromise);
const whiteboardManager = createWhiteboardManager(
store,
widgetApiPromise,
false,
);

const container = document.getElementById('root');
const root = createRoot(container!);
Expand Down
30 changes: 18 additions & 12 deletions matrix-neoboard-widget/src/widgetCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,25 @@ import {
STATE_EVENT_ROOM_MEMBER,
} from '@matrix-widget-toolkit/api';
import {
isMatrixRtcMode,
ROOM_EVENT_4143_RTC_MEMBER,
ROOM_EVENT_DOCUMENT_CHUNK,
ROOM_EVENT_DOCUMENT_CREATE,
ROOM_EVENT_DOCUMENT_SNAPSHOT,
STATE_EVENT_4143_RTC_SLOT,
STATE_EVENT_ROOM_NAME,
STATE_EVENT_RTC_MEMBER,
STATE_EVENT_WHITEBOARD,
STATE_EVENT_WHITEBOARD_SESSIONS,
TO_DEVICE_MESSAGE_CONNECTION_SIGNALING,
} from '@nordeck/matrix-neoboard-react-sdk';
import { matrixRtcMode } from '@nordeck/matrix-neoboard-react-sdk/src/components/Whiteboard';
import {
EventDirection,
MatrixCapabilities,
WidgetApiFromWidgetAction,
WidgetEventCapability,
} from 'matrix-widget-api';

const { userId, deviceId } = extractWidgetParameters();
const { userId } = extractWidgetParameters();

export const widgetCapabilities = [
WidgetEventCapability.forRoomEvent(
Expand Down Expand Up @@ -87,12 +88,16 @@ export const widgetCapabilities = [
),

WidgetEventCapability.forStateEvent(
EventDirection.Send,
EventDirection.Receive,
STATE_EVENT_WHITEBOARD,
),
WidgetEventCapability.forStateEvent(
EventDirection.Send,
STATE_EVENT_4143_RTC_SLOT,
),
WidgetEventCapability.forStateEvent(
EventDirection.Receive,
STATE_EVENT_WHITEBOARD,
STATE_EVENT_4143_RTC_SLOT,
),
WidgetEventCapability.forStateEvent(
EventDirection.Receive,
Expand All @@ -112,18 +117,19 @@ export const widgetCapabilities = [
WidgetApiFromWidgetAction.MSC4039DownloadFileAction,
];

if (matrixRtcMode) {
if (isMatrixRtcMode()) {
widgetCapabilities.push(
WidgetEventCapability.forStateEvent(
MatrixCapabilities.MSC4515RtcTransports,
WidgetEventCapability.forRoomEvent(
EventDirection.Send,
STATE_EVENT_RTC_MEMBER,
// We only need to write the own state, but read state from everyone
`_${userId}_${deviceId}`,
ROOM_EVENT_4143_RTC_MEMBER,
),
WidgetEventCapability.forStateEvent(
WidgetEventCapability.forRoomEvent(
EventDirection.Receive,
STATE_EVENT_RTC_MEMBER,
ROOM_EVENT_4143_RTC_MEMBER,
),
MatrixCapabilities.MSC4407ReceiveStickyEvent,
MatrixCapabilities.MSC4407SendStickyEvent,
MatrixCapabilities.MSC4157SendDelayedEvent,
MatrixCapabilities.MSC4157UpdateDelayedEvent,
);
Expand Down
4 changes: 2 additions & 2 deletions packages/react-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"emoji-regex": "^10.6.0",
"joi": "^17.13.4",
"js-base64": "^3.7.8",
"livekit-client": "^2.18.10",
"livekit-client": "^2.21.0",
"localforage": "^1.10.0",
"lodash": "^4.18.1",
"loglevel": "^1.9.2",
"matrix-widget-api": "^1.16.1",
"matrix-widget-api": "1.18.0",
"pdfmake": "^0.2.23",
"react-beautiful-dnd": "^13.1.1",
"react-draggable": "^4.4.6",
Expand Down
Loading