@caido-utils/backend 1.6.1 → 1.7.0

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.
@@ -0,0 +1,8 @@
1
+ import type { HttpClientOptions, HttpClientRequest, HttpClientResponse, Result } from "./types";
2
+ export declare class HttpClient {
3
+ private readonly options;
4
+ private readonly semaphore;
5
+ constructor(options: HttpClientOptions);
6
+ send(request: HttpClientRequest): Promise<Result<HttpClientResponse>>;
7
+ private execute;
8
+ }
@@ -0,0 +1,119 @@
1
+ import { RequestSpec } from "caido:utils";
2
+ import { checkSignal, waitForUnpause } from "./signal.js";
3
+ function createSemaphore(limit) {
4
+ let active = 0;
5
+ const waiting = [];
6
+ function acquire() {
7
+ if (active < limit) {
8
+ active++;
9
+ return Promise.resolve();
10
+ }
11
+ return new Promise((resolve) => {
12
+ waiting.push(resolve);
13
+ });
14
+ }
15
+ function release() {
16
+ const next = waiting.shift();
17
+ if (next !== void 0) {
18
+ next();
19
+ } else {
20
+ active--;
21
+ }
22
+ }
23
+ return { acquire, release };
24
+ }
25
+ function delay(ms) {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+ export class HttpClient {
29
+ options;
30
+ semaphore;
31
+ constructor(options) {
32
+ this.options = options;
33
+ this.semaphore = createSemaphore(options.concurrency);
34
+ }
35
+ async send(request) {
36
+ if (this.options.signal !== void 0 && checkSignal(this.options.signal) === "stopped") {
37
+ return { kind: "Error", error: "Stopped" };
38
+ }
39
+ if (this.options.signal !== void 0) {
40
+ await waitForUnpause(this.options.signal);
41
+ }
42
+ if (this.options.signal !== void 0 && checkSignal(this.options.signal) === "stopped") {
43
+ return { kind: "Error", error: "Stopped" };
44
+ }
45
+ await this.semaphore.acquire();
46
+ try {
47
+ if (this.options.delayMs > 0) {
48
+ await delay(this.options.delayMs);
49
+ }
50
+ return await this.execute(request);
51
+ } finally {
52
+ this.semaphore.release();
53
+ }
54
+ }
55
+ async execute(request, attempt = 0) {
56
+ try {
57
+ const spec = new RequestSpec(request.url);
58
+ spec.setMethod(request.method);
59
+ const headers = { ...request.headers };
60
+ if (this.options.userAgents !== void 0 && this.options.userAgents.length > 0 && headers["User-Agent"] === void 0) {
61
+ const index = Math.floor(
62
+ Math.random() * this.options.userAgents.length
63
+ );
64
+ headers["User-Agent"] = this.options.userAgents[index];
65
+ }
66
+ for (const [name, value] of Object.entries(headers)) {
67
+ spec.setHeader(name, value);
68
+ }
69
+ if (request.body !== void 0) {
70
+ spec.setBody(request.body);
71
+ }
72
+ const result = await this.options.sdk.requests.send(spec, {
73
+ save: true,
74
+ timeouts: {
75
+ global: this.options.timeoutMs
76
+ }
77
+ });
78
+ const response = result.response;
79
+ const status = response.getCode();
80
+ const caidoId = result.request.getId() !== void 0 ? String(result.request.getId()) : void 0;
81
+ const contentTypeHeader = response.getHeader("content-type");
82
+ const responseHeaders = {};
83
+ if (contentTypeHeader !== void 0 && contentTypeHeader[0] !== void 0) {
84
+ responseHeaders["content-type"] = contentTypeHeader[0];
85
+ }
86
+ const locationHeader = response.getHeader("location");
87
+ if (locationHeader !== void 0 && locationHeader[0] !== void 0) {
88
+ responseHeaders["location"] = locationHeader[0];
89
+ }
90
+ const bodyBuffer = response.getBody();
91
+ const body = bodyBuffer !== void 0 ? bodyBuffer.toText() : "";
92
+ const maxRetries = this.options.retries ?? 0;
93
+ if (attempt < maxRetries && (status === 429 || status >= 500)) {
94
+ const retryDelay = this.options.retryDelayMs ?? 1e3;
95
+ await delay(retryDelay * (attempt + 1));
96
+ return this.execute(request, attempt + 1);
97
+ }
98
+ return {
99
+ kind: "Ok",
100
+ value: {
101
+ status,
102
+ headers: responseHeaders,
103
+ body,
104
+ url: result.request.getUrl() ?? request.url,
105
+ caidoId
106
+ }
107
+ };
108
+ } catch (error) {
109
+ const maxRetries = this.options.retries ?? 0;
110
+ if (attempt < maxRetries) {
111
+ const retryDelay = this.options.retryDelayMs ?? 1e3;
112
+ await delay(retryDelay * (attempt + 1));
113
+ return this.execute(request, attempt + 1);
114
+ }
115
+ const message = error instanceof Error ? error.message : "Unknown error";
116
+ return { kind: "Error", error: message };
117
+ }
118
+ }
119
+ }
@@ -1 +1,6 @@
1
+ export { HttpClient } from "./client";
1
2
  export { HttpRequest, type HttpRequestOptions } from "./request";
3
+ export { checkSignal, waitForUnpause } from "./signal";
4
+ export { getStatusText } from "./status";
5
+ export type { HttpClientOptions, HttpClientRequest, HttpClientResponse, HttpMethod, Result, Signal, } from "./types";
6
+ export { parseUrl } from "./url";
@@ -1 +1,5 @@
1
+ export { HttpClient } from "./client.js";
1
2
  export { HttpRequest } from "./request.js";
3
+ export { checkSignal, waitForUnpause } from "./signal.js";
4
+ export { getStatusText } from "./status.js";
5
+ export { parseUrl } from "./url.js";
@@ -0,0 +1,3 @@
1
+ import type { Signal } from "./types";
2
+ export declare function checkSignal(signal: Signal): "continue" | "stopped";
3
+ export declare function waitForUnpause(signal: Signal): Promise<void>;
@@ -0,0 +1,20 @@
1
+ const PAUSE_POLL_MS = 200;
2
+ export function checkSignal(signal) {
3
+ if (signal.stopped) return "stopped";
4
+ return "continue";
5
+ }
6
+ export function waitForUnpause(signal) {
7
+ if (!signal.paused || signal.stopped) {
8
+ return Promise.resolve();
9
+ }
10
+ return new Promise((resolve) => {
11
+ const poll = () => {
12
+ if (!signal.paused || signal.stopped) {
13
+ resolve();
14
+ return;
15
+ }
16
+ setTimeout(poll, PAUSE_POLL_MS);
17
+ };
18
+ setTimeout(poll, PAUSE_POLL_MS);
19
+ });
20
+ }
@@ -0,0 +1 @@
1
+ export declare function getStatusText(code: string): string;
@@ -0,0 +1,22 @@
1
+ const STATUS_TEXTS = {
2
+ "200": "OK",
3
+ "201": "Created",
4
+ "204": "No Content",
5
+ "301": "Moved Permanently",
6
+ "302": "Found",
7
+ "304": "Not Modified",
8
+ "307": "Temporary Redirect",
9
+ "308": "Permanent Redirect",
10
+ "400": "Bad Request",
11
+ "401": "Unauthorized",
12
+ "403": "Forbidden",
13
+ "404": "Not Found",
14
+ "405": "Method Not Allowed",
15
+ "500": "Internal Server Error",
16
+ "501": "Not Implemented",
17
+ "502": "Bad Gateway",
18
+ "503": "Service Unavailable"
19
+ };
20
+ export function getStatusText(code) {
21
+ return STATUS_TEXTS[code] ?? "Unknown";
22
+ }
@@ -0,0 +1,36 @@
1
+ import type { SDK } from "caido:plugin";
2
+ export type Signal = {
3
+ stopped: boolean;
4
+ paused: boolean;
5
+ };
6
+ export type Result<T> = {
7
+ kind: "Ok";
8
+ value: T;
9
+ } | {
10
+ kind: "Error";
11
+ error: string;
12
+ };
13
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
14
+ export type HttpClientOptions = {
15
+ sdk: SDK;
16
+ concurrency: number;
17
+ delayMs: number;
18
+ timeoutMs: number;
19
+ signal?: Signal;
20
+ userAgents?: string[];
21
+ retries?: number;
22
+ retryDelayMs?: number;
23
+ };
24
+ export type HttpClientRequest = {
25
+ url: string;
26
+ method: HttpMethod;
27
+ headers?: Record<string, string>;
28
+ body?: string;
29
+ };
30
+ export type HttpClientResponse = {
31
+ status: number;
32
+ headers: Record<string, string>;
33
+ body: string;
34
+ url: string;
35
+ caidoId: string | undefined;
36
+ };
File without changes
@@ -0,0 +1,5 @@
1
+ export declare function parseUrl(url: string): {
2
+ hostname: string;
3
+ pathname: string;
4
+ query: string;
5
+ };
@@ -0,0 +1,9 @@
1
+ import parse from "url-parse";
2
+ export function parseUrl(url) {
3
+ const parsed = parse(url);
4
+ return {
5
+ hostname: parsed.hostname,
6
+ pathname: parsed.pathname,
7
+ query: parsed.query
8
+ };
9
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { spawnCommand } from "./process";
2
2
  export { sha256 } from "./crypto";
3
3
  export { resolveFilterQuery } from "./filter";
4
- export { HttpRequest, type HttpRequestOptions } from "./http";
4
+ export { HttpClient, HttpRequest, checkSignal, getStatusText, parseUrl, waitForUnpause, type HttpClientOptions, type HttpClientRequest, type HttpClientResponse, type HttpMethod, type HttpRequestOptions, type Result, type Signal, } from "./http";
5
5
  export { Queue } from "./pool";
6
6
  export type { PoolCallbacks, PoolConfig } from "./pool";
7
7
  export { getProjectId } from "./project";
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  export { spawnCommand } from "./process/index.js";
2
2
  export { sha256 } from "./crypto/index.js";
3
3
  export { resolveFilterQuery } from "./filter/index.js";
4
- export { HttpRequest } from "./http/index.js";
4
+ export {
5
+ HttpClient,
6
+ HttpRequest,
7
+ checkSignal,
8
+ getStatusText,
9
+ parseUrl,
10
+ waitForUnpause
11
+ } from "./http/index.js";
5
12
  export { Queue } from "./pool/index.js";
6
13
  export { getProjectId } from "./project/index.js";
7
14
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caido-utils/backend",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"