feat: Enhance protocol status handling and repair functionality in About and SSOLogin components
This commit is contained in:
@@ -19,41 +19,31 @@ export function About({ requestClose }: AboutProps) {
|
|||||||
const [protocolBusy, setProtocolBusy] = useState<boolean>(false);
|
const [protocolBusy, setProtocolBusy] = useState<boolean>(false);
|
||||||
|
|
||||||
const formatProtocolStatus = useCallback((data: {
|
const formatProtocolStatus = useCallback((data: {
|
||||||
|
scheme: string;
|
||||||
platform: string;
|
platform: string;
|
||||||
primaryScheme: string;
|
isDefault: boolean;
|
||||||
schemes: Record<string, {
|
registerCall: boolean;
|
||||||
isDefault: boolean;
|
error: string | null;
|
||||||
registerCall: boolean;
|
windows: null | {
|
||||||
error: string | null;
|
|
||||||
}>;
|
|
||||||
windows: null | Record<string, {
|
|
||||||
userChoiceProgId: string | null;
|
userChoiceProgId: string | null;
|
||||||
userChoiceCommand: string | null;
|
userChoiceCommand: string | null;
|
||||||
hkcuCommand: string | null;
|
hkcuCommand: string | null;
|
||||||
hklmCommand: string | null;
|
hklmCommand: string | null;
|
||||||
}>;
|
};
|
||||||
}): string => {
|
}): string => {
|
||||||
const { element } = data.schemes;
|
const state = data.isDefault ? 'registered' : 'not registered';
|
||||||
const primary = data.schemes[data.primaryScheme];
|
const startupState = data.registerCall ? 'ok' : 'no-change';
|
||||||
const primaryState = primary?.isDefault ? 'registered' : 'not registered';
|
const owner =
|
||||||
const elementState = element?.isDefault ? 'registered' : 'not registered';
|
data.platform === 'win32' && data.windows?.userChoiceProgId
|
||||||
const primaryCall = primary?.registerCall ? 'ok' : 'no-change';
|
? ` Current default ProgId=${data.windows.userChoiceProgId}.`
|
||||||
const elementCall = element?.registerCall ? 'ok' : 'no-change';
|
|
||||||
|
|
||||||
const primaryError = primary?.error ? ` ${data.primaryScheme} error: ${primary.error}` : '';
|
|
||||||
const elementError = element?.error ? ` element error: ${element.error}` : '';
|
|
||||||
|
|
||||||
const primaryWindows = data.windows?.[data.primaryScheme];
|
|
||||||
const primaryOwner =
|
|
||||||
data.platform === 'win32' && primaryWindows?.userChoiceProgId
|
|
||||||
? ` Current ${data.primaryScheme} default ProgId=${primaryWindows.userChoiceProgId}.`
|
|
||||||
: '';
|
: '';
|
||||||
const primaryMissingHint =
|
const missingHint =
|
||||||
data.platform === 'win32' && !primaryWindows?.hkcuCommand && !primaryWindows?.hklmCommand
|
data.platform === 'win32' && !data.windows?.hkcuCommand && !data.windows?.hklmCommand
|
||||||
? ` ${data.primaryScheme} link type is missing from Windows registry. Use Repair to create it.`
|
? ` ${data.scheme} link type is missing from Windows registry. Use Repair to create it.`
|
||||||
: '';
|
: '';
|
||||||
|
const errorPart = data.error ? ` Error: ${data.error}` : '';
|
||||||
|
|
||||||
return `Primary ${data.primaryScheme} is ${primaryState} (startup=${primaryCall}) on ${data.platform}. Legacy element is ${elementState} (startup=${elementCall}).${primaryOwner}${primaryMissingHint}${primaryError}${elementError}`;
|
return `${data.scheme} is ${state} (startup=${startupState}) on ${data.platform}.${owner}${missingHint}${errorPart}`;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const refreshProtocolStatus = useCallback(() => {
|
const refreshProtocolStatus = useCallback(() => {
|
||||||
@@ -92,11 +82,7 @@ export function About({ requestClose }: AboutProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsHint =
|
setProtocolStatus(formatProtocolStatus(result.data));
|
||||||
result.data.platform === 'win32' && result.data.openedSettings
|
|
||||||
? ' Windows Default Apps was opened; set PAARROT to Paarrot and press Refresh.'
|
|
||||||
: '';
|
|
||||||
setProtocolStatus(`${formatProtocolStatus(result.data)}${settingsHint}`);
|
|
||||||
setProtocolBusy(false);
|
setProtocolBusy(false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -189,7 +175,7 @@ export function About({ requestClose }: AboutProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Protocol Handlers (paarrot:// + element://)"
|
title="Protocol Handler (paarrot://)"
|
||||||
description={protocolStatus}
|
description={protocolStatus}
|
||||||
after={
|
after={
|
||||||
<Box gap="100">
|
<Box gap="100">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Avatar, AvatarImage, Box, Button, Text } from 'folds';
|
import { Avatar, AvatarImage, Box, Button, Text } from 'folds';
|
||||||
import { IIdentityProvider, SSOAction, createClient } from 'matrix-js-sdk';
|
import { IIdentityProvider, SSOAction, createClient } from 'matrix-js-sdk';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
|
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
|
||||||
|
import { openExternalUrl } from '../../utils/tauri';
|
||||||
|
|
||||||
type SSOLoginProps = {
|
type SSOLoginProps = {
|
||||||
providers?: IIdentityProvider[];
|
providers?: IIdentityProvider[];
|
||||||
@@ -13,12 +14,39 @@ export function SSOLogin({ providers, redirectUrl, action, saveScreenSpace }: SS
|
|||||||
const discovery = useAutoDiscoveryInfo();
|
const discovery = useAutoDiscoveryInfo();
|
||||||
const baseUrl = discovery['m.homeserver'].base_url;
|
const baseUrl = discovery['m.homeserver'].base_url;
|
||||||
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
|
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
|
||||||
|
const [launchingProviderId, setLaunchingProviderId] = useState<string | null>(null);
|
||||||
|
|
||||||
const getSSOIdUrl = (ssoId?: string): string => {
|
const getSSOIdUrl = (ssoId?: string): string => {
|
||||||
const existingDeviceId = localStorage.getItem('cinny_device_id') ?? undefined;
|
const existingDeviceId = localStorage.getItem('cinny_device_id') ?? undefined;
|
||||||
return mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId, action, existingDeviceId);
|
return mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId, action, existingDeviceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleContinue = useCallback(
|
||||||
|
async (providerId?: string) => {
|
||||||
|
if (launchingProviderId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentProviderId = providerId ?? '__default__';
|
||||||
|
setLaunchingProviderId(currentProviderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (window.electron?.protocol?.repair) {
|
||||||
|
await window.electron.protocol.repair();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[SSOLogin] Protocol repair failed before SSO launch:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await openExternalUrl(getSSOIdUrl(providerId));
|
||||||
|
} finally {
|
||||||
|
setLaunchingProviderId(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[getSSOIdUrl, launchingProviderId]
|
||||||
|
);
|
||||||
|
|
||||||
const withoutIcon = providers
|
const withoutIcon = providers
|
||||||
? providers.find(
|
? providers.find(
|
||||||
(provider) => !provider.icon || !mx.mxcUrlToHttp(provider.icon, 96, 96, 'crop', false)
|
(provider) => !provider.icon || !mx.mxcUrlToHttp(provider.icon, 96, 96, 'crop', false)
|
||||||
@@ -41,11 +69,13 @@ export function SSOLogin({ providers, redirectUrl, action, saveScreenSpace }: SS
|
|||||||
<Avatar
|
<Avatar
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
key={id}
|
key={id}
|
||||||
as="a"
|
as="button"
|
||||||
href={getSSOIdUrl(id)}
|
|
||||||
aria-label={buttonTitle}
|
aria-label={buttonTitle}
|
||||||
size="300"
|
size="300"
|
||||||
radii="300"
|
radii="300"
|
||||||
|
onClick={() => {
|
||||||
|
handleContinue(id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AvatarImage src={iconUrl!} alt={name} title={buttonTitle} />
|
<AvatarImage src={iconUrl!} alt={name} title={buttonTitle} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -56,12 +86,14 @@ export function SSOLogin({ providers, redirectUrl, action, saveScreenSpace }: SS
|
|||||||
<Button
|
<Button
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
key={id}
|
key={id}
|
||||||
as="a"
|
|
||||||
href={getSSOIdUrl(id)}
|
|
||||||
size="500"
|
size="500"
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
fill="Soft"
|
fill="Soft"
|
||||||
outlined
|
outlined
|
||||||
|
disabled={Boolean(launchingProviderId)}
|
||||||
|
onClick={() => {
|
||||||
|
handleContinue(id);
|
||||||
|
}}
|
||||||
before={
|
before={
|
||||||
iconUrl && (
|
iconUrl && (
|
||||||
<Avatar size="200" radii="300">
|
<Avatar size="200" radii="300">
|
||||||
@@ -79,12 +111,14 @@ export function SSOLogin({ providers, redirectUrl, action, saveScreenSpace }: SS
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
as="a"
|
|
||||||
href={getSSOIdUrl()}
|
|
||||||
size="500"
|
size="500"
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
fill="Soft"
|
fill="Soft"
|
||||||
outlined
|
outlined
|
||||||
|
disabled={Boolean(launchingProviderId)}
|
||||||
|
onClick={() => {
|
||||||
|
handleContinue();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text align="Center" size="B500" truncate>
|
<Text align="Center" size="B500" truncate>
|
||||||
Continue with SSO
|
Continue with SSO
|
||||||
|
|||||||
33
src/ext.d.ts
vendored
33
src/ext.d.ts
vendored
@@ -61,41 +61,36 @@ interface ElectronAPI {
|
|||||||
getStatus: () => Promise<{
|
getStatus: () => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data?: {
|
data?: {
|
||||||
|
scheme: string;
|
||||||
platform: string;
|
platform: string;
|
||||||
primaryScheme: string;
|
isDefault: boolean;
|
||||||
schemes: Record<string, {
|
before: boolean;
|
||||||
isDefault: boolean;
|
registerCall: boolean;
|
||||||
before: boolean;
|
error: string | null;
|
||||||
registerCall: boolean;
|
windows: null | {
|
||||||
error: string | null;
|
|
||||||
}>;
|
|
||||||
windows: null | Record<string, {
|
|
||||||
userChoiceProgId: string | null;
|
userChoiceProgId: string | null;
|
||||||
userChoiceCommand: string | null;
|
userChoiceCommand: string | null;
|
||||||
hkcuCommand: string | null;
|
hkcuCommand: string | null;
|
||||||
hklmCommand: string | null;
|
hklmCommand: string | null;
|
||||||
}>;
|
};
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
repair: () => Promise<{
|
repair: () => Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data?: {
|
data?: {
|
||||||
openedSettings: boolean;
|
scheme: string;
|
||||||
platform: string;
|
platform: string;
|
||||||
primaryScheme: string;
|
isDefault: boolean;
|
||||||
schemes: Record<string, {
|
before: boolean;
|
||||||
isDefault: boolean;
|
registerCall: boolean;
|
||||||
before: boolean;
|
error: string | null;
|
||||||
registerCall: boolean;
|
windows: null | {
|
||||||
error: string | null;
|
|
||||||
}>;
|
|
||||||
windows: null | Record<string, {
|
|
||||||
userChoiceProgId: string | null;
|
userChoiceProgId: string | null;
|
||||||
userChoiceCommand: string | null;
|
userChoiceCommand: string | null;
|
||||||
hkcuCommand: string | null;
|
hkcuCommand: string | null;
|
||||||
hklmCommand: string | null;
|
hklmCommand: string | null;
|
||||||
}>;
|
};
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
Reference in New Issue
Block a user