关于 Nanote 的思考
结构模型
思想斗争共进行了四个版本,它们的图示如下
版本 1
插件与 Electron
直接通信,导致 ipc
接口泄露
版本 2
接入 qiankun
沙盒模型,但插件可以控制 props
,伪造 appName
版本 3 <current>
nanote-api
做中间代理掌握 props
,但插件仍可以通过篡改源码的方式伪造 appName
,甚至直接无视 nanote-api
版本 4 不再考虑,将跳过此版本
nanote-api
在插件开发时可引入,打包时必须去除,运行时动态引入
⭐ 版本 5 <future>
考虑直接使用 iframe
而不是 qiankun
,因为它原生的 JS、CSS 隔离机制是最好的,之前使用 qiankun
是因为安全问题,
现在可以模仿类似机制,通过 fetch
方法获取它的资源引用,最后把 nanote-api
注入,创建虚拟环境并执行
既创建了良好的隔离环境,又避免了接口被篡改,同时取到了 qiankun
和 iframe
的优点
简要描述:
首先通过 import-html-entry
获取页面的 html 模板
、样式表
、脚本
,然后随即把样式表和页面模板注入,并注释掉之前的引入如:
<!-- <link href="main.css" rel="stylesheet"></link> replaced by nanote-->
<style>
// injected by nanote
<style>
随后调用 bootstrap
生命周期函数(由插件导出),在其执行完成之后(如果是 Promise,则在 then 中),通过上述 import-html-entry
的一个函数 execScripts 开始执行上面收集到的脚本,向其传递提前准备好的 proxyWindow
,这样脚本就无法通过 window.parent 访问主页面
保持 IPC_API 私密性
原接口名称固定为 IPC_API,现在把接口作为 Symbol 属性的值,对外暴露一个一次性接口来让主页面获得该值
const key = {
// 利用 Symbol 不可遍历的特性
value: Symbol('secret'),
visited: false
}
contextBridge.exposeInMainWorld('api', {
getKey() {
if (!key.visited) {
key.visited = true
return key.value;
}
return null
},
[key.value]: {
// some api
}
})