OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop commits / d179cae

fix: surface legacy app migration instead of remote launch
d179cae6a2b4bbfc5b073b7f36118e1d08c0aa4b
by bigdestiny2 <33146784+bigdestiny2@users.noreply.github.com> · 2026-07-22T19:42:25.000Z

Changed files (2) +27 -27

backend/index.js +20 -24
……@@ -1274,32 +1274,24 @@ rpc.handle(C.CMD_RESET_APP, async () => {
12741274
12751275rpc.handle(C.CMD_LAUNCH_PEAR_LINK, async (data) => {
12761276 const link = String(data?.link || '').trim()
1277 if (!link) throw new Error('pear:// link required')
1277 if (!link) throw new Error('legacy app link required')
12781278 if (!/^pear:\/\/.+/.test(link) && !/^file:\/\/.+/.test(link)) {
1279 throw new Error('Only pear:// and file:// links can be launched')
1280 }
1281 const keyHex = (data?.keyHex && /^[0-9a-f]{64}$/i.test(data.keyHex)) ? data.keyHex.toLowerCase() : null
1282 // Fire-and-forget: pre-download the app bundle with progress (so the UI can
1283 // render an inline bar driven by EVT_LAUNCH_PROGRESS instead of looking
1284 // hung), then spawn it. Return immediately.
1285 launchPearLinkWithProgress(link, keyHex).catch((err) => {
1286 try { rpc.event(C.EVT_LAUNCH_PROGRESS, { key: keyHex, link, phase: 'error', error: (err && err.message) || 'launch failed' }) } catch {}
1287 })
1288 return { started: true, link }
1279 throw new Error('Only legacy pear:// and file:// app links can be assessed')
1280 }
1281 return {
1282 started: false,
1283 action: 'legacy-migration-required',
1284 link,
1285 message: 'This legacy Pear app cannot be launched by PearBrowser v3. Install a verified native v3 package when its publisher provides one.'
1286 }
12891287})
12901288
1291// Spawn a pear:// / file:// app in its own window. For pear:// links with a
1292// known drive key, first pull the bundle into OUR store (which also seeds the
1293// swarm, so the subsequent pear-run launch pulls from localhost and the window
1294// appears promptly) while streaming download progress to the UI.
1289// Retained only as a compatibility seam for callers compiled against older
1290// builds. A remote link is not a v3 worker entrypoint and must never execute.
12951291async function launchPearLinkWithProgress (link, keyHex) {
12961292 const emit = (p) => { try { rpc.event(C.EVT_LAUNCH_PROGRESS, { key: keyHex, link, ...p }) } catch {} }
12971293 const spawn = () => {
1298 const run = require('pear-run')
1299 const pipe = run(link)
1300 try { pipe.on('data', () => {}) } catch {}
1301 try { pipe.on('crash', (info) => console.error('[pear-run] child crashed:', info)) } catch {}
1302 try { pipe.on('error', (err) => console.error('[pear-run] child error:', err && err.message)) } catch {}
1294 throw new Error('legacy app migration required; remote links are not v3 worker entrypoints')
13031295 }
13041296
13051297 // No drive to pre-fetch (file:// or unknown key) → straight to launch.
……@@ -1318,7 +1310,7 @@ async function launchPearLinkWithProgress (link, keyHex) {
13181310 registerHiveRelayDrive(keyHex, drive)
13191311 await updateDriveBestEffort(drive)
13201312 } catch {
1321 // Can't open it ourselves — let pear-run handle the download itself.
1313 // Do not fall back to a remote launcher; surface the migration boundary.
13221314 emit({ phase: 'launching', percent: 100 })
13231315 spawn()
13241316 emit({ phase: 'done', percent: 100 })
……@@ -1370,8 +1362,12 @@ async function launchPearLinkWithProgress (link, keyHex) {
13701362rpc.handle(C.CMD_RUN_APP_IN_TAB, async (data) => {
13711363 const link = String(data?.link || 'demo').trim()
13721364 if (!tabRuntime) throw new Error('tab runtime is not available')
1373 if (link !== 'demo' && !/^pear:\/\/.+/.test(link) && !/^file:\/\/.+/.test(link)) {
1374 throw new Error('Only the demo, or pear:// / file:// apps, can run in a tab')
1365 if (link !== 'demo') {
1366 return {
1367 action: 'legacy-migration-required',
1368 link,
1369 message: 'This legacy app needs a verified v3 package before it can run in a PearBrowser tab.'
1370 }
13751371 }
13761372 const res = tabRuntime.open(link)
13771373 console.log('[tab-runtime] run-in-tab')
……@@ -3313,7 +3309,7 @@ async function boot () {
33133309 // bridges each tab's WebSocket to a pear-request worker pipe. Best-effort —
33143310 // a failure here just means the in-tab path is unavailable, not a boot block.
33153311 try {
3316 tabRuntime = new TabRuntime({ pearRun: (link) => require('pear-run')(link) })
3312 tabRuntime = new TabRuntime()
33173313 await tabRuntime.start()
33183314 } catch (err) {
33193315 console.error('[tab-runtime] failed to start:', err && err.message)
ui/shell.js +7 -3
……@@ -1315,8 +1315,8 @@ function Browse ({ rpc, C, navUrl, onNavigated, tabs, setTabs, activeId, setActi
13151315 if (/^(?:pear|file):\/\//i.test(link)) {
13161316 updateTab(id, { status: `opening ${resolved.label || nameQuery} · ${resolved.provenance}…` })
13171317 try {
1318 await rpc.request(C.CMD_LAUNCH_PEAR_LINK, { link }, 60000)
1319 updateTab(id, { status: '' })
1318 const result = await rpc.request(C.CMD_LAUNCH_PEAR_LINK, { link }, 60000)
1319 updateTab(id, { status: result?.message || '' })
13201320 } catch (err) {
13211321 updateTab(id, { status: `error: ${err.message}` })
13221322 }
……@@ -2916,7 +2916,11 @@ function Apps ({ rpc, C, onLaunch }) {
29162916 setErr(''); setBusy('pear-link'); setLaunched('')
29172917 if (keyHex) setLaunchProg((p) => ({ ...p, [keyHex]: { phase: 'connecting', percent: 0, peers: 0, downloaded: 0, total: 0 } }))
29182918 try {
2919 await rpc.request(C.CMD_LAUNCH_PEAR_LINK, { link, keyHex }, 60000)
2919 const result = await rpc.request(C.CMD_LAUNCH_PEAR_LINK, { link, keyHex }, 60000)
2920 if (result?.action === 'legacy-migration-required') {
2921 setErr(result.message)
2922 return
2923 }
29202924 setPearLink('')
29212925 // pear:// launches show progress inline via EVT_LAUNCH_PROGRESS; file://
29222926 // and others have no bundle to track, so keep the toast for them.