Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce experimental real time channels #77

Closed
wants to merge 18 commits into from
3 changes: 2 additions & 1 deletion src-docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
- [sendToChat](./spec/sendToChat.md)
- [importFiles](./spec/importFiles.md)
- [selfAddr & selfName](./spec/selfAddr_and_selfName.md)
- [setRealtimeListener](./spec/setRealtimeListener.md)
- [sendRealtimeData](./spec/sendRealtimeData.md)
- [Messenger implementations](./spec/messenger.md)

- [Shared Web Application state](./shared_state/README.md)
- [Detecting conflicts](./shared_state/conflicts.md)
- [Theory of Conflict-free Replicated Data Types (CRDTs)](./shared_state/crdts.md)
Expand Down
48 changes: 48 additions & 0 deletions src-docs/spec/sendRealtimeData.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# sendRealtimeData()

```js
window.webxdc.sendRealtimeData(data);
```

**Note that this API is experimental and not fully settled (April 2024)**

Send `Uint8Array` data to other realtime peers for this app.
You must first call [`setRealtimeListener`](./setRealtimeListener.md)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know of any other web APIs that behave this way where an event listener has to be added first before any corresponding events can be emitted.

I can't think of any use-cases where you'd want to send but not listen for responses, but this seems like something that could be very frustrating for any devs that overlook this warning.

I assume the reason is that you'd like to avoid instantiating the ephemeral messaging stack unless it's required. Perhaps that stack could be launched on sending a message as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point -- i had thought about it but one iteration before the one you commented, we had setRealtimeListener return a promise that resolved only when a first peer is connected. So there was a way to do a sendRealtimeData after waiting for the promise to resolve and be optimistic that at least one peer will see it.
Now, that we removed the promise, we could as well trigger the setup of the realtime machinery on sendRealtimeData as well even though it's almost guaranteed the sent data will not land anywhere if we are just starting the machinery. @link2xt what do you think? You were the most critical about the promise IIRC.

Copy link
Contributor Author

@hpk42 hpk42 Apr 22, 2024

Choose a reason for hiding this comment

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

after some more thought, i addressed the comment and made the two methods symmetric in the sense that they both implicitely trigger the setup of realtime connections.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think there is no need to even specify when the stack is initialized. You can just call both methods whenever you want and API should do something meaningful, i.e. try to initialize the connection instead of dropping the message right away.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if we keep connection setup totally implicit i wonder how would an app be able to ever force disconnection from peers? Ensuring that no code in an app still tries to send data may not be easy which would implicitely set it up again. I think it's better to have some explicit way to manage connectivity. If we also want to avoid having a dependency of top-level send and receive APIs then we should consider this API:

const realtimeSession = webxdc.joinRealtimeSession(receiveCallback)
and then having realtimeSession.sendData() and realtimeSession.disconnect() methods.
There would be no way to send data or leave a session without first joining, and it's natural that after disconnect the session is invalidated and you need to start fresh to reconnect and send/receive data.
This API design gives both explicit control over connectivity, and does not need a reminder to only send data after you registered a receive-listener.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is my last comment implemented in a separate PR: https://github.com/webxdc/website/pull/78/files

to announce participation in realtime data transmission.
Note that any data sent before a first peer is connected might not arrive.
It is up to the app to implement a "synchronization" protocol
so that peers can detect each other presences.

Any sent data is

- **private to the chat**: Only chat members can receive realtime data.

- **scoped to the app**: different apps in a
chat can not discover or receive realtime data of other apps in the chat.

- **ephemeral**: any sent data will only be received by the currently
connected chat peers but not by peers joining later.
There is no guarantee anyone is receiving the sent data
because there might be no currently listening peers,
or network connections broke.

## Example

```js
let timeout
hpk42 marked this conversation as resolved.
Show resolved Hide resolved
window.webxdc.setRealtimeListener((data) => {
console.log("Received realtime data: ", data);
const msg = new TextDecoder().decode(data);
console.log("decoded message: ", msg);
})

let pings = 0
setInterval(() => {
const myId = window.webxdc.selfAddr;
const data = new TextEncoder().encode(`[${pings}] hello from ${myId}`);
pings += 1
console.log("Sending message", data);
window.webxdc.sendRealtimeData(data);
}, 1000)
```
```
hpk42 marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 37 additions & 0 deletions src-docs/spec/setRealtimeListener.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# setRealtimeListener

```js
window.webxdc.setRealtimeListener((data) => {});
```

**Note that this API is experimental and not fully settled (April 2024)**

Announce participation in realtime data sending and receiving to other peers.

The `setRealtimeListener` callback receives `Uint8Array` data items
that were sent from connected peers by [`sendRealtimeData(data)`](./sendRealtimeData.md).

Calling `setRealtimeListener` with a `null` value
will disconnect from receiving and sending realtime data.
You may afterwards call `setRealtimeListener` again with a callback
to re-establish participation in realtime sending and receiving.

## Example

```js
let timeout
window.webxdc.setRealtimeListener((data) => {
console.log("Received realtime data: ", data);
const msg = new TextDecoder().decode(data);
console.log("decoded message: ", msg);
})

let pings = 0
setInterval(() => {
const myId = window.webxdc.selfAddr;
const data = new TextEncoder().encode(`[${pings}] hello from ${myId}`);
pings += 1
console.log("Sending message", data);
window.webxdc.sendRealtimeData(data);
}, 1000)
```