@fastls/fastls 0.0.0-stage → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,203 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
203
+
package/README.md CHANGED
@@ -1,3 +1,372 @@
1
- # Temporary Holding Version
1
+ # Fastls Node.js SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > 发布状态(2026-10-27):0.10.1 已完成认证,尚待 npm 账号验证后上传。当前可安装 0.10.1-dev.0;下文新增接口与 0.10.1 安装命令需正式发布后使用。
4
+
5
+ Node.js 22+ 的 Fastls C ABI v1 绑定,提供 Promise API、TypeScript 声明、ESM 和 CommonJS 入口。本文对应正式版 **0.10.1**,发行文件与版本状态以 npm registry 为准。
6
+
7
+ 支持 HTTP/1.1、HTTP/2、流式上传下载、双工、SSE、WebSocket、会话 Cookie 和 HTTP/HTTPS/SOCKS 代理。网络请求由 Go 执行,通过 Koffi 在当前 Node 进程中调用;使用者无需 Python 或 RPC 服务。
8
+
9
+ 只支持 Node.js 主线程。暂不支持 worker_threads、Electron、Bun 或浏览器。默认校验证书;生成的客户端身份不代表已获得与真实浏览器完全相同的线上指纹。
10
+
11
+ - 用户 API 与安装方式:本文。
12
+ - Python 行为对应、语言差异与测试证据:[PARITY.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/PARITY.md)。
13
+ - 维护者打包、首次发布和 Trusted Publisher:[NPM_RELEASE.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/NPM_RELEASE.md)。
14
+ - 当前交付状态及后续批次:[NEXT_PHASE.md](https://github.com/FastTLS/fastls-ffi/blob/main/node/NEXT_PHASE.md)。
15
+
16
+ ## 安装与快速开始
17
+
18
+ 安装指定版本:
19
+
20
+ ```sh
21
+ npm install @fastls/fastls@0.10.1
22
+ ```
23
+
24
+ 主包通过同版本的可选依赖自动安装当前平台的 Fastls 原生库。保留 optional dependencies;支持 Windows x64、Linux glibc x64/arm64、macOS x64/arm64。Linux musl 和其它架构没有内置包。具体系统最低版本见原生包的 `native/manifest.json`。
25
+
26
+ npm 分发包没有 Fastls 编译或下载脚本;安装成功后不需要 Go、Python、C 编译器或 `FASTLS_LIBRARY`。Koffi 自身也使用对应平台的原生组件。
27
+
28
+ ```js
29
+ import { Session } from '@fastls/fastls';
30
+ // CommonJS: const { Session } = require('@fastls/fastls');
31
+
32
+ const session = new Session({ profile: 'chrome/156/windows', timeoutMs: 30_000 });
33
+ try {
34
+ const response = await session.get('https://example.com', {
35
+ headers: { 'User-Agent': 'my-client/1.0' },
36
+ signal: AbortSignal.timeout(5_000),
37
+ });
38
+ response.raiseForStatus();
39
+ console.log(response.status, response.url, response.httpVersion, response.text());
40
+ } finally {
41
+ await session.closeAsync();
42
+ }
43
+ ```
44
+
45
+ 后续片段假定 `session` 已创建且未关闭;`consume`、`produceBytes` 等是应用自行实现的业务函数。每次实际使用均应关闭会话和未消费完的响应。
46
+
47
+ 源码构建、测试和发包见 [开发指南](DEVELOPMENT.md)。普通 npm 用户不需要这些步骤。
48
+
49
+ ## 从 Node fetch 迁移
50
+
51
+ 新增 `fetch` 入口沿用 Node 原生 fetch 的主要调用习惯,并增加指纹、代理和证书配置:
52
+
53
+ ```js
54
+ import { fetch } from '@fastls/fastls';
55
+
56
+ const response = await fetch('https://tls.peet.ws/api/all', {
57
+ profile: 'chrome/156/windows',
58
+ headers: new Headers({ Accept: 'application/json' }),
59
+ signal: AbortSignal.timeout(10_000),
60
+ });
61
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
62
+ console.log(await response.json());
63
+ ```
64
+
65
+ 接受字符串、URL、Request,以及标准 Headers、FormData、URLSearchParams、Blob、字节和可读流正文。流式请求使用 `duplex: 'half'`,一次性正文不会为 307/308 自动重放。返回 Node 标准 `Response`,在响应头到达时返回;`text/json/arrayBuffer()` 为异步方法,`body` 为 Web ReadableStream,`ok` 为 200–299。正文只消费一次;不用时必须 `await response.body?.cancel()`。另附只读 `match` 和 `httpVersion`。
66
+
67
+ 顶层 `fetch` 与 Node 一样不保存 Cookie,每次请求的临时资源会在正文读完或取消后关闭。需要 Cookie 和连接长期复用时,使用 `session.fetch(url, options)`,最后 `await session.closeAsync()`。会话级 `library/caFile` 应在构造 Session 时设置。HTTP 错误状态仍返回响应;`redirect` 支持 `follow/manual/error`。默认最多跟随 30 次,可设置 `maxRedirects`。
68
+
69
+ 这不是浏览器环境:不实现 CORS、缓存、自动认证、referrer、integrity 或 keepalive 策略。显式传入 `cache/credentials/integrity/keepalive/mode/referrer/referrerPolicy` 会报错。默认校验 TLS,不支持关闭验证;不接收 Node http/https Agent、dispatcher 或 Axios 配置。超时以毫秒计,使用 `timeoutMs` 或 AbortSignal。
70
+
71
+ Fastls 原有 `Session.request/stream/duplex/sse/websocket` 保留,适合需要分阶段超时、原始字节、Trailer 和更精细资源控制的场景。Cookie、重定向和默认参数由 JavaScript SDK 管理;动态库负责网络传输、指纹、证书与连接池。旧 C Cookie 接口仅供旧客户端兼容,新 SDK 不使用原生 Cookie 存储。
72
+
73
+ ## 请求和响应
74
+
75
+ - `await session.request(method, url, options)`,以及 `get/post/put/patch/delete/head/options`,返回完整缓存的 Response。
76
+ - 请求选项包括 `profile`、`headers`、`trailers`、`params`、`json`、`form`、`files`、`body`、`timeoutMs`、`allowRedirects`、`maxRedirects`、`signal`、`proxy`、`proxyCaFile`、`decodeContent`、`maxDecodedBytes`、`duplex`。会话级选项还包括 `library`、`caFile`、`cookies`、`notifications`;以公开 TypeScript 类型为准。
77
+ - `body` 为字符串、Buffer、Uint8Array、同步/异步字节迭代器、Node Readable 或接收 AbortSignal 的生产者工厂。超过512 KiB的字符串/字节自动使用上传通道。
78
+ - `timeoutMs` 为毫秒整数或 `{ total, connect?, write?, headers?, read? }`,每项范围1..86400000;默认总时限30000。请求级数字覆盖会清除会话分层限制,结构化覆盖替换整份策略。
79
+ - 会话默认头和请求头按大小写不敏感方式合并,请求值优先,包括空字符串。
80
+ - `Response` 提供 `status`、`ok`、`headers`、`wireHeaders`、`decoded`、`match`、`httpVersion`、`url`、`trailers`、`content: Buffer`,以及同步 `text()` / `json()`。
81
+
82
+ `ok` 表示状态小于400,与Python一致,因此302/304也为true。
83
+ `raiseForStatus()` 只对400–599抛出 `FastlsHTTPError`,异常包含原响应 `response` 和 `status`,消息仅含状态码;流式响应可在读取正文前检查,随后仍需关闭。
84
+ `encoding` 从Content-Type的charset初始化,缺省utf-8,可修改;`text()`替换坏字节,未知编码报错。Latin-1保留完整字节映射,其它字符集使用Node内置TextDecoder的WHATWG编码表,不自动猜测。
85
+ `json()`直接读取原始字节,支持UTF-8/16/32及BOM,独立于encoding;JSON数字仍受JavaScript数字精度限制。
86
+ - 4xx/5xx 仍返回 Response;参数、网络、TLS 和原生错误抛出异常。
87
+ - `Headers.get(name)` 返回第一个值或 undefined;`getAll(name)` 保留所有重复值,不按逗号拆分。
88
+ `entries()` / 默认迭代返回每个字段的首值,`multiItems()` 返回全部字段值。
89
+ - `listProfiles()` 返回规范 ID;`stats()` 返回 Go 运行时只读快照,其统计范围是该动态库实例,不仅是当前会话。
90
+
91
+ 未知 profile 报错;Match 保留生成身份和协议基线信息,不承诺与真实浏览器指纹完全等价。
92
+
93
+ `request('PUT', url, options)` 等可使用其它合法方法;便捷方法包括 `get/post/put/patch/delete/head/options`。请求头的值为字符串;`trailers` 的值为字符串或只读字符串数组,名称和值由原生层继续验证,且要求 `request_trailers` 能力。
94
+
95
+ `url` 是最终响应 URL,`httpVersion` 是实际 HTTP 协议版本。缓存响应已经读到 EOF,包含最终 `trailers`;流式响应先消费正文,再读取最终尾部字段。提前关闭不代表已经收到全部 trailers。
96
+
97
+ ## 请求参数
98
+
99
+ ```ts
100
+ const response = await session.get('https://example.com/search?old=a%20b#fragment', {
101
+ params: [['q', '中文'], ['tag', ['a', 'b']], ['empty', '']],
102
+ });
103
+ response.raiseForStatus();
104
+ await session.post('https://example.com/api', { json: null });
105
+ await session.post('https://example.com/form', { form: { tag: ['a', 'b'], enabled: true } });
106
+
107
+ // FileHandle归调用方拥有;适配read签名,从当前位置读取,始终由调用方关闭。
108
+ const file = await open('/path/to/archive.bin', 'r'); // import { open } from 'node:fs/promises'
109
+ try {
110
+ await session.post('https://example.com/upload', {
111
+ form: { description: '附件' },
112
+ files: { file: ['archive.bin', {
113
+ async read(maxBytes, signal) {
114
+ signal.throwIfAborted();
115
+ const buffer = Buffer.allocUnsafe(maxBytes);
116
+ const { bytesRead } = await file.read(buffer, 0, maxBytes, null);
117
+ return buffer.subarray(0, bytesRead);
118
+ },
119
+ }, 'application/octet-stream'] },
120
+ });
121
+ } finally { await file.close(); }
122
+ ```
123
+
124
+ - `params`/`form`接受普通对象、Map或键值对数组。列表值展开成重复键;null/undefined值省略,空字符串保留;布尔值与Python一致为True/False,Unicode用UTF-8,空格为+。键可用字符串/Uint8Array,值可用字符串/Uint8Array、有限数或布尔值。与Python不同,查询及表单拒绝非有限数。
125
+ - `params`另接受不含#的已编码ASCII字符串,原查询编码和fragment保留。fragment不发送到服务器。
126
+ - `json`只接受严格JSON值、普通对象及数组,拒绝NaN/Infinity、undefined、bigint、稀疏数组、循环和toJSON。显式null发送null;省略json无正文。`json`与body/form/files冲突;`body`与form/files冲突。不要把Python的data映射直接传给body;Node用form显式编码,现有字节块数组仍是原始body。
127
+ - JSON/表单在内存中编码;超过512KiB自动转为流式上传,不是大型数据增量编码器。默认Content-Type仅在没有会话/请求自定义头时设置;编码正文拒绝Content-Length/Transfer-Encoding。原始body沿用原有规则。
128
+ - `files`接受对象、Map或重复字段对数组。每个文件是Uint8Array、`MultipartReader`,或`[filename, body, contentType?]`;filename为null省略filename参数。默认从读取器name/字段名取文件名;内置固定MIME推断只覆盖txt/json/html/csv/png/jpg/jpeg/pdf,其它类型请显式指定,缺省application/octet-stream。不支持自定义分部头。
129
+ - multipart可配合form字段,每次read最多64KiB,空Uint8Array表示EOF;支持同步/Promise读取器。无需seek/长度/临时文件,不全量缓存文件;调用方负责close和重新定位,成功、失败、取消均保留读取器所有权。提供给read的AbortSignal可中断自定义等待;不可取消的Promise会延后closeAsync清理。Node Readable及FileHandle不能直接作为此读取器,应适配其读取接口;现有原始body的Readable销毁规则不变。
130
+ - multipart管理随机boundary及Content-Type,拒绝会话或请求中显式Content-Type;元数据拒绝控制字符。正文仅使用一次,307/308不自动重放,301/302/303按 SDK 规则转换方法和丢弃正文。
131
+ - request/get/post/stream/sse与自动duplex共享参数处理;multipart不支持duplex。手动duplex只接受params,正文继续send()/finishUpload()。自动双工支持JSON/表单且关闭重定向。
132
+
133
+ ## 流式下载和取消
134
+
135
+ ```js
136
+ const controller = new AbortController();
137
+ const response = await session.stream('GET', 'https://example.com/large-file', {
138
+ signal: controller.signal,
139
+ });
140
+ try {
141
+ for await (const chunk of response.body) {
142
+ await consume(chunk);
143
+ }
144
+ } finally {
145
+ response.close();
146
+ }
147
+ ```
148
+
149
+ 响应头到达后 `stream()` 即返回。`body` 是单次消费的 `AsyncIterable<Buffer>`,每个块独立持有内存,
150
+ 可保留供后续使用。停止拉取时 SDK 不继续读取;块边界不代表行、SSE 事件或 HTTP 帧。
151
+ 可通过 `Readable.from(response.body, { objectMode: false })` 接入 Node 流并使用 `pipeline()`。
152
+
153
+ `StreamResponse.read()` / `text()` / `json()` 异步读取完整正文,上限同普通请求的 32 MiB;更大响应必须逐块消费。
154
+ 默认在Go侧逐块解码gzip、deflate、br及zstd,`decodeContent: false`取得原始字节;`text()` 按响应 `encoding` 解码,缺省 UTF-8,替换非法字节;JSON 解码规则与缓存响应相同。
155
+ 解码时headers移除Content-Encoding/Content-Length,wireHeaders保留线上字段;decoded表示解码视图。
156
+ `maxDecodedBytes`默认256 MiB,范围1..1 TiB,只限制解码输出;普通缓存32 MiB限制仍独立适用。未知编码、校验失败、截断或超限抛出FastlsDecodeError。
157
+
158
+ 正常 EOF、读取异常或提前 `break` 会关闭请求。尚未开始迭代的响应需要显式 `close()`,
159
+ 也可调用 `cancel()` 或中止 AbortSignal。AbortSignal 在等待响应头和正文读取期间都有效,
160
+ 抛出 `AbortError`(`code: 'ABORT_ERR'`,`cause` 保留 signal.reason)。超时抛出 FastlsTimeoutError,其phase为connect/write/headers/read/total。解码错误、超时错误和AbortError均继承FastlsError。
161
+ `session.close()` 取消并关闭所有活动请求,重复关闭无害;不依赖 GC 清理资源。
162
+
163
+ ## 连接池、Cookie 和证书
164
+
165
+ Session 复用原生会话的 H1 keep-alive 和 H2 多路复用;不同 Session 隔离。
166
+ `session.closeIdleConnections()` 释放空闲连接,保留 Cookie。
167
+ Cookie 自动管理和重定向处理均由 JavaScript SDK 完成;原生上下文只负责传输:
168
+
169
+ ```js
170
+ session.cookies.set('token', 'value', { url: 'https://example.com', path: '/', secure: true });
171
+ session.cookies.get('token', 'https://example.com/account');
172
+ session.cookies.getAll('https://example.com/account'); // [{ name, value }, ...]
173
+ session.cookies.delete('token', { url: 'https://example.com', path: '/' });
174
+ session.cookies.clear();
175
+ ```
176
+
177
+ 设置选项还包括 `domain`、`httpOnly`、`maxAge`(秒)和 `expires`(RFC3339)。
178
+ `cookies: false` 禁用自动收发。显式 Cookie 请求头优先。规则和存储限制见上级 COOKIES_HEADERS.md。
179
+
180
+ 始终校验证书。可通过 `caFile` 使用仅属于本会话的私有根证书(替代系统根);文件在构造时读取。
181
+ 不修改系统信任库,不提供关闭 TLS 验证选项。
182
+
183
+ ## 代理
184
+
185
+ ```js
186
+ const session = new Session({
187
+ proxy: 'http://user:password@127.0.0.1:8080',
188
+ timeoutMs: { total: 30000, connect: 5000 },
189
+ });
190
+ try {
191
+ await session.get('https://example.com/');
192
+ await session.get('https://example.com/', { proxy: null }); // 显式直连
193
+ await session.get('https://example.com/', { proxy: 'socks5h://127.0.0.1:1080' });
194
+ } finally { await session.closeAsync(); }
195
+ ```
196
+
197
+ Session和请求的`proxy`接受URL字符串或null。请求省略或undefined继承会话默认值;null或空字符串直连。普通请求、stream、sse、自动/手动duplex遵循相同规则。传入其它类型会报TypeError。显式代理要求原生`proxy_tunnels`能力;当前 SDK 始终要求 transport_context_v1;缺少代理能力时,代理请求在登记句柄前报错。
198
+
199
+ `http://`对HTTP和HTTPS目标均使用HTTP/1.1 CONNECT,默认端口80。`socks5://`本机解析目标域名,`socks5h://`交给代理解析,默认端口1080。支持HTTP Basic和SOCKS用户名/密码认证,凭据按URL规则转义,例如密码中的@写成%40。不读取HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY及其小写形式。`https://`在代理TLS中运行HTTP/1.1 CONNECT,默认端口443,证书和主机名验证始终开启,不回退到明文。HTTPS代理另要求原生`https_proxy_tls`能力;暂不支持SOCKS4或PAC,详情见上级PROXY.md。
200
+
201
+ `proxyCaFile`只控制HTTPS代理信任,`caFile`只控制目标信任,两者互不代替。未设置代理CA使用系统根;提供文件则替换系统根。构造Session时读取默认代理CA一次,请求指定路径时重新读取;请求undefined继承默认,null恢复系统根。直连、HTTP和SOCKS忽略代理CA默认值及请求覆盖。代理地址及凭据、profile、代理CA和目标CA共同隔离连接池。目标证书仍需验证;目标请求禁止设置Proxy-Authorization和Proxy-Connection。认证只用于代理握手,错误消息不回显凭据或代理任意响应短语。握手计入connect和total预算,AbortSignal取消会关闭未建立的隧道;取消单条H2流继续复用健康的共享隧道。代理拒绝后不降级或自动重试。
202
+
203
+ 代理测试使用Node测试进程中的独立、仅访问loopback的CONNECT/RFC1928/RFC1929靶场;SDK本身不依赖Python或代理辅助进程。16 组 Python 行为映射由源码、内置库候选及 npm 拆分包统一验收;语言适配见PARITY.md。
204
+
205
+ `npm run test:proxy:soak`默认以32路并发运行60秒,轮换正常请求、共享H2流取消、错误认证和停滞握手取消;四种scheme分别使用真实loopback代理。FASTLS_NODE_PROXY_SOAK_SECONDS可设为1..300。Linux当前Go/Node组合执行该独立测试,沿用现有RSS、Go堆、goroutine及句柄阈值,逐批记录写入build/node-proxy-recovery-*.json,不计入231项主套件。
206
+
207
+ 代理独立长测与SSE长测一样,显式使用`--max-semi-space-size=16`固定测试进程的V8半空间上限。本机Windows Node24默认策略在持续分配时将new_space保留空间从4MiB扩大至128MiB,而Go堆、原生请求和隧道已回收。该参数仅作用于独立测试进程,不修改SDK用户进程的运行配置,也不放宽RSS或句柄阈值。证据保存运行参数、V8空间快照,以及阈值判定之后的完整GC诊断;判定使用GC前的最终采样。
208
+
209
+ 私有HTTPS代理示例:`new Session({ proxy: 'https://proxy.example:443', proxyCaFile: '/path/to/proxy-ca.pem', caFile: '/path/to/target-ca.pem' })`。两类CA分别验证代理与目标;请求可用`{ proxyCaFile: null }`恢复代理系统信任,或指定其它代理CA文件,均不会复用不相符的已有隧道。
210
+
211
+ 代理长测按靶场握手阶段同步取消,避免固定毫秒计时在慢runner上提前中止TLS而未触达待测CONNECT阶段。HTTPS靶场延迟50毫秒处理CONNECT,确保该时序在本地也能覆盖。H2可合并同一代理的在途建连;每批每种scheme至少触发一次真实握手取消,并逐批核对启动/回收计数,不依赖请求数等于物理建连数。资源阈值保持不变。
212
+
213
+ ## HTTP/2 双工
214
+
215
+ `duplex: true` 允许响应头到达后继续上传,可同时读取响应。仅支持 HTTP/2,并始终禁止自动重定向;普通上传仍拒绝未完成上传时提前返回的 2xx。
216
+
217
+ ```js
218
+ const response = await session.stream('POST', url, {
219
+ duplex: true,
220
+ body: signal => produceBytes(signal),
221
+ });
222
+ try {
223
+ for await (const chunk of response.body) await consume(chunk);
224
+ await response.waitUpload();
225
+ } finally {
226
+ await response.closeAsync();
227
+ }
228
+ ```
229
+
230
+ `waitUpload()` 等待原生上传 EOF 接受;需要与响应读取并行,单独等待可能被 HTTP/2 流量控制阻塞。
231
+ 手动模式可先发送数据,再等待响应头,也可在获得响应后交替收发:
232
+
233
+ ```js
234
+ const exchange = session.duplex('POST', url);
235
+ try {
236
+ const reading = exchange.response.then(response => consumeResponse(response.body));
237
+ const writing = (async () => {
238
+ await exchange.send(Buffer.from('first'));
239
+ await exchange.finishUpload();
240
+ })();
241
+ await Promise.all([reading, writing]);
242
+ } finally {
243
+ exchange.close();
244
+ }
245
+ ```
246
+
247
+ `send()` 接受 Buffer/Uint8Array,按64 KiB分块等待原生队列容量;同一 exchange 同时只允许一个 send/finish 调用,重叠调用会被拒绝。发送完成前调用方应保持输入字节不变。
248
+ `finishUpload()` 只关闭上传方向,响应仍可继续读取;响应EOF、取消、超时或会话关闭会停止生产者。
249
+ 服务器提前结束上传时,手动send会报错,已返回的响应仍可读取;waitUpload会指出上传EOF未被接受。
250
+ 响应头到达后的生产者异常会中止读取并保留原异常。
251
+
252
+ `close()` 立即释放原生请求;`response.closeAsync()` / `session.closeAsync()` 还等待生产者清理,并报告清理中的异常。
253
+ 应使用接收 AbortSignal 的工厂或可销毁 Readable 来取消挂起I/O。JavaScript不能中断任意Promise;不配合信号的生产者可能使等待清理的Promise持续挂起,但原生请求仍会及时释放。双工 `read()`、`request()` 和迭代结束也会等待清理。
254
+ 示例见 `examples/duplex.mjs`。
255
+
256
+ 重复收发、取消和提前拒绝可运行 `npm run test:duplex:soak`(默认60秒,FASTLS_NODE_DUPLEX_SOAK_SECONDS可设为1..300)。Linux当前Go组合把这项独立压力检查纳入CI,检查原生请求、JS生产者任务、RSS、句柄和goroutine的回收。
257
+
258
+ ## SSE 事件流
259
+
260
+ ```js
261
+ const events = await session.sse('GET', 'https://example.com/events', {
262
+ timeoutMs: { total: 3_600_000, read: 45_000 },
263
+ lastEventId: '41', // 可选:上次完整提交的事件 ID
264
+ });
265
+ try {
266
+ for await (const event of events) {
267
+ console.log(event.event, event.id, event.data);
268
+ if (event.data === '[DONE]') break;
269
+ }
270
+ } finally { await events.closeAsync(); }
271
+ ```
272
+
273
+ `session.sse(method, url, options)` 返回单消费者 `SSEStream`,复用普通请求的配置、Cookie、连接池和 AbortSignal。
274
+ 事件为不可变的 `ServerSentEvent`,含 `data`、`event`(默认message)、`id` 和可选的 `retry`(毫秒);`event.json()` 解析 JSON,非法 JSON 抛出 SyntaxError。
275
+ `[DONE]` 是普通数据,由应用决定是否停止。`events.response` 提供响应元数据,请勿同时读取其正文。
276
+
277
+ 解析支持任意字节分块、UTF-8替换、初始BOM、CR/LF/CRLF、多行及空data;空行提交事件,EOF丢弃未完成的事件。
278
+ ID跨事件保留,空值重置,含NUL的ID忽略。`events.lastEventId` 仅在完整块结束时更新,包括仅含ID的控制块;`events.retry` 保留最近合法的重试提示。
279
+ retry只接受ASCII数字并截到2147483647;单个事件的retry只反映其当前块。
280
+ `lastEventId` 同时设置请求头和初始解析状态,拒绝NUL/CR/LF。SDK不会自动重连或重放请求。
281
+
282
+ 默认请求头为Accept:text/event-stream和Cache-Control:no-cache,请求headers可覆盖,优先于同名会话默认头。
283
+ HTTP200要求唯一的text/event-stream内容类型(可带MIME参数);HTTP204返回空流,其它状态拒绝。
284
+ 始终按UTF-8解析;压缩沿用原生解码及完整性检查,收到压缩流时不能关闭decodeContent。
285
+
286
+ `maxEventBytes` 默认1MiB,范围1..64MiB,分别限制原始单行及累计data的UTF-8字节数(含多行分隔符);超限关闭请求。
287
+ 无后台事件队列,停止拉取时停止解析和原生读取,整个事件流可超过32MiB缓存上限。解析器还持有当前块、ID、事件类型及解码开销。
288
+ 事件之间让出事件循环,确保同一网络块包含大量事件时,计时器和取消仍能执行。
289
+
290
+ 默认总超时仍为30秒;可显式增加total(最长24小时),目前不支持null或关闭总超时。
291
+ 注释心跳刷新网络read预算,total仍从请求开始计时。
292
+ `cancel()`、`close()`、`closeAsync()` 和提前break沿用响应清理;未开始迭代时也应显式关闭。
293
+ HTTP/2可组合 `{ duplex: true, body: signal => source(signal) }` 边上传边接收事件;上传完成检查为 `events.response.waitUpload()`。
294
+ 可运行示例 `examples/sse.mjs`,完整解析规则与Python对应验收见上级SSE.md和本目录PARITY.md。
295
+ `npm run test:sse:soak` 默认以32路并发重复运行60秒,验证正常结束、提前退出、错误头、解析超限和解码失败后的资源回收;可设置FASTLS_NODE_SSE_SOAK_SECONDS为1..300。Linux当前Go组合纳入CI。
296
+ 该独立长测固定V8年轻代参数以比较RSS增量,保留原有资源阈值,记录运行参数和堆空间快照;不会更改SDK用户进程的配置,说明见PARITY.md。
297
+
298
+ ## WebSocket
299
+
300
+ ```ts
301
+ const ws = await session.websocket('wss://example.com/chat', {
302
+ subprotocols: ['chat.v1'], handshakeTimeoutMs: 30000,
303
+ readTimeoutMs: null, writeTimeoutMs: 30000, closeTimeoutMs: 1000,
304
+ maxMessageBytes: 1024 * 1024,
305
+ });
306
+ try {
307
+ await ws.send('你好');
308
+ const message = await ws.recv(); // string或Buffer,空消息也是有效消息
309
+ } finally { await ws.closeAsync(1000, 'done'); }
310
+ ```
311
+
312
+ send/ping/pong等到原生写入完成;同时最多一名读者和一名写入者,重叠调用立即报错。recv返回完整消息,入站分片重组由原生完成。默认1MiB消息上限,可设置1..16MiB;控制帧最多125字节。Node组装、复制和返回当前消息另占有界内存,业务持有的消息不计入原生队列。不是无限文件流接口。
313
+
314
+ 正常远端关闭抛出WebSocketClosed(code/reason),连接保留closeCode/closeReason。closeAsync的整个握手受closeTimeoutMs约束(最多60000),超时直接释放;abort立即中断。连接级AbortSignal中断当前及后续操作,Session关闭回收全部所属WS。读/写/握手超时使连接终止,FastlsTimeoutError.phase为read/write/handshake;HTTP会话总超时不限定WS寿命。
315
+
316
+ 复用Session的profile、头、CA、代理与代理CA;params、请求级覆盖/显式直连沿用HTTP规则。wss始终验证证书和主机名,ALPN适配HTTP/1.1;不承诺适配后的Upgrade头序与浏览器完全一致。使用websocket_v1和三个可选ABI符号,当前 SDK 还要求 transport_context_v1;ABI 号相同不能代替能力检查。通知默认共享一条会话连接,poll可显式选择,不阻塞主线程或增加网络线程。
317
+
318
+ 默认与 HTTP 共享 SDK Cookie 罐;原生库不保存 Cookie。cookieMode:"manual" 可禁用本次握手的自动 Cookie 收发。显式Cookie头(包括空值)优先,默认仍接受握手Set-Cookie;Session的cookies:false禁用自动收发。域/路径/Secure/过期规则与HTTP一致。有效101及普通HTTP拒绝/重定向响应可写回;无效101或TLS失败不写回。Authorization/Origin由调用方设置。无压缩、H2/H3 CONNECT、握手重定向、自动心跳/重连或消息重放。ping只证明写入完成,可用info.websocket.pongs/last_pong_base64观察响应。协议细节与原生资源限制见根目录WEBSOCKET.md。
319
+
320
+ npm run test:quick先运行类型、清单及WS/原生用例;test:ws针对25项WS和16项共享Cookie;test:ws:soak运行独立60秒/32路资源回收。快速反馈不能替代231项源码/安装包完整回归与六组平台验收。
321
+
322
+ 0.10.1 SDK 使用不含 Cookie 的原生传输上下文,并在 JavaScript 中完成 Cookie 选择及响应更新。旧原生 Cookie 能力只为历史客户端保留。npm run test:compat 验证当前完整库的收发、显式头和失败握手。
323
+
324
+ ## 原生就绪通知
325
+
326
+ 默认使用`notifications: 'auto'`:新库带session_notifications能力且符号完整时启用,缺少通知能力时轮询,但库仍须满足 transport_context_v1 等 SDK 基础能力。`required`缺少能力时在创建原生会话前报错;`poll`显式使用原调度器。
327
+
328
+ ```js
329
+ const session = await Session.open({ notifications: 'required' });
330
+ try {
331
+ console.log(session.notificationMode); // async-events
332
+ const response = await session.get(url);
333
+ } finally { await session.closeAsync(); }
334
+ ```
335
+
336
+ 普通`new Session()`仍是同步构造;首次请求自动初始化,也可先`await session.ready(signal)`。Session.open初始化失败会关闭新会话,ready失败允许重试。首次并发请求共享初始化;取消一个等待者不会取消其它等待者,全部退出时关闭未就绪的候选通道。HTTP总预算从创建原生请求开始,通道初始化另限3秒。
337
+
338
+ 每会话最多一条经32字节随机令牌认证的IPv4本机TCP连接,监听地址为127.0.0.1临时端口,认证后关闭监听器;候选连接最多两个。线上只有握手和唤醒字节,HTTP数据仍经原ABI读取。没有Go到JavaScript回调或JavaScript工作线程。
339
+
340
+ `notificationMode`为async-events或poll。通道运行中断开会报错并唤醒等待者,不静默回退;关闭后可显式poll重建会话。关闭会话释放通道,closeAsync还等待候选socket关闭及上传清理。状态集合只含活动请求,溢出提示重查本会话全部请求。
341
+
342
+ 手动duplex仍同步返回交换对象,send/response在通道就绪后才创建请求;初始化期间关闭交换不会发送HTTP请求。上传、SSE、总超时及AbortSignal继续适用。验收记录包含空闲请求的ABI调用次数,不以单次套件运行时间证明性能优劣。
343
+
344
+ ## 调度与边界
345
+
346
+ 网络由 Go goroutine 执行。新库默认通过每会话一条本机TCP连接接收就绪提示,Node事件循环随后重查非阻塞C接口,不定时查询空闲请求。
347
+ 通知是重查提示,不保证读写一定成功;通过代次快照防止漏唤醒,并独立取消双工的读写等待。
348
+ 立即调用会让出事件循环;不承诺固定吞吐量或延迟,原生控制调用及JSON/Buffer转换仍在主线程执行。
349
+ 显式 poll 或配套库缺少通知能力时使用共享约 10ms 定时器,保留每轮处理数量及时间上限。
350
+
351
+ 句柄使用 bigint,无损解析和序列化 ABI JSON 数字;返回的 C 字符串逐次由 fastls_free 释放。
352
+ 外部动态库基础能力要求为 `transport_context_v1`、`stream_decode`、`phase_timeouts`、`stream_upload`、`write_timeout`;双工、代理、WebSocket 和请求 trailers 在调用时另检查对应能力。加载时检查 ABI 及所需能力,并使用系统级固定加载(Windows PIN / POSIX NODELETE),避免Node环境清理时卸载仍有Go后台线程的动态库。
353
+ 原生限制仍适用:最多 64 会话、256 未关闭请求,每会话最多 16 种传输配置。
354
+ 代理支持 HTTP/HTTPS CONNECT、socks5 及 socks5h,见代理章节;WebSocket见对应章节。
355
+
356
+ ## 与 Python 的语言差异
357
+
358
+ Node统一使用Promise API,不提供Python同步Session或共享同步会话多线程调用;对应场景采用同步迭代器输入和96路Promise请求验证,不宣称线程安全。Node Readable按自身流的生命周期管理文件句柄,区别于Python保留调用方文件对象。
359
+ JavaScript无法强行中断任意Promise:取消会立即释放原生请求,并请求迭代器return/destroy;生产者正在等待的自定义I/O必须支持取消,才能保证其finally及时执行。推荐使用接收AbortSignal的上传工厂。Python的协程强制取消场景对应为工厂信号取消,报告中明确标记适配。
360
+
361
+ ## 文档、许可证与使用声明
362
+
363
+ [完整 Node 文档](https://fasttls.github.io/node/) · [Apache-2.0](LICENSE)。原生第三方声明随平台包提供。
364
+
365
+ ## 使用边界与免责声明
366
+
367
+ 仅在拥有权限的系统、数据和网络上使用,并遵守适用法律、服务条款及隐私要求。软件按许可证约定以“现状”提供,不承诺可用性、特定用途适用性、第三方服务放行、匿名性或与真实浏览器完全一致。浏览器名称和版本用于描述配置,不表示获得相应厂商认证或背书。部署方负责访问控制、密钥和个人信息保护、请求限额及数据备份;担保排除与责任限制以随附许可证及适用法律为准。本说明不改变许可证授予的权利或新增许可限制。
368
+
369
+
370
+ ## 动态库选择与安装排错
371
+
372
+ 加载优先级:显式 SessionOptions.library → FASTLS_LIBRARY → 包内 manifest → 同版本平台可选依赖;源码目录可回退仓库 dist,已安装 thin 包不回退仓库。普通 npm 用户应保留 optionalDependencies,不必设置外部库路径。外部旧库可能覆盖新包,ABI 号相同也不保证扩展能力相同。Linux glibc 与 macOS 最低版本、摘要及动态依赖以 native/manifest.json 为准。更多排错见 [在线帮助](https://fasttls.github.io/help/)。
@@ -0,0 +1,30 @@
1
+ export interface CookieOptions {
2
+ url: string;
3
+ path?: string;
4
+ domain?: string;
5
+ secure?: boolean;
6
+ httpOnly?: boolean;
7
+ maxAge?: number;
8
+ /** RFC3339 timestamp. */
9
+ expires?: string;
10
+ }
11
+ export interface Cookie {
12
+ name: string;
13
+ value: string;
14
+ }
15
+ export declare class Cookies {
16
+ private readonly ensureOpen;
17
+ private readonly enabled;
18
+ private readonly jar;
19
+ constructor(ensureOpen: () => void, enabled?: boolean);
20
+ private url;
21
+ getAll(url: string): Cookie[];
22
+ get(name: string, url: string): string | undefined;
23
+ set(name: string, value: string, options: CookieOptions): void;
24
+ delete(name: string, options: Pick<CookieOptions, "url" | "path" | "domain">): void;
25
+ clear(): void;
26
+ /** @internal */
27
+ header(url: string): string;
28
+ /** @internal */
29
+ extract(url: string, fields: string[]): void;
30
+ }