Add bridge debug info to options page

This commit is contained in:
hensm
2018-12-11 23:11:01 +00:00
parent c555d72a0d
commit c24c6412c0
6 changed files with 281 additions and 130 deletions

View File

@@ -97,11 +97,11 @@ async function handleMessage (message) {
switch (message.subject) { switch (message.subject) {
case "bridge:initialize": { case "bridge:getInfo": {
const extensionVersion = message.data; const extensionVersion = message.data;
return { return {
subject: "main:bridgeInitialized" subject: "main:bridgeInfo"
, data: __applicationVersion , data: __applicationVersion
}; };
}; };

View File

@@ -18,6 +18,34 @@
} }
, "optionsBridgeCategoryName": {
"message": "Bridge"
}
, "optionsBridgeCategoryDescription": {
"message": "Native bridge application."
}
, "optionsBridgeLoading": {
"message": "Loading bridge info..."
}
, "optionsBridgeMissing": {
"message": "Bridge application not found. Try downloading and installing the latest version."
}
, "optionsBridgeInfo": {
"message": "Bridge info:"
}
, "optionsBridgeStatusCompatible": {
"message": "STATUS: COMPATIBLE"
}
, "optionsBridgeStatusIncompatible": {
"message": "STATUS: INCOMPATIBLE"
}
, "optionsBridgeOlder": {
"message": "Bridge version older than expected, try updating bridge to the latest bridge version."
}
, "optionsBridgeNewer": {
"message": "Bridge version newer than expected, try updating extension to the latest bridge version."
}
, "optionsMediaCategoryName": { , "optionsMediaCategoryName": {
"message": "Media casting" "message": "Media casting"
} }

View File

@@ -0,0 +1,37 @@
import semver from "semver";
export default async function getBridgeInfo () {
let applicationVersion;
try {
const response = await browser.runtime.sendNativeMessage(
APPLICATION_NAME
, { subject: "bridge:getInfo"
, data: EXTENSION_VERSION });
applicationVersion = response.data;
} catch (err) {
return null;
}
/**
* Compare installed bridge version to the version the
* extension was built alongside and is known to be
* compatible with.
*
* TODO: Determine compatibility with semver and enforce/notify
* user.
*/
if (applicationVersion !== APPLICATION_VERSION) {
console.error(`Expecting ${APPLICATION_NAME} v${APPLICATION_VERSION}, found v${applicationVersion}.`
, semver.lt(applicationVersion, APPLICATION_VERSION)
? "Try updating the native app to the latest version."
: "Try updating the extension to the latest version");
}
return {
version: applicationVersion
, isVersionCompatible: applicationVersion === APPLICATION_VERSION
, isVersionOlder: semver.lt(applicationVersion, APPLICATION_VERSION)
, isVersionNewer: semver.gt(applicationVersion, APPLICATION_VERSION)
};
}

View File

@@ -340,12 +340,6 @@ function initBridge (tabId, frameId) {
bridgeMap.set(tabId, port); bridgeMap.set(tabId, port);
} }
// Start version handoff
port.postMessage({
subject: "bridge:initialize"
, data: EXTENSION_VERSION
});
port.onDisconnect.addListener(p => { port.onDisconnect.addListener(p => {
if (p.error) { if (p.error) {
console.error(`${APPLICATION_NAME} disconnected:`, p.error.message); console.error(`${APPLICATION_NAME} disconnected:`, p.error.message);
@@ -431,27 +425,6 @@ messageRouter.register("main", async (message, sender) => {
break; break;
}; };
case "main:bridgeInitialized": {
const applicationVersion = message.data;
/**
* Compare installed bridge version to the version the
* extension was built alongside and is known to be
* compatible with.
*
* TODO: Determine compatibility with semver and enforce/notify
* user.
*/
if (applicationVersion !== APPLICATION_VERSION) {
console.error(`Expecting ${APPLICATION_NAME} v${APPLICATION_VERSION}, found v${applicationVersion}.`
, semver.lt(applicationVersion, APPLICATION_VERSION)
? "Try updating the native app to the latest version."
: "Try updating the extension to the latest version");
}
break;
};
case "main:openPopup": { case "main:openPopup": {
await openPopup(tabId); await openPopup(tabId);
break; break;

View File

@@ -6,6 +6,8 @@ import ReactDOM from "react-dom";
import defaultOptions from "./defaultOptions"; import defaultOptions from "./defaultOptions";
import EditableList from "./EditableList"; import EditableList from "./EditableList";
import getBridgeInfo from "../lib/getBridgeInfo";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -41,6 +43,8 @@ class App extends Component {
this.state = { this.state = {
options: props.options options: props.options
, bridgeInfo: null
, bridgeLoading: true
, isFormValid: true , isFormValid: true
}; };
@@ -54,6 +58,14 @@ class App extends Component {
= this.getWhitelistItemPatternError.bind(this); = this.getWhitelistItemPatternError.bind(this);
} }
async componentDidMount () {
const bridgeInfo = await getBridgeInfo();
this.setState({
bridgeInfo
, bridgeLoading: false
});
}
/** /**
* Set stored option values to current state * Set stored option values to current state
*/ */
@@ -126,140 +138,199 @@ class App extends Component {
return _("optionsUserAgentWhitelistInvalidMatchPattern", info); return _("optionsUserAgentWhitelistInvalidMatchPattern", info);
} }
async updateBridgeInfo () {
this.setState({
bridgeLoading: true
});
const bridgeInfo = await getBridgeInfo();
this.setState({
bridgeInfo
, bridgeLoading: false
});
}
render () { render () {
return ( return (
<form id="form" ref={ form => { this.form = form; }} <div>
onSubmit={ this.handleFormSubmit } <form id="form" ref={ form => { this.form = form; }}
onChange={ this.handleFormChange }> onSubmit={ this.handleFormSubmit }
<fieldset className="category"> onChange={ this.handleFormChange }>
<legend className="category__name">
{ _("optionsMediaCategoryName") }
</legend>
<p className="category__description">
{ _("optionsMediaCategoryDescription") }
</p>
<label className="option option--inline"> <fieldset className="category">
<input name="mediaEnabled"
type="checkbox"
checked={ this.state.options.mediaEnabled }
onChange={ this.handleInputChange } />
<div className="option__label">
{ _("optionsMediaEnabled") }
</div>
</label>
<fieldset className="category"
disabled={ !this.state.options.mediaEnabled }>
<legend className="category__name"> <legend className="category__name">
{ _("optionsLocalMediaCategoryName") } { _("optionsMediaCategoryName") }
</legend> </legend>
<p className="category__description"> <p className="category__description">
{ _("optionsLocalMediaCategoryDescription") } { _("optionsMediaCategoryDescription") }
</p> </p>
<label className="option option--inline"> <label className="option option--inline">
<input name="localMediaEnabled" <input name="mediaEnabled"
type="checkbox" type="checkbox"
checked={ this.state.options.localMediaEnabled } checked={ this.state.options.mediaEnabled }
onChange={ this.handleInputChange } /> onChange={ this.handleInputChange } />
<div className="option__label"> <div className="option__label">
{ _("optionsLocalMediaEnabled") } { _("optionsMediaEnabled") }
</div>
</label>
<fieldset className="category"
disabled={ !this.state.options.mediaEnabled }>
<legend className="category__name">
{ _("optionsLocalMediaCategoryName") }
</legend>
<p className="category__description">
{ _("optionsLocalMediaCategoryDescription") }
</p>
<label className="option option--inline">
<input name="localMediaEnabled"
type="checkbox"
checked={ this.state.options.localMediaEnabled }
onChange={ this.handleInputChange } />
<div className="option__label">
{ _("optionsLocalMediaEnabled") }
</div>
</label>
<label className="option">
<div className="option__label">
{ _("optionsLocalMediaServerPort") }
</div>
<input name="localMediaServerPort"
type="number"
required
min="1025"
max="65535"
value={ this.state.options.localMediaServerPort }
onChange={ this.handleInputChange } />
</label>
</fieldset>
</fieldset>
<fieldset className="category">
<legend className="category__name">
{ _("optionsMirroringCategoryName") }
</legend>
<p className="category__description">
{ _("optionsMirroringCategoryDescription") }
</p>
<label className="option option--inline">
<input name="mirroringEnabled"
type="checkbox"
checked={ this.state.options.mirroringEnabled }
onChange={ this.handleInputChange } />
<div className="option__label">
{ _("optionsMirroringEnabled") }
</div> </div>
</label> </label>
<label className="option"> <label className="option">
<div className="option__label"> <div className="option__label">
{ _("optionsLocalMediaServerPort") } { _("optionsMirroringAppId") }
</div> </div>
<input name="localMediaServerPort" <input name="mirroringAppId"
type="number" type="text"
required required
min="1025" value={ this.state.options.mirroringAppId }
max="65535"
value={ this.state.options.localMediaServerPort }
onChange={ this.handleInputChange } /> onChange={ this.handleInputChange } />
</label> </label>
</fieldset> </fieldset>
</fieldset>
<fieldset className="category"> <fieldset className="category">
<legend className="category__name"> <legend className="category__name">
{ _("optionsMirroringCategoryName") } { _("optionsUserAgentWhitelistCategoryName") }
</legend> </legend>
<p className="category__description"> <p className="category__description">
{ _("optionsMirroringCategoryDescription") } { _("optionsUserAgentWhitelistCategoryDescription") }
</p> </p>
<label className="option option--inline"> <label className="option option--inline">
<input name="mirroringEnabled" <input name="userAgentWhitelistEnabled"
type="checkbox" type="checkbox"
checked={ this.state.options.mirroringEnabled } checked={ this.state.options.userAgentWhitelistEnabled }
onChange={ this.handleInputChange } /> onChange={ this.handleInputChange } />
<div className="option__label"> <div className="option__label">
{ _("optionsMirroringEnabled") } { _("optionsUserAgentWhitelistEnabled") }
</div>
</label>
<div className="option">
<div className="option__label">
{ _("optionsUserAgentWhitelistContent") }
</div>
<EditableList data={ this.state.options.userAgentWhitelist }
onChange={ this.handleWhitelistChange }
itemPattern={ MATCH_PATTERN_REGEX }
itemPatternError={ this.getWhitelistItemPatternError }/>
</div> </div>
</label> </fieldset>
<label className="option"> <div id="buttons">
<div className="option__label"> <button onClick={ this.handleReset }>
{ _("optionsMirroringAppId") } { _("optionsReset") }
</div> </button>
<input name="mirroringAppId" <button type="submit"
type="text" default
required disabled={ !this.state.isFormValid }>
value={ this.state.options.mirroringAppId } { _("optionsSubmit") }
onChange={ this.handleInputChange } /> </button>
</label>
</fieldset>
<fieldset className="category">
<legend className="category__name">
{ _("optionsUserAgentWhitelistCategoryName") }
</legend>
<p className="category__description">
{ _("optionsUserAgentWhitelistCategoryDescription") }
</p>
<label className="option option--inline">
<input name="userAgentWhitelistEnabled"
type="checkbox"
checked={ this.state.options.userAgentWhitelistEnabled }
onChange={ this.handleInputChange } />
<div className="option__label">
{ _("optionsUserAgentWhitelistEnabled") }
</div>
</label>
<div className="option">
<div className="option__label">
{ _("optionsUserAgentWhitelistContent") }
</div>
<EditableList data={ this.state.options.userAgentWhitelist }
onChange={ this.handleWhitelistChange }
itemPattern={ MATCH_PATTERN_REGEX }
itemPatternError={ this.getWhitelistItemPatternError }/>
</div> </div>
</fieldset> </form>
<div id="buttons"> <div className="bridge">
<button onClick={ this.handleReset }> { do {
{ _("optionsReset") } if (this.state.bridgeLoading) {
</button> <div className="bridge__loading">
<button type="submit" { _("optionsBridgeLoading") }
default <progress></progress>
disabled={ !this.state.isFormValid }> </div>
{ _("optionsSubmit") } } else if (this.state.bridgeInfo) {
</button> const bridgeInfo = this.state.bridgeInfo;
let debugInfo =
`${bridgeInfo.isVersionCompatible
? _("optionsBridgeStatusCompatible")
: _("optionsBridgeStatusIncompatible")}\n\n`
+ `${APPLICATION_NAME} v${bridgeInfo.version}\n`
+ `Expected: ${APPLICATION_VERSION}\n`
+ `Found: ${bridgeInfo.version}\n`;
if (bridgeInfo.isVersionOlder) {
debugInfo += `\n${_("optionsBridgeOlder")}`
}
if (bridgeInfo.isVersionNewer) {
debugInfo += `\n${_("optionsBridgeNewer")}`
}
<div>
{ _("optionsBridgeInfo") }
<div className="bridge__found">
<textarea className="bridge__info">{ debugInfo }</textarea>
</div>
</div>
} else {
<div>
{ _("optionsBridgeInfo") }
<div className="bridge__missing">
{ _("optionsBridgeMissing") }
</div>
</div>
}
}}
</div> </div>
</form> </div>
); );
} }
} }
browser.storage.sync.get("options").then(({options}) => { (async () => {
const { options } = await browser.storage.sync.get("options");
ReactDOM.render( ReactDOM.render(
<App options={options} /> <App options={options} />
, document.querySelector("#root")); , document.querySelector("#root"));
}); })()

View File

@@ -17,6 +17,48 @@
} }
.bridge {
margin-top: 20px;
}
.bridge,
.bridge__loading {
display: flex;
flex-direction: column;
}
.bridge__loading {
align-items: center;
align-self: center;
font-size: 1.25em;
font-weight: 300;
width: 30%;
}
.bridge__loading progress {
margin-top: 5px;
width: 100%;
}
.bridge__missing {
background-color: #d70022;
border-radius: 5px;
color: white;
padding: 5px;
margin-top: 5px;
}
.bridge__found {
margin-top: 5px;
}
.bridge__info {
height: 150px;
margin-inline-start: 5px;
width: 80%;
}
.category { .category {
border: initial; border: initial;
display: grid; display: grid;
@@ -24,7 +66,7 @@
grid-column-gap: 20px; grid-column-gap: 20px;
grid-row-gap: 5px; grid-row-gap: 5px;
margin: initial; margin: initial;
padding: 15px 0; padding: 10px 0;
} }
.category:disabled { .category:disabled {