Electron Cookies API 详解:Session 级 Cookie 的查询、写入、删除与变更监听完整指南
本文围绕 Electron 的 Cookies 类 API 展开,完整覆盖 docs/api/cookies.md 中定义的实例事件与四个实例方法,并结合浏览器进程侧的 C++ 实现(shell/browser/api/electron_api_cookies.cc)与官方测试用例(spec/api-session-spec.ts)解释每个参数在底层是如何解析和生效的。读完本文,你可以掌握在 Electron 主进程中按 URL/域名/属性精确查询 Cookie、正确设置 SameSite 与安全属性、删除指定 Cookie、控制 Cookie 落盘时机,以及如何通过 changed 事件监听 Cookie 的全生命周期变化。
1. Cookies 类的定位与获取方式
Cookies 类用于查询和修改某个 Session 的 Cookie("Query and modify a session's cookies")。它有两个关键特性,决定了使用姿势:
- 只在主进程可用:
Cookies属于 Main Process 的 API; - 不从
'electron'模块导出:它只能通过其他 API 的返回值获得,即作为Session的cookies属性访问。
const { session } = require('electron')
// 查询所有 cookies。
session.defaultSession.cookies.get({})
.then((cookies) => {
console.log(cookies)
}).catch((error) => {
console.log(error)
})
// 查询与某个 URL 关联的所有 cookies。
session.defaultSession.cookies.get({ url: 'https://www.github.com' })
.then((cookies) => {
console.log(cookies)
}).catch((error) => {
console.log(error)
})
// 设置一个 cookie;若存在等价 cookie 则覆盖。
const cookie = { url: 'https://www.github.com', name: 'dummy_name', value: 'dummy' }
session.defaultSession.cookies.set(cookie)
.then(() => {
// success
}, (error) => {
console.error(error)
})
从源码结构看,每个 Cookies 实例在构造时持有一个 ElectronBrowserContext*(见 shell/browser/api/electron_api_cookies.h 中的 browser_context_ 成员),并且所有 get/set/remove/flushStore 操作都通过 browser_context_->GetDefaultStoragePartition()->GetCookieManagerForBrowserProcess() 拿到对应的 Cookie 管理器来执行。这意味着 Cookie 是按 Session(及其底层的 BrowserContext)隔离的:defaultSession 与各 session.fromPartition('...') 分区各自拥有独立的 Cookie 存储。测试中也是通过 testSession.cookies.set/get 在分区会话上独立操作验证了这一点(见 spec/api-session-spec.ts 中 testSession.cookies 相关断言)。
2. 返回值中的 Cookie 对象结构
所有查询结果与变更事件中的 cookie 参数都是 Cookie Object,字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | Cookie 名称 |
value |
string | Cookie 值 |
domain |
string(可选) | Cookie 域名;会规范化为带前导点的形式,使其对子域名同样有效 |
hostOnly |
boolean(可选) | 是否为 host-only cookie;仅在设置时未传 domain 时才可能为 true |
path |
string(可选) | Cookie 路径 |
secure |
boolean(可选) | 是否标记为 Secure |
httpOnly |
boolean(可选) | 是否标记为 HTTP Only |
session |
boolean(可选) | 是会话 Cookie 还是带过期时间的持久 Cookie |
expirationDate |
Double(可选) | 过期时间,UNIX 纪元起的秒数;会话 Cookie 不提供该字段 |
sameSite |
string | Same Site 策略:unspecified、no_restriction、lax 或 strict |
这个结构不是手工拼装的,而是由 C++ 转换器直接从 net::CanonicalCookie 生成:shell/browser/api/electron_api_cookies.cc 中的 Converter<net::CanonicalCookie> 会把 Chromium 网络栈的规范化 Cookie 逐字段映射为 JS 对象,其中 hostOnly 由 net::cookie_util::DomainIsHostOnly 判定,session 由 !val.IsPersistent() 得出——这也解释了为什么文档规定 expirationDate 只在持久 Cookie 上出现。
3. 实例事件:changed
Cookies 实例支持如下事件:
事件:changed
返回值参数:
eventEventcookieCookie — 发生变化的那个 Cookiecausestring — 变更原因,取以下值之一:inserted— Cookie 被插入。inserted-no-change-overwrite— 新插入的 Cookie 覆盖了旧 Cookie 但没有产生任何变化(例如插入一个完全相同的 Cookie)。inserted-no-value-change-overwrite— 新插入的 Cookie 覆盖了旧 Cookie,值没有变化,但对 Web 可观测(例如更新了过期时间)。explicit— Cookie 被消费者直接删除。overwrite— Cookie 因一次覆盖写入操作而被自动移除。expired— Cookie 因过期被自动移除。evicted— Cookie 在垃圾回收过程中被自动逐出。expired-overwrite— Cookie 被写入一个已经过期的过期时间而覆盖。
removedboolean — 若该 Cookie 被移除则为true,否则为false。
当某个 Cookie 因新增、编辑、删除或过期而发生变化时触发。
3.1 变更通知的底层链路
从源码看,这条链路分为两跳:
-
网络进程到浏览器进程:
Cookies构造函数中通过browser_context_->cookie_change_notifier()->RegisterCookieChangeCallback(...)订阅变更回调。CookieChangeNotifier(shell/browser/cookie_change_notifier.h)实现了network::mojom::CookieChangeListener接口,把网络服务的 Mojo Cookie 变更通知转发到 UI 线程,再分发给所有注册者。 -
事件发射与
removed的判定:Cookies::OnCookieChanged(shell/browser/api/electron_api_cookies.cc#L456-L463)收到net::CookieChangeInfo后,调用IsDeletion(change.cause)计算第三个参数removed,然后以Emit("changed", cookie, cause, removed)发出事件。
关于 cause 到字符串的映射与 removed 的判定逻辑,可以直接在 shell/browser/api/electron_api_cookies.cc 的 Converter<net::CookieChangeCause> 和 同文件的 IsDeletion 函数 中一一对应:inserted、inserted-no-change-overwrite、inserted-no-value-change-overwrite 三类对应 removed = false,其余原因(explicit/overwrite/expired/evicted/expired-overwrite)均视为删除,removed = true。这与文档中各 cause 的语义描述完全一致。
官方测试 spec/api-session-spec.ts 中的 collectCookieChanges 辅助函数展示了监听 changed 事件的典型用法:注册监听器 → 执行 set/remove 等动作 → 按收到的变更条数收集 { cause, cookie, removed },可用于回归验证 Cookie 生命周期行为。
4. cookies.get(filter):按条件查询 Cookie
cookies.get(filter)
filterObjecturlstring(可选)— 获取与该url关联的 Cookie;留空表示获取所有 URL 的 Cookie。namestring(可选)— 按名称过滤。domainstring(可选)— 获取域名匹配domain或其子域名的 Cookie。pathstring(可选)— 获取路径匹配path的 Cookie。secureboolean(可选)— 按 Secure 属性过滤。sessionboolean(可选)— 过滤会话 Cookie 或持久 Cookie。httpOnlyboolean(可选)— 按 httpOnly 过滤。
返回 Promise<Cookie[]>,解析为一个 Cookie 对象数组。语义是“发送请求获取所有匹配 filter 的 Cookie 并解析 Promise”。
4.1 两条查询路径(源码层面)
Cookies::Get 的实现(shell/browser/api/electron_api_cookies.cc#L310-L340)按 url 是否为空分两条路径:
url为空:调用CookieManager::GetAllCookies,拿到全量 Cookie 后在 Electron 侧用MatchesCookie做二次过滤;url非空:调用CookieManager::GetCookieList(GURL(url), options, ...),并设置了三个关键的net::CookieOptions:set_include_httponly()— 保证 HTTP-only Cookie 也会被返回(即主进程 API 不受 httpOnly 限制);set_same_site_cookie_context(SameSiteCookieContext::MakeInclusive())— 让查询不受 SameSite 限制条件约束;set_do_not_update_access_time()— 查询本身不会更新 Cookie 的访问时间,这对依赖“过期即清除”语义的应用很重要。
过滤逻辑 MatchesCookie(同文件 L110-L129)与文档参数表一一对应:name/path 为字符串精确比较,domain 使用 cookie.IsDomainMatch(*str)(因此文档说“域名匹配或是其子域”),secure/session/httpOnly 为布尔比较。测试中用 cookies.get({ domain: '127.0.0.1' }) 验证了不传 url 时按域名过滤可以命中之前对具体 URL 写入的 Cookie(spec/api-session-spec.ts#L156-L164)。
5. cookies.set(details):写入 Cookie 的完整参数
cookies.set(details)
detailsObjecturlstring — 与 Cookie 关联的 URL;URL 非法时 Promise 会被拒绝。namestring(可选)— Cookie 名称,省略时默认为空。valuestring(可选)— Cookie 值,省略时默认为空。domainstring(可选)— Cookie 域名,会规范化为带前导点的形式以便对子域名生效;省略时默认为空。pathstring(可选)— Cookie 路径,省略时默认为空。secureboolean(可选)— 是否标记为 Secure。默认false,但使用Same Site=None属性时除外。httpOnlyboolean(可选)— 是否标记为 HTTP Only,默认false。expirationDateDouble(可选)— 过期时间,UNIX 纪元起的秒数;省略时 Cookie 成为会话 Cookie,不会跨会话保留。sameSitestring(可选)— 对该 Cookie 应用的 Same Site 策略,可取unspecified、no_restriction、lax、strict,默认lax。
返回 Promise<void>,Cookie 设置成功后解析。
5.1 参数解析细节(源码印证)
Cookies::Set 的实现(shell/browser/api/electron_api_cookies.cc#L366-L441)确认了几个容易踩坑的细节:
url是强制项:缺失时立即拒绝并提示"Missing required option 'url'";URL 无法解析为合法GURL时,会以EXCLUDE_INVALID_DOMAIN的原因拒绝 Promise。sameSite默认值与取值:StringToCookieSameSite(同文件 L262-L282)在未传sameSite时落到LAX_MODE,即默认lax;传入其他字符串(如'garbage')会拒绝并抛出"Failed to convert 'garbage' to an appropriate cookie same site value"——测试 spec/api-session-spec.ts#L148-L154 正是断言了这条错误消息。secure的隐式默认:源码中secure = details.FindBool("secure").value_or(same_site == NO_RESTRICTION),即 当sameSite为no_restriction而未显式指定secure时,secure自动取true。这正是文档那句“Defaults to false unless Same Site=None attribute is used”的出处。name/value可省略但会创建空名 Cookie:未提供时以空字符串参与net::CanonicalCookie::CreateSanitizedCookie构造;测试“sets cookies without name”验证了此时取回的 Cookiename为空字符串、value正常(spec/api-session-spec.ts#L126-L134)。- 落库失败的原因会翻译成可读错误:当
CreateSanitizedCookie产出非法 Cookie 或SetCanonicalCookie返回的访问状态不是 include 时,Promise 会以InclusionStatusToString生成的错误文本拒绝。该函数(同文件 L161-L260)把net::CookieInclusionStatus::ExclusionReason逐一映射为英文原因,例如“域名不匹配”“Cookie 超过 name/value 尺寸上限”“包含非 ASCII 域”“Secure Cookie 不允许被非 Secure 写入覆盖”等——实际排错时应直接阅读拒绝 Promise 抛出的这段文本。
此外,从源码结构看,Set 还会从 details 中读取 creationDate 与 lastAccessDate(通过 ParseTimeProperty(details.FindDouble(...)) 解析),尽管文档参数表未列出这两项,它们同样以 UNIX 纪元秒数接受并可影响 Cookie 的创建/访问时间戳。
5.2 会话 Cookie 与持久 Cookie 的验证
测试用例给出了两组对照(spec/api-session-spec.ts#L102-L124):
// 持久 Cookie:带 expirationDate
await cookies.set({ url, name, value, expirationDate: Date.now() / 1000 + 120 })
const c1 = (await cookies.get({ url }))[0]
expect(c1.session).to.equal(false)
// 会话 Cookie:不传 expirationDate
await cookies.set({ url, name, value })
const c2 = (await cookies.get({ url }))[0]
expect(c2.session).to.equal(true)
6. cookies.remove(url, name):删除 Cookie
cookies.remove(url, name)
urlstring — 与 Cookie 关联的 URL。namestring — 要删除的 Cookie 名称。
返回 Promise<void>,Cookie 移除后解析。删除匹配 url 与 name 的 Cookie。
底层实现是构造 network::mojom::CookieDeletionFilter(仅含 url 与 cookie_name 两个字段)并调用 CookieManager::DeleteCookies(shell/browser/api/electron_api_cookies.cc#L342-L364),回调中的删除计数被忽略——只要调用成功 Promise 就解析。实践中常见的清理套路与测试里的 afterEach 钩子一致:先 get({ url }) 列出目标 URL 的全部 Cookie,再逐个 remove,保证测试或会话重置后存储干净。删除动作也会经 changed 事件以 cause: 'explicit'、removed: true 的形式通知监听者。
7. cookies.flushStore():强制 Cookie 落盘
cookies.flushStore()
返回 Promise<void>,Cookie 存储完成写入后解析。
将尚未写盘的 Cookie 数据写入磁盘。需要理解其必要性:任何方法写入的 Cookie 都不会立即落盘,而是每 30 秒或每 512 次操作批量写入一次。调用 flushStore 可让当前 Cookie 立即写盘,典型场景包括应用退出前保存状态、或需要在磁盘层(备份、迁移)确认可见时。实现上它直接调用 CookieManager::FlushCookieStore 并在完成回调中解析 Promise(shell/browser/api/electron_api_cookies.cc#L443-L454)。
8. 小结与延伸阅读
Cookies挂在Session上(session.cookies/session.defaultSession.cookies),按会话(分区)隔离,全部方法返回 Promise,适合async/await风格组织;- 查询用
get(filter)组合url、domain、name、path、secure、session、httpOnly过滤;写入用set(details)时注意url必填、sameSite默认lax、no_restriction隐含secure: true; - 需要感知 Cookie 增删改(含过期、逐出、覆盖)时监听
changed事件,用cause区分八种变更原因、用removed判断是否删除; - 关心磁盘持久化时机时在关键节点调用
flushStore。
相关文档与代码:Session API、Cookie 结构、API 术语表(Main Process 等概念)、Cookies C++ 实现、Cookie 变更通知器、会话/Cookie 测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00