preview-desktop-onboarding.mjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. /** Browser preview of the actual desktop HTML/CSS and controller, using fixture
  2. * services only. Run: node scripts/preview-desktop-onboarding.mjs */
  3. import { createServer } from 'node:http'
  4. import { readFile } from 'node:fs/promises'
  5. import { resolve, extname } from 'node:path'
  6. const root = resolve(import.meta.dirname, '..')
  7. const bridge = `
  8. const flowReady = import('/desktop/onboarding.mjs').then(({DesktopOnboarding}) => {
  9. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  10. const flow = new DesktopOnboarding({
  11. platform: 'darwin',
  12. remoteServer: () => 'http://192.168.1.10:15300',
  13. inspect: async () => { await delay(400); return {authenticated:false,registrationAvailable:true}; },
  14. localSession: async () => ({authenticated:true,user:{username:'admin',role:'admin'}}),
  15. register: async () => { await delay(400); },
  16. prepareLocal: async () => {
  17. for (const [stage,message] of [['checking','正在检查运行环境…'],['installing','正在准备服务组件…'],['starting','正在启动并检查服务…']]) {
  18. flow.serviceChanged({phase:'starting',stage,message}); await delay(stage==='installing'?3000:1000);
  19. }
  20. return 'http://127.0.0.1:15300';
  21. },
  22. authenticate: async ({username,password}) => { await delay(600); if(password==='error')throw new Error('用户名或密码不正确'); return {username}; },
  23. activate: async () => {},
  24. navigate: async () => { document.querySelector('#session-loading-message').textContent='预览完成:正式应用将在这里进入工作区。'; },
  25. });
  26. flow.open();
  27. return flow;
  28. });
  29. window.yaoyaoDesktop={status:()=>flowReady.then(f=>f.snapshot()),selectServer:mode=>flowReady.then(f=>f.select(mode)),prepareServer:input=>flowReady.then(f=>f.prepare(input)),login:input=>flowReady.then(f=>f.submit(input)),forceSync:()=>flowReady.then(f=>f.prepare()),logs:async()=>{document.getElementById('install-log').textContent+='\\n交互预览使用模拟服务,无本机安装日志。';}};
  30. window.addEventListener('DOMContentLoaded',()=>{document.querySelector('#session-loading-message').textContent='交互预览 · 模拟服务';});
  31. `
  32. const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.mjs': 'text/javascript', '.css': 'text/css', '.png': 'image/png', '.svg': 'image/svg+xml' }
  33. const server = createServer(async (req, res) => {
  34. const path = new URL(req.url, 'http://localhost').pathname
  35. if (path === '/preview-bridge.js') { res.setHeader('content-type', 'text/javascript'); res.end(bridge); return }
  36. if (path === '/') { res.writeHead(302, { location: '/desktop/boot.html' }); res.end(); return }
  37. const allowed = ['boot.html', 'boot.css', 'boot.js', 'boot-icons.svg', 'icon.png', 'onboarding.mjs', 'remote-login.mjs']
  38. if (!allowed.some(file => path === '/desktop/' + file)) { res.writeHead(404); res.end(); return }
  39. try {
  40. let data = await readFile(resolve(root, '.' + path))
  41. if (path.endsWith('boot.html')) data = data.toString().replace('<script src="boot.js">', '<script src="/preview-bridge.js"></script><script src="boot.js">')
  42. res.setHeader('content-type', types[extname(path)]); res.end(data)
  43. } catch { res.writeHead(404); res.end() }
  44. })
  45. server.listen(Number(process.env.PORT || 4388), '127.0.0.1', () => console.log(`Desktop onboarding preview: http://127.0.0.1:${server.address().port}`))