Skip to content

Commit

Permalink
[Try] HTTP based PHP signaling server for colaborative editing (#53922)
Browse files Browse the repository at this point in the history
* in progress (+9 squashed commits)
Squashed commits:
[0e51c10685] In progress.
[fdecb78889] new base copy
[0cd4b10502] package lock
[401a7b415a] php lint fixes
[62c4829170] endpoint updates
[8c95d06ba2] fixes and remove unlimited connection
[05d5134c0b] package lock & lint fixes
[9a85abdd68] Working signaling server using wp ajax admin
[f5f13f7d57] Base y-webrtc copied

* Update lib/sync/endpoint.php

Co-authored-by: Anton Vlasenko <[email protected]>

* Update lib/sync/endpoint.php

Co-authored-by: Anton Vlasenko <[email protected]>

* Update lib/sync/endpoint.php

Co-authored-by: Anton Vlasenko <[email protected]>

* Refactor to a class.

* Use fetch instead of XMLHttpRequest.

* Fixed possible race condition and added file access error handling.

* Refactor to createWebRTCConnection.

* Rename unique to subscriber_id. And improve code sending messages.

* Remove use of an hack for multiple messages.

* Use fetch instead of XMLHttpRequest

* Add documentation for the signalling server

* Add signalling server loading back

* Remove hardcoded url from the docs.

* Updated sync readme.

* Fix undefined error on test.

* Update package lock.

* Document the Event stream format

* Update docs for signaling server

* Update lib/experimental/sync/class-gutenberg-http-signaling-server.php

Co-authored-by: André <[email protected]>

* refactor in progress

* tmp

* fix some bugs after refactoring

* Added retry mechanism with error events.

* Document clean_up_old_connections.

* Make parameter order consistent.

* Document remaining parameters.

* Follow y-webrtc room name convention

* Conditionally enable endpoint depending if setting is set or not.

* Remove debug code.

* Fix rebase: remove no longer existing file

* Update docs

---------

Co-authored-by: Anton Vlasenko <[email protected]>
Co-authored-by: André Maneiro <[email protected]>
  • Loading branch information
3 people authored Oct 19, 2023
1 parent 8f65e35 commit 04e1738
Show file tree
Hide file tree
Showing 14 changed files with 1,871 additions and 43 deletions.
183 changes: 183 additions & 0 deletions lib/experimental/sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Signaling Server Documentation

The signaling server allows multiple clients to exchange messages with each other through various communication topics.

Topics are not defined upfront, but clients define them by subscribing to them. By subscribing to a given topic, the client tells the server to keep track of its unread messages in the given topic. By unsubscribing from a topic, the client tells the server to free the bookeeping it maintains for the given client and topic.

Every client communicates with the server via `GET` or `POST`. Clients must have a unique identifier, which can be randomly generated. This identifier should be included as a parameter named `subscriber_id` in every request.

Available operations:

- Subscribe to topics
- Unsubscribe from topics
- Publish a message
- Read pending messages
- Ping the server

## Subscribe to topics

To subscribe to a set of topics, a client must send a `POST` request with the following parameters:

- `subscriber_id`: Subscriber ID of the client.
- `action`: should be set to `gutenberg_signaling_server`.
- `message`:
- `type`: should be set to `subscribe`.
- `topics`: array of topics that the client is interested in reading messages from, e.g., `[ 'WordPress', 'Drupal' ]`.

If the action is executed successfully by the server, the server will respond with `{"result":"ok"}`.

### Sample request

```js
await (
await fetch( window.wp.ajax.settings.url, {
body: new URLSearchParams( {
subscriber_id: '1',
action: 'gutenberg_signaling_server',
message: JSON.stringify( {
type: 'subscribe',
topics: [ 'WordPress', 'Drupal' ],
} ),
} ),
method: 'POST',
} )
).text();
```

## Unsubscribe from topics

To unsubscribe from a set of topics, a client must send a `POST` request with the following parameters:

- `subscriber_id`: subscriber ID of the client.
- `action`: should be set to `gutenberg_signaling_server`.
- `message`:
- `type`: should be set as `unsubscribe`.
- `topics`: an array of topics that the client is no longer interested in reading messages from, e.g., `['WordPress', 'Drupal']`.

If the action is executed successfully by the server, the server will respond with `{"result":"ok"}`.

### Sample request

```js
await (
await fetch( window.wp.ajax.settings.url, {
body: new URLSearchParams( {
subscriber_id: '1',
action: 'gutenberg_signaling_server',
message: JSON.stringify( {
type: 'unsubscribe',
topics: [ 'WordPress', 'Drupal' ],
} ),
} ),
method: 'POST',
} )
).text();
```

## Publish a message

To publish a message in a specific topic, a client must send a `POST` request with the following parameters:

- `subscriber_id`: subscriber ID of the client.
- `action`: should be set to `gutenberg_signaling_server`.
- `message`:
- `type`: should be set as `publish`.
- `topic`: the topic where the message should be published, e.g., `WordPress`.
- `data`: The data to be broadcasted to every client that subscribed to the topic. The data can be any string and may be encrypted to prevent the server from reading the messages.

If the action is executed successfully by the server, the server will respond with `{"result":"ok"}`.

### Sample request

```js
await (
await fetch( window.wp.ajax.settings.url, {
body: new URLSearchParams( {
subscriber_id: '1',
action: 'gutenberg_signaling_server',
message: JSON.stringify( {
type: 'publish',
topic: 'WordPress',
data: 'hello I am client 1!',
} ),
} ),
method: 'POST',
} )
).text();
```

## Read pending messages

To read pending messages, the client should send a `GET` request with the following parameters:

- `subscriber_id`: Subscriber ID of the client.
- `action`: should be set to `gutenberg_signaling_server`.

The server will respond using the [Event stream format](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format), whose content type is set to `text/event-stream;charset=UTF-8`. The Event stream format defines the following fields:

- `retry`: the reconnection time, in ms. The time after which the client should check again for messages.
- `id`: unique identifier for the server response.
- `event`: one of `message` or `error`.
- `data`:
- If `event` is `message`, data is a JSON encoded string containing an array of messages that the given client has not read yet. Each message is similar to the published message object but includes an additional property named `clients`. This property specifies the number of clients for which the message was sent. Note it does not indicate whether they have already received/requested it.
- If `event` is `error`, data is a description of the error.

If there are no pending messages, the server's response only contains the `retry:` field. If there are pending messages, the server will respond including all the fields.

### Sample request

```js
await (
await fetch(
window.wp.url.addQueryArgs( window.wp.ajax.settings.url, {
subscriber_id: '1',
action: 'gutenberg_signaling_server',
} )
)
).text();
```

Sample answer from the server when there are no unread messages:

```
retry: 3000
```

Sample answer from the server when there are unread messages:

```
retry: 3000
id: 1694809781
event: message
data: [{"type":"publish","topic":"WordPress","data":"hello I am client 1!","clients":2},{"type":"publish","topic":"WordPress","data":"Hi client 1 I am client 2","clients":2}]
```

## Ping the server

To ensure that the server is listening and to indicate that a client is still alive, the client can periodically send a ping to the server. When the server receives a ping from a client, it will respond with a message containing `pong`. The client will receive this `pong` message when it asks the server for new messages.

To send a ping, the client should send a `POST` request with the following parameters:

- `subscriber_id`: Subscriber ID of the client.
- `action`: should be set to `gutenberg_signaling_server`.
- `message`:
- `type`: Should be set as `ping`.

If the action is executed successfully by the server, the server will respond with `{"result":"ok"}`.

#### Sample request

```js
await (
await fetch( window.wp.ajax.settings.url, {
body: new URLSearchParams( {
subscriber_id: '1',
action: 'gutenberg_signaling_server',
message: JSON.stringify( {
type: 'ping',
} ),
} ),
method: 'POST',
} )
).text();
```
Loading

1 comment on commit 04e1738

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 04e1738.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6576700608
📝 Reported issues:

Please sign in to comment.