Skip to content
This repository has been archived by the owner on Aug 25, 2021. It is now read-only.

Commit

Permalink
Jason WebSocket reconnection (#75, #47)
Browse files Browse the repository at this point in the history
- impl RpcClient reconnection
- add ServerMsg::RpcSettings and its sending/processing
- add Room.on_connection_loss callback which fires when RpcClient loses connection
- add ReconnectHandle for JS side to start reconnection
- impl reconnection with backoff and simple reconnection with a constant retry delay
- add connection loss notification with button for manual reconnection to 'e2e-demo' app
- add WebSocket connection state indicator to 'demo' app
- remove RpcTransport::on_close and use RpcTransport::on_state_change instead

Additionally:
- rewrite 'satisfies_by_device_id!' and 'console_error!' macros as functions
- remove 'weak_map!' macro and add 'upgrade_or_detached!' instead
- add 'new_js_error!' macro for creating JasonError with 'tracerr' information for JS side
- reverse ping/pong mechanism: server sends Ping and expects Pong from client
- add 'rpc.ping_interval' configuration option for Medea
- use 'fakerator' instead of 'faker' for generating random male usernames in demos
  • Loading branch information
evdokimovs authored Jan 16, 2020
1 parent 75a4718 commit 73673fb
Show file tree
Hide file tree
Showing 43 changed files with 2,587 additions and 1,054 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ All user visible changes to this project will be documented in this file. This p

- Configuration:
- Rename `[server]` section of Client API HTTP server as `[server.client.http]` ([#33]).
- RPC messaging:
- Reverse `Ping`/`Pong` naming: server sends `Ping` and expects `Pongs` from client now. ([#75]).

### Added

Expand All @@ -31,16 +33,19 @@ All user visible changes to this project will be documented in this file. This p
- Dynamic `Peer`s creation when client connects ([#28]);
- Auto-removing `Peer`s when `Member` disconnects ([#28]);
- Filter `SetIceCandidate` messages without `candidate` ([#50](/../../pull/50));
- Send reason of closing WebSocket connection as [Close](https://tools.ietf.org/html/rfc4566#section-5.14) frame's description ([#58](/../../pull/58)).
- Send reason of closing WebSocket connection as [Close](https://tools.ietf.org/html/rfc4566#section-5.14) frame's description ([#58](/../../pull/58));
- Send `Event::RpcSettingsUpdated` when `Member` connects ([#75]);
- Configuration:
- `[server.control.grpc]` section to configure Control API gRPC server ([#33]);
- `server.client.http.public_url` option to configure public URL of Client API HTTP server ([#33]).
- `server.client.http.public_url` option to configure public URL of Client API HTTP server ([#33]);
- `rpc.ping_interval` option to configure `Ping`s sending interval ([#75]).
- Testing:
- E2E tests for signalling ([#28]).

[#28]: /../../pull/28
[#33]: /../../pull/33
[#63]: /../../pull/63
[#75]: /../../pull/75



Expand Down
293 changes: 147 additions & 146 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
# Default:
# reconnect_timeout = "10s"

# Interval between pings that server sends to clients.
#
# Env var: MEDEA_RPC__PING_INTERVAL
# Default:
# ping_interval = "3s"




Expand Down
9 changes: 8 additions & 1 deletion jason/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ All user visible changes to this project will be documented in this file. This p
- Remove error argument from `on_local_stream` callback ([#54]);
- Room initialization ([#46]):
- Remove `Jason.join_room()`.
- Transport and messaging:
- Reverse `ping`/`pong` mechanism: expect `Ping`s from server and answer with `Pong`s ([#75]).

### Added

Expand All @@ -40,9 +42,13 @@ All user visible changes to this project will be documented in this file. This p
- `Room.join()`;
- Ability to inject local video/audio stream into `Room` via `Room.inject_local_stream()` ([#54]);
- `Room.on_failed_local_stream` callback ([#54]);
- Room management:
- Library API:
- `Room.on_connection_loss` callback that JS side can start Jason reconnection on connection loss with ([#75]);
- `Room.on_close` callback for WebSocket close initiated by server ([#55]).
- RPC messaging:
- Cleanup Jason state on normal (`code = 1000`) WebSocket close ([#55]).
- Cleanup Jason state on normal (`code = 1000`) WebSocket close ([#55]);
- `RpcClient` and `RpcTransport` reconnection ([#75]).
- Signalling:
- Emitting of RPC commands:
- `AddPeerConnectionMetrics` with `IceConnectionState` ([#71](/../../pull/71)).
Expand All @@ -58,6 +64,7 @@ All user visible changes to this project will be documented in this file. This p
[#46]: /../../pull/46
[#54]: /../../pull/54
[#55]: /../../pull/55
[#75]: /../../pull/75



Expand Down
31 changes: 27 additions & 4 deletions jason/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<title>Chat</title>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fakerator@0.3.0/dist/fakerator.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

Expand Down Expand Up @@ -156,7 +156,7 @@

let joinCallerButton = document.getElementById('join__join');
let usernameInput = document.getElementById('join__username');
usernameInput.value = faker.name.firstName();
usernameInput.value = Fakerator().names.firstNameM();

let room = await jason.init_room();

Expand Down Expand Up @@ -206,7 +206,23 @@
});
});

let connectionState = document.getElementById('connection-state__state');
room.on_connection_loss(async (reconnectHandle) => {
connectionState.className = 'badge badge-warning';
connectionState.textContent = 'Reconnecting';

try {
await reconnectHandle.reconnect_with_backoff(500, 2.0, 10000);
connectionState.className = 'badge badge-success';
connectionState.textContent = 'Connected';
} catch (e) {
console.error(e);
}
});

room.on_close(function (on_closed) {
connectionState.className = 'badge badge-danger';
connectionState.textContent = "Closed";
alert(
`Call was ended.
Reason: ${on_closed.reason()};
Expand Down Expand Up @@ -263,6 +279,8 @@
await axios.delete(controlUrl + roomId + '/' + username);
console.log("Creating new member.");
await room.join(await addNewMember(roomId, username));
connectionState.className = 'badge badge-success';
connectionState.textContent = 'Connected';

} catch (e) {
logError("Join to room failed", e);
Expand Down Expand Up @@ -362,8 +380,13 @@
</div>

<div id="control" class="input-group mb-3" style="display: none">
<button class="btn btn-info" id="control__mute_audio">Mute audio</button>
<button class="btn btn-info" id="control__mute_video">Mute video</button>
<button class="btn btn-info" id="control__mute_audio">Mute audio</button>
<button class="btn btn-info" id="control__mute_video">Mute video</button>
</div>

<div>
<span>Connection state: </span>
<span id="connection-state__state" class="badge badge-danger">Closed</span>
</div>

</div>
Expand Down
28 changes: 28 additions & 0 deletions jason/e2e-demo/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,34 @@ window.onload = async function() {
console.error(error);
});

room.on_connection_loss( async (reconnectHandle) => {
let connectionLossNotification = document.getElementsByClassName('connection-loss-notification')[0];
contentVisibility.show(connectionLossNotification);

let manualReconnectBtn = document.getElementsByClassName('connection-loss-notification__manual-reconnect')[0];
let connectionLossMsg = document.getElementsByClassName('connection-loss-notification__msg')[0];
let connectionLossDefaultText = connectionLossMsg.textContent;

manualReconnectBtn.onclick = async () => {
try {
connectionLossMsg.textContent = 'Trying to manually reconnect...';
await reconnectHandle.reconnect_with_delay(0);
contentVisibility.hide(connectionLossNotification);
console.error("Reconnected!");
} catch (e) {
console.error("Failed to manually reconnect: " + e.message());
} finally {
connectionLossMsg.textContent = connectionLossDefaultText;
}
};
try {
await reconnectHandle.reconnect_with_backoff(3000, 2.0, 10000);
} catch (e) {
console.error('Error in reconnection with backoff:\n' + e.message());
}
contentVisibility.hide(connectionLossNotification);
});

room.on_close(function (on_closed) {
let videos = document.getElementsByClassName('remote-videos')[0];
while (videos.firstChild) {
Expand Down
28 changes: 28 additions & 0 deletions jason/e2e-demo/video-call.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,28 @@
.control-debug__table-result>table>tr>th {
font-weight: normal;
}

.connection-loss-notification {
right: 50px;
top: 50px;
position: absolute;
background: #fff;
padding: 10px;
border-radius: 3px;
font-size: 11pt;
}

.connection-loss-notification__msg {
display: block;
}

.connection-loss-notification__manual-reconnect {
display: block;
color: #49668C;
text-align: center;
cursor: pointer;
margin-top: 5px;
}
</style>
</head>
<body>
Expand Down Expand Up @@ -343,6 +365,12 @@
</div>
</div>

<div class="connection-loss-notification toggle-content">
<span class="connection-loss-notification__msg">Connection lost.</span>
<span>Trying to reconnect with backoff...</span>
<a class="connection-loss-notification__manual-reconnect">Reconnect now</a>
</div>

<div class="control-debug-menu__toggle">
Dynamic Control API
</div>
Expand Down
11 changes: 6 additions & 5 deletions jason/src/api/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use std::{

use wasm_bindgen::prelude::*;

use crate::{peer::MediaStreamHandle, utils::Callback};
use crate::{
peer::MediaStreamHandle,
utils::{Callback, HandlerDetachedError},
};

/// Actual data of a connection with a specific remote [`Member`].
///
Expand All @@ -31,10 +34,8 @@ impl ConnectionHandle {
&mut self,
f: js_sys::Function,
) -> Result<(), JsValue> {
map_weak!(self, |inner| inner
.borrow_mut()
.on_remote_stream
.set_func(f))
upgrade_or_detached!(self.0)
.map(|inner| inner.borrow_mut().on_remote_stream.set_func(f))
}
}

Expand Down
18 changes: 14 additions & 4 deletions jason/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use wasm_bindgen_futures::spawn_local;
use crate::{
media::{MediaManager, MediaManagerHandle},
peer,
rpc::{ClientDisconnect, RpcClient as _, WebSocketRpcClient},
rpc::{
ClientDisconnect, RpcClient as _, RpcTransport, WebSocketRpcClient,
WebSocketRpcTransport,
},
set_panic_hook,
};

Expand Down Expand Up @@ -44,13 +47,20 @@ impl Jason {

/// Returns [`RoomHandle`] for [`Room`].
pub fn init_room(&self) -> RoomHandle {
let rpc = Rc::new(WebSocketRpcClient::new(3000));
let rpc = WebSocketRpcClient::new(Box::new(|token| {
Box::pin(async move {
let ws = WebSocketRpcTransport::new(&token)
.await
.map_err(|e| tracerr::new!(e))?;
Ok(Rc::new(ws) as Rc<dyn RpcTransport>)
})
}));
let peer_repository = Box::new(peer::Repository::new(Rc::clone(
&self.0.borrow().media_manager,
)));

let inner = self.0.clone();
spawn_local(rpc.on_close().map(move |res| {
spawn_local(rpc.on_normal_close().map(move |res| {
// TODO: Don't close all rooms when multiple rpc connections
// will be supported.
let reason = res.unwrap_or_else(|_| {
Expand All @@ -64,7 +74,7 @@ impl Jason {
inner.borrow_mut().media_manager = Rc::default();
}));

let room = Room::new(rpc, peer_repository);
let room = Room::new(Rc::new(rpc), peer_repository);
let handle = room.new_handle();
self.0.borrow_mut().rooms.push(room);
handle
Expand Down
Loading

0 comments on commit 73673fb

Please sign in to comment.