11import { useState , useEffect , useRef } from 'react' ;
22import { motion , AnimatePresence } from 'framer-motion' ;
33import type { SigningStatus , SigningDefaults } from '../types' ;
4- import { getSigningStatus , getSigning , saveSigning } from '../api' ;
4+ import { getSigningStatus , getSigning , saveSigning , getState } from '../api' ;
55
66const pageVariants = {
77 initial : { opacity : 0 } ,
@@ -10,6 +10,7 @@ const pageVariants = {
1010} ;
1111
1212type Platform = 'darwin' | 'windows' | 'linux' ;
13+ type HostOS = 'darwin' | 'windows' | 'linux' ;
1314
1415interface Props {
1516 onNext : ( ) => void ;
@@ -22,6 +23,7 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
2223 const [ status , setStatus ] = useState < SigningStatus | null > ( null ) ;
2324 const [ config , setConfig ] = useState < SigningDefaults | null > ( null ) ;
2425 const [ loading , setLoading ] = useState ( true ) ;
26+ const [ hostOS , setHostOS ] = useState < HostOS > ( 'linux' ) ;
2527 const [ selectedPlatform , setSelectedPlatform ] = useState < Platform > ( 'darwin' ) ;
2628 const [ configuring , setConfiguring ] = useState ( false ) ;
2729 const [ saving , setSaving ] = useState ( false ) ;
@@ -34,9 +36,12 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
3436
3537 const loadData = async ( ) => {
3638 try {
37- const [ s , c ] = await Promise . all ( [ getSigningStatus ( ) , getSigning ( ) ] ) ;
39+ const [ s , c , state ] = await Promise . all ( [ getSigningStatus ( ) , getSigning ( ) , getState ( ) ] ) ;
3840 setStatus ( s ) ;
3941 setConfig ( c || { darwin : { } , windows : { } , linux : { } } ) ;
42+ if ( state . system ?. os ) {
43+ setHostOS ( state . system . os as HostOS ) ;
44+ }
4045 } catch ( e ) {
4146 console . error ( 'Failed to load signing data:' , e ) ;
4247 } finally {
@@ -62,8 +67,24 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
6267 if ( ! config ) return null ;
6368
6469 if ( selectedPlatform === 'darwin' ) {
70+ const isOnMac = hostOS === 'darwin' ;
71+
6572 return (
6673 < div className = "space-y-4" >
74+ { ! isOnMac && (
75+ < div className = "p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-sm" >
76+ < p className = "text-amber-800 dark:text-amber-200 font-medium mb-1" > Cross-platform signing</ p >
77+ < p className = "text-amber-700 dark:text-amber-300 text-xs" >
78+ You can sign macOS apps from { hostOS === 'linux' ? 'Linux' : 'Windows' } using{ ' ' }
79+ < a href = "https://github.com/indygreg/apple-platform-rs/tree/main/apple-codesign"
80+ target = "_blank"
81+ rel = "noopener noreferrer"
82+ className = "underline hover:no-underline" > rcodesign</ a > .
83+ You'll need a .p12 certificate file exported from a Mac.
84+ </ p >
85+ </ div >
86+ ) }
87+
6788 < div >
6889 < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
6990 Signing Identity
@@ -78,9 +99,11 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
7899 placeholder = "Developer ID Application: Your Name (TEAMID)"
79100 className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
80101 />
81- < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
82- Find with: < code className = "bg-gray-100 dark:bg-gray-800 px-1 rounded" > security find-identity -v -p codesigning</ code >
83- </ p >
102+ { isOnMac && (
103+ < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
104+ Find with: < code className = "bg-gray-100 dark:bg-gray-800 px-1 rounded" > security find-identity -v -p codesigning</ code >
105+ </ p >
106+ ) }
84107 </ div >
85108
86109 < div >
@@ -99,9 +122,30 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
99122 />
100123 </ div >
101124
125+ { ! isOnMac && (
126+ < div >
127+ < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
128+ P12 Certificate Path
129+ </ label >
130+ < input
131+ type = "text"
132+ value = { config . darwin ?. p12Path || '' }
133+ onChange = { ( e ) => setConfig ( {
134+ ...config ,
135+ darwin : { ...config . darwin , p12Path : e . target . value }
136+ } ) }
137+ placeholder = "/path/to/certificate.p12"
138+ className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
139+ />
140+ < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
141+ Export from Keychain Access on a Mac, or generate via Apple Developer Portal
142+ </ p >
143+ </ div >
144+ ) }
145+
102146 < div >
103147 < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
104- Notarization Profile (optional)
148+ Notarization Profile { ! isOnMac && '(Mac only)' }
105149 </ label >
106150 < input
107151 type = "text"
@@ -111,12 +155,82 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
111155 darwin : { ...config . darwin , keychainProfile : e . target . value }
112156 } ) }
113157 placeholder = "notarytool-profile"
114- className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
158+ disabled = { ! isOnMac }
159+ className = { `w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500 ${ ! isOnMac ? 'opacity-50 cursor-not-allowed' : '' } ` }
115160 />
116- < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
117- Create with: < code className = "bg-gray-100 dark:bg-gray-800 px-1 rounded" > xcrun notarytool store-credentials</ code >
118- </ p >
161+ { isOnMac && (
162+ < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
163+ Create with: < code className = "bg-gray-100 dark:bg-gray-800 px-1 rounded" > xcrun notarytool store-credentials</ code >
164+ </ p >
165+ ) }
166+ { ! isOnMac && (
167+ < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
168+ For cross-platform notarization, use App Store Connect API keys instead
169+ </ p >
170+ ) }
119171 </ div >
172+
173+ { ! isOnMac && (
174+ < >
175+ < div className = "border-t border-gray-200 dark:border-gray-700 pt-4 mt-4" >
176+ < p className = "text-xs text-gray-500 dark:text-gray-400 mb-3 font-medium" >
177+ App Store Connect API (for notarization from { hostOS === 'linux' ? 'Linux' : 'Windows' } )
178+ </ p >
179+ </ div >
180+ < div >
181+ < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
182+ API Key ID
183+ </ label >
184+ < input
185+ type = "text"
186+ value = { config . darwin ?. apiKeyID || '' }
187+ onChange = { ( e ) => setConfig ( {
188+ ...config ,
189+ darwin : { ...config . darwin , apiKeyID : e . target . value }
190+ } ) }
191+ placeholder = "ABC123DEF4"
192+ className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
193+ />
194+ </ div >
195+ < div >
196+ < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
197+ Issuer ID
198+ </ label >
199+ < input
200+ type = "text"
201+ value = { config . darwin ?. apiIssuerID || '' }
202+ onChange = { ( e ) => setConfig ( {
203+ ...config ,
204+ darwin : { ...config . darwin , apiIssuerID : e . target . value }
205+ } ) }
206+ placeholder = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
207+ className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
208+ />
209+ </ div >
210+ < div >
211+ < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" >
212+ API Key Path (.p8 file)
213+ </ label >
214+ < input
215+ type = "text"
216+ value = { config . darwin ?. apiKeyPath || '' }
217+ onChange = { ( e ) => setConfig ( {
218+ ...config ,
219+ darwin : { ...config . darwin , apiKeyPath : e . target . value }
220+ } ) }
221+ placeholder = "/path/to/AuthKey_ABC123DEF4.p8"
222+ className = "w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
223+ />
224+ < p className = "text-xs text-gray-500 dark:text-gray-400 mt-1" >
225+ Create at{ ' ' }
226+ < a href = "https://appstoreconnect.apple.com/access/api"
227+ target = "_blank"
228+ rel = "noopener noreferrer"
229+ className = "text-blue-500 hover:underline" > App Store Connect → Users and Access → Keys</ a >
230+ </ p >
231+ </ div >
232+ </ >
233+ ) }
120234 </ div >
121235 ) ;
122236 }
0 commit comments