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

Improved Hubs connectivity diagnostics #4133

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0c54d9c
Merge pull request #2525 from mozilla/master
gfodor May 26, 2020
3c583cd
Merge pull request #2568 from mozilla/feature/hubs-cloud-06122020
gfodor Jun 12, 2020
590fdc3
Merge branch 'hubs-cloud' of https://github.com/mozilla/hubs into hub…
robertlong Jun 18, 2020
fdf7ed1
Merge pull request #2700 from mozilla/hubs-cloud-deploy
johnshaughnessy Jul 23, 2020
476d7fd
Merge pull request #2990 from mozilla/master
johnshaughnessy Sep 9, 2020
2d653b3
Merge pull request #3165 from mozilla/hubs-cloud-checkpoint
johnshaughnessy Oct 14, 2020
0220834
Mark the public_api_access flag as internal
johnshaughnessy Jan 12, 2021
ab65165
Merge pull request #3697 from mozilla/qa-stage-cherry-public-api-inte…
johnshaughnessy Jan 12, 2021
15a187b
updated jenkins workflow for qa builds
brianpeiris Jan 14, 2021
699ca05
Merge branch 'qa-test' into qa-stage
brianpeiris Jan 14, 2021
22bb1bb
Merge pull request #3704 from mozilla/admin-ui-update-banner
robinkwilson Jan 14, 2021
35f133a
Merge pull request #3674 from mozilla/qa-stage
Softvision-GeluHaiduc Jan 15, 2021
e30caed
Merge pull request #3851 from mozilla/qa-test
brianpeiris Feb 3, 2021
5408572
Merge pull request #3988 from mozilla/master
johnshaughnessy Mar 12, 2021
feda579
Merge pull request #4013 from mozilla/qa-stage
Softvision-GeluHaiduc Mar 15, 2021
47fcdec
Merge pull request #4124 from mozilla/master
johnshaughnessy Apr 7, 2021
c85c9db
Merge pull request #4125 from mozilla/qa-stage
Softvision-RemusDranca Apr 8, 2021
e817639
Initial connection debugging
rawnsley Apr 9, 2021
7df4534
Make redirect optional when opening new Hubs rooms (used by Connectio…
rawnsley Apr 9, 2021
c5b6e89
Added test for opening microphone
rawnsley Apr 9, 2021
fba9386
Tidied up stray debug code
rawnsley Apr 9, 2021
4a21d35
Minor change to code and label
rawnsley Apr 9, 2021
57fe889
Example usage of the ConnectionTest component. Not in the right place…
rawnsley Apr 9, 2021
a470173
Fixed missing client_id (didn't actually influence the test result)
rawnsley Apr 13, 2021
8b55094
Merge pull request #4158 from mozilla/qa-test
johnshaughnessy Apr 20, 2021
0421b8b
Merge branch 'master' of https://github.com/mozilla/hubs into test-co…
rawnsley Apr 20, 2021
f62b9d5
Merge branch 'hubs-cloud' of https://github.com/mozilla/hubs into tes…
rawnsley Apr 20, 2021
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
34 changes: 34 additions & 0 deletions src/assets/stylesheets/connection-test.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
:local(.results-container) {
width: 100%;
}

:local(.results-table) {
width: 100%;
margin-bottom: 20px;
}

:local(.results-table) tr {
border-bottom: solid #DDD 1px;
}

:local(.results-table) td, :local(.results-table) th {
text-align: left;
vertical-align: top;
padding: 5px 20px 5px 0px;
}

:local(.results-table) th {
white-space: nowrap;
}

:local(.test-name) {
white-space: nowrap;
}

:local(.numeric) {
text-align: right !important;
}

:local(.action-button) {
margin: 0 auto;
}
336 changes: 336 additions & 0 deletions src/react-components/debug-panel/ConnectionTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
import React from "react";
import { Button } from "../input/Button";
import {
getReticulumMeta,
connectToReticulum,
createAndRedirectToNewHub,
} from "../../utils/phoenix-utils";
import HubChannel from "../../utils/hub-channel";
import "aframe";
import "networked-aframe";
import { DialogAdapter } from "../../naf-dialog-adapter";

import { App } from "../../App";
window.APP = new App();
const store = window.APP.store;

import styles from "../../assets/stylesheets/connection-test.scss";

class TestState {
constructor(name) {
this.name = name;
this.state = "";
this.notes = "";
}

start() {
this.state = "Running";
this._startMs = performance.now();
}

stop(success) {
this.state = success ? "OK" : "Error";
this._endMs = performance.now();
}

durationMs() {
if(this._startMs && this._endMs) {
return this._endMs - this._startMs;
}
return null;
}
}

export class ConnectionTest extends React.Component {

constructor(props) {
super(props);
this.state = {
isStarted: false,
copyButtonLabel: "Copy results to clipboard",
tests: {
metadataTest: new TestState("Fetch Metadata"),
roomTest: new TestState("Open Room"),
reticulumTest: new TestState("Connect to Reticulum"),
retChannelTest: new TestState("Open RET Channel"),
hubChannelTest: new TestState("Open HUB Channel"),
joinTest: new TestState("Join Room"),
enterTest: new TestState("Enter Room"),
micTest: new TestState("Open Microphone"),
},
hubId: null,
};

// Uncomment to test with existing room ID rather than creating a new one each time
//this._roomId = "3z2zcE3";
}

dowloadMetadata = async () => {
console.info("Called dowloadMetadata");
const test = this.state.tests.metadataTest;
test.start();
this.setState(state => state.tests.metadataTest = test);
getReticulumMeta().then(reticulumMeta => {
test.stop(true);
test.notes = `Reticulum @ ${reticulumMeta.phx_host}: v${reticulumMeta.version} on ${reticulumMeta.pool}`;
this.openNewRoom();
}).catch(error => {
test.stop(false);
test.notes = error.toString();
}).finally(() => {
this.setState(state => state.tests.metadataTest = test);
});
this.setState({ isStarted: true });
};

openNewRoom = async () => {
console.info("Called openNewRoom");
const test = this.state.tests.roomTest;
if(this._roomId) {
test.state = "Skipped";
test.notes = `Room ID ${this._roomId}`;
this.connectToReticulum();
} else {
test.start();
this.setState(state => state.tests.roomTest = test);
try {
const hub = await createAndRedirectToNewHub(null, null, false, false);
test.stop(true);
test.notes = `Room ID ${hub.hub_id}`;
this._roomId = hub.hub_id;
this.connectToReticulum();
} catch (error) {
test.stop(false);
test.notes = error.toString();
} finally {
this.setState(state => state.tests.roomTest = test);
}
}
};

connectToReticulum = async () => {
console.info("Called connectToReticulum");
const test = this.state.tests.reticulumTest;
test.start();
const socket = await connectToReticulum(true);
test.notes = socket.endPointURL();
this.setState(state => state.tests.reticulumTest = test);
socket.onOpen(() => {
test.stop(true);
this._socket = socket;
this.setState(state => state.tests.reticulumTest = test);
this.openRetChannel();
});
socket.onError(error => {
test.stop(false);
this.setState(state => state.tests.reticulumTest = test);
});
}

openRetChannel = async () => {
console.info("Called openRetChannel");
const test = this.state.tests.retChannelTest;
test.start();
this.setState(state => state.tests.retChannelTest = test);
const retPhxChannel = this._socket.channel(`ret`, { hub_id: this._roomId });
retPhxChannel
.join()
.receive("ok", async data => {
test.stop(true);
console.info(data);
this._clientId = data.session_id;
test.notes = `Session ID ${data.session_id}`;
this.setState(state => state.tests.retChannelTest = test);
this.openHubChannel();
})
.receive("error", error => {
test.stop(false);
test.notes = error.reason;
this.setState(state => state.tests.retChannelTest = test);
});
}

openHubChannel = async () => {
console.info("Called openHubChannel");
const test = this.state.tests.hubChannelTest;
test.start();
this.setState(state => state.tests.hubChannelTest = test);
const params = {
profile: {
displayName: "CONNECTION_TEST",
},
context: {
mobile: false,
embed: false
},
push_subscription_endpoint: null,
perms_token: null,
hub_invite_id: null,
};
const hubPhxChannel = this._socket.channel(`hub:${this._roomId}`, params);
const hubChannel = new HubChannel(store, this._roomId);
this._hubChannel = hubChannel
window.APP.hubChannel = hubChannel;
hubChannel.setPhoenixChannel(hubPhxChannel);
hubPhxChannel
.join()
.receive("ok", async data => {
test.stop(true);
console.info(data);
this.setState(state => state.tests.hubChannelTest = test);
this._hub = data.hubs[0];
this._perms_token = data.perms_token;
this.joinRoom();
})
.receive("error", error => {
console.error(error);
test.stop(false);
test.notes = error.reason;
this.setState(state => state.tests.hubChannelTest = test);
});
}

joinRoom = async () => {
console.info("Called joinRoom");
const connectionUrl = `wss://${this._hub.host}:${this._hub.port}`;
const test = this.state.tests.joinTest;
test.notes = connectionUrl;
test.start();
this.setState(state => state.tests.joinTest = test);

try {
// This mimics what happens in NAF::networked-scene::setupNetworkAdapter
var adapter = NAF.adapters.make("dialog");
this._adapter = adapter;
NAF.connection.setNetworkAdapter(adapter);

// Normally set in hubs.js in response to the adapter-ready event
adapter.setClientId(this._clientId);
adapter.setRoom(this._roomId);
adapter.setJoinToken(this._perms_token);

// Normally hubs.js links these up to the phoenix hub channel
adapter.reliableTransport = (clientId, dataType, data) => {
console.info(`adapter.reliableTransport: ${clientId} ${dataType} ${data}}`);
};
adapter.unreliableTransport = (clientId, dataType, data) => {
console.info(`adapter.unreliableTransport: ${clientId} ${dataType} ${data}}`);
};

// Calls NAF::NetworkConnection::connect, which in turn calls DialogAdapter::connect
NAF.connection.connect(connectionUrl, "default", this._roomId, true).then(async () => {
test.stop(true);
this.setState(state => state.tests.joinTest = test);
this.enterRoom();
}).catch(error => {
console.error(error);
test.stop(false);
test.notes = error.reason;
this.setState(state => state.tests.joinTest = test);
});
} catch (error) {
console.error(error);
test.stop(false);
test.notes = error.toString();
this.setState(state => state.tests.joinTest = test);
}
}

enterRoom = async () => {
console.info("Called enterRoom");
const test = this.state.tests.enterTest;
test.start();
this.setState(state => state.tests.enterTest = test);

try {
this._hubChannel.sendEnteredEvent();
test.stop(true);
this.openMic()
} catch (error) {
console.error(error);
test.stop(false);
test.notes = error.toString();
} finally {
this.setState(state => state.tests.enterTest = test);
}

}

openMic = async () => {
console.info("Called openMic");
const test = this.state.tests.micTest;
test.start();
this.setState(state => state.tests.micTest = test);

try {
// Normally handled by the hubs media-devices-manager
const newStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
this._adapter.setLocalMediaStream(newStream);
this._adapter.enableMicrophone(true);
newStream.getAudioTracks().forEach(track => {
test.notes = `Microphone: ${track.label}`;
});
test.stop(true);
} catch (error) {
console.error(error);
test.stop(false);
test.notes = error.toString();
} finally {
this.setState(state => state.tests.micTest = test);
}

// Comment out to keep connection open after test
this.disconnectAll();
}

disconnectAll = async () => {
console.info("Called disconnectAll");
this._socket.disconnect();
}

copyTable = () => {
const html = document.getElementById('resultsTable').outerHTML;
var data = [new ClipboardItem({ "text/html": new Blob([html], { type: "text/html" }) })];
navigator.clipboard.write(data).then(() => {
this.setState({copyButtonLabel: "Copied!"})
}, function(reason) {
alert("Clipboard error: " + reason);
});
}

render() {
if (this.state.isStarted) {
return (
<div className={styles.resultsContainer}>
<table id="resultsTable" className={styles.resultsTable}>
<thead>
<tr>
<th>Test</th>
<th>State</th>
<th>Latency (ms)</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{Object.values(this.state.tests).map(test => (
<tr key={test.name}>
<td className={styles.testName}>{test.name}</td>
<td>{test.state}</td>
<td className={styles.numeric}>{test.durationMs()?.toFixed(0)}</td>
<td>{test.notes}</td>
</tr>

))}
</tbody>
</table>
<Button preset="blue" className={styles.actionButton} onClick={this.copyTable}>{this.state.copyButtonLabel}</Button>
</div>

);
} else {
return (
<Button preset="blue" className={styles.actionButton} onClick={this.dowloadMetadata}>Start Connection Test</Button>
);
}
}
}
2 changes: 2 additions & 0 deletions src/react-components/home/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { scaledThumbnailUrlFor } from "../../utils/media-url-utils";
import { Column } from "../layout/Column";
import { Button } from "../input/Button";
import { Container } from "../layout/Container";
import { ConnectionTest } from "../../react-components/debug-panel/ConnectionTest";

export function HomePage() {
const auth = useContext(AuthContext);
Expand Down Expand Up @@ -165,6 +166,7 @@ export function HomePage() {
</Button>
</Column>
</Container>
<Container><ConnectionTest></ConnectionTest></Container>
</PageContainer>
);
}
Loading