Merge branch 'dev-sw' into 'develop'
Developers: add Service Worker debug page See merge request soapbox-pub/soapbox!1863
This commit is contained in:
commit
0d82e129a0
9 changed files with 199 additions and 15 deletions
|
@ -8,6 +8,7 @@ import { Text, Stack } from 'soapbox/components/ui';
|
||||||
import { captureException } from 'soapbox/monitoring';
|
import { captureException } from 'soapbox/monitoring';
|
||||||
import KVStore from 'soapbox/storage/kv_store';
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
import { unregisterSw } from 'soapbox/utils/sw';
|
||||||
|
|
||||||
import SiteLogo from './site-logo';
|
import SiteLogo from './site-logo';
|
||||||
|
|
||||||
|
@ -15,16 +16,6 @@ import type { RootState } from 'soapbox/store';
|
||||||
|
|
||||||
const goHome = () => location.href = '/';
|
const goHome = () => location.href = '/';
|
||||||
|
|
||||||
/** Unregister the ServiceWorker */
|
|
||||||
// https://stackoverflow.com/a/49771828/8811886
|
|
||||||
const unregisterSw = async(): Promise<void> => {
|
|
||||||
if (navigator.serviceWorker) {
|
|
||||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
||||||
const unregisterAll = registrations.map(r => r.unregister());
|
|
||||||
await Promise.all(unregisterAll);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => {
|
const mapStateToProps = (state: RootState) => {
|
||||||
const { links, logo } = getSoapboxConfig(state);
|
const { links, logo } = getSoapboxConfig(state);
|
||||||
|
|
||||||
|
|
24
app/soapbox/features/developers/components/indicator.tsx
Normal file
24
app/soapbox/features/developers/components/indicator.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import classNames from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface IIndicator {
|
||||||
|
state?: 'active' | 'pending' | 'error' | 'inactive',
|
||||||
|
size?: 'sm',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicator dot component. */
|
||||||
|
const Indicator: React.FC<IIndicator> = ({ state = 'inactive', size = 'sm' }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('rounded-full outline-double', {
|
||||||
|
'w-1.5 h-1.5 shadow-sm': size === 'sm',
|
||||||
|
'bg-green-500 outline-green-400': state === 'active',
|
||||||
|
'bg-yellow-500 outline-yellow-400': state === 'pending',
|
||||||
|
'bg-red-500 outline-red-400': state === 'error',
|
||||||
|
'bg-neutral-500 outline-neutral-400': state === 'inactive',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Indicator;
|
|
@ -89,6 +89,14 @@ const Developers: React.FC = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</DashWidget>
|
</DashWidget>
|
||||||
|
|
||||||
|
<DashWidget to='/developers/sw'>
|
||||||
|
<SvgIcon src={require('@tabler/icons/script.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
<FormattedMessage id='developers.navigation.service_worker_label' defaultMessage='Service Worker' />
|
||||||
|
</Text>
|
||||||
|
</DashWidget>
|
||||||
|
|
||||||
<DashWidget onClick={leaveDevelopers}>
|
<DashWidget onClick={leaveDevelopers}>
|
||||||
<SvgIcon src={require('@tabler/icons/logout.svg')} className='text-gray-700 dark:text-gray-600' />
|
<SvgIcon src={require('@tabler/icons/logout.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||||
|
|
||||||
|
|
140
app/soapbox/features/developers/service-worker-info.tsx
Normal file
140
app/soapbox/features/developers/service-worker-info.tsx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
import { HStack, Text, Column, FormActions, Button, Stack, Icon } from 'soapbox/components/ui';
|
||||||
|
import { unregisterSw } from 'soapbox/utils/sw';
|
||||||
|
|
||||||
|
import Indicator from './components/indicator';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.developers.service_worker', defaultMessage: 'Service Worker' },
|
||||||
|
status: { id: 'sw.status', defaultMessage: 'Status' },
|
||||||
|
url: { id: 'sw.url', defaultMessage: 'Script URL' },
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Hook that returns the active ServiceWorker registration. */
|
||||||
|
const useRegistration = () => {
|
||||||
|
const [isLoading, setLoading] = useState(true);
|
||||||
|
const [registration, setRegistration] = useState<ServiceWorkerRegistration>();
|
||||||
|
|
||||||
|
const isSupported = 'serviceWorker' in navigator;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSupported) {
|
||||||
|
navigator.serviceWorker.getRegistration()
|
||||||
|
.then(r => {
|
||||||
|
setRegistration(r);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(() => setLoading(false));
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
registration,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IServiceWorkerInfo {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mini ServiceWorker debugging component. */
|
||||||
|
const ServiceWorkerInfo: React.FC<IServiceWorkerInfo> = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { isLoading, registration } = useRegistration();
|
||||||
|
|
||||||
|
const url = registration?.active?.scriptURL;
|
||||||
|
|
||||||
|
const getState = () => {
|
||||||
|
if (registration?.waiting) {
|
||||||
|
return 'pending';
|
||||||
|
} else if (registration?.active) {
|
||||||
|
return 'active';
|
||||||
|
} else {
|
||||||
|
return 'inactive';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMessage = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='sw.state.loading'
|
||||||
|
defaultMessage='Loading…'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (!isLoading && !registration) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='sw.state.unavailable'
|
||||||
|
defaultMessage='Unavailable'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (registration?.waiting) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='sw.state.waiting'
|
||||||
|
defaultMessage='Waiting'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (registration?.active) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='sw.state.active'
|
||||||
|
defaultMessage='Active'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='sw.state.unknown'
|
||||||
|
defaultMessage='Unknown'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestart = async() => {
|
||||||
|
await unregisterSw();
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||||
|
<Stack space={4}>
|
||||||
|
<List>
|
||||||
|
<ListItem label={intl.formatMessage(messages.status)}>
|
||||||
|
<HStack alignItems='center' space={2}>
|
||||||
|
<Indicator state={getState()} />
|
||||||
|
<Text size='md' theme='muted'>{getMessage()}</Text>
|
||||||
|
</HStack>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{url && (
|
||||||
|
<ListItem label={intl.formatMessage(messages.url)}>
|
||||||
|
<a href={url} target='_blank' className='flex space-x-1 items-center truncate'>
|
||||||
|
<span className='truncate'>{url}</span>
|
||||||
|
<Icon
|
||||||
|
className='w-4 h-4'
|
||||||
|
src={require('@tabler/icons/external-link.svg')}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button theme='tertiary' type='button' onClick={handleRestart}>
|
||||||
|
<FormattedMessage id='sw.restart' defaultMessage='Restart' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Stack>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceWorkerInfo;
|
|
@ -112,6 +112,7 @@ import {
|
||||||
TestTimeline,
|
TestTimeline,
|
||||||
LogoutPage,
|
LogoutPage,
|
||||||
AuthTokenList,
|
AuthTokenList,
|
||||||
|
ServiceWorkerInfo,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import { WrappedRoute } from './util/react_router_helpers';
|
import { WrappedRoute } from './util/react_router_helpers';
|
||||||
|
|
||||||
|
@ -311,6 +312,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {
|
||||||
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
|
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
|
||||||
<WrappedRoute path='/developers/settings_store' developerOnly page={DefaultPage} component={SettingsStore} content={children} />
|
<WrappedRoute path='/developers/settings_store' developerOnly page={DefaultPage} component={SettingsStore} content={children} />
|
||||||
<WrappedRoute path='/developers/timeline' developerOnly page={DefaultPage} component={TestTimeline} content={children} />
|
<WrappedRoute path='/developers/timeline' developerOnly page={DefaultPage} component={TestTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/developers/sw' developerOnly page={DefaultPage} component={ServiceWorkerInfo} content={children} />
|
||||||
<WrappedRoute path='/developers' page={DefaultPage} component={Developers} content={children} />
|
<WrappedRoute path='/developers' page={DefaultPage} component={Developers} content={children} />
|
||||||
<WrappedRoute path='/error/network' developerOnly page={EmptyPage} component={() => new Promise((_resolve, reject) => reject())} content={children} />
|
<WrappedRoute path='/error/network' developerOnly page={EmptyPage} component={() => new Promise((_resolve, reject) => reject())} content={children} />
|
||||||
<WrappedRoute path='/error' developerOnly page={EmptyPage} component={IntentionalError} content={children} />
|
<WrappedRoute path='/error' developerOnly page={EmptyPage} component={IntentionalError} content={children} />
|
||||||
|
|
|
@ -470,6 +470,10 @@ export function TestTimeline() {
|
||||||
return import(/* webpackChunkName: "features/test_timeline" */'../../test_timeline');
|
return import(/* webpackChunkName: "features/test_timeline" */'../../test_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ServiceWorkerInfo() {
|
||||||
|
return import(/* webpackChunkName: "features/developers" */'../../developers/service-worker-info');
|
||||||
|
}
|
||||||
|
|
||||||
export function DatePicker() {
|
export function DatePicker() {
|
||||||
return import(/* webpackChunkName: "date_picker" */'../../birthdays/date_picker');
|
return import(/* webpackChunkName: "date_picker" */'../../birthdays/date_picker');
|
||||||
}
|
}
|
||||||
|
|
15
app/soapbox/utils/sw.ts
Normal file
15
app/soapbox/utils/sw.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/** Unregister the ServiceWorker */
|
||||||
|
// https://stackoverflow.com/a/49771828/8811886
|
||||||
|
const unregisterSw = async(): Promise<void> => {
|
||||||
|
if (navigator.serviceWorker) {
|
||||||
|
// FIXME: this only works if using a single tab.
|
||||||
|
// Send a message to sw.js instead to refresh all tabs.
|
||||||
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||||
|
const unregisterAll = registrations.map(r => r.unregister());
|
||||||
|
await Promise.all(unregisterAll);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
unregisterSw,
|
||||||
|
};
|
|
@ -67,7 +67,7 @@
|
||||||
"@sentry/browser": "^7.11.1",
|
"@sentry/browser": "^7.11.1",
|
||||||
"@sentry/react": "^7.11.1",
|
"@sentry/react": "^7.11.1",
|
||||||
"@sentry/tracing": "^7.11.1",
|
"@sentry/tracing": "^7.11.1",
|
||||||
"@tabler/icons": "^1.73.0",
|
"@tabler/icons": "^1.109.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.7",
|
"@tailwindcss/typography": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.0.10",
|
"@tanstack/react-query": "^4.0.10",
|
||||||
|
|
|
@ -2290,10 +2290,10 @@
|
||||||
remark "^13.0.0"
|
remark "^13.0.0"
|
||||||
unist-util-find-all-after "^3.0.2"
|
unist-util-find-all-after "^3.0.2"
|
||||||
|
|
||||||
"@tabler/icons@^1.73.0":
|
"@tabler/icons@^1.109.0":
|
||||||
version "1.73.0"
|
version "1.109.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-1.73.0.tgz#26d81858baf41be939504e1f9b4b32835eda6fdb"
|
resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-1.109.0.tgz#11626c3fc097f2f70c4c197e4b9909fb05380752"
|
||||||
integrity sha512-MhAHFzVj79ZWlAIRD++7Mk55PZsdlEdkfkjO3DD257mqj8iJZQRAQtkx2UFJXVs2mMrcOUu1qtj4rlVC8BfnKA==
|
integrity sha512-B0YetE4pB6HY2Wa57v/LJ3NgkJzKYPze4U0DurIqPoKSptatKv2ga76FZSkO6EUpkYfHMtGPM6QjpJljfuCmAQ==
|
||||||
|
|
||||||
"@tailwindcss/forms@^0.5.3":
|
"@tailwindcss/forms@^0.5.3":
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
|
|
Loading…
Reference in a new issue