Skip to content

Commit df1e291

Browse files
committed
refactor: push through the remaining mobileconfig changes
1 parent 64a63bb commit df1e291

20 files changed

Lines changed: 509 additions & 272 deletions

File tree

src-tauri/src/app/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ pub fn run() {
102102
utils::fs::read_file_bytes,
103103
utils::markdown::parse_and_sanitize_markdown,
104104
utils::mobileconfig::decode_mobile_config,
105-
utils::plist::convert_plist_to_xml,
106105
#[cfg(target_os = "macos")]
107106
window::set_hide_dock_icon_when_window_closed,
108107
])

src-tauri/src/utils/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
pub mod fs;
22
pub mod markdown;
33
pub mod mobileconfig;
4-
pub mod plist;

src-tauri/src/utils/plist.rs

Lines changed: 0 additions & 96 deletions
This file was deleted.

src/components/AppModals.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CalendarModal } from '$components/modals/CalendarModal';
33
import { ChangelogModal } from '$components/modals/ChangelogModal';
44
import { ExportModal } from '$components/modals/ExportModal';
55
import { ImportModal } from '$components/modals/ImportModal/ImportModal';
6+
import { MobileConfigImportChooserModal } from '$components/modals/MobileConfigImportChooserModal';
67
import { OnboardingModal } from '$components/modals/OnboardingModal/OnboardingModal';
78
import { SettingsModal } from '$components/modals/SettingsModal';
89
import { TaskActionsModal } from '$components/modals/TaskActionsModal';
@@ -12,7 +13,11 @@ import type { UpdateError, UpdateInfo } from '$hooks/system/useUpdateChecker';
1213
import { getTasksByCalendar } from '$lib/store/tasks';
1314
import type { Account } from '$types';
1415
import type { AppModalActions, AppModalState } from '$types/controller';
15-
import type { MobileConfigCalDAVSettings } from '$types/mobileconfig';
16+
import type {
17+
MobileConfigCalDAVSettings,
18+
MobileConfigImportProfile,
19+
MobileConfigImportSelection,
20+
} from '$types/mobileconfig';
1621

1722
interface AppModalsOnboarding {
1823
show: boolean;
@@ -21,10 +26,15 @@ interface AppModalsOnboarding {
2126

2227
interface AppModalsFileDrop {
2328
preloadedFile: FileDropResult | null;
24-
preloadedConfig: MobileConfigCalDAVSettings | null;
29+
preloadedConfigProfile: MobileConfigImportProfile | null;
30+
preloadedConfig: MobileConfigImportSelection | null;
2531
handleImportClose: () => void;
2632
clearDragState: () => void;
2733
clearPreloadedConfig: () => void;
34+
selectPreloadedConfig: (
35+
settings: MobileConfigCalDAVSettings,
36+
profile: MobileConfigImportProfile,
37+
) => void;
2838
}
2939

3040
interface AppModalsUpdates {
@@ -84,10 +94,12 @@ export const AppModals = ({
8494
} = modalActions;
8595
const {
8696
preloadedFile,
97+
preloadedConfigProfile,
8798
preloadedConfig,
8899
handleImportClose,
89100
clearDragState,
90101
clearPreloadedConfig,
102+
selectPreloadedConfig,
91103
} = fileDrop;
92104
const {
93105
changelogData,
@@ -140,6 +152,14 @@ export const AppModals = ({
140152
onFileDrop={clearDragState}
141153
/>
142154

155+
{preloadedConfigProfile && (
156+
<MobileConfigImportChooserModal
157+
profile={preloadedConfigProfile}
158+
onSelect={(settings) => selectPreloadedConfig(settings, preloadedConfigProfile)}
159+
onClose={clearPreloadedConfig}
160+
/>
161+
)}
162+
143163
{showAccountModal && (
144164
<AccountModal
145165
account={editingAccount}

src/components/modals/AccountModal/AccountModal.tsx

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
} from '$components/modals/AccountModal/QuickConnectFlow';
1515
import { QuickConnectFlow } from '$components/modals/AccountModal/QuickConnectFlow';
1616
import { ServerTypePicker } from '$components/modals/AccountModal/ServerTypePicker';
17+
import { MobileConfigSignatureWarning } from '$components/modals/MobileConfigSignatureWarning';
1718
import { getPredefinedServerUrl, SERVER_TYPE_OPTIONS } from '$constants/settings';
1819
import { useConfirmDialog } from '$context/confirmDialogContext';
1920
import { useAddCalendar, useCreateAccount, useUpdateAccount } from '$hooks/queries/useAccounts';
@@ -32,7 +33,7 @@ import { ensureTagExists } from '$lib/store/sync';
3233
import { createTask } from '$lib/store/tasks';
3334
import { isCertError, tauriRequest } from '$lib/tauriHttp';
3435
import type { Account, Calendar, ServerType } from '$types';
35-
import type { MobileConfigCalDAVSettings } from '$types/mobileconfig';
36+
import type { MobileConfigImportSelection } from '$types/mobileconfig';
3637
import { generateUUID } from '$utils/misc';
3738

3839
const log = loggers.account;
@@ -50,7 +51,7 @@ const CONNECT_METHOD_SERVER_TYPES = new Set<ServerType>([
5051
interface AccountModalProps {
5152
account: Account | null;
5253
onClose: () => void;
53-
preloadedConfig?: MobileConfigCalDAVSettings;
54+
preloadedConfig?: MobileConfigImportSelection;
5455
zIndex?: 'z-60' | 'z-70';
5556
}
5657

@@ -66,27 +67,30 @@ export function AccountModal({
6667
const createAccountMutation = useCreateAccount();
6768
const updateAccountMutation = useUpdateAccount();
6869
const addCalendarMutation = useAddCalendar();
70+
const preloadedSettings = preloadedConfig?.settings;
6971

7072
const hasInitialType = !!(account || preloadedConfig);
7173
const [step, setStep] = useState<Step>(hasInitialType ? 'credentials' : 'pick-type');
7274

73-
const [name, setName] = useState(() => preloadedConfig?.accountName || account?.name || '');
75+
const [name, setName] = useState(() => preloadedSettings?.accountName || account?.name || '');
7476
const [icon, setIcon] = useState(() => account?.icon || 'user');
7577
const [emoji, setEmoji] = useState(() => account?.emoji || '');
7678
const [serverUrl, setServerUrl] = useState(
77-
() => preloadedConfig?.serverUrl || account?.caldav?.serverUrl || '',
79+
() => preloadedSettings?.serverUrl || account?.caldav?.serverUrl || '',
7880
);
7981
const [username, setUsername] = useState(
80-
() => preloadedConfig?.username || account?.caldav?.username || '',
82+
() => preloadedSettings?.username || account?.caldav?.username || '',
8183
);
82-
const [password, setPassword] = useState(() => preloadedConfig?.password || '');
84+
const [password, setPassword] = useState(() => preloadedSettings?.password || '');
8385
const [serverType, setServerType] = useState<ServerType>(
84-
() => preloadedConfig?.serverType || account?.caldav?.serverType || 'generic',
86+
() => preloadedSettings?.serverType || account?.caldav?.serverType || 'generic',
8587
);
8688
const [calendarHomeUrl, setCalendarHomeUrl] = useState(
8789
() => account?.caldav?.calendarHomeUrl || '',
8890
);
89-
const [principalUrl, setPrincipalUrl] = useState(() => account?.caldav?.principalUrl || '');
91+
const [principalUrl, setPrincipalUrl] = useState(
92+
() => preloadedSettings?.principalUrl || account?.caldav?.principalUrl || '',
93+
);
9094
const [isLoading, setIsLoading] = useState(false);
9195
const [isTesting, setIsTesting] = useState(false);
9296
const [testSuccess, setTestSuccess] = useState(false);
@@ -728,32 +732,39 @@ export function AccountModal({
728732
)}
729733

730734
{step === 'credentials' && (
731-
<CredentialsForm
732-
serverType={serverType}
733-
name={name}
734-
onNameChange={setName}
735-
icon={icon}
736-
onIconChange={setIcon}
737-
emoji={emoji}
738-
onEmojiChange={setEmoji}
739-
serverUrl={serverUrl}
740-
onServerUrlChange={setServerUrl}
741-
username={username}
742-
onUsernameChange={setUsername}
743-
password={password}
744-
onPasswordChange={setPassword}
745-
principalUrl={principalUrl}
746-
onPrincipalUrlChange={setPrincipalUrl}
747-
calendarHomeUrl={calendarHomeUrl}
748-
onCalendarHomeUrlChange={setCalendarHomeUrl}
749-
account={account}
750-
error={setupError}
751-
setupNotice={setupNotice}
752-
testSuccess={testSuccess}
753-
testedCalendarCount={testedCalendars.length}
754-
testedPushSupportedCount={testedCalendars.filter((c) => c.pushSupported).length}
755-
onSubmit={handleSubmit}
756-
/>
735+
<div>
736+
{preloadedConfig?.signature === 'signed-unverified' && (
737+
<div className="px-4 pt-4">
738+
<MobileConfigSignatureWarning signature={preloadedConfig.signature} />
739+
</div>
740+
)}
741+
<CredentialsForm
742+
serverType={serverType}
743+
name={name}
744+
onNameChange={setName}
745+
icon={icon}
746+
onIconChange={setIcon}
747+
emoji={emoji}
748+
onEmojiChange={setEmoji}
749+
serverUrl={serverUrl}
750+
onServerUrlChange={setServerUrl}
751+
username={username}
752+
onUsernameChange={setUsername}
753+
password={password}
754+
onPasswordChange={setPassword}
755+
principalUrl={principalUrl}
756+
onPrincipalUrlChange={setPrincipalUrl}
757+
calendarHomeUrl={calendarHomeUrl}
758+
onCalendarHomeUrlChange={setCalendarHomeUrl}
759+
account={account}
760+
error={setupError}
761+
setupNotice={setupNotice}
762+
testSuccess={testSuccess}
763+
testedCalendarCount={testedCalendars.length}
764+
testedPushSupportedCount={testedCalendars.filter((c) => c.pushSupported).length}
765+
onSubmit={handleSubmit}
766+
/>
767+
</div>
757768
)}
758769
</div>
759770
</ModalWrapper>

src/components/modals/MobileConfigExportModal.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import Download from 'lucide-react/icons/download';
22
import KeyRound from 'lucide-react/icons/key-round';
33
import Smartphone from 'lucide-react/icons/smartphone';
4+
import TriangleAlert from 'lucide-react/icons/triangle-alert';
45
import Wifi from 'lucide-react/icons/wifi';
56
import { useState } from 'react';
67
import { ModalButton } from '$components/ModalButton';
78
import { ModalWrapper } from '$components/ModalWrapper';
9+
import {
10+
getMobileConfigCredentialWarnings,
11+
getMobileConfigExportEligibility,
12+
} from '$lib/mobileconfig/generate';
813
import type { Account } from '$types';
914
import { isMacPlatform } from '$utils/platform';
1015

@@ -20,6 +25,9 @@ export const MobileConfigExportModal = ({
2025
onClose,
2126
}: MobileConfigExportModalProps) => {
2227
const [includePassword, setIncludePassword] = useState(false);
28+
const credentialWarnings = getMobileConfigCredentialWarnings(account, includePassword);
29+
const eligibility = getMobileConfigExportEligibility(account);
30+
const hasOAuthTokenWarning = credentialWarnings.includes('oauth-token-may-expire');
2331

2432
return (
2533
<ModalWrapper
@@ -34,7 +42,7 @@ export const MobileConfigExportModal = ({
3442
<ModalButton variant="secondary" onClick={onClose}>
3543
Cancel
3644
</ModalButton>
37-
<ModalButton onClick={() => onConfirm(includePassword)}>
45+
<ModalButton onClick={() => onConfirm(includePassword)} disabled={!eligibility.eligible}>
3846
<Download className="h-4 w-4" />
3947
Export
4048
</ModalButton>
@@ -88,6 +96,17 @@ export const MobileConfigExportModal = ({
8896
</a>
8997
</p>
9098

99+
{!eligibility.eligible && (
100+
<div className="flex gap-2 rounded-lg border border-semantic-error/30 bg-semantic-error/10 px-3 py-2 text-surface-700 text-xs dark:text-surface-300">
101+
<TriangleAlert className="mt-px size-3.5 shrink-0 text-semantic-error" />
102+
<span>
103+
{eligibility.reason === 'local-account'
104+
? 'Local accounts cannot be exported as CalDAV configuration profiles.'
105+
: 'This account has an invalid CalDAV server URL, so Chiri cannot generate a profile for it.'}
106+
</span>
107+
</div>
108+
)}
109+
91110
{/* Password option */}
92111
<div className="rounded-lg border border-surface-200 dark:border-surface-700">
93112
<label className="flex cursor-pointer items-start gap-3 p-3">
@@ -105,12 +124,22 @@ export const MobileConfigExportModal = ({
105124
</p>
106125
<p className="mt-0.5 text-surface-500 text-xs leading-relaxed dark:text-surface-400">
107126
{includePassword
108-
? 'Password will be embedded. The device signs in automatically with no prompts.'
127+
? 'The credential will be embedded. The device signs in automatically with no prompts.'
109128
: 'Password will not be stored in the file. The device will ask for it during installation.'}
110129
</p>
111130
</div>
112131
</label>
113132
</div>
133+
134+
{hasOAuthTokenWarning && (
135+
<div className="flex gap-2 rounded-lg border border-semantic-warning/30 bg-semantic-warning/10 px-3 py-2 text-surface-700 text-xs dark:text-surface-300">
136+
<TriangleAlert className="mt-px size-3.5 shrink-0 text-semantic-warning" />
137+
<span>
138+
This account uses OAuth. Chiri can export the current token, but Apple cannot refresh
139+
it from the profile; use an app password if you need a long-lived setup file.
140+
</span>
141+
</div>
142+
)}
114143
</div>
115144
</ModalWrapper>
116145
);

0 commit comments

Comments
 (0)