Implement receiver selector whitelist suggestion banner

This commit is contained in:
hensm
2022-04-16 12:02:19 +01:00
committed by Matt Hensman
parent 124a5eb92d
commit 1da709eb5e
14 changed files with 751 additions and 415 deletions

View File

@@ -12,7 +12,7 @@ import EditableList from "./EditableList";
import bridge, { BridgeInfo, BridgeTimedOutError } from "../../lib/bridge";
import logger from "../../lib/logger";
import options, { Options } from "../../lib/options";
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/utils";
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/matchPattern";
const _ = browser.i18n.getMessage;

View File

@@ -2,14 +2,13 @@
--shadow-10: 0 1px 4px rgba(12, 12, 13, 0.1);
--shadow-20: 0 2px 8px rgba(12, 12, 13, 0.1);
--shadow-30: 0 4px 16px rgba(12, 12, 13, 0.1);
--focus-border-color: var(--blue-50);
--box-background: var(--white-100);
--box-color: var(--grey-90);
--focus-box-shadow:
0 0 0 1px var(--focus-border-color);
--focus-box-shadow: 0 0 0 1px var(--focus-border-color);
--button-background: var(--grey-90-a10);
--button-background-hover: var(--grey-90-a20);
@@ -26,12 +25,10 @@
--field-border-color: var(--grey-90-a20);
--field-border-color-hover: var(--grey-90-a30);
--field-box-shadow-warning:
0 0 0 1px var(--yellow-60)
, 0 0 0 4px var(--yellow-60-a30);
--field-box-shadow-error:
0 0 0 1px var(--red-60)
, 0 0 0 4px var(--red-60-a30);
--field-box-shadow-warning: 0 0 0 1px var(--yellow-60),
0 0 0 4px var(--yellow-60-a30);
--field-box-shadow-error: 0 0 0 1px var(--red-60),
0 0 0 4px var(--red-60-a30);
}
@media (prefers-color-scheme: dark) {
@@ -102,7 +99,9 @@ textarea:invalid {
}
button:disabled,
input:disabled {
input:disabled,
textarea:disabled,
select:disabled {
opacity: 0.35;
}
@@ -111,6 +110,7 @@ input,
textarea,
select {
padding: 4px 8px;
font: inherit;
}
/* No inset for spinbox control */
@@ -130,13 +130,14 @@ button:default:hover:active {
}
.select-wrapper {
--arrow-width: 20px;
--arrow-width: 16px;
position: relative;
display: inline-block;
}
.select-wrapper::after {
align-items: center;
content: "▼";
opacity: 0.5;
display: flex;
height: 100%;
margin-right: 4px;

View File

@@ -4,17 +4,19 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import knownApps from "../../cast/knownApps";
import knownApps, { KnownApp } from "../../cast/knownApps";
import options from "../../lib/options";
import messaging, { Message, Port } from "../../messaging";
import { getNextEllipsis } from "../../lib/utils";
import { RemoteMatchPattern } from "../../lib/matchPattern";
import { ReceiverDevice } from "../../types";
import {
ReceiverSelectionActionType,
ReceiverSelectorMediaType
} from "../../background/receiverSelector";
import { PageInfo } from "../../background/receiverSelector/ReceiverSelector";
const _ = browser.i18n.getMessage;
@@ -37,8 +39,14 @@ interface PopupAppState {
filePath?: string;
appId?: string;
pageInfo?: PageInfo;
mirroringEnabled: boolean;
userAgentWhitelistEnabled: boolean;
userAgentWhitelist: string[];
knownApp?: KnownApp;
isPageWhitelisted: boolean;
}
class PopupApp extends Component<PopupAppProps, PopupAppState> {
@@ -54,7 +62,10 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
mediaType: ReceiverSelectorMediaType.App,
availableMediaTypes: ReceiverSelectorMediaType.App,
isLoading: false,
mirroringEnabled: false
mirroringEnabled: false,
userAgentWhitelistEnabled: true,
userAgentWhitelist: [],
isPageWhitelisted: false
};
// Store window ref
@@ -66,6 +77,7 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
this.updateWindowHeight();
}).observe(document.body);
this.onAddToWhitelist = this.onAddToWhitelist.bind(this);
this.onSelectChange = this.onSelectChange.bind(this);
this.onCast = this.onCast.bind(this);
this.onStop = this.onStop.bind(this);
@@ -91,7 +103,8 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
switch (message.subject) {
case "popup:init": {
this.setState({
appId: message.data?.appId
appId: message.data?.appId,
pageInfo: message.data?.pageInfo
});
break;
@@ -114,6 +127,8 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
});
}
this.updateKnownApp();
break;
}
@@ -124,9 +139,15 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
}
});
const opts = await options.getAll();
this.setState({
mirroringEnabled: await options.get("mirroringEnabled")
mirroringEnabled: opts.mirroringEnabled,
userAgentWhitelistEnabled: opts.userAgentWhitelistEnabled,
userAgentWhitelist: opts.userAgentWhitelist
});
this.updateKnownApp();
}
public componentDidUpdate() {
@@ -135,6 +156,58 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
}, 1);
}
private updateKnownApp() {
const isAppMediaTypeAvailable = !!(
this.state.availableMediaTypes & ReceiverSelectorMediaType.App
);
let knownApp: Nullable<KnownApp> = null;
/**
* Check knownApps for an app with an ID matching the registered
* app on the target page.
* Or if there isn't an registered app, check for an app with a
* match pattern matching the target page URL.
*/
if (isAppMediaTypeAvailable && this.state.appId) {
knownApp = knownApps[this.state.appId];
} else if (this.state.pageInfo) {
const pageUrl = this.state.pageInfo.url;
for (const [, app] of Object.entries(knownApps)) {
if (!app.matches) {
continue;
}
const pattern = new RemoteMatchPattern(app.matches);
if (pattern.matches(pageUrl)) {
knownApp = app;
break;
}
}
}
let isPageWhitelisted = false;
/**
* Check if target page URL is whitelisted.
*/
if (this.state.pageInfo) {
for (const patternString of this.state.userAgentWhitelist) {
const pattern = new RemoteMatchPattern(patternString);
if (pattern.matches(this.state.pageInfo.url)) {
isPageWhitelisted = true;
break;
}
}
}
this.setState({
knownApp: knownApp ?? undefined,
isPageWhitelisted
});
}
public render() {
/*
@@ -166,9 +239,42 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
this.state.availableMediaTypes & ReceiverSelectorMediaType.App
);
return (
<>
<div
className="whitelist-suggest"
hidden={
// If we don't know the app
!this.state.knownApp ||
// If the whitelist is disabled
!this.state.userAgentWhitelistEnabled ||
// If the whitelist is enabled, and the page is whitelisted
(this.state.userAgentWhitelistEnabled &&
this.state.isPageWhitelisted) ||
// If an app is already loaded on the page
isAppMediaTypeAvailable
}
>
<img src="photon_info.svg" />
{_(
"popupWhitelistNotWhitelisted",
this.state.knownApp?.name
)}
<button
onClick={() => {
if (!this.state.knownApp || !this.state.pageInfo) {
return;
}
this.onAddToWhitelist(
this.state.knownApp,
this.state.pageInfo
);
}}
>
{_("popupWhitelistAddToWhitelist")}
</button>
</div>
<div className="media-select">
<div className="media-select__label-cast">
{_("popupMediaSelectCastLabel")}
@@ -187,8 +293,7 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
selected={isAppMediaTypeSelected}
disabled={!isAppMediaTypeAvailable}
>
{(this.state.appId &&
knownApps[this.state.appId]?.name) ??
{this.state.knownApp?.name ??
_("popupMediaTypeApp")}
</option>
@@ -248,6 +353,21 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
);
}
private async onAddToWhitelist(app: KnownApp, pageInfo: PageInfo) {
if (!app.matches) {
return;
}
const whitelist = await options.get("userAgentWhitelist");
if (!whitelist.includes(app.matches)) {
whitelist.push(app.matches);
await options.set("userAgentWhitelist", whitelist);
await browser.tabs.reload(pageInfo.tabId);
window.close();
}
}
private onCast(receiver: ReceiverDevice) {
this.setState({
isLoading: true

View File

@@ -0,0 +1,13 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<style>
@media (prefers-color-scheme: dark) {
path {
fill: rgba(249, 249, 250, .8);
}
}
</style>
<path fill="rgba(12, 12, 13, .8)" d="M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zm0-7a1 1 0 0 0-1 1v3a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1zm0-3.188A1.188 1.188 0 1 0 9.188 5 1.188 1.188 0 0 0 8 3.812z"></path>
</svg>

After

Width:  |  Height:  |  Size: 710 B

View File

@@ -1,9 +1,3 @@
:root {
--button-background: #474749;
--button-background-hover: #505054;
--button-background-active: #5c5c5e;
}
body {
background: var(--grey-10);
color: var(--grey-90);
@@ -12,6 +6,10 @@ body {
font-size: 13px;
}
[hidden] {
display: none !important;
}
@media (prefers-color-scheme: dark) {
body {
background: var(--grey-80) !important;
@@ -26,8 +24,35 @@ body {
}
}
.media-select {
.whitelist-suggest {
align-items: center;
background-color: var(--blue-50-a30);
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
display: flex;
font-size: 0.9em;
gap: 0.5em;
padding: 0.75em;
}
.whitelist-suggest > button {
--button-background: hsla(0, 0%, 50%, 0.3);
--button-background-hover: hsla(0, 0%, 30%, 0.3);
--button-background-active: hsla(0, 0%, 10%, 0.3);
margin-left: auto;
}
@media (prefers-color-scheme: dark) {
.whitelist-suggest {
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
}
.whitelist-suggest > button {
--button-background: hsla(0, 0%, 50%, 0.3);
--button-background-hover: hsla(0, 0%, 70%, 0.3);
--button-background-active: hsla(0, 0%, 90%, 0.3);
}
}
.media-select {
align-items: baseline;
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
display: flex;
margin: 0 1em;
@@ -45,11 +70,6 @@ body {
margin-inline-start: 0.5em;
}
.media-select__dropdown {
padding-top: 2px;
padding-bottom: 2px;
}
.receivers {
list-style: none;
margin: initial;