experiments in a post-browser web
10
fork

Configure Feed

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

extension docs

+280
+280
extensions/README.md
··· 1 + # Peek Extensions 2 + 3 + Extensions are isolated modules that run in their own BrowserWindow processes and communicate with the core app via IPC and pubsub messaging. 4 + 5 + ## Extension Structure 6 + 7 + Each extension lives in its own directory under `extensions/`: 8 + 9 + ``` 10 + extensions/ 11 + example/ 12 + manifest.json # Extension metadata 13 + settings-schema.json # Settings UI schema (optional) 14 + background.html # Entry point (loads background.js) 15 + background.js # Main extension logic 16 + *.html, *.js, *.css # Additional UI files 17 + ``` 18 + 19 + ### manifest.json 20 + 21 + Required fields: 22 + ```json 23 + { 24 + "id": "example", 25 + "shortname": "example", 26 + "name": "Example Extension", 27 + "description": "What this extension does", 28 + "version": "1.0.0", 29 + "background": "background.html" 30 + } 31 + ``` 32 + 33 + Optional fields: 34 + ```json 35 + { 36 + "builtin": true, 37 + "settingsSchema": "./settings-schema.json" 38 + } 39 + ``` 40 + 41 + ### settings-schema.json 42 + 43 + Defines the settings UI for the extension. Used by Settings to render configuration forms. 44 + 45 + ```json 46 + { 47 + "prefs": { 48 + "type": "object", 49 + "properties": { 50 + "greeting": { 51 + "type": "string", 52 + "description": "Custom greeting message", 53 + "default": "Hello World" 54 + } 55 + } 56 + }, 57 + "storageKeys": { 58 + "PREFS": "prefs" 59 + }, 60 + "defaults": { 61 + "prefs": { 62 + "greeting": "Hello World" 63 + } 64 + } 65 + } 66 + ``` 67 + 68 + For extensions with list-based settings (like peeks/slides), add an `item` schema: 69 + ```json 70 + { 71 + "prefs": { ... }, 72 + "item": { 73 + "type": "object", 74 + "properties": { 75 + "title": { "type": "string", "title": "Title" }, 76 + "enabled": { "type": "boolean", "title": "Enabled" } 77 + } 78 + }, 79 + "storageKeys": { 80 + "PREFS": "prefs", 81 + "ITEMS": "items" 82 + }, 83 + "defaults": { 84 + "prefs": { ... }, 85 + "items": [] 86 + } 87 + } 88 + ``` 89 + 90 + ### background.html 91 + 92 + Entry point that loads the extension as an ES module: 93 + 94 + ```html 95 + <!DOCTYPE html> 96 + <html> 97 + <head> 98 + <meta charset="UTF-8"> 99 + <title>My Extension</title> 100 + </head> 101 + <body> 102 + <script type="module"> 103 + import extension from './background.js'; 104 + 105 + const api = window.app; 106 + const extId = extension.id; 107 + 108 + console.log(`[ext:${extId}] background.html loaded`); 109 + 110 + // Signal ready to main process 111 + api.publish('ext:ready', { 112 + id: extId, 113 + manifest: { 114 + id: extension.id, 115 + labels: extension.labels, 116 + version: '1.0.0' 117 + } 118 + }, api.scopes.SYSTEM); 119 + 120 + // Initialize extension 121 + if (extension.init) { 122 + console.log(`[ext:${extId}] calling init()`); 123 + extension.init(); 124 + } 125 + 126 + // Handle shutdown 127 + api.subscribe('app:shutdown', () => { 128 + if (extension.uninit) extension.uninit(); 129 + }, api.scopes.SYSTEM); 130 + 131 + api.subscribe(`ext:${extId}:shutdown`, () => { 132 + if (extension.uninit) extension.uninit(); 133 + }, api.scopes.SYSTEM); 134 + </script> 135 + </body> 136 + </html> 137 + ``` 138 + 139 + ### background.js 140 + 141 + Main extension logic as an ES module: 142 + 143 + ```javascript 144 + const api = window.app; 145 + 146 + const extension = { 147 + id: 'example', 148 + labels: { 149 + name: 'Example' 150 + }, 151 + 152 + init() { 153 + console.log('[example] init'); 154 + 155 + // Register commands 156 + api.commands.register({ 157 + name: 'my-command', 158 + description: 'Does something', 159 + execute: () => { 160 + console.log('Command executed!'); 161 + } 162 + }); 163 + 164 + // Register shortcuts 165 + api.shortcuts.register('Option+x', () => { 166 + console.log('Shortcut triggered!'); 167 + }); 168 + 169 + // Subscribe to events 170 + api.subscribe('some:event', (msg) => { 171 + console.log('Event received:', msg); 172 + }, api.scopes.GLOBAL); 173 + }, 174 + 175 + uninit() { 176 + console.log('[example] uninit'); 177 + api.commands.unregister('my-command'); 178 + api.shortcuts.unregister('Option+x'); 179 + } 180 + }; 181 + 182 + export default extension; 183 + ``` 184 + 185 + ## Extension API 186 + 187 + Extensions access the API via `window.app`: 188 + 189 + ### Commands 190 + ```javascript 191 + api.commands.register({ name, description, execute }) 192 + api.commands.unregister(name) 193 + ``` 194 + 195 + ### Shortcuts 196 + ```javascript 197 + api.shortcuts.register(shortcut, callback) // e.g., 'Option+1' 198 + api.shortcuts.unregister(shortcut) 199 + ``` 200 + 201 + ### Pubsub Messaging 202 + ```javascript 203 + api.publish(topic, data, scope) 204 + api.subscribe(topic, callback, scope) 205 + 206 + // Scopes 207 + api.scopes.SELF // Only this window 208 + api.scopes.SYSTEM // System-level events 209 + api.scopes.GLOBAL // All windows 210 + ``` 211 + 212 + ### Windows 213 + ```javascript 214 + api.window.open(url, options) 215 + // Options: modal, keepLive, transparent, height, width, key 216 + ``` 217 + 218 + ### Datastore 219 + ```javascript 220 + await api.datastore.getRow(table, id) 221 + await api.datastore.setRow(table, id, data) 222 + await api.datastore.deleteRow(table, id) 223 + await api.datastore.getTable(table) 224 + ``` 225 + 226 + ### Extension Settings 227 + ```javascript 228 + await api.extensions.getSettings(extId) 229 + await api.extensions.setSettings(extId, key, value) 230 + ``` 231 + 232 + ## Extension Loading 233 + 234 + ### Built-in Extensions 235 + 236 + Built-in extensions are registered in `index.js`: 237 + ```javascript 238 + registerExtensionPath('example', path.join(__dirname, 'extensions', 'example')); 239 + ``` 240 + 241 + And listed in `loadEnabledExtensions()`: 242 + ```javascript 243 + const builtinExtensions = ['groups', 'peeks', 'slides']; 244 + ``` 245 + 246 + ### External Extensions 247 + 248 + External extensions are: 249 + 1. Added via Settings UI (stored in datastore `extensions` table) 250 + 2. Loaded on startup if `enabled === 1` and have a valid `path` 251 + 252 + ## Settings Integration 253 + 254 + Extensions with `settingsSchema` in their manifest automatically get a settings section in the Settings UI. The schema is loaded at runtime when the extension window is created. 255 + 256 + Settings are stored in the `extension_settings` datastore table with: 257 + - `extensionId`: The extension's ID 258 + - `key`: Setting key (e.g., 'prefs', 'items') 259 + - `value`: JSON-encoded setting value 260 + 261 + Extensions can listen for settings changes: 262 + ```javascript 263 + api.subscribe(`${extId}:settings-changed`, (msg) => { 264 + // Reload configuration 265 + }, api.scopes.GLOBAL); 266 + ``` 267 + 268 + ## Lifecycle Events 269 + 270 + - `ext:ready` - Published when extension is initialized 271 + - `ext:all-loaded` - Published when all extensions finish loading 272 + - `app:shutdown` - Sent before app closes 273 + - `ext:{id}:shutdown` - Sent when specific extension is being unloaded 274 + - `{extId}:settings-changed` - Sent when extension settings are modified 275 + 276 + ## Debugging 277 + 278 + Console logs from extensions are forwarded to stdout with prefix `[ext:{id}]`. 279 + 280 + Run with `DEBUG=1 yarn start` for verbose logging.