Extensions: hub bridge for tasks, contacts and calendar (asked of the person once), Sites in My Store, and four example extensions

- hub.tasks / hub.contacts / hub.calendar in the extension sandbox; each kind of access is approved by the person and remembered per extension
- My Store: Sites kind (any web link, optionally shown inside the hub)
- Examples: notes and checklists, habit tracker, kitchen converter and scaler, weather and verse of the day

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-26 09:25:39 -04:00
1 parent f292294ad7
commit efd17f4263
8 files changed
+204 -18

No files matched your search

+7
View File
@@ -43,6 +43,13 @@ const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] ==
assert.equal((await post(`/ext/${ext.id}/data`, { big: 'x'.repeat(300000) }, bob)).status, 413);
assert.deepEqual((await (await post(`/ext/${ext.id}/uninstall`, {}, bob)).json()).installed, []); assert.deepEqual(await (await fetch(`${B}/ext/${ext.id}/data`, { headers: bob })).json(), {}, 'uninstalling forgets its settings');
assert.equal((await fetch(`${B}/ext/${book.id}/page`)).status, 404, 'only extensions are framed');
// permissions an extension has been given
assert.deepEqual(await (await fetch(`${B}/ext/${ext.id}/grants`, { headers: bob })).json(), { grants: [] }); assert.equal((await post(`/ext/${ext.id}/install`, {}, bob)).status, 200);
assert.deepEqual((await (await post(`/ext/${ext.id}/grants`, { grants: ['tasks.read', 'tasks.read', 'root.everything'] }, bob)).json()).grants, ['tasks.read'], 'only known permissions are kept');
assert.deepEqual(await (await fetch(`${B}/ext/${ext.id}/grants`)).json(), { grants: [] }, 'they are per person'); await post(`/ext/${ext.id}/uninstall`, {}, bob); assert.deepEqual((await (await fetch(`${B}/ext/${ext.id}/grants`, { headers: bob })).json()).grants, [], 'removing the extension takes them back');
// a site (a link, optionally shown inside the hub)
assert.equal((await post('/items', { kind: 'link', title: 'Site' })).status, 400, 'a site needs an address'); let site = await (await post('/items', { kind: 'link', title: 'Docs', url: 'https://example.com/', embed: true, icon: 'D' })).json(); assert.equal(site.embed, true); assert.equal(site.icon, 'D');
assert.equal((await put(`/items/${site.id}/files?name=a.html`, 'x')).status, 400, 'a site has no files');
// a Seafile copy needs a sign-in
r = await post(`/items/${alb.id}/files`, { repo: 'a'.repeat(36), path: '/x.mp3' }); assert.equal(r.status, 401);
// changing, deleting