Electron Cookie 对象结构详解:字段语义、源码实现与 Session Cookie API 实战
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
本文以 Electron 的Cookie结构对象为核心,完整讲解其 10 个字段的语义、默认值与取值边界,并结合 shell/browser/api/electron_api_cookies.cc 的 C++ 实现,说明每个字段是如何从 Chromium 的CanonicalCookie转换而来、cookies.set()失败时底层如何诊断错误,以及如何在Session与net.request中正确读取、写入和监听 Cookie 变更。读完后你可以掌握:基于 Cookie 结构文档 编写可运行的 Cookie 读写代码,并理解sameSite、hostOnly、session、expirationDate等字段在源码层面的真实判定逻辑。
Cookie 对象字段总览
Cookie是 Electron 网络栈中统一的数据结构,由 docs/api/structures/cookie.md 定义。它是session.cookies.get()的返回元素,也是cookies实例changed事件的参数。字段完整列表如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | Cookie 的名称 |
value | string | 是 | Cookie 的值 |
domain | string | 可选 | Cookie 的域名;会被规范化为带前导点(.)的形式,使其同样对子域名生效 |
hostOnly | boolean | 可选 | 是否为 host-only cookie;仅当设置时未传入domain才为true |
path | string | 可选 | Cookie 的路径 |
secure | boolean | 可选 | 是否标记为 Secure(仅 HTTPS 传输) |
httpOnly | boolean | 可选 | 是否标记为 HttpOnly(JS 不可访问) |
session | boolean | 可选 | 是会话 Cookie 还是带过期时间的持久 Cookie |
expirationDate | Double | 可选 | 过期时间,UNIX 时间戳(秒);会话 Cookie 不提供该字段 |
sameSite | string | 是 | SameSite 策略,取值unspecified、no_restriction、lax或strict |
一个服务端通过Set-Cookie写入的典型返回示例(来自 spec/api-net-session-spec.ts 中的真实断言,该测试对cookies.get({})的结果做了逐字段deep.equal校验):
{ name: 'foo', value: 'bar', domain: '127.0.0.1', // 未显式传 domain,因此为 host-only hostOnly: true, path: '/', secure: false, httpOnly: false, session: true, // 未设置 expirationDate,即会话 Cookie sameSite: 'unspecified' }注意expirationDate在session: true时不存在,这正是文档中“会话 Cookie 不提供该字段”的体现。
字段是如何从 C++ 侧生成的:CanonicalCookie转换器
Electron 的Cookie对象并不是凭空拼装的 JSON,而是由 gin 框架把 Chromium 网络库的net::CanonicalCookie逐字段转换而来。在 shell/browser/api/electron_api_cookies.cc 中可以看到Converter<net::CanonicalCookie>的完整映射:
template <> struct Converter<net::CanonicalCookie> { static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, const net::CanonicalCookie& val) { gin::Dictionary dict(isolate, v8::Object::New(isolate)); dict.Set("name", val.Name()); dict.Set("value", val.Value()); dict.Set("domain", val.Domain()); dict.Set("hostOnly", net::cookie_util::DomainIsHostOnly(val.Domain())); dict.Set("path", val.Path()); dict.Set("secure", val.SecureAttribute()); dict.Set("httpOnly", val.IsHttpOnly()); dict.Set("session", !val.IsPersistent()); if (val.IsPersistent()) dict.Set("expirationDate", val.ExpiryDate().InSecondsFSinceUnixEpoch()); dict.Set("sameSite", val.SameSite()); return ConvertToV8(isolate, dict).As<v8::Object>(); } };从这段源码可以确认三件对理解文档语义很关键的事:
hostOnly不是存储的字段,而是推导值。它调用net::cookie_util::DomainIsHostOnly(val.Domain())根据域名形式判断。这与文档“仅当设置时未传入domain才为true”的说法一致:设置 Cookie 时不给domain,底层就会按 host-only 存储,读回来时DomainIsHostOnly返回true。session与expirationDate是互斥呈现的。session直接取!val.IsPersistent(),只有IsPersistent()为真时才写入expirationDate,且取ExpiryDate().InSecondsFSinceUnixEpoch()——这解释了为什么文档强调expirationDate的单位是 UNIX 时间戳的秒。sameSite枚举到字符串的映射位于同文件的 Converternet::CookieSameSite:UNSPECIFIED -> "unspecified"、NO_RESTRICTION -> "no_restriction"、LAX_MODE -> "lax"、STRICT_MODE -> "strict"。这就是文档中sameSite四个取值的来源,写死在转换层,不会出现第五种合法值。
通过 Session 读写 Cookie 的完整实战
Cookie对象由主进程 Cookies 类 产出。该类不从'electron'模块直接导出,只能通过Session实例的cookies属性访问。Electron 侧的绑定见 shell/browser/api/electron_api_session.cc 中Session::Cookies(isolate)的实现(以cppgc::Member<api::Cookies>成员持有)。
查询:session.cookies.get(filter)
完整示例(继承自 docs/api/cookies.md):
const { session } = require('electron') // 查询该 session 下的所有 cookie session.defaultSession.cookies.get({}) .then((cookies) => { console.log(cookies) // Cookie[] }).catch((error) => { console.log(error) }) // 只查询与特定 URL 关联的 cookie session.defaultSession.cookies.get({ url: 'https://www.github.com' }) .then((cookies) => console.log(cookies)) .catch((error) => console.log(error))filter支持以下字段:
| 字段 | 类型 | 行为 |
|---|---|---|
url | string (可选) | 只取与该 URL 关联的 cookie;省略或为空表示取全部 |
name | string (可选) | 按名称精确过滤 |
domain | string (可选) | 取域名匹配或其子域名的 cookie |
path | string (可选) | 按路径精确过滤 |
secure | boolean (可选) | 按 Secure 属性过滤 |
session | boolean (可选) | 区分会话 Cookie 与持久 Cookie |
httpOnly | boolean (可选) | 按 HttpOnly 过滤 |
底层实现在 Cookies::Get:url为空时走manager->GetAllCookies();带url时构造net::CookieOptions(显式set_include_httponly()、MakeInclusive()的 SameSite 上下文、set_do_not_update_access_time())后调用GetCookieList(),因此按 URL 查询能看到 HttpOnly Cookie 且不会刷新访问时间戳。随后的字段级过滤由 MatchesCookie 完成,它逐字段比对name、path、domain(用cookie.IsDomainMatch支持子域匹配)、secure、session、httpOnly。
几个容易被忽视的行为细节:
session过滤的方向性:源码中判断为*session_filter == cookie.IsPersistent()则剔除,即session: true只保留会话 Cookie,session: false只保留持久 Cookie。spec/api-net-session-spec.ts 中“should be able correctly filter out cookies that are session”用例分别设置了不带和带expirationDate的两枚 Cookie,验证了双向过滤各得一条。domain匹配支持子域:IsDomainMatch意味着domain: 'example.com'能匹配.example.com下的 Cookie。- 无效域名的 set 会精确报错:测试用例验证了传入
domain: 'wssss.iamabaddomain.fun'时 Promise 以 “The cookie was set with an invalid Domain attribute” 拒绝,该错误文案同样来自下文的InclusionStatusToString表。
写入:session.cookies.set(details)
// 设置 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) })details参数与默认值(结合 docs/api/cookies.md 与源码实现):
| 字段 | 类型 | 默认值 / 说明 |
|---|---|---|
url | string | 必填,Cookie 关联的 URL;URL 无效时 Promise 被拒绝 |
name | string (可选) | 省略时为空 |
value | string (可选) | 省略时为空 |
domain | string (可选) | 省略时为空,此时生成 host-only Cookie |
path | string (可选) | 省略时为空 |
secure | boolean (可选) | 默认false;除非使用SameSite=None,此时强制为true |
httpOnly | boolean (可选) | 默认false |
expirationDate | Double (可选) | UNIX 秒;省略则成为会话 Cookie,不跨会话保留 |
sameSite | string (可选) | unspecified/no_restriction/lax/strict,默认lax |
Cookies::Set 中的源码细节印证并补充了文档:
secure的强制逻辑:bool secure = details.FindBool("secure").value_or(same_site == net::CookieSameSite::NO_RESTRICTION);—— 与文档“默认 false 除非 SameSite=None”完全对应,这是由SameSite=None必须 Secure 的规范要求驱动的。sameSite解析:StringToCookieSameSite 在未传值时回退为LAX_MODE(即文档所说的默认lax),传入非法值时 Promise 以Failed to convert '...' to an appropriate cookie same site value拒绝。url缺失与 URL 无效:缺url拒绝Missing required option 'url';GURL解析失败时按EXCLUDE_INVALID_DOMAIN构造状态串后拒绝。- 失败诊断表:InclusionStatusToString 维护了一张完整的排除原因表(
EXCLUDE_INVALID_DOMAIN、EXCLUDE_SAMESITE_NONE_INSECURE、EXCLUDE_DOMAIN_NON_ASCII、EXCLUDE_OVERWRITE_SECURE等 30 余种),当CanonicalCookie::CreateSanitizedCookie产出的 Cookie 非 canonical、或SetCanonicalCookie返回非 Include 状态时,Promise 会以 “Failed to set cookie - ...” 开头的可读文案拒绝。排查set()失败时可直接对照这张表定位原因。 - 可选时间参数:实现中还读取了
creationDate与lastAccessDate(经 ParseTimeProperty 解析,0映射为 UNIX 纪元),当前 docs/api/cookies.md 的set()文档中仅列出expirationDate,阅读源码时留意这一差异。
删除与落盘:remove()与flushStore()
// 移除匹配 url 与 name 的 cookie session.defaultSession.cookies.remove('https://www.github.com', 'dummy_name') .then(() => console.log('removed')) // 立即把内存中的 cookie 写入磁盘 session.defaultSession.cookies.flushStore() .then(() => console.log('flushed'))remove(url, name)在 Cookies::Remove 中构造network::mojom::CookieDeletionFilter并调用DeleteCookies(),回调携带被删除数量(num_deleted)但 API 层不暴露该值。flushStore()对应FlushCookieStore()。文档指出:任何方法写入的 Cookie 不会立即落盘,而是每 30 秒或每 512 次操作写盘一次;在应用退出前调用flushStore()可避免持久 Cookie 丢失。
监听变更:changed事件与 cause 语义
cookies实例支持changed事件(docs/api/cookies.md),回调参数为(event, cookie, cause, removed),其中cookie就是一个Cookie结构对象。cause的八个取值及其含义:
| cause | 含义 |
|---|---|
inserted | Cookie 被插入 |
inserted-no-change-overwrite | 新插入的 Cookie 覆盖了旧值但未产生变化(如插入完全相同的 Cookie) |
inserted-no-value-change-overwrite | 覆盖写入且值未变,但对 Web 可观测(例如更新了过期时间) |
explicit | 被使用者操作直接删除 |
overwrite | 因插入操作覆盖而被自动移除 |
expired | 因过期被自动移除 |
evicted | 垃圾回收时被自动驱逐 |
expired-overwrite | 被一个已过期的时间戳覆盖 |
这些字符串与 Chromium 的net::CookieChangeCause枚举一一对应,映射代码见 Converternet::CookieChangeCause。事件的分发链路是:Cookies构造时向browser_context_->cookie_change_notifier()注册回调(electron_api_cookies.cc L300-L306),每次变更触发 Cookies::OnCookieChanged,其中removed布尔值由 IsDeletion 判定——三种INSERTED_*视为未删除,其余一律视为删除。典型用途如会话同步、登出时清理、持久化登录态:
const { session } = require('electron') session.defaultSession.cookies.on('changed', (_event, cookie, cause, removed) => { if (removed) { console.log(`cookie ${cookie.name} removed, cause: ${cause}`) } else { console.log(`cookie ${cookie.name}=${cookie.value} changed, cause: ${cause}`) } })Cookie 存储与net.request的联动
理解Cookie结构后,一个高频实战场景是主进程net模块复用会话 Cookie 存储。spec/api-net-session-spec.ts 用测试锁定了几个关键行为:
- 默认不附带 Cookie:
net.request({ url, session })默认不使用该 session 的 Cookie 存储(测试断言服务端收到的 Cookie 为undefined)。 - 开启方式:传
{ credentials: 'include' }或{ useSessionCookies: true }后,cookies.set()写入的 Cookie 会随请求自动附带,服务端能收到wild_cookie=<value>。 session.fetch同理:sess.fetch(serverUrl, { credentials: 'include' })会带上存储中的 Cookie。- SameSite 与重定向安全:测试“safely across redirects”表明,
lax/strictCookie 在跨站重定向后不会跟随,只有no_restriction才会;且重定向到新域后,存储层会自动改为附带新目标域的 Cookie——这正是sameSite字段在结构层设计存在的意义。 - 手动覆盖:即使启用了会话 Cookie,
request.setHeader('Cookie', ...)仍可整体覆盖 Cookie 头(测试“should be able to set cookie header line”)。
小结
Cookie结构是 Electron 网络能力的数据基石:字段语义由 docs/api/structures/cookie.md 定义,实际值由 electron_api_cookies.cc 中的CanonicalCookie转换器逐字段生成,读取过滤、写入校验、变更事件与落盘策略均有对应的 C++ 实现与 spec/api-net-session-spec.ts 的测试用例佐证。掌握这张“文档—源码—测试”三对照表后,无论是实现自动登录、Cookie 同步还是网络诊断,都可以在这套结构上可靠地构建功能。相关延伸文档:Cookies 类、Session 类、net 模块。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考