import Layout from '@app/components/Layout'; import LoadingBar from '@app/components/LoadingBar'; import PWAHeader from '@app/components/PWAHeader'; import ServiceWorkerSetup from '@app/components/ServiceWorkerSetup'; import StatusChecker from '@app/components/StatusChecker'; import Toast from '@app/components/Toast'; import ToastContainer from '@app/components/ToastContainer'; import { InteractionProvider } from '@app/context/InteractionContext'; import type { AvailableLocale } from '@app/context/LanguageContext'; import { LanguageContext } from '@app/context/LanguageContext'; import { SettingsProvider } from '@app/context/SettingsContext'; import { UserContext } from '@app/context/UserContext'; import type { User } from '@app/hooks/useUser'; import '@app/styles/globals.css'; import { polyfillIntl } from '@app/utils/polyfillIntl'; import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces'; import axios from 'axios'; import type { AppInitialProps, AppProps } from 'next/app'; import App from 'next/app'; import Head from 'next/head'; import { useEffect, useState } from 'react'; import { IntlProvider } from 'react-intl'; import { ToastProvider } from 'react-toast-notifications'; import { SWRConfig } from 'swr'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const loadLocaleData = (locale: AvailableLocale): Promise => { switch (locale) { case 'ar': return import('../i18n/locale/ar.json'); case 'ca': return import('../i18n/locale/ca.json'); case 'cs': return import('../i18n/locale/cs.json'); case 'da': return import('../i18n/locale/da.json'); case 'de': return import('../i18n/locale/de.json'); case 'el': return import('../i18n/locale/el.json'); case 'es': return import('../i18n/locale/es.json'); case 'fr': return import('../i18n/locale/fr.json'); case 'hr': return import('../i18n/locale/hr.json'); case 'hu': return import('../i18n/locale/hu.json'); case 'it': return import('../i18n/locale/it.json'); case 'ja': return import('../i18n/locale/ja.json'); case 'lt': return import('../i18n/locale/lt.json'); case 'nb-NO': return import('../i18n/locale/nb_NO.json'); case 'nl': return import('../i18n/locale/nl.json'); case 'pl': return import('../i18n/locale/pl.json'); case 'pt-BR': return import('../i18n/locale/pt_BR.json'); case 'pt-PT': return import('../i18n/locale/pt_PT.json'); case 'ru': return import('../i18n/locale/ru.json'); case 'sq': return import('../i18n/locale/sq.json'); case 'sr': return import('../i18n/locale/sr.json'); case 'sv': return import('../i18n/locale/sv.json'); case 'zh-CN': return import('../i18n/locale/zh_Hans.json'); case 'zh-TW': return import('../i18n/locale/zh_Hant.json'); default: return import('../i18n/locale/en.json'); } }; // Custom types so we can correctly type our GetInitialProps function // with our combined user prop // This is specific to _app.tsx. Other pages will not need to do this! type NextAppComponentType = typeof App; type MessagesType = Record; interface ExtendedAppProps extends AppProps { user: User; messages: MessagesType; locale: AvailableLocale; currentSettings: PublicSettingsResponse; } if (typeof window === 'undefined') { global.Intl = require('intl'); } const CoreApp: Omit = ({ Component, pageProps, router, user, messages, locale, currentSettings, }: ExtendedAppProps) => { let component: React.ReactNode; const [loadedMessages, setMessages] = useState(messages); const [currentLocale, setLocale] = useState(locale); useEffect(() => { loadLocaleData(currentLocale).then(setMessages); }, [currentLocale]); if (router.pathname.match(/(login|setup|resetpassword)/)) { component = ; } else { component = ( ); } return ( axios.get(url).then((res) => res.data), fallback: { '/api/v1/auth/me': user, }, }} > {currentSettings.applicationTitle} {component} ); }; CoreApp.getInitialProps = async (initialProps) => { const { ctx, router } = initialProps; let user: User | undefined = undefined; let currentSettings: PublicSettingsResponse = { initialized: false, applicationTitle: '', applicationUrl: '', hideAvailable: false, movie4kEnabled: false, series4kEnabled: false, localLogin: true, region: '', originalLanguage: '', partialRequestsEnabled: true, cacheImages: false, vapidPublic: '', enablePushRegistration: false, locale: 'en', emailEnabled: false, newPlexLogin: true, }; if (ctx.res) { // Check if app is initialized and redirect if necessary const response = await axios.get( `http://localhost:${process.env.PORT || 5055}/api/v1/settings/public` ); currentSettings = response.data; const initialized = response.data.initialized; if (!initialized) { if (!router.pathname.match(/(setup|login\/plex)/)) { ctx.res.writeHead(307, { Location: '/setup', }); ctx.res.end(); } } else { try { // Attempt to get the user by running a request to the local api const response = await axios.get( `http://localhost:${process.env.PORT || 5055}/api/v1/auth/me`, { headers: ctx.req && ctx.req.headers.cookie ? { cookie: ctx.req.headers.cookie } : undefined, } ); user = response.data; if (router.pathname.match(/(setup|login)/)) { ctx.res.writeHead(307, { Location: '/', }); ctx.res.end(); } } catch (e) { // If there is no user, and ctx.res is set (to check if we are on the server side) // _AND_ we are not already on the login or setup route, redirect to /login with a 307 // before anything actually renders if (!router.pathname.match(/(login|setup|resetpassword)/)) { ctx.res.writeHead(307, { Location: '/login', }); ctx.res.end(); } } } } // Run the default getInitialProps for the main nextjs initialProps const appInitialProps: AppInitialProps = await App.getInitialProps( initialProps ); const locale = user?.settings?.locale ? user.settings.locale : currentSettings.locale; const messages = await loadLocaleData(locale as AvailableLocale); await polyfillIntl(locale); return { ...appInitialProps, user, messages, locale, currentSettings }; }; export default CoreApp;