mirror of
https://github.com/hanzoai/extension.git
synced 2026-10-31 21:57:06 +00:00
A Microsoft Outlook mail add-in embedding Hanzo AI into email — the highest-value law-office surface after Word. Mail add-in manifest (MailApp), Read + Compose command surfaces, ribbon button opens the task pane. Quick actions: Summarize thread, Draft reply, Extract deadlines & action items, Explain plainly — all streamed (askStream/SSE). - Reuses the Office add-in's pure core identically: config.ts (api.hanzo.ai /v1 gateway), chat.ts (ask/askStream/listModels), auth.ts (@hanzo/auth OAuth2+PKCE via roamingSettings + hk- API-key path). Shares the hanzo-office IAM client — no new auth surface. - New outlook-host.ts: Office.context.mailbox.item glue (read body/subject/ from; compose prepend/setSelectedData; displayReplyForm) + pure shaping helpers (stripHtml, collapseWhitespace, shapeMessage, formatFrom, mailMode). - build.js mirrors office (esbuild -> dist/, base-URL stamp, dev HTTPS serve). - 39 vitest tests pass; manifest validated by office-addin-manifest. - Registered packages/outlook in pnpm-workspace.yaml.
75 lines
2.7 KiB
JavaScript
75 lines
2.7 KiB
JavaScript
// Self-contained HTTPS static server for local Office sideload testing on any
|
|
// machine (Windows included). It serves the files next to it — the built
|
|
// dist/ — at https://localhost:3000, which is the base a `localhost` build of
|
|
// the add-in points its manifest at. No framework, only Node stdlib.
|
|
//
|
|
// 1. npx office-addin-dev-certs install # trust a localhost HTTPS cert (once)
|
|
// 2. node serve.mjs # serve dist/ at https://localhost:3000
|
|
// 3. sideload manifest.xml (see SIDELOAD-WINDOWS.md)
|
|
//
|
|
// The cert/key are read from the office-addin-dev-certs default location
|
|
// (~/.office-addin-dev-certs). If they are absent the script prints the exact
|
|
// install command instead of failing cryptically.
|
|
|
|
import { createServer } from 'node:https';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { existsSync } from 'node:fs';
|
|
import { join, extname, normalize } from 'node:path';
|
|
import { homedir } from 'node:os';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = fileURLToPath(new URL('.', import.meta.url));
|
|
const PORT = Number(process.env.PORT || 3000);
|
|
const CERT_DIR = join(homedir(), '.office-addin-dev-certs');
|
|
|
|
const TYPES = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.js': 'text/javascript; charset=utf-8',
|
|
'.map': 'application/json',
|
|
'.json': 'application/json',
|
|
'.xml': 'text/xml',
|
|
'.png': 'image/png',
|
|
'.css': 'text/css',
|
|
};
|
|
|
|
async function loadCerts() {
|
|
const cert = join(CERT_DIR, 'localhost.crt');
|
|
const key = join(CERT_DIR, 'localhost.key');
|
|
if (!existsSync(cert) || !existsSync(key)) {
|
|
console.error(
|
|
'No localhost HTTPS certificate found.\n' +
|
|
'Install a trusted one first (Office refuses an untrusted task-pane origin):\n\n' +
|
|
' npx office-addin-dev-certs install\n',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
return { cert: await readFile(cert), key: await readFile(key) };
|
|
}
|
|
|
|
const { cert, key } = await loadCerts();
|
|
|
|
createServer({ cert, key }, async (req, res) => {
|
|
// Resolve the request path safely under ROOT (no traversal).
|
|
let rel = decodeURIComponent((req.url || '/').split('?')[0]);
|
|
if (rel === '/') rel = '/taskpane.html';
|
|
const path = normalize(join(ROOT, rel));
|
|
if (!path.startsWith(ROOT)) {
|
|
res.writeHead(403).end('forbidden');
|
|
return;
|
|
}
|
|
try {
|
|
const body = await readFile(path);
|
|
res.writeHead(200, {
|
|
'Content-Type': TYPES[extname(path)] || 'application/octet-stream',
|
|
// Office loads the pane in an iframe; allow it and permit the API host.
|
|
'Access-Control-Allow-Origin': '*',
|
|
});
|
|
res.end(body);
|
|
} catch {
|
|
res.writeHead(404).end('not found: ' + rel);
|
|
}
|
|
}).listen(PORT, () => {
|
|
console.log(`Hanzo Office add-in served at https://localhost:${PORT}`);
|
|
console.log('Sideload manifest.xml — see SIDELOAD-WINDOWS.md.');
|
|
});
|