tls-client-node
Advanced tools
| import type { MultipartBodyLike } from "./types"; | ||
| export type MultipartFieldValue = string | number | boolean | Blob; | ||
| export type MultipartFileSource = string | Blob | ArrayBuffer | ArrayBufferView; | ||
| export interface MultipartFileOptions { | ||
| filename: string; | ||
| contentType?: string; | ||
| } | ||
| export interface MultipartFileValue extends MultipartFileOptions { | ||
| data: MultipartFileSource; | ||
| } | ||
| export type MultipartValue = MultipartFieldValue | MultipartFileValue; | ||
| export type MultipartRecord = Record<string, MultipartValue | MultipartValue[]>; | ||
| export declare class MultipartForm implements MultipartBodyLike { | ||
| private readonly formData; | ||
| append(name: string, value: MultipartFieldValue): this; | ||
| appendFile(name: string, value: MultipartFileSource, options: MultipartFileOptions): this; | ||
| appendJson(name: string, value: unknown, filename?: string): this; | ||
| toFormData(): FormData; | ||
| static fromRecord(record: MultipartRecord): MultipartForm; | ||
| } | ||
| export declare function createMultipartForm(record?: MultipartRecord): MultipartForm; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.MultipartForm = void 0; | ||
| exports.createMultipartForm = createMultipartForm; | ||
| function isMultipartFileValue(value) { | ||
| return typeof value === "object" && value !== null && "data" in value; | ||
| } | ||
| function createBlobFromSource(value, contentType) { | ||
| if (value instanceof Blob && contentType === undefined) { | ||
| return value; | ||
| } | ||
| if (value instanceof Blob) { | ||
| return new Blob([value], { type: contentType }); | ||
| } | ||
| if (value instanceof ArrayBuffer) { | ||
| return new Blob([value], { type: contentType }); | ||
| } | ||
| if (ArrayBuffer.isView(value)) { | ||
| const bytes = Uint8Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)); | ||
| return new Blob([ | ||
| bytes, | ||
| ], { type: contentType }); | ||
| } | ||
| return new Blob([value], { type: contentType }); | ||
| } | ||
| class MultipartForm { | ||
| formData = new FormData(); | ||
| append(name, value) { | ||
| if (value instanceof Blob) { | ||
| this.formData.append(name, value); | ||
| } | ||
| else { | ||
| this.formData.append(name, String(value)); | ||
| } | ||
| return this; | ||
| } | ||
| appendFile(name, value, options) { | ||
| this.formData.append(name, createBlobFromSource(value, options.contentType), options.filename); | ||
| return this; | ||
| } | ||
| appendJson(name, value, filename = `${name}.json`) { | ||
| return this.appendFile(name, JSON.stringify(value), { | ||
| filename, | ||
| contentType: "application/json", | ||
| }); | ||
| } | ||
| toFormData() { | ||
| return this.formData; | ||
| } | ||
| static fromRecord(record) { | ||
| const form = new MultipartForm(); | ||
| for (const [name, rawValue] of Object.entries(record)) { | ||
| const values = Array.isArray(rawValue) ? rawValue : [rawValue]; | ||
| for (const value of values) { | ||
| if (isMultipartFileValue(value)) { | ||
| form.appendFile(name, value.data, value); | ||
| } | ||
| else { | ||
| form.append(name, value); | ||
| } | ||
| } | ||
| } | ||
| return form; | ||
| } | ||
| } | ||
| exports.MultipartForm = MultipartForm; | ||
| function createMultipartForm(record) { | ||
| return record ? MultipartForm.fromRecord(record) : new MultipartForm(); | ||
| } |
+3
-3
@@ -8,3 +8,3 @@ import { CookieJar } from "tough-cookie"; | ||
| } | ||
| declare function buildForwardPayload(url: string, sessionDefaults: SessionOptions | undefined, requestOptions: RequestOptions, sessionId?: string): { | ||
| declare function buildForwardPayload(url: string, sessionDefaults: SessionOptions | undefined, requestOptions: RequestOptions, sessionId?: string): Promise<{ | ||
| requestUrl: string; | ||
@@ -47,4 +47,4 @@ requestMethod: HttpMethod; | ||
| streamOutputEOFSymbol: string | undefined; | ||
| }; | ||
| type ForwardPayload = ReturnType<typeof buildForwardPayload>; | ||
| }>; | ||
| type ForwardPayload = Awaited<ReturnType<typeof buildForwardPayload>>; | ||
| export declare class TLSClient { | ||
@@ -51,0 +51,0 @@ private readonly options; |
+47
-16
@@ -226,4 +226,20 @@ "use strict"; | ||
| } | ||
| function encodeBody(body, headers, forceBinary) { | ||
| if (body === undefined || body === null) { | ||
| function normalizeRedirectBehavior(value) { | ||
| if (value === undefined) { | ||
| return undefined; | ||
| } | ||
| if (typeof value === "boolean") { | ||
| return value; | ||
| } | ||
| return value === "follow"; | ||
| } | ||
| function isMultipartBodyLike(body) { | ||
| return typeof body === "object" && body !== null && "toFormData" in body; | ||
| } | ||
| function isFormDataBody(body) { | ||
| return typeof FormData !== "undefined" && body instanceof FormData; | ||
| } | ||
| async function encodeBody(body, headers, forceBinary) { | ||
| const normalizedBody = isMultipartBodyLike(body) ? body.toFormData() : body; | ||
| if (normalizedBody === undefined || normalizedBody === null) { | ||
| return { | ||
@@ -234,9 +250,9 @@ requestBody: undefined, | ||
| } | ||
| if (typeof body === "string") { | ||
| if (typeof normalizedBody === "string") { | ||
| return { | ||
| requestBody: body, | ||
| requestBody: normalizedBody, | ||
| isByteRequest: Boolean(forceBinary), | ||
| }; | ||
| } | ||
| if (body instanceof URLSearchParams) { | ||
| if (normalizedBody instanceof URLSearchParams) { | ||
| if (!headers["content-type"]) { | ||
@@ -246,10 +262,25 @@ headers["content-type"] = "application/x-www-form-urlencoded;charset=UTF-8"; | ||
| return { | ||
| requestBody: body.toString(), | ||
| requestBody: normalizedBody.toString(), | ||
| isByteRequest: false, | ||
| }; | ||
| } | ||
| if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) { | ||
| const buffer = body instanceof ArrayBuffer | ||
| ? Buffer.from(body) | ||
| : Buffer.from(body.buffer, body.byteOffset, body.byteLength); | ||
| if (isFormDataBody(normalizedBody)) { | ||
| const request = new Request("http://127.0.0.1/", { | ||
| method: "POST", | ||
| body: normalizedBody, | ||
| }); | ||
| const contentType = request.headers.get("content-type"); | ||
| const arrayBuffer = await request.arrayBuffer(); | ||
| if (contentType) { | ||
| headers["content-type"] = contentType; | ||
| } | ||
| return { | ||
| requestBody: Buffer.from(arrayBuffer).toString("base64"), | ||
| isByteRequest: true, | ||
| }; | ||
| } | ||
| if (normalizedBody instanceof ArrayBuffer || ArrayBuffer.isView(normalizedBody)) { | ||
| const buffer = normalizedBody instanceof ArrayBuffer | ||
| ? Buffer.from(normalizedBody) | ||
| : Buffer.from(normalizedBody.buffer, normalizedBody.byteOffset, normalizedBody.byteLength); | ||
| if (!headers["content-type"]) { | ||
@@ -267,3 +298,3 @@ headers["content-type"] = "application/octet-stream"; | ||
| return { | ||
| requestBody: JSON.stringify(body), | ||
| requestBody: JSON.stringify(normalizedBody), | ||
| isByteRequest: false, | ||
@@ -311,3 +342,3 @@ }; | ||
| } | ||
| function buildForwardPayload(url, sessionDefaults, requestOptions, sessionId) { | ||
| async function buildForwardPayload(url, sessionDefaults, requestOptions, sessionId) { | ||
| const headers = mergeHeaders(sessionDefaults?.headers, requestOptions.headers); | ||
@@ -317,3 +348,3 @@ const profileSelection = getProfileSelection(sessionDefaults, requestOptions); | ||
| const timeoutSeconds = pickDefined(requestOptions.timeoutSeconds, sessionDefaults?.timeoutSeconds, timeoutMilliseconds !== undefined ? 0 : 30); | ||
| const { requestBody, isByteRequest } = encodeBody(requestOptions.body, headers, requestOptions.isByteRequest); | ||
| const { requestBody, isByteRequest } = await encodeBody(requestOptions.body, headers, requestOptions.isByteRequest); | ||
| return { | ||
@@ -326,3 +357,3 @@ requestUrl: url, | ||
| customTlsClient: profileSelection.customTlsClient, | ||
| followRedirects: pickDefined(requestOptions.followRedirects, sessionDefaults?.followRedirects, false), | ||
| followRedirects: pickDefined(normalizeRedirectBehavior(requestOptions.redirect), requestOptions.followRedirects, normalizeRedirectBehavior(sessionDefaults?.redirect), sessionDefaults?.followRedirects, false), | ||
| insecureSkipVerify: pickDefined(requestOptions.insecureSkipVerify, sessionDefaults?.insecureSkipVerify, false), | ||
@@ -628,3 +659,3 @@ withoutCookieJar: pickDefined(requestOptions.withoutCookieJar, sessionDefaults?.withoutCookieJar, false), | ||
| async request(url, options = {}) { | ||
| const payload = buildForwardPayload(url, undefined, options, options.sessionId); | ||
| const payload = await buildForwardPayload(url, undefined, options, options.sessionId); | ||
| return this.forward(payload); | ||
@@ -873,3 +904,3 @@ } | ||
| : normalizeCookieInput(options.cookies); | ||
| const payload = buildForwardPayload(url, this.defaults, { | ||
| const payload = await buildForwardPayload(url, this.defaults, { | ||
| ...options, | ||
@@ -876,0 +907,0 @@ cookies: requestCookies, |
+2
-2
@@ -7,4 +7,4 @@ export { TLSClient, Session, fetch } from "./client"; | ||
| export { TLSResponse } from "./response"; | ||
| export { ClientIdentifier } from "./types"; | ||
| export type { SerializedCookieJar } from "./types"; | ||
| export { MultipartForm, createMultipartForm } from "./multipart"; | ||
| export { ClientIdentifier, Emulation } from "./types"; | ||
| export type * from "./types"; |
+5
-1
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ClientIdentifier = exports.TLSResponse = exports.TLSClientError = exports.CookieJar = exports.Session = exports.TLSClient = void 0; | ||
| exports.Emulation = exports.ClientIdentifier = exports.createMultipartForm = exports.MultipartForm = exports.TLSResponse = exports.TLSClientError = exports.CookieJar = exports.Session = exports.TLSClient = void 0; | ||
| var client_1 = require("./client"); | ||
@@ -14,3 +14,7 @@ Object.defineProperty(exports, "TLSClient", { enumerable: true, get: function () { return client_1.TLSClient; } }); | ||
| Object.defineProperty(exports, "TLSResponse", { enumerable: true, get: function () { return response_1.TLSResponse; } }); | ||
| var multipart_1 = require("./multipart"); | ||
| Object.defineProperty(exports, "MultipartForm", { enumerable: true, get: function () { return multipart_1.MultipartForm; } }); | ||
| Object.defineProperty(exports, "createMultipartForm", { enumerable: true, get: function () { return multipart_1.createMultipartForm; } }); | ||
| var types_1 = require("./types"); | ||
| Object.defineProperty(exports, "ClientIdentifier", { enumerable: true, get: function () { return types_1.ClientIdentifier; } }); | ||
| Object.defineProperty(exports, "Emulation", { enumerable: true, get: function () { return types_1.Emulation; } }); |
+91
-1
@@ -86,3 +86,88 @@ import type { CookieJar } from "tough-cookie"; | ||
| }; | ||
| export declare const Emulation: { | ||
| readonly chrome_103: "chrome_103"; | ||
| readonly chrome_104: "chrome_104"; | ||
| readonly chrome_105: "chrome_105"; | ||
| readonly chrome_106: "chrome_106"; | ||
| readonly chrome_107: "chrome_107"; | ||
| readonly chrome_108: "chrome_108"; | ||
| readonly chrome_109: "chrome_109"; | ||
| readonly chrome_110: "chrome_110"; | ||
| readonly chrome_111: "chrome_111"; | ||
| readonly chrome_112: "chrome_112"; | ||
| readonly chrome_116_PSK: "chrome_116_PSK"; | ||
| readonly chrome_116_PSK_PQ: "chrome_116_PSK_PQ"; | ||
| readonly chrome_117: "chrome_117"; | ||
| readonly chrome_120: "chrome_120"; | ||
| readonly chrome_124: "chrome_124"; | ||
| readonly chrome_130_PSK: "chrome_130_PSK"; | ||
| readonly chrome_131: "chrome_131"; | ||
| readonly chrome_131_PSK: "chrome_131_PSK"; | ||
| readonly chrome_131_psk: "chrome_131_PSK"; | ||
| readonly chrome_133: "chrome_133"; | ||
| readonly chrome_133_PSK: "chrome_133_PSK"; | ||
| readonly chrome_136: "chrome_136"; | ||
| readonly chrome_144: "chrome_144"; | ||
| readonly chrome_144_PSK: "chrome_144_PSK"; | ||
| readonly chrome_145: "chrome_145"; | ||
| readonly chrome_146: "chrome_146"; | ||
| readonly chrome_146_PSK: "chrome_146_PSK"; | ||
| readonly brave_146: "brave_146"; | ||
| readonly brave_146_PSK: "brave_146_PSK"; | ||
| readonly firefox_102: "firefox_102"; | ||
| readonly firefox_104: "firefox_104"; | ||
| readonly firefox_105: "firefox_105"; | ||
| readonly firefox_106: "firefox_106"; | ||
| readonly firefox_108: "firefox_108"; | ||
| readonly firefox_110: "firefox_110"; | ||
| readonly firefox_117: "firefox_117"; | ||
| readonly firefox_120: "firefox_120"; | ||
| readonly firefox_123: "firefox_123"; | ||
| readonly firefox_132: "firefox_132"; | ||
| readonly firefox_133: "firefox_133"; | ||
| readonly firefox_135: "firefox_135"; | ||
| readonly firefox_146_PSK: "firefox_146_PSK"; | ||
| readonly firefox_147: "firefox_147"; | ||
| readonly firefox_147_PSK: "firefox_147_PSK"; | ||
| readonly firefox_148: "firefox_148"; | ||
| readonly safari_15_6_1: "safari_15_6_1"; | ||
| readonly safari_16_0: "safari_16_0"; | ||
| readonly safari_ipad_15_6: "safari_ipad_15_6"; | ||
| readonly safari_ios_15_5: "safari_ios_15_5"; | ||
| readonly safari_ios_15_6: "safari_ios_15_6"; | ||
| readonly safari_ios_16_0: "safari_ios_16_0"; | ||
| readonly safari_ios_17_0: "safari_ios_17_0"; | ||
| readonly safari_ios_18_0: "safari_ios_18_0"; | ||
| readonly safari_ios_18_5: "safari_ios_18_5"; | ||
| readonly safari_ios_26_0: "safari_ios_26_0"; | ||
| readonly opera_89: "opera_89"; | ||
| readonly opera_90: "opera_90"; | ||
| readonly opera_91: "opera_91"; | ||
| readonly okhttp4_android_7: "okhttp4_android_7"; | ||
| readonly okhttp4_android_8: "okhttp4_android_8"; | ||
| readonly okhttp4_android_9: "okhttp4_android_9"; | ||
| readonly okhttp4_android_10: "okhttp4_android_10"; | ||
| readonly okhttp4_android_11: "okhttp4_android_11"; | ||
| readonly okhttp4_android_12: "okhttp4_android_12"; | ||
| readonly okhttp4_android_13: "okhttp4_android_13"; | ||
| readonly confirmed_android: "confirmed_android"; | ||
| readonly confirmed_ios: "confirmed_ios"; | ||
| readonly mesh_android: "mesh_android"; | ||
| readonly mesh_android_1: "mesh_android_1"; | ||
| readonly mesh_android_2: "mesh_android_2"; | ||
| readonly mesh_ios: "mesh_ios"; | ||
| readonly mesh_ios_1: "mesh_ios_1"; | ||
| readonly mesh_ios_2: "mesh_ios_2"; | ||
| readonly mms_ios: "mms_ios"; | ||
| readonly mms_ios_1: "mms_ios_1"; | ||
| readonly mms_ios_2: "mms_ios_2"; | ||
| readonly mms_ios_3: "mms_ios_3"; | ||
| readonly nike_android_mobile: "nike_android_mobile"; | ||
| readonly nike_ios_mobile: "nike_ios_mobile"; | ||
| readonly zalando_android_mobile: "zalando_android_mobile"; | ||
| readonly zalando_ios_mobile: "zalando_ios_mobile"; | ||
| readonly cloudscraper: "cloudscraper"; | ||
| }; | ||
| export type ClientIdentifier = (typeof ClientIdentifier)[keyof typeof ClientIdentifier] | (string & {}); | ||
| export type Emulation = ClientIdentifier; | ||
| export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; | ||
@@ -92,2 +177,6 @@ export type HeaderInputValue = string | number | boolean; | ||
| export type CookieMap = Record<string, string>; | ||
| export type RedirectBehavior = boolean | "follow" | "manual"; | ||
| export interface MultipartBodyLike { | ||
| toFormData(): FormData; | ||
| } | ||
| export interface Cookie { | ||
@@ -167,2 +256,3 @@ name: string; | ||
| isRotatingProxy?: boolean; | ||
| redirect?: RedirectBehavior; | ||
| followRedirects?: boolean; | ||
@@ -195,3 +285,3 @@ insecureSkipVerify?: boolean; | ||
| } | ||
| export type RequestBody = string | URLSearchParams | ArrayBuffer | ArrayBufferView | Record<string, unknown> | null | undefined; | ||
| export type RequestBody = string | URLSearchParams | FormData | MultipartBodyLike | ArrayBuffer | ArrayBufferView | Record<string, unknown> | null | undefined; | ||
| export interface RequestOptions extends SessionOptions { | ||
@@ -198,0 +288,0 @@ method?: HttpMethod | Lowercase<HttpMethod>; |
+2
-1
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ClientIdentifier = void 0; | ||
| exports.Emulation = exports.ClientIdentifier = void 0; | ||
| exports.ClientIdentifier = { | ||
@@ -88,1 +88,2 @@ chrome_103: "chrome_103", | ||
| }; | ||
| exports.Emulation = exports.ClientIdentifier; |
+3
-0
@@ -5,3 +5,6 @@ import pkg from "./dist/index.js"; | ||
| ClientIdentifier, | ||
| Emulation, | ||
| CookieJar, | ||
| MultipartForm, | ||
| createMultipartForm, | ||
| TLSClient, | ||
@@ -8,0 +11,0 @@ Session, |
+1
-1
| { | ||
| "name": "tls-client-node", | ||
| "version": "0.1.7", | ||
| "version": "0.1.8", | ||
| "description": "Node.js client for bogdanfinn/tls-client with native shared-library loading and optional managed runtime support.", | ||
@@ -5,0 +5,0 @@ "author": "Fatih Kabak", |
+65
-2
@@ -79,3 +79,6 @@ <div align="center"> | ||
| ClientIdentifier, | ||
| Emulation, | ||
| MultipartForm, | ||
| TLSClient, | ||
| createMultipartForm, | ||
| } from "tls-client-node"; | ||
@@ -85,3 +88,3 @@ | ||
| const session = client.session({ | ||
| clientIdentifier: ClientIdentifier.chrome_136, | ||
| clientIdentifier: Emulation.chrome_136, | ||
| }); | ||
@@ -93,3 +96,3 @@ ``` | ||
| ```js | ||
| const { ClientIdentifier, TLSClient } = require("tls-client-node"); | ||
| const { ClientIdentifier, Emulation, MultipartForm, TLSClient, createMultipartForm } = require("tls-client-node"); | ||
| ``` | ||
@@ -161,2 +164,59 @@ | ||
| ## Multipart Form Uploads | ||
| ```ts | ||
| import { MultipartForm, TLSClient, createMultipartForm } from "tls-client-node"; | ||
| const client = new TLSClient(); | ||
| const form = createMultipartForm({ | ||
| title: "example", | ||
| file: { | ||
| data: "hello world", | ||
| filename: "hello.txt", | ||
| contentType: "text/plain", | ||
| }, | ||
| }); | ||
| const builder = new MultipartForm() | ||
| .append("kind", "builder") | ||
| .appendJson("meta", { ok: true }); | ||
| const response = await client.request("https://example.com/upload", { | ||
| method: "POST", | ||
| body: form, | ||
| }); | ||
| console.log(response.status); | ||
| await client.request("https://example.com/upload-builder", { | ||
| method: "POST", | ||
| body: builder, | ||
| }); | ||
| await client.stop(); | ||
| ``` | ||
| ## Redirect Ergonomics | ||
| ```ts | ||
| import { TLSClient } from "tls-client-node"; | ||
| const client = new TLSClient(); | ||
| const session = client.session({ | ||
| redirect: "follow", | ||
| }); | ||
| await session.get("https://example.com/start", { | ||
| redirect: "manual", | ||
| }); | ||
| await client.stop(); | ||
| ``` | ||
| `redirect` is a higher-level alias for `followRedirects`. | ||
| - `redirect: "follow"` maps to `followRedirects: true` | ||
| - `redirect: "manual"` maps to `followRedirects: false` | ||
| - `redirect: true` and `redirect: false` are also accepted | ||
| ## Runtime Modes | ||
@@ -259,3 +319,6 @@ | ||
| - Each `Session` keeps a `tough-cookie` jar in sync with request and response cookies. You can inspect URL cookies with `session.cookies(url)` or serialize the jar with `session.exportCookies()`. | ||
| - `Emulation` is exported as a higher-level alias for `ClientIdentifier`, so `Emulation.chrome_136` and `ClientIdentifier.chrome_136` are equivalent. | ||
| - Binary responses are returned as a data URL when `byteResponse: true` is enabled, matching upstream behavior. | ||
| - `FormData`, `MultipartForm`, and `createMultipartForm()` can all be used for multipart uploads, with the generated boundary preserved in the `content-type` header. | ||
| - `redirect` is a higher-level alias over `followRedirects`; it improves call-site clarity without changing upstream redirect semantics. | ||
| - WebSocket upgrade and frame APIs are not currently implemented in this wrapper. | ||
@@ -262,0 +325,0 @@ - New upstream client identifiers can be passed as plain strings even before this package adds them to `ClientIdentifier`. |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
110441
10.95%22
10%2242
10.83%337
22.99%