experiments in a post-browser web
10
fork

Configure Feed

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

fix(cmd): replay registered commands when cmd loads to avoid timing race

+51 -3
+51 -3
preload.js
··· 33 33 let registrationTimer = null; 34 34 const BATCH_DELAY_MS = 16; // ~1 frame 35 35 36 + // Persistent record of every command registered via this preload context. 37 + // Used to re-publish to cmd if it loads after these registrations were sent — 38 + // otherwise the initial cmd:register-batch is lost (cmd's subscriber not yet 39 + // installed). Deduped server-side by name (commandRegistry.set). 40 + const registeredCommandsByName = new Map(); 41 + let cmdReadySubscribed = false; 42 + 36 43 function flushRegistrations() { 37 44 if (pendingRegistrations.length === 0) return; 38 45 ··· 48 55 }); 49 56 50 57 DEBUG && console.log('[preload] commands.flush: sent batch of', batch.length, 'commands'); 58 + } 59 + 60 + // Subscribe once to ext:ready — when cmd extension reports ready, re-publish 61 + // the entire accumulated registration set so commands registered before cmd 62 + // loaded still land in the registry. Safe: cmd dedupes by name. 63 + function ensureCmdReadyReplaySubscribed() { 64 + if (cmdReadySubscribed) return; 65 + cmdReadySubscribed = true; 66 + 67 + const replyTopic = `ext:ready:${rndm()}`; 68 + ipcRenderer.send('subscribe', { 69 + source: sourceAddress, 70 + scope: 1, // SYSTEM — ext:ready is published on SYSTEM scope 71 + topic: 'ext:ready', 72 + replyTopic 73 + }); 74 + 75 + ipcRenderer.on(replyTopic, (_ev, msg) => { 76 + if (!msg || msg.id !== 'cmd') return; 77 + if (registeredCommandsByName.size === 0) return; 78 + 79 + const commands = Array.from(registeredCommandsByName.values()); 80 + DEBUG && console.log('[preload] cmd ready — replaying', commands.length, 'command registrations'); 81 + 82 + ipcRenderer.send('publish', { 83 + source: sourceAddress, 84 + scope: 3, // GLOBAL 85 + topic: 'cmd:register-batch', 86 + data: { commands } 87 + }); 88 + }); 51 89 } 52 90 53 91 // Context detection for permission tiers ··· 1364 1402 } 1365 1403 }); 1366 1404 1367 - // Queue registration for batching (improves startup performance) 1368 - pendingRegistrations.push({ 1405 + const registrationEntry = { 1369 1406 name: command.name, 1370 1407 description: command.description || '', 1371 1408 source: sourceAddress, ··· 1375 1412 accepts: command.accepts || [], 1376 1413 produces: command.produces || [], 1377 1414 params: command.params || [] 1378 - }); 1415 + }; 1416 + 1417 + // Queue registration for batching (improves startup performance) 1418 + pendingRegistrations.push(registrationEntry); 1419 + 1420 + // Remember every command registered through this preload so we can replay 1421 + // them when cmd extension loads (handles register-before-cmd-ready race). 1422 + registeredCommandsByName.set(command.name, registrationEntry); 1423 + ensureCmdReadyReplaySubscribed(); 1379 1424 1380 1425 // Debounce: flush after BATCH_DELAY_MS of no new registrations 1381 1426 clearTimeout(registrationTimer); ··· 1399 1444 if (window._cmdHandlers) { 1400 1445 delete window._cmdHandlers[name]; 1401 1446 } 1447 + 1448 + // Drop from replay record so it isn't re-published on the next cmd ready 1449 + registeredCommandsByName.delete(name); 1402 1450 1403 1451 // Notify cmd to remove the command (GLOBAL scope for cross-window) 1404 1452 ipcRenderer.send('publish', {