likes
comments
collection
share

Electron 会话管理

作者站长头像
站长
· 阅读数 13

浏览器向 Web 服务器发出的一系列请求被称为会话,Electron 提供的 session API 就是用于管理浏览器会话的,包括浏览器缓存、本地存储(localstorage、cookie、indexdb等)、网络代理、设备权限、请求拦截、插件安装的等。

有两种方法可以拿到 session 实例:

  • session.defaultSession应用的默认 session
  • someWebContents.session某个 webContents 使用的 session

默认情况下,整个 Electron 软件使用同一个 session 对象,创建窗口(BrowserWindow)时,如果没有在 webPreferences 中指定 session,那么就会使用默认 session 对象:

const win = new BrowserWindow({ width: 400, height: 300 })
win.loadURL('https://www.baidu.com')
console.log(session.defaultSession === win.webContents.session) // true

webPreferences 中两个属性可以创建不同的 session:

  • session:指定一个 session 对象
  • partition:指定 partition 字符串
// 指定一个 session 对象
const newSession = session.fromPartition('new')
const win = new BrowserWindow({ width: 400, height: 300, webPreferences: { session: newSession } })
win.loadURL('https://www.baidu.com')
console.log(session.defaultSession === win.webContents.session) // false
console.log(win.webContents.session === newSession) // true

// 指定 partition 字符串
win = new BrowserWindow({ width: 400, height: 300, webPreferences: { partition: 'new' } })
win.loadURL('https://www.baidu.com')
console.log(session.defaultSession === win.webContents.session) // false

当同时使用 session 和 partition 的时候,session 的优先级更高。接下来看看如何创建 session:

创建 session

提供的 fromPartition方法可以用于创建 Session 对象,那么 partition 是什么意思呢?字面含义为:

隔墙,隔板;(化学)分离层;(计算机)存储分区;(数学)分割

即通过分隔标识符来创建唯一的 Session 实例,用法如下:

const s1 = session.fromPartition('hello', { cache: false })
const s2 = session.fromPartition('world')
const s3 = session.fromPartition('hello', { cache: true })
console.log(s1 === s2) // false
console.log(s1 === s3) // true

可以看到,该函数需要两个参数:

  • partition:字面意思是分隔、隔板,用于区分是否是同一个会话
  • options:配置项,目前里面只有一个 cache字段表示是否开启缓存

这种语法有点类似于 ES6 中 Symbol 的用法:

Symbol.for() 方法创建的的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个。

如果 partition 以 persist:开头,那么整个应用的所有页面都会使用这个持久化的 session,否则页面会使用一个存储在内存当中的 session,可以通过 isPersistent方法判断是否是持久化的 session,Electron 默认的 session 是持久化的:

session.defaultSession.isPersistent() // true

使用 session

session 模块看起来非常抽象,初学者往往不知道有什么用,其实 session 的能力非常强大,接下来通过案例的方式为大家讲解 session 模块的常用 API。

设置代理

什么是代理,当电脑 A 无权限访问网络,电脑 B 可以上网,将电脑 A 连接电脑 B,然后通过 B 来上网,那么 B 就是 A 的代理服务器。常见的代理服务器有 http 代理、https 代理、socks 代理。

win = new BrowserWindow({ width: 400, height: 300 })
win.loadURL('https://www.ip138.com')

Electron 会话管理

如果在本地开启了代理,

win.webContents.session.setProxy({
  proxyRules: 'socks5://127.0.0.1:13659',
})

Electron 会话管理

win.webContents.session.resolveProxy('https://www.baidu.com')
  .then((info) => console.log(info))

如果未使用代理,返回 DIRECT,如果使用代理,返回 SOCKS5 127.0.0.1:13659

页面缓存

正常情况下,浏览器使用缓存机制来提升页面加载速度,内存和硬盘里面都会有一个缓存区,内存的缓冲区叫做 memory cache,硬盘的缓冲区叫做 disk cache,我们创建一个窗口,加载百度首页,首次加载是没有缓存的,所有资源全部通过发送 http 请求获取:

Electron 会话管理

如果你在短时间快速刷新(cmd+R),会看到,大部分的资源都变成从 memory cache 中获取的:

Electron 会话管理

如果隔一段时间刷新,内存中的部分缓存会过期,但有些资源在是 disk cache 中还能查到:

Electron 会话管理

你可能会好奇:什么时候会命中 memory cache ,什么时候回命中 disk cache?这个问题知乎上面有人问过,由于浏览器内部实现非常复杂,没有人能够解释清楚背后的具体逻辑。控制台里面的缓存来源,据说是一个开发者一时兴起加的。再回到 session 模块来,如果在创建的时候,指定了 cache 为 false:

