只读插件
插件是在原版公开页面中增加的独立面板。它不会替换主布局,也不是 Worker 或管理后台插件。
Manifest
json
{
"schema": "nstatus-extension-v1",
"id": "example-status-panel",
"name": "Example Status Panel",
"version": "1.0.0",
"type": "plugin",
"entry": "index.html",
"permissions": ["status:read"],
"height": 360,
"author": "Developer",
"description": "A read-only status summary.",
"license": "MIT",
"files": [
"manifest.json",
"index.html",
"plugin.css",
"plugin.js",
"LICENSE"
]
}插件高度限制为 200–1200。entry 必须是包内 HTML,唯一有效权限是 status:read。
数据生命周期
- 宿主以
sandbox="allow-scripts"创建 iframe; - 插件加载包内 HTML、CSS 和 JS;
- 插件发送
nstatus:ready; - 宿主发送
nstatus:status与公开状态快照; - 每次状态刷新,宿主再次发送完整快照;
- 插件内容高度变化时可发送
nstatus:resize。
插件不能使用 nstatus:request 读取历史端点。需要完整数据导航和历史图表时,应开发 Canvas 主题或浏览器替代前端。
安全渲染
js
const root = document.querySelector('#app')
window.addEventListener('message', event => {
if (event.source !== parent) return
if (event.data?.type !== 'nstatus:status') return
if (event.data.api_version !== 'v1') return
const payload = event.data.payload || {}
const targets = Array.isArray(payload.targets) ? payload.targets : []
root.replaceChildren()
for (const target of targets) {
const row = document.createElement('p')
row.textContent = `${String(target.name || 'Unknown')}: ${target.ok ? 'Online' : 'Offline'}`
root.append(row)
}
})
parent.postMessage({ type: 'nstatus:ready' }, '*')使用 textContent 和安全 DOM API。不要把 API 字符串拼入 innerHTML,也不要把完整 payload 写到控制台。
编码标准
- 初始化和重复状态消息必须幂等;
- 字段缺失、
null、空数组和超长文本都能正常显示; - 只接受
event.source === parent的已知消息; - 忽略未知字段和消息类型;
- 所有交互可用键盘完成,焦点清晰可见;
- 不采集指纹、不做第三方遥测、不展示广告;
- 不依赖卸载回调保存任何关键状态;
- 在 320 像素宽度和
1200最大高度内仍可使用。
测试矩阵
至少覆盖:正常、全部离线、尚未探测、Agent 不上报、空目标、100 个目标、长中英文名称、恶意 HTML 字符串、重复消息、resize 上下限、浅色/深色背景和禁网 CSP。