mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 02:48:38 -04:00
app: only show extension install banner on Chromium/Firefox
The dashboard nudge is useless on Safari and other browsers that cannot run the WROLPi extension. Share detectBrowser and gate the banner with isExtensionSupportedBrowser.
This commit is contained in:
parent
46ba804a1b
commit
db7603012e
4 changed files with 54 additions and 12 deletions
|
|
@ -3,10 +3,13 @@ import {Link} from "react-router";
|
|||
import {Button, Icon, Message} from "semantic-ui-react";
|
||||
import {useLocalStorage} from "../Common";
|
||||
import {useExtensionInstalled} from "../../hooks/customHooks";
|
||||
import {isExtensionSupportedBrowser} from "./browserDetect";
|
||||
|
||||
// Top-of-dashboard nudge for users who don't yet have the WROLPi browser
|
||||
// extension installed (or installed but haven't added this WROLPi as a
|
||||
// destination). Hidden once the user installs+configures, or dismisses it.
|
||||
// Only shown on Chromium / Firefox — Safari and other browsers cannot run
|
||||
// the extension.
|
||||
//
|
||||
// Detection is via a meta tag injected by the extension's content script.
|
||||
// The extension only injects on configured destination origins, so seeing
|
||||
|
|
@ -19,7 +22,8 @@ export function ExtensionInstallSuggestion() {
|
|||
|
||||
// installed === null means "still checking" — render nothing rather
|
||||
// than flashing the banner before the content-script race resolves.
|
||||
if (installed !== false || dismissed) return null;
|
||||
// Also skip on browsers that cannot install the extension (e.g. Safari).
|
||||
if (installed !== false || dismissed || !isExtensionSupportedBrowser()) return null;
|
||||
|
||||
return <Message info icon onDismiss={() => setDismissed(true)}>
|
||||
<Icon name='puzzle piece'/>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import React from 'react';
|
|||
import {render, screen, waitFor} from '../../test-utils';
|
||||
import {ExtensionInstallSuggestion} from './ExtensionInstallSuggestion';
|
||||
|
||||
const CHROME_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36';
|
||||
const FIREFOX_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0';
|
||||
const SAFARI_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15';
|
||||
|
||||
const setUserAgent = (ua) => {
|
||||
Object.defineProperty(window.navigator, 'userAgent', {value: ua, configurable: true});
|
||||
};
|
||||
|
||||
const setMeta = (version) => {
|
||||
const existing = document.querySelector('meta[name="wrolpi-extension"]');
|
||||
if (existing) existing.remove();
|
||||
|
|
@ -15,6 +23,7 @@ const setMeta = (version) => {
|
|||
describe('ExtensionInstallSuggestion', () => {
|
||||
beforeEach(() => {
|
||||
setMeta(undefined);
|
||||
setUserAgent(CHROME_UA);
|
||||
window.localStorage.removeItem('wrolpi-extension-banner-dismissed');
|
||||
});
|
||||
|
||||
|
|
@ -39,6 +48,22 @@ describe('ExtensionInstallSuggestion', () => {
|
|||
expect(screen.getByText(/Open install page/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the install message on Firefox when the marker is absent', async () => {
|
||||
setUserAgent(FIREFOX_UA);
|
||||
render(<ExtensionInstallSuggestion/>);
|
||||
await waitFor(
|
||||
() => expect(screen.getByText(/Install the WROLPi browser extension/i)).toBeInTheDocument(),
|
||||
{timeout: 1500},
|
||||
);
|
||||
});
|
||||
|
||||
it('renders nothing on Safari even when the marker is absent', async () => {
|
||||
setUserAgent(SAFARI_UA);
|
||||
render(<ExtensionInstallSuggestion/>);
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
expect(screen.queryByText(/Install the WROLPi browser extension/i)).toBeNull();
|
||||
});
|
||||
|
||||
it('renders nothing when the user has dismissed the banner', async () => {
|
||||
window.localStorage.setItem('wrolpi-extension-banner-dismissed', 'true');
|
||||
render(<ExtensionInstallSuggestion/>);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import {Button, Header, Icon, Segment} from "../Theme";
|
||||
import {Input, Message} from "semantic-ui-react";
|
||||
import {ThemeContext} from "../../contexts/contexts";
|
||||
import {detectBrowser} from "./browserDetect";
|
||||
|
||||
// The WROLPi browser extension is distributed off-store: Chrome users
|
||||
// sideload the unpacked .zip; Firefox users get a Mozilla-signed .xpi that
|
||||
|
|
@ -11,17 +12,6 @@ import {ThemeContext} from "../../contexts/contexts";
|
|||
const CHROME_ZIP_PATH = '/api/extensions/wrolpi-chrome.zip';
|
||||
const FIREFOX_XPI_PATH = '/api/extensions/wrolpi-firefox.xpi';
|
||||
|
||||
function detectBrowser() {
|
||||
if (typeof navigator === 'undefined') return 'unknown';
|
||||
const ua = navigator.userAgent || '';
|
||||
// Order matters: Edge / Brave / Opera all include 'Chrome' in their UA.
|
||||
if (ua.includes('Firefox')) return 'firefox';
|
||||
if (ua.includes('Edg/')) return 'chromium';
|
||||
if (ua.includes('OPR/') || ua.includes('Opera')) return 'chromium';
|
||||
if (ua.includes('Chrome')) return 'chromium';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (!bytes) return '';
|
||||
const kb = bytes / 1024;
|
||||
|
|
|
|||
23
app/src/components/admin/browserDetect.js
Normal file
23
app/src/components/admin/browserDetect.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Shared UA sniffing for the WROLPi browser extension UI.
|
||||
// The extension only ships for Chromium (Chrome/Brave/Edge/Opera) and Firefox;
|
||||
// Safari and other browsers should not be nudged to install it.
|
||||
|
||||
/**
|
||||
* @returns {'firefox' | 'chromium' | 'unknown'}
|
||||
*/
|
||||
export function detectBrowser() {
|
||||
if (typeof navigator === 'undefined') return 'unknown';
|
||||
const ua = navigator.userAgent || '';
|
||||
// Order matters: Edge / Brave / Opera all include 'Chrome' in their UA.
|
||||
if (ua.includes('Firefox')) return 'firefox';
|
||||
if (ua.includes('Edg/')) return 'chromium';
|
||||
if (ua.includes('OPR/') || ua.includes('Opera')) return 'chromium';
|
||||
if (ua.includes('Chrome')) return 'chromium';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/** True when the current browser can run the WROLPi extension. */
|
||||
export function isExtensionSupportedBrowser() {
|
||||
const browser = detectBrowser();
|
||||
return browser === 'firefox' || browser === 'chromium';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue