core startup error handling (#579)

* core startup error handling

* lazy load `PasswordMeter` so that the huge `@zxcvbn-ts` isn't in the core bundle

* please clippy

---------

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
Oscar Beaumont 2023-02-25 21:50:22 +08:00 committed by GitHub
parent 9aa2832530
commit 7192ead2c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 108 additions and 61 deletions

View file

@ -57,7 +57,7 @@ pub(super) async fn setup<R: Runtime>(
.expect("Error with HTTP server!");
});
app.plugin(spacedrive_plugin_init(&auth_token, listen_addr))
app.plugin(tauri_plugin(&auth_token, listen_addr))
}
async fn auth_middleware<B>(
@ -83,11 +83,8 @@ async fn auth_middleware<B>(
(StatusCode::UNAUTHORIZED, "Unauthorized!").into_response()
}
pub fn spacedrive_plugin_init<R: Runtime>(
auth_token: &str,
listen_addr: SocketAddr,
) -> TauriPlugin<R> {
tauri::plugin::Builder::new("spacedrive")
fn tauri_plugin<R: Runtime>(auth_token: &str, listen_addr: SocketAddr) -> TauriPlugin<R> {
tauri::plugin::Builder::new("spacedrive-linux")
.js_init_script(format!(
r#"window.__SD_CUSTOM_SERVER_AUTH_TOKEN__ = "{auth_token}"; window.__SD_CUSTOM_URI_SERVER__ = "http://{listen_addr}";"#
))

View file

@ -3,11 +3,11 @@
windows_subsystem = "windows"
)]
use std::{error::Error, path::PathBuf, sync::Arc, time::Duration};
use std::{path::PathBuf, sync::Arc, time::Duration};
use sd_core::{custom_uri::create_custom_uri_endpoint, Node};
use sd_core::{custom_uri::create_custom_uri_endpoint, Node, NodeError};
use tauri::{api::path, async_runtime::block_on, Manager, RunEvent};
use tauri::{api::path, async_runtime::block_on, plugin::TauriPlugin, Manager, RunEvent, Runtime};
use tokio::{task::block_in_place, time::sleep};
use tracing::{debug, error};
@ -26,8 +26,14 @@ async fn app_ready(app_handle: tauri::AppHandle) {
window.show().unwrap();
}
pub fn tauri_error_plugin<R: Runtime>(err: NodeError) -> TauriPlugin<R> {
tauri::plugin::Builder::new("spacedrive")
.js_init_script(format!(r#"window.__SD_ERROR__ = "{err}";"#))
.build()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
async fn main() -> tauri::Result<()> {
let data_dir = path::data_dir()
.unwrap_or_else(|| PathBuf::from("./"))
.join("spacedrive");
@ -35,21 +41,32 @@ async fn main() -> Result<(), Box<dyn Error>> {
#[cfg(debug_assertions)]
let data_dir = data_dir.join("dev");
let (node, router) = Node::new(data_dir).await?;
let result = Node::new(data_dir).await;
let app = tauri::Builder::default().plugin(rspc::integrations::tauri::plugin(router, {
let node = Arc::clone(&node);
move || node.get_request_context()
}));
let app = tauri::Builder::default();
let (node, app) = match result {
Ok((node, router)) => {
// This is a super cringe workaround for: https://github.com/tauri-apps/tauri/issues/3725 & https://bugs.webkit.org/show_bug.cgi?id=146351#c5
let endpoint = create_custom_uri_endpoint(Arc::clone(&node));
// This is a super cringe workaround for: https://github.com/tauri-apps/tauri/issues/3725 & https://bugs.webkit.org/show_bug.cgi?id=146351#c5
let endpoint = create_custom_uri_endpoint(Arc::clone(&node));
#[cfg(target_os = "linux")]
let app = app_linux::setup(app, Arc::clone(&node), endpoint).await;
#[cfg(target_os = "linux")]
let app = app_linux::setup(app, Arc::clone(&node), endpoint).await;
#[cfg(not(target_os = "linux"))]
let app = app.register_uri_scheme_protocol(
"spacedrive",
endpoint.tauri_uri_scheme("spacedrive"),
);
#[cfg(not(target_os = "linux"))]
let app = app.register_uri_scheme_protocol("spacedrive", endpoint.tauri_uri_scheme("spacedrive"));
let app = app.plugin(rspc::integrations::tauri::plugin(router, {
let node = Arc::clone(&node);
move || node.get_request_context()
}));
(Some(node), app)
}
Err(err) => (None, app.plugin(tauri_error_plugin(err))),
};
let app = app
.setup(|app| {
@ -104,7 +121,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
});
block_in_place(|| block_on(node.shutdown()));
if let Some(node) = &node {
block_in_place(|| block_on(node.shutdown()));
}
app_handler.exit(0);
}
});

View file

@ -7,6 +7,7 @@ import { useEffect } from 'react';
import { getDebugState, hooks, queryClient } from '@sd/client';
import SpacedriveInterface, { OperatingSystem, Platform, PlatformProvider } from '@sd/interface';
import { KeybindEvent } from '@sd/interface';
import { ErrorPage } from '@sd/interface';
import '@sd/ui/style';
const client = hooks.createClient({
@ -33,6 +34,7 @@ async function getOs(): Promise<OperatingSystem> {
let customUriServerUrl = (window as any).__SD_CUSTOM_URI_SERVER__ as string | undefined;
const customUriAuthToken = (window as any).__SD_CUSTOM_URI_TOKEN__ as string | undefined;
const startupError = (window as any).__SD_ERROR__ as string | undefined;
if (customUriServerUrl && !customUriServerUrl?.endsWith('/')) {
customUriServerUrl += '/';
@ -79,6 +81,10 @@ export default function App() {
};
}, []);
if (startupError) {
return <ErrorPage message={startupError} />;
}
return (
<hooks.Provider client={client} queryClient={queryClient}>
<PlatformProvider platform={platform}>

View file

@ -1,6 +1,6 @@
import { relativeAliasResolver } from '@sd/config/vite';
import react from '@vitejs/plugin-react';
// import { visualizer } from 'rollup-plugin-visualizer';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import svg from 'vite-plugin-svgr';
@ -18,11 +18,11 @@ export default defineConfig({
svg({ svgrOptions: { icon: true } }),
createHtmlPlugin({
minify: true
}),
visualizer({
gzipSize: true,
brotliSize: true
})
// visualizer({
// gzipSize: true,
// brotliSize: true
// })
],
resolve: {
alias: [relativeAliasResolver]

View file

@ -4,11 +4,27 @@ import { useDebugState } from '@sd/client';
import { Button } from '@sd/ui';
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
const onClick = () => {
captureException(error);
resetErrorBoundary();
};
return (
<ErrorPage
message={error.message}
sendReportBtn={() => {
captureException(error);
resetErrorBoundary();
}}
reloadBtn={resetErrorBoundary}
/>
);
}
export function ErrorPage({
reloadBtn,
sendReportBtn,
message
}: {
reloadBtn?: () => void;
sendReportBtn?: () => void;
message: string;
}) {
const debug = useDebugState();
return (
@ -19,19 +35,23 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
>
<p className="text-ink-faint m-3 text-sm font-bold">APP CRASHED</p>
<h1 className="text-ink text-2xl font-bold">We're past the event horizon...</h1>
<pre className="text-ink m-2">Error: {error.message}</pre>
<pre className="text-ink m-2">Error: {message}</pre>
{debug.enabled && (
<pre className="text-ink-dull m-2 text-sm">
Check the console (CMD/CRTL + OPTION + i) for stack trace.
</pre>
)}
<div className="text-ink flex flex-row space-x-2">
<Button variant="accent" className="mt-2" onClick={resetErrorBoundary}>
Reload
</Button>
<Button variant="gray" className="mt-2" onClick={onClick}>
Send report
</Button>
{reloadBtn && (
<Button variant="accent" className="mt-2" onClick={reloadBtn}>
Reload
</Button>
)}
{sendReportBtn && (
<Button variant="gray" className="mt-2" onClick={sendReportBtn}>
Send report
</Button>
)}
</div>
</div>
);

View file

@ -1,12 +1,13 @@
import { useQueryClient } from '@tanstack/react-query';
import { ArrowsClockwise, Clipboard, Eye, EyeSlash } from 'phosphor-react';
import { useState } from 'react';
import { lazy, useState } from 'react';
import { Algorithm, useBridgeMutation } from '@sd/client';
import { Button, Dialog, Select, SelectOption, UseDialogProps, useDialog } from '@sd/ui';
import { forms } from '@sd/ui';
import { getHashingAlgorithmSettings } from '~/screens/settings/library/KeysSetting';
import { generatePassword } from '../key/KeyMounter';
import { PasswordMeter } from '../key/PasswordMeter';
const PasswordMeter = lazy(() => import('../key/PasswordMeter'));
const { Input, z, useZodForm } = forms;

View file

@ -1,12 +1,13 @@
import { ArrowsClockwise, Clipboard, Eye, EyeSlash } from 'phosphor-react';
import { useState } from 'react';
import { lazy, useState } from 'react';
import { Algorithm, useLibraryMutation } from '@sd/client';
import { Button, Dialog, Input, Select, SelectOption, UseDialogProps, useDialog } from '@sd/ui';
import { useZodForm, z } from '@sd/ui/src/forms';
import { getHashingAlgorithmSettings } from '~/screens/settings/library/KeysSetting';
import { showAlertDialog } from '~/util/dialog';
import { generatePassword } from '../key/KeyMounter';
import { PasswordMeter } from '../key/PasswordMeter';
const PasswordMeter = lazy(() => import('../key/PasswordMeter'));
export type MasterPasswordChangeDialogProps = UseDialogProps;

View file

@ -13,7 +13,7 @@ const options = {
};
zxcvbnOptions.setOptions(options);
export const PasswordMeter = (props: { password: string }) => {
export default function PasswordMeterInner(props: { password: string }) {
const ratings = ['Poor', 'Weak', 'Good', 'Strong', 'Perfect'];
const zx = zxcvbn(props.password);
@ -54,4 +54,4 @@ export const PasswordMeter = (props: { password: string }) => {
</div>
</div>
);
};
}

View file

@ -3,7 +3,7 @@ import { cx } from '@sd/ui';
export default function DragRegion(props: PropsWithChildren & { className?: string }) {
return (
<div data-tauri-drag-region className={cx('flex flex-shrink-0 w-full h-5', props.className)}>
<div data-tauri-drag-region className={cx('flex h-5 w-full flex-shrink-0', props.className)}>
{props.children}
</div>
);

View file

@ -1,11 +1,12 @@
import { useState } from 'react';
import { lazy, useState } from 'react';
import { useNavigate } from 'react-router';
import { getOnboardingStore, useBridgeMutation, useOnboardingStore } from '@sd/client';
import { Button, Card, forms } from '@sd/ui';
import { PasswordMeter } from '../key/PasswordMeter';
import { useUnlockOnboardingScreen } from './OnboardingProgress';
import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './OnboardingRoot';
const PasswordMeter = lazy(() => import('../key/PasswordMeter'));
const { PasswordShowHideInput, z, useZodForm, Form } = forms;
const schema = z.object({

View file

@ -11,8 +11,8 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
return (
<kbd
className={clsx(
`px-1 border border-b-2`,
`rounded-md text-xs font-ink-dull font-bold`,
`border border-b-2 px-1`,
`font-ink-dull rounded-md text-xs font-bold`,
`border-app-line dark:border-transparent`,
className
)}

View file

@ -6,7 +6,7 @@ export const SubtleButton: React.FC<{ icon?: React.FC }> = (props) => {
return (
<Button className="!p-[5px]" variant="subtle">
{/* @ts-expect-error */}
<Icon weight="bold" className="w-3 h-3" />
<Icon weight="bold" className="h-3 w-3" />
</Button>
);
};

View file

@ -4,3 +4,5 @@ export { KeybindEvent } from './util/keybind';
export * from './util/Platform';
export default SpacedriveInterface;
export { ErrorPage } from './ErrorFallback';

View file

@ -18,7 +18,7 @@ export default function DebugScreen() {
const { mutate: identifyFiles } = useLibraryMutation('jobs.identifyUniqueFiles');
return (
<ScreenContainer>
<div className="flex flex-col p-5 pt-2 space-y-5 pb-7">
<div className="flex flex-col space-y-5 p-5 pt-2 pb-7">
<h1 className="text-lg font-bold ">Developer Debugger</h1>
{/* <div className="flex flex-row pb-4 space-x-2">
<Button

View file

@ -40,7 +40,7 @@ function DropItem(props: DropItemProps) {
}
if (brandIconSrc) {
icon = (
<div className="flex items-center justify-center h-full p-3">
<div className="flex h-full items-center justify-center p-3">
<img className="rounded-full " src={brandIconSrc} alt={props.name} />
</div>
);
@ -48,18 +48,18 @@ function DropItem(props: DropItemProps) {
} else {
//
const Icon = props.icon || User;
icon = <Icon className={clsx('w-8 h-8 m-3', !props.name && 'opacity-20')} />;
icon = <Icon className={clsx('m-3 h-8 w-8', !props.name && 'opacity-20')} />;
}
return (
<div
className={clsx(classes.honeycombItem, 'overflow-hidden bg-app-box/20 hover:bg-app-box/50')}
className={clsx(classes.honeycombItem, 'bg-app-box/20 hover:bg-app-box/50 overflow-hidden')}
>
<div className="relative flex flex-col items-center justify-center w-full h-full group ">
<div className="group relative flex h-full w-full flex-col items-center justify-center ">
<SubtleButtonContainer className="absolute left-[12px] top-[55px]">
<SubtleButton icon={Star} />
</SubtleButtonContainer>
<div className="rounded-full w-14 h-14 bg-app-button">{icon}</div>
<div className="bg-app-button h-14 w-14 rounded-full">{icon}</div>
<SubtleButtonContainer className="absolute right-[12px] top-[55px] rotate-90">
<SubtleButton />
</SubtleButtonContainer>
@ -69,7 +69,7 @@ function DropItem(props: DropItemProps) {
{props.connectionType && (
<Pill
className={clsx(
'!text-white uppercase',
'uppercase !text-white',
props.connectionType === 'lan' && 'bg-green-500',
props.connectionType === 'p2p' && 'bg-blue-500'
)}
@ -89,7 +89,7 @@ export default function SpacedropScreen() {
return (
<ScreenContainer
dragRegionChildren={
<div className="flex flex-row items-center justify-center w-full h-8 pt-3">
<div className="flex h-8 w-full flex-row items-center justify-center pt-3">
<SearchBar className="ml-[13px]" ref={searchRef} />
{/* <Button variant="outline">Add</Button> */}
</div>

View file

@ -14,13 +14,13 @@ export default function AboutSpacedrive() {
return (
<SettingsContainer>
<div className="flex flex-row items-center">
<img src={Logo} className="w-[88px] h-[88px] mr-8" />
<img src={Logo} className="mr-8 h-[88px] w-[88px]" />
<div className="flex flex-col">
<h1 className="text-2xl font-bold">
Spacedrive {os !== 'unknown' && <>for {currentPlatformNiceName}</>}
</h1>
<span className="mt-1 text-sm text-ink-dull">The file manager from the future.</span>
<span className="mt-1 text-xs text-ink-faint/80">
<span className="text-ink-dull mt-1 text-sm">The file manager from the future.</span>
<span className="text-ink-faint/80 mt-1 text-xs">
v{buildInfo.data?.version || '-.-.-'} - {buildInfo.data?.commit || 'dev'}
</span>
</div>