const newSession = session.fromPartition('new', { cache: false })
win = new BrowserWindow({ width: 400, height: 300, webPreferences: { session: newSession } })

经过多次刷新,发现无论怎样 disk cache 都没了,只剩下 memory cache 了:

Electron 会话管理

所以 session 中的 cache 是用于控制是否缓存到硬盘,而并非完全禁用所有缓存(包括内存缓存、代码缓存等),表现行为有点类似于浏览器的隐私模式。所以下面的两个 API 可以暂时理解为对硬盘缓存的操作:

  • getCacheSize:获取缓存大小
  • clearCache:清空缓存

除了上述缓存之外,还有很多的缓存种类,开发中一般用不到,了解即可:

  • clearHostResolverCache:清空 host 缓存
  • clearAuthCache:清空 http 认证缓存
  • clearCodeCaches:清空代码缓存
  • setCodeCachePath:设置代码缓存路径

资源加载

设置和获取预加载的脚本,:

  • setPreloads:设置 preload 脚本
  • getPreloads:获取 preload 脚本

一般情况下,preload 脚本是在 webPreferences 中设置的,

const win = new BrowserWindow({
  width: 400,
  height: 300,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.js'),
  },
})
win.loadURL('https://www.baidu.com')

不过只能设置一个 preload 文件,通过 session 提供的 setPreloads 方法,不仅可以设置多个,而且执行顺序在比 webPreferences 更靠前,例如 preload/index.js 中打印 preload,preload/p1.js 中打印 p1,preload/p2.js 中打印 p2:

const newSession = session.fromPartition('new', { cache: false })
const win = new BrowserWindow({
  width: 400,
  height: 300,
  webPreferences: {
    session: newSession,
    preload: path.join(__dirname, '../preload/index.js'),
  },
})
win.loadURL('https://www.baidu.com')
const preloads = newSession.getPreloads()
console.log(preloads) // []
newSession.setPreloads([
  path.join(__dirname, '../preload/p1.js'),
  path.join(__dirname, '../preload/p2.js'),
])
console.log(newSession.getPreloads())
/*
[
  '/Users/keliq/electron-desktop/src-apis/session/preload/p1.js',
  '/Users/keliq/electron-desktop/src-apis/session/preload/p2.js'
]
*/

最终的效果是:

Electron 会话管理

本地存储

存储相关的操作:

  • getStoragePath:获取存储的路径(内存会话则返回 null)
  • clearStorageData:清空本地存储数据(可以指定 origin 和存储类型)
  • flushStorageData:将所有的存储数据持久化到磁盘

代码演示:

const newSession = session.fromPartition('new', { cache: false })
console.log(newSession.getStoragePath()) // null

const newSession = session.fromPartition('new', { cache: true })
console.log(newSession.getStoragePath()) // null

const newSession = session.fromPartition('persist:new', { cache: false })
console.log(newSession.getStoragePath()) // ~/Library/Application Support/electron-desktop/Partitions/new

const newSession = session.fromPartition('persist:new', { cache: true })
console.log(newSession.getStoragePath()) // ~/Library/Application Support/electron-desktop/Partitions/new

可以看到,如果以 persist:开头,那么是存在实际存储路径的,通过 getStoragePath方法拿到具体路径后,进入发现有四个目录:

  • Cache
  • Code Cache
  • Local Storage
  • blob_storage

其中 Cache 目录就是用于存放硬盘缓存数据的,后面的 clearStorageDataflushStorageData 方法也是针对于这个目录进行操作的。另外,getStoragePath 方法也可以直接用 session.storagePath属性替代,效果一样。

权限控制

session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
  desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
    callback({ video: sources[0] })
  })
})

插件管理

Electron 中也可以加载 Chrome 插件,提供了 4 个方法:

  • loadExtension:加载插件
  • removeExtension:移除插件
  • getExtension:根据 id 获取插件
  • getAllExtensions:查看所有插件

还有 3 个对应的事件:

  • extension-loaded:插件加载事件
  • extension-unloaded:插件卸载事件
  • extension-ready:插件就绪事件

文件下载

downloadURL方法可以初始化一个下载,然后在 will-download事件当中拿到 download-item 对象,可以定制下载行为。还可以通过 createInterruptedDownload来实现断点续传的效果。

操作 cookie

可以拿到所有的 cookie,包括 http-only 的也能拿到,常用方法有:

  • get:获取 cookie
  • set:设置 cookie
  • remove:删除 cookie
  • flushStore:写入磁盘

另外,changed事件还可以监听 cookie 发生变化,包括添加、删除、修改或者过期。