@@ -93,6 +93,7 @@ let installer = (function () {
9393 const PREFLIGHT_URL = "https://ipa.s0n1c.ca/preflight" ;
9494 const INSTALL_BASE = "https://ipa.s0n1c.ca" ;
9595 const DEFAULT_REPO = "ProStore-iOS/ProStore" ; // repo to inspect by default
96+ const README_RAW = "https://raw.githubusercontent.com/ProStore-iOS/certificates/refs/heads/main/README.md" ;
9697
9798 // internal state
9899 let _progress = 0 ; // 0..100
@@ -131,13 +132,49 @@ let installer = (function () {
131132 return res . json ( ) ;
132133 }
133134
135+ async function _fetchText ( url , opts = { } ) {
136+ const res = await fetch ( url , opts ) ;
137+ if ( ! res . ok ) {
138+ const text = await res . text ( ) . catch ( ( ) => "" ) ;
139+ const msg = `HTTP ${ res . status } ${ text ? " - " + text : "" } ` ;
140+ const e = new Error ( msg ) ;
141+ e . status = res . status ;
142+ throw e ;
143+ }
144+ return res . text ( ) ;
145+ }
146+
147+ // Try to fetch the README and extract the Recommend Certificate short name
148+ async function _getRecommendedCertificate ( ) {
149+ try {
150+ const raw = await _fetchText ( README_RAW ) ;
151+ // Grab the Recommend Certificate section (everything after its heading until next '---' or next heading)
152+ const sectionMatch = raw . match ( / # \s * R e c o m m e n d C e r t i f i c a t e \s * ( [ \s \S ] * ?) (?: \n - - - | \n # | $ ) / i) ;
153+ const section = sectionMatch ? sectionMatch [ 1 ] : raw ;
154+
155+ // Find the first bold line inside that section: **...**
156+ const boldMatch = section . match ( / \* \* ( .+ ?) \* \* / ) ;
157+ if ( ! boldMatch ) return null ;
158+
159+ let rec = boldMatch [ 1 ] . trim ( ) ;
160+ // Remove trailing " - ❌ Revoked" or any " - ..." suffix
161+ rec = rec . replace ( / [ ' " “ ” ] / g, '' ) . split ( / \s * - \s * / ) [ 0 ] . trim ( ) ;
162+ if ( ! rec ) return null ;
163+ console . log ( "Recommended certificate parsed from README:" , rec ) ;
164+ return rec ;
165+ } catch ( e ) {
166+ console . warn ( "Failed to fetch/parse recommended certificate README:" , e ) ;
167+ return null ;
168+ }
169+ }
170+
134171 return {
135172 // Return percentage (integer)
136173 getStatus : function ( ) {
137174 return _progress ;
138175 } ,
139176
140- // Begin the install flow (auto-select latest release & preferred asset)
177+ // Begin the install flow (auto-select release & preferred asset)
141178 // options: { repo: "owner/repo", token: "GITHUB_PAT (optional)", preferPrerelease: false }
142179 // Returns a Promise that resolves to the final preflight response object when finished.
143180 beginInstall : async function ( options = { } ) {
@@ -174,29 +211,67 @@ let installer = (function () {
174211 . filter ( r => preferPrerelease ? true : ! r . prerelease )
175212 . sort ( ( a , b ) => new Date ( b . created_at ) - new Date ( a . created_at ) ) ;
176213
214+ // If no visible after filtering, fallback to non-draft
215+ let defaultRelease = null ;
177216 if ( ! visible . length ) {
178- // if strict filter removed everything, try less strict (non-draft)
179217 const fallback = releases . filter ( r => ! r . draft ) . sort ( ( a , b ) => new Date ( b . created_at ) - new Date ( a . created_at ) ) ;
180218 if ( ! fallback . length ) throw new Error ( "No suitable releases found (all drafts?)." ) ;
181- _releaseInfo = fallback [ 0 ] ;
219+ defaultRelease = fallback [ 0 ] ;
182220 } else {
183- _releaseInfo = visible [ 0 ] ; // latest
221+ defaultRelease = visible [ 0 ] ;
184222 }
185223
186224 _setProgress ( 30 ) ;
187225
226+ // TRY: get recommended certificate name from README and find matching release
227+ let chosenRelease = null ;
228+ try {
229+ const recommended = await _getRecommendedCertificate ( ) ;
230+ if ( recommended ) {
231+ // Try to find a release that mentions the recommended certificate in name, tag_name or body
232+ const found = releases . find ( r => {
233+ const hay = ( ( r . name || "" ) + " " + ( r . tag_name || "" ) + " " + ( r . body || "" ) ) . toLowerCase ( ) ;
234+ return hay . includes ( recommended . toLowerCase ( ) ) ;
235+ } ) ;
236+ if ( found ) {
237+ chosenRelease = found ;
238+ console . log ( "Using release matched to recommended certificate:" , chosenRelease . name || chosenRelease . tag_name ) ;
239+ } else {
240+ console . log ( "Recommended certificate not found in releases; will fall back to latest release." ) ;
241+ }
242+ } else {
243+ console . log ( "No recommended certificate parsed; using latest release fallback." ) ;
244+ }
245+ } catch ( e ) {
246+ console . warn ( "Error while trying to apply recommended certificate logic:" , e ) ;
247+ }
248+
249+ // If we didn't pick a recommended release, use the default latest/visible
250+ _releaseInfo = chosenRelease || defaultRelease ;
251+
252+ _setProgress ( 45 ) ;
253+
188254 // pick an asset (prefer "signed" .ipa)
189- const chosen = _chooseAssetFromRelease ( _releaseInfo ) ;
190- if ( ! chosen ) throw new Error ( "No .ipa assets found in the latest release." ) ;
255+ let chosen = _chooseAssetFromRelease ( _releaseInfo ) ;
256+ if ( ! chosen ) {
257+ // If the chosen release didn't have an ipa, try to fallback to the visible list to find any release with an .ipa
258+ const otherWithIpa = ( visible . length ? visible : releases ) . find ( r => _chooseAssetFromRelease ( r ) ) ;
259+ if ( otherWithIpa ) {
260+ _releaseInfo = otherWithIpa ;
261+ chosen = _chooseAssetFromRelease ( otherWithIpa ) ;
262+ }
263+ }
264+
265+ if ( ! chosen ) throw new Error ( "No .ipa assets found in the selected release(s)." ) ;
191266 _chosenAsset = chosen ;
192267
193- _setProgress ( 45 ) ;
268+ _setProgress ( 60 ) ;
194269
195270 // Prepare signing using preflight endpoint (same as original flow)
196271 const ipaUrl = chosen . browser_download_url ;
197272 if ( ! ipaUrl ) throw new Error ( "Chosen asset has no browser_download_url." ) ;
198273
199- _setProgress ( 60 ) ;
274+ _setProgress ( 75 ) ;
200275
201276 const resp = await fetch ( PREFLIGHT_URL , {
202277 method : "POST" ,
0 commit comments