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}
/>
)}