diff --git a/test/automated/browser/cypress/e2e/offline/01_offline_basic.cy.js b/test/automated/browser/cypress/e2e/offline/01_offline_basic.cy.js index 6605dab29..202054f5e 100644 --- a/test/automated/browser/cypress/e2e/offline/01_offline_basic.cy.js +++ b/test/automated/browser/cypress/e2e/offline/01_offline_basic.cy.js @@ -9,8 +9,8 @@ describe(`Basic tests`, () => { // Offline banner it('Has correct offline banner values', () => { cy.contains( - 'This stream is offline. Be notified the next time New Owncast Server goes live.' - ).should('be.visible'); + 'This stream is offline. You can be notified the next time New Owncast Server goes live or follow streamer@testing.biz on the Fediverse.' + ).should('exist'); }); // Verify the tags show up diff --git a/test/automated/browser/cypress/e2e/online/01_online_live.cy.js b/test/automated/browser/cypress/e2e/online/01_online_live.cy.js index 352b1e21f..65f56053b 100644 --- a/test/automated/browser/cypress/e2e/online/01_online_live.cy.js +++ b/test/automated/browser/cypress/e2e/online/01_online_live.cy.js @@ -1,4 +1,5 @@ import { setup } from '../../support/setup.js'; +import fetchData from '../../support/fetchData.js'; setup(); @@ -81,45 +82,3 @@ describe(`Live tests`, () => { cy.visit('http://localhost:8080'); }); }); - -async function fetchData(url, options) { - const ADMIN_USERNAME = 'admin'; - const ADMIN_STREAMKEY = 'abc123'; - - const { data, method = 'GET', auth = true } = options || {}; - - // eslint-disable-next-line no-undef - const requestOptions = { - method, - }; - - if (data) { - requestOptions.body = JSON.stringify(data); - } - - if (auth && ADMIN_USERNAME && ADMIN_STREAMKEY) { - const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); - requestOptions.headers = { - Authorization: `Basic ${encoded}`, - }; - requestOptions.mode = 'cors'; - requestOptions.credentials = 'include'; - } - - try { - const response = await fetch(url, requestOptions); - const json = await response.json(); - - if (!response.ok) { - const message = - json.message || `An error has occurred: ${response.status}`; - throw new Error(message); - } - return json; - } catch (error) { - console.error(error); - return error; - // console.log(error) - // throw new Error(error) - } -} diff --git a/test/automated/browser/cypress/fixtures/example.json b/test/automated/browser/cypress/fixtures/example.json deleted file mode 100644 index 02e425437..000000000 --- a/test/automated/browser/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/test/automated/browser/cypress/support/e2e.js b/test/automated/browser/cypress/support/e2e.js index 5df9c0186..bbad3483f 100644 --- a/test/automated/browser/cypress/support/e2e.js +++ b/test/automated/browser/cypress/support/e2e.js @@ -15,6 +15,21 @@ // Import commands.js using ES2015 syntax: import './commands'; +import fetchData from './fetchData.js'; // Alternatively you can use CommonJS syntax: // require('./commands') + +// Put Owncast in a state where it's ready to be tested. + +// Set server URL +fetchData('http://localhost:8080/api/admin/config/serverurl', { + method: 'POST', + data: { value: 'https://testing.biz' }, +}); + +// Enable Fediverse features. +fetchData('http://localhost:8080/api/admin/config/federation/enable', { + method: 'POST', + data: { value: true }, +}); diff --git a/test/automated/browser/cypress/support/fetchData.js b/test/automated/browser/cypress/support/fetchData.js new file mode 100644 index 000000000..651c1e716 --- /dev/null +++ b/test/automated/browser/cypress/support/fetchData.js @@ -0,0 +1,43 @@ +async function fetchData(url, options) { + const ADMIN_USERNAME = 'admin'; + const ADMIN_STREAMKEY = 'abc123'; + + const { data, method = 'GET', auth = true } = options || {}; + + // eslint-disable-next-line no-undef + const requestOptions = { + method, + }; + + if (data) { + requestOptions.body = JSON.stringify(data); + } + + if (auth && ADMIN_USERNAME && ADMIN_STREAMKEY) { + const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`); + requestOptions.headers = { + Authorization: `Basic ${encoded}`, + }; + requestOptions.mode = 'cors'; + requestOptions.credentials = 'include'; + } + + try { + const response = await fetch(url, requestOptions); + const json = await response.json(); + + if (!response.ok) { + const message = + json.message || `An error has occurred: ${response.status}`; + throw new Error(message); + } + return json; + } catch (error) { + console.error(error); + return error; + // console.log(error) + // throw new Error(error) + } +} + +export default fetchData; diff --git a/web/components/modals/AuthModal/AuthModal.stories.tsx b/web/components/modals/AuthModal/AuthModal.stories.tsx index 83616f75b..5906a2faa 100644 --- a/web/components/modals/AuthModal/AuthModal.stories.tsx +++ b/web/components/modals/AuthModal/AuthModal.stories.tsx @@ -21,7 +21,7 @@ const Example = () => { return (
- +
); }; diff --git a/web/components/modals/AuthModal/AuthModal.tsx b/web/components/modals/AuthModal/AuthModal.tsx index bdb0a3412..28c765ba8 100644 --- a/web/components/modals/AuthModal/AuthModal.tsx +++ b/web/components/modals/AuthModal/AuthModal.tsx @@ -12,18 +12,26 @@ import { currentUserAtom, chatAuthenticatedAtom, accessTokenAtom, + clientConfigStateAtom, } from '../../stores/ClientConfigStore'; +import { ClientConfig } from '../../../interfaces/client-config.model'; -export const AuthModal: FC = () => { +export type AuthModalProps = { + forceTabs?: boolean; +}; + +export const AuthModal: FC = ({ forceTabs }) => { const authenticated = useRecoilValue(chatAuthenticatedAtom); const accessToken = useRecoilValue(accessTokenAtom); const currentUser = useRecoilValue(currentUserAtom); + const clientConfig = useRecoilValue(clientConfigStateAtom); + if (!currentUser) { return null; } const { displayName } = currentUser; - - const federationEnabled = true; + const { federation } = clientConfig; + const { enabled: fediverseEnabled } = federation; const indieAuthTabTitle = ( @@ -67,7 +75,7 @@ export const AuthModal: FC = () => { items={items} type="card" size="small" - renderTabBar={federationEnabled ? null : () => null} + renderTabBar={fediverseEnabled || forceTabs ? null : () => null} /> ); diff --git a/web/components/ui/Content/Content.tsx b/web/components/ui/Content/Content.tsx index 9775098ea..81bb1a30b 100644 --- a/web/components/ui/Content/Content.tsx +++ b/web/components/ui/Content/Content.tsx @@ -3,6 +3,7 @@ import { Layout, Tabs, Skeleton } from 'antd'; import { FC, useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage'; +import isPushNotificationSupported from '../../../utils/browserPushNotifications'; import { clientConfigStateAtom, @@ -66,16 +67,17 @@ const DesktopContent = ({ socialHandles, extraPageContent, setShowFollowModal, + supportFediverseFeatures, }) => { const aboutTabContent = ; const followersTabContent = ( setShowFollowModal(true)} /> ); - const items = [ - { label: 'About', key: '2', children: aboutTabContent }, - { label: 'Followers', key: '3', children: followersTabContent }, - ]; + const items = [{ label: 'About', key: '2', children: aboutTabContent }]; + if (supportFediverseFeatures) { + items.push({ label: 'Followers', key: '3', children: followersTabContent }); + } return ( <> @@ -91,7 +93,7 @@ const DesktopContent = ({
- + {items.length > 1 ? : aboutTabContent}
); @@ -111,6 +113,8 @@ const MobileContent = ({ setExternalActionToDisplay, setShowNotifyPopup, setShowFollowModal, + supportFediverseFeatures, + supportsBrowserNotifications, }) => { if (!currentUser) { return null; @@ -153,8 +157,8 @@ const MobileContent = ({
setShowNotifyPopup(true)} @@ -217,11 +221,14 @@ export const Content: FC = () => { const [showNotifyReminder, setShowNotifyReminder] = useState(false); const [showNotifyModal, setShowNotifyModal] = useState(false); const [showFollowModal, setShowFollowModal] = useState(false); - const { account: fediverseAccount } = federation; + const { account: fediverseAccount, enabled: fediverseEnabled } = federation; const { browser: browserNotifications } = notifications; const { enabled: browserNotificationsEnabled } = browserNotifications; const [externalActionToDisplay, setExternalActionToDisplay] = useState(null); + const [supportsBrowserNotifications, setSupportsBrowserNotifications] = useState(false); + const supportFediverseFeatures = fediverseEnabled; + const externalActionSelected = (action: ExternalAction) => { const { openExternally, url } = action; if (openExternally) { @@ -277,6 +284,12 @@ export const Content: FC = () => { }; }, []); + useEffect(() => { + // isPushNotificationSupported relies on `navigator` so that needs to be + // fired from this useEffect. + setSupportsBrowserNotifications(isPushNotificationSupported() && browserNotificationsEnabled); + }, [browserNotificationsEnabled]); + const showChat = !chatDisabled && isChatAvailable && isChatVisible; return ( @@ -312,14 +325,18 @@ export const Content: FC = () => { {!isMobile && ( {externalActionButtons} - setShowFollowModal(true)} /> - setShowNotifyModal(true)} - notificationClosed={() => disableNotifyReminderPopup()} - > - setShowNotifyModal(true)} /> - + {supportFediverseFeatures && ( + setShowFollowModal(true)} /> + )} + {supportsBrowserNotifications && ( + setShowNotifyModal(true)} + notificationClosed={() => disableNotifyReminderPopup()} + > + setShowNotifyModal(true)} /> + + )} )} @@ -348,6 +365,8 @@ export const Content: FC = () => { setExternalActionToDisplay={externalActionSelected} setShowNotifyPopup={setShowNotifyModal} setShowFollowModal={setShowFollowModal} + supportFediverseFeatures={supportFediverseFeatures} + supportsBrowserNotifications={supportsBrowserNotifications} /> ) : ( { socialHandles={socialHandles} extraPageContent={extraPageContent} setShowFollowModal={setShowFollowModal} + supportFediverseFeatures={supportFediverseFeatures} /> )}