experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(tile-ipc): tile:backup/shell/app strict shims for settings migration (Phase 3.11b-shims)

Add 7 new trustedBuiltin-gated IPC handlers to tile-ipc.ts mirroring legacy
backup-*, shell-open-path, default-browser-status, set-default-browser, and
get-app-prefs channels. Add corresponding api.backup, api.shell, and api.app
wrapper namespaces to tile-preload.cts. Additive only — legacy handlers untouched.

+229 -1
+166 -1
backend/electron/tile-ipc.ts
··· 17 17 * Every handler validates the capability token before executing. 18 18 */ 19 19 20 - import { ipcMain, BrowserWindow, net, dialog, webContents, nativeTheme, screen, app } from 'electron'; 20 + import { ipcMain, BrowserWindow, net, dialog, webContents, nativeTheme, screen, app, shell } from 'electron'; 21 21 import fs from 'node:fs'; 22 22 import path from 'node:path'; 23 23 ··· 115 115 import { getActiveThemeId, setActiveThemeId, getRegisteredThemeIds, getThemePath } from './protocol.js'; 116 116 import { validateTileDatastoreRequest } from './tile-datastore-scope.js'; 117 117 import { resolveSettingDefault } from './tile-settings-defaults.js'; 118 + import { 119 + getBackupConfig, 120 + createBackup, 121 + listBackups, 122 + } from './backup.js'; 123 + import { getPrefs } from './windows.js'; 118 124 119 125 // Re-export for test access — keeps the public enforcement API on tile-ipc. 120 126 export { validateTileDatastoreRequest } from './tile-datastore-scope.js'; ··· 6460 6466 DEBUG && console.log('[tile-ipc] app:restart requested from:', args.source ?? grant.tileId); 6461 6467 app.relaunch(); 6462 6468 app.quit(); 6469 + }); 6470 + 6471 + // ── tile:backup:* strict shims (Phase 3.11b) ───────────────────────── 6472 + // 6473 + // Strict counterparts of the legacy `backup-*` channels in ipc.ts. 6474 + // All handlers require trustedBuiltin — settings/diagnostic/page-host 6475 + // tiles will consume these once they migrate off preload.js. 6476 + 6477 + ipcMain.handle('tile:backup:create', async (_event, args: { 6478 + token: string; 6479 + }) => { 6480 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6481 + const grant = getGrantForToken(args.token); 6482 + if (!grant) return { success: false, error: 'Invalid token' }; 6483 + if (!grant.trustedBuiltin) { 6484 + handleViolation(grant, 'backup', 'tile:backup:create', 'trustedBuiltin required', args.token); 6485 + return { success: false, error: 'trustedBuiltin required for tile:backup:create' }; 6486 + } 6487 + try { 6488 + const result = await createBackup(); 6489 + return result; 6490 + } catch (err) { 6491 + return { success: false, error: err instanceof Error ? err.message : String(err) }; 6492 + } 6493 + }); 6494 + 6495 + ipcMain.handle('tile:backup:list', async (_event, args: { 6496 + token: string; 6497 + }) => { 6498 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6499 + const grant = getGrantForToken(args.token); 6500 + if (!grant) return { success: false, error: 'Invalid token' }; 6501 + if (!grant.trustedBuiltin) { 6502 + handleViolation(grant, 'backup', 'tile:backup:list', 'trustedBuiltin required', args.token); 6503 + return { success: false, error: 'trustedBuiltin required for tile:backup:list' }; 6504 + } 6505 + try { 6506 + const backups = listBackups(); 6507 + const config = getBackupConfig(); 6508 + return { 6509 + success: true, 6510 + data: { 6511 + backups, 6512 + backupDir: config.backupDir, 6513 + }, 6514 + }; 6515 + } catch (err) { 6516 + return { success: false, error: err instanceof Error ? err.message : String(err) }; 6517 + } 6518 + }); 6519 + 6520 + ipcMain.handle('tile:backup:get-config', async (_event, args: { 6521 + token: string; 6522 + }) => { 6523 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6524 + const grant = getGrantForToken(args.token); 6525 + if (!grant) return { success: false, error: 'Invalid token' }; 6526 + if (!grant.trustedBuiltin) { 6527 + handleViolation(grant, 'backup', 'tile:backup:get-config', 'trustedBuiltin required', args.token); 6528 + return { success: false, error: 'trustedBuiltin required for tile:backup:get-config' }; 6529 + } 6530 + try { 6531 + const config = getBackupConfig(); 6532 + return { success: true, data: config }; 6533 + } catch (err) { 6534 + return { success: false, error: err instanceof Error ? err.message : String(err) }; 6535 + } 6536 + }); 6537 + 6538 + // ── tile:shell:open-path strict shim (Phase 3.11b) ─────────────────── 6539 + // 6540 + // Strict counterpart of the legacy `shell-open-path` channel. 6541 + // Requires trustedBuiltin — opens a path in the OS file manager. 6542 + 6543 + ipcMain.handle('tile:shell:open-path', async (_event, args: { 6544 + token: string; 6545 + path: string; 6546 + }) => { 6547 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6548 + const grant = getGrantForToken(args.token); 6549 + if (!grant) return { success: false, error: 'Invalid token' }; 6550 + if (!grant.trustedBuiltin) { 6551 + handleViolation(grant, 'shell', 'tile:shell:open-path', 'trustedBuiltin required', args.token); 6552 + return { success: false, error: 'trustedBuiltin required for tile:shell:open-path' }; 6553 + } 6554 + try { 6555 + const pathToOpen = args.path; 6556 + if (!pathToOpen) return { success: false, error: 'Missing path' }; 6557 + if (!fs.existsSync(pathToOpen)) { 6558 + fs.mkdirSync(pathToOpen, { recursive: true }); 6559 + } 6560 + await shell.openPath(pathToOpen); 6561 + return { success: true }; 6562 + } catch (err) { 6563 + return { success: false, error: err instanceof Error ? err.message : String(err) }; 6564 + } 6565 + }); 6566 + 6567 + // ── tile:app:default-browser-status / set-default-browser / get-prefs ── 6568 + // 6569 + // Strict counterparts of the legacy `default-browser-status`, 6570 + // `set-default-browser`, and `get-app-prefs` channels. 6571 + // All require trustedBuiltin. 6572 + 6573 + ipcMain.handle('tile:app:default-browser-status', async (_event, args: { 6574 + token: string; 6575 + }) => { 6576 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6577 + const grant = getGrantForToken(args.token); 6578 + if (!grant) return { success: false, error: 'Invalid token' }; 6579 + if (!grant.trustedBuiltin) { 6580 + handleViolation(grant, 'app', 'tile:app:default-browser-status', 'trustedBuiltin required', args.token); 6581 + return { success: false, error: 'trustedBuiltin required for tile:app:default-browser-status' }; 6582 + } 6583 + try { 6584 + const isDefaultHttp = app.isDefaultProtocolClient('http'); 6585 + const isDefaultHttps = app.isDefaultProtocolClient('https'); 6586 + return { isDefault: isDefaultHttp && isDefaultHttps }; 6587 + } catch (err) { 6588 + return { isDefault: false, error: err instanceof Error ? err.message : String(err) }; 6589 + } 6590 + }); 6591 + 6592 + ipcMain.handle('tile:app:set-default-browser', async (_event, args: { 6593 + token: string; 6594 + }) => { 6595 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6596 + const grant = getGrantForToken(args.token); 6597 + if (!grant) return { success: false, error: 'Invalid token' }; 6598 + if (!grant.trustedBuiltin) { 6599 + handleViolation(grant, 'app', 'tile:app:set-default-browser', 'trustedBuiltin required', args.token); 6600 + return { success: false, error: 'trustedBuiltin required for tile:app:set-default-browser' }; 6601 + } 6602 + try { 6603 + app.setAsDefaultProtocolClient('http'); 6604 + app.setAsDefaultProtocolClient('https'); 6605 + const isDefaultHttp = app.isDefaultProtocolClient('http'); 6606 + const isDefaultHttps = app.isDefaultProtocolClient('https'); 6607 + if (isDefaultHttp && isDefaultHttps) { 6608 + return { success: true }; 6609 + } else { 6610 + return { success: false, error: 'System did not accept the change. You may need to set the default browser in System Settings.' }; 6611 + } 6612 + } catch (err) { 6613 + return { success: false, error: err instanceof Error ? err.message : String(err) }; 6614 + } 6615 + }); 6616 + 6617 + ipcMain.handle('tile:app:get-prefs', (_event, args: { 6618 + token: string; 6619 + }) => { 6620 + if (!args?.token) return { success: false, error: 'Invalid token' }; 6621 + const grant = getGrantForToken(args.token); 6622 + if (!grant) return { success: false, error: 'Invalid token' }; 6623 + if (!grant.trustedBuiltin) { 6624 + handleViolation(grant, 'app', 'tile:app:get-prefs', 'trustedBuiltin required', args.token); 6625 + return { success: false, error: 'trustedBuiltin required for tile:app:get-prefs' }; 6626 + } 6627 + return getPrefs(); 6463 6628 }); 6464 6629 6465 6630 DEBUG && console.log('[tile-ipc] All tile IPC handlers registered');
+63
backend/electron/tile-preload.cts
··· 2045 2045 }, 2046 2046 }; 2047 2047 2048 + // ── Backup (trustedBuiltin only) ───────────────────────────────────── 2049 + // 2050 + // Strict counterparts of the legacy `backup-*` channels. Consumed by the 2051 + // settings tile once it migrates off preload.js (Phase 3.11b). 2052 + api.backup = { 2053 + create: () => { 2054 + if (!trustedBuiltin) { 2055 + return Promise.resolve({ success: false, error: 'api.backup requires trustedBuiltin' }); 2056 + } 2057 + return ipcRenderer.invoke('tile:backup:create', { token: tileToken }); 2058 + }, 2059 + list: () => { 2060 + if (!trustedBuiltin) { 2061 + return Promise.resolve({ success: false, error: 'api.backup requires trustedBuiltin' }); 2062 + } 2063 + return ipcRenderer.invoke('tile:backup:list', { token: tileToken }); 2064 + }, 2065 + getConfig: () => { 2066 + if (!trustedBuiltin) { 2067 + return Promise.resolve({ success: false, error: 'api.backup requires trustedBuiltin' }); 2068 + } 2069 + return ipcRenderer.invoke('tile:backup:get-config', { token: tileToken }); 2070 + }, 2071 + }; 2072 + 2073 + // ── Shell (trustedBuiltin only) ─────────────────────────────────────── 2074 + // 2075 + // Strict counterpart of the legacy `shell-open-path` channel. 2076 + api.shell = { 2077 + openPath: (filePath: string) => { 2078 + if (!trustedBuiltin) { 2079 + return Promise.resolve({ success: false, error: 'api.shell requires trustedBuiltin' }); 2080 + } 2081 + return ipcRenderer.invoke('tile:shell:open-path', { token: tileToken, path: filePath }); 2082 + }, 2083 + }; 2084 + 2085 + // ── App (trustedBuiltin only) — extended methods ────────────────────── 2086 + // 2087 + // Strict counterparts of the legacy `default-browser-status`, 2088 + // `set-default-browser`, and `get-app-prefs` channels. Extends the 2089 + // existing api.quit / api.restart app control surface. 2090 + api.app = { 2091 + getDefaultBrowserStatus: () => { 2092 + if (!trustedBuiltin) { 2093 + return Promise.resolve({ success: false, error: 'api.app requires trustedBuiltin' }); 2094 + } 2095 + return ipcRenderer.invoke('tile:app:default-browser-status', { token: tileToken }); 2096 + }, 2097 + setDefaultBrowser: () => { 2098 + if (!trustedBuiltin) { 2099 + return Promise.resolve({ success: false, error: 'api.app requires trustedBuiltin' }); 2100 + } 2101 + return ipcRenderer.invoke('tile:app:set-default-browser', { token: tileToken }); 2102 + }, 2103 + getPrefs: () => { 2104 + if (!trustedBuiltin) { 2105 + return Promise.resolve({ success: false, error: 'api.app requires trustedBuiltin' }); 2106 + } 2107 + return ipcRenderer.invoke('tile:app:get-prefs', { token: tileToken }); 2108 + }, 2109 + }; 2110 + 2048 2111 return api; 2049 2112 } 2050 2113