@caido-utils/backend 1.7.0 → 1.8.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.
Files changed (43) hide show
  1. package/dist/http/client/index.d.ts +6 -0
  2. package/dist/http/client/index.js +7 -0
  3. package/dist/http/client/normalize.d.ts +10 -0
  4. package/dist/http/client/normalize.js +27 -0
  5. package/dist/http/client/raw.d.ts +49 -0
  6. package/dist/http/client/raw.js +97 -0
  7. package/dist/http/client/transport.d.ts +7 -0
  8. package/dist/http/client/transport.js +131 -0
  9. package/dist/http/{types.d.ts → client/types.d.ts} +17 -0
  10. package/dist/http/helpers/index.d.ts +5 -0
  11. package/dist/http/helpers/index.js +5 -0
  12. package/dist/http/index.d.ts +2 -6
  13. package/dist/http/index.js +17 -5
  14. package/dist/index.d.ts +3 -2
  15. package/dist/index.js +8 -5
  16. package/dist/jobs/index.d.ts +3 -0
  17. package/dist/jobs/index.js +2 -0
  18. package/dist/jobs/manager.d.ts +18 -0
  19. package/dist/jobs/manager.js +98 -0
  20. package/dist/jobs/types.d.ts +10 -0
  21. package/dist/jobs/types.js +0 -0
  22. package/dist/jobs/worker-pool.d.ts +14 -0
  23. package/dist/jobs/worker-pool.js +41 -0
  24. package/package.json +1 -1
  25. package/dist/requests/fetcher.d.ts +0 -17
  26. package/dist/requests/fetcher.js +0 -43
  27. package/dist/requests/index.d.ts +0 -3
  28. package/dist/requests/index.js +0 -3
  29. /package/dist/http/{signal.d.ts → client/signal.d.ts} +0 -0
  30. /package/dist/http/{signal.js → client/signal.js} +0 -0
  31. /package/dist/http/{client.d.ts → client/spec.d.ts} +0 -0
  32. /package/dist/http/{client.js → client/spec.js} +0 -0
  33. /package/dist/http/{types.js → client/types.js} +0 -0
  34. /package/dist/{requests → http/helpers}/headers.d.ts +0 -0
  35. /package/dist/{requests → http/helpers}/headers.js +0 -0
  36. /package/dist/{requests → http/helpers}/query.d.ts +0 -0
  37. /package/dist/{requests → http/helpers}/query.js +0 -0
  38. /package/dist/http/{request.d.ts → helpers/request.d.ts} +0 -0
  39. /package/dist/http/{request.js → helpers/request.js} +0 -0
  40. /package/dist/http/{status.d.ts → helpers/status.d.ts} +0 -0
  41. /package/dist/http/{status.js → helpers/status.js} +0 -0
  42. /package/dist/http/{url.d.ts → helpers/url.d.ts} +0 -0
  43. /package/dist/http/{url.js → helpers/url.js} +0 -0
@@ -0,0 +1,6 @@
1
+ export { applyCloseConnection, applyContentLength } from "./normalize";
2
+ export { RawHttpClient, type BatchCallbacks, type RawHttpSettings, type RawRequest, } from "./raw";
3
+ export { checkSignal, waitForUnpause } from "./signal";
4
+ export { HttpClient } from "./spec";
5
+ export { resolveRedirectUrl, sendRawRequest } from "./transport";
6
+ export type { HttpClientOptions, HttpClientRequest, HttpClientResponse, HttpMethod, RawHttpConfig, RawSendResult, RedirectPolicy, Result, Signal, } from "./types";
@@ -0,0 +1,7 @@
1
+ export { applyCloseConnection, applyContentLength } from "./normalize.js";
2
+ export {
3
+ RawHttpClient
4
+ } from "./raw.js";
5
+ export { checkSignal, waitForUnpause } from "./signal.js";
6
+ export { HttpClient } from "./spec.js";
7
+ export { resolveRedirectUrl, sendRawRequest } from "./transport.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Ensure Connection: close header is present in the raw request.
3
+ */
4
+ export declare function applyCloseConnection(raw: string): string;
5
+ /**
6
+ * Recalculate and set the Content-Length header based on the body.
7
+ * Uses string length as a byte proxy — raw HTTP bodies from Caido are
8
+ * single-byte encoded, so string length matches byte length.
9
+ */
10
+ export declare function applyContentLength(raw: string): string;
@@ -0,0 +1,27 @@
1
+ export function applyCloseConnection(raw) {
2
+ const headerEnd = raw.indexOf("\r\n\r\n");
3
+ if (headerEnd === -1) return raw;
4
+ const headerSection = raw.slice(0, headerEnd);
5
+ if (/^Connection\s*:/im.test(headerSection)) {
6
+ return raw.replace(/^Connection\s*:.*$/im, "Connection: close");
7
+ }
8
+ return raw.slice(0, headerEnd) + "\r\nConnection: close" + raw.slice(headerEnd);
9
+ }
10
+ export function applyContentLength(raw) {
11
+ const headerEnd = raw.indexOf("\r\n\r\n");
12
+ if (headerEnd === -1) return raw;
13
+ const body = raw.slice(headerEnd + 4);
14
+ const bodyLength = body.length;
15
+ const headerSection = raw.slice(0, headerEnd);
16
+ if (/^Content-Length\s*:/im.test(headerSection)) {
17
+ return headerSection.replace(
18
+ /^Content-Length\s*:.*$/im,
19
+ `Content-Length: ${bodyLength}`
20
+ ) + raw.slice(headerEnd);
21
+ }
22
+ if (bodyLength > 0) {
23
+ return headerSection + `\r
24
+ Content-Length: ${bodyLength}` + raw.slice(headerEnd);
25
+ }
26
+ return raw;
27
+ }
@@ -0,0 +1,49 @@
1
+ import type { SDK } from "caido:plugin";
2
+ import type { RawSendResult, RedirectPolicy } from "./types";
3
+ export type RawHttpSettings = {
4
+ threads: number;
5
+ delay: number;
6
+ redirection: {
7
+ max: number;
8
+ strategy: RedirectPolicy;
9
+ };
10
+ retry: {
11
+ max: number;
12
+ delay: number;
13
+ };
14
+ normalization: {
15
+ closeConnection: boolean;
16
+ updateContentLength: boolean;
17
+ };
18
+ };
19
+ export type RawRequest = {
20
+ url: string;
21
+ raw: string;
22
+ };
23
+ export type BatchCallbacks = {
24
+ onResult?: (result: RawSendResult, index: number) => void;
25
+ onError?: (error: string, index: number) => void;
26
+ onDone?: (total: number) => void;
27
+ };
28
+ export declare class RawHttpClient {
29
+ settings: RawHttpSettings;
30
+ private sdk;
31
+ private signal;
32
+ private queue;
33
+ constructor(sdk: SDK, settings?: Partial<RawHttpSettings>);
34
+ /**
35
+ * Send a single request. Returns the result.
36
+ */
37
+ send(request: RawRequest): Promise<RawSendResult>;
38
+ /**
39
+ * Send multiple requests through a concurrent queue.
40
+ * Results are delivered via callbacks.
41
+ */
42
+ send(requests: RawRequest[], callbacks?: BatchCallbacks): Promise<void>;
43
+ pause(): void;
44
+ resume(): void;
45
+ stop(): void;
46
+ private toConfig;
47
+ private sendSingle;
48
+ private sendBatch;
49
+ }
@@ -0,0 +1,97 @@
1
+ import { Queue } from "../../pool/pool.js";
2
+ import { sendRawRequest } from "./transport.js";
3
+ import { checkSignal, waitForUnpause } from "./signal.js";
4
+ const DEFAULT_SETTINGS = {
5
+ threads: 1,
6
+ delay: 0,
7
+ redirection: { max: 5, strategy: "never" },
8
+ retry: { max: 0, delay: 1e3 },
9
+ normalization: { closeConnection: false, updateContentLength: true }
10
+ };
11
+ export class RawHttpClient {
12
+ settings;
13
+ sdk;
14
+ signal;
15
+ queue;
16
+ constructor(sdk, settings) {
17
+ this.sdk = sdk;
18
+ this.signal = { stopped: false, paused: false };
19
+ this.settings = { ...DEFAULT_SETTINGS, ...settings };
20
+ }
21
+ async send(input, callbacks) {
22
+ if (!Array.isArray(input)) {
23
+ return this.sendSingle(input);
24
+ }
25
+ return this.sendBatch(input, callbacks);
26
+ }
27
+ pause() {
28
+ this.signal.paused = true;
29
+ this.queue?.pause();
30
+ }
31
+ resume() {
32
+ this.signal.paused = false;
33
+ this.queue?.resume();
34
+ }
35
+ stop() {
36
+ this.signal.stopped = true;
37
+ this.queue?.stop();
38
+ this.queue = void 0;
39
+ }
40
+ toConfig() {
41
+ return {
42
+ redirectPolicy: this.settings.redirection.strategy,
43
+ maxRedirects: this.settings.redirection.max,
44
+ maxRetries: this.settings.retry.max,
45
+ retryDelayMs: this.settings.retry.delay,
46
+ closeConnection: this.settings.normalization.closeConnection,
47
+ updateContentLength: this.settings.normalization.updateContentLength
48
+ };
49
+ }
50
+ sendSingle(request) {
51
+ return sendRawRequest(
52
+ this.sdk,
53
+ request.url,
54
+ request.raw,
55
+ this.toConfig()
56
+ );
57
+ }
58
+ sendBatch(requests, callbacks) {
59
+ this.signal = { stopped: false, paused: false };
60
+ return new Promise((resolve) => {
61
+ const signal = this.signal;
62
+ const config = this.toConfig();
63
+ const queue = new Queue(
64
+ { concurrency: this.settings.threads, delayMs: this.settings.delay },
65
+ async (item) => {
66
+ if (checkSignal(signal) === "stopped") return;
67
+ await waitForUnpause(signal);
68
+ if (checkSignal(signal) === "stopped") return;
69
+ const result = await sendRawRequest(
70
+ this.sdk,
71
+ item.request.url,
72
+ item.request.raw,
73
+ config,
74
+ signal
75
+ );
76
+ callbacks?.onResult?.(result, item.index);
77
+ },
78
+ {
79
+ onError: (item, error) => {
80
+ callbacks?.onError?.(error, item.index);
81
+ }
82
+ }
83
+ );
84
+ this.queue = queue;
85
+ const items = requests.map((request, index) => ({ request, index }));
86
+ queue.enqueue(items);
87
+ const checkDone = setInterval(() => {
88
+ if (!queue.isRunning() && queue.pending === 0) {
89
+ clearInterval(checkDone);
90
+ callbacks?.onDone?.(queue.completed);
91
+ this.queue = void 0;
92
+ resolve();
93
+ }
94
+ }, 200);
95
+ });
96
+ }
97
+ }
@@ -0,0 +1,7 @@
1
+ import type { SDK } from "caido:plugin";
2
+ import type { RawHttpConfig, RawSendResult, Signal } from "./types";
3
+ export declare function resolveRedirectUrl(currentUrl: string, location: string): string;
4
+ /**
5
+ * Send a raw HTTP request with optional normalization, retry, and redirect support.
6
+ */
7
+ export declare function sendRawRequest(sdk: SDK, url: string, raw: string, config?: RawHttpConfig, signal?: Signal): Promise<RawSendResult>;
@@ -0,0 +1,131 @@
1
+ import { RequestSpec, RequestSpecRaw } from "caido:utils";
2
+ import { applyCloseConnection, applyContentLength } from "./normalize.js";
3
+ import { checkSignal, waitForUnpause } from "./signal.js";
4
+ const REDIRECT_CODES = [301, 302, 303, 307, 308];
5
+ function delay(ms) {
6
+ return new Promise((resolve) => setTimeout(resolve, ms));
7
+ }
8
+ export function resolveRedirectUrl(currentUrl, location) {
9
+ if (location.startsWith("http://") || location.startsWith("https://")) {
10
+ return location;
11
+ }
12
+ const url = new URL(currentUrl);
13
+ if (location.startsWith("/")) {
14
+ return `${url.protocol}//${url.host}${location}`;
15
+ }
16
+ const basePath = url.pathname.substring(
17
+ 0,
18
+ url.pathname.lastIndexOf("/") + 1
19
+ );
20
+ return `${url.protocol}//${url.host}${basePath}${location}`;
21
+ }
22
+ async function sendWithRetry(sdk, url, raw, useRawSpec, maxRetries, retryDelayMs, signal) {
23
+ let lastError;
24
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
25
+ if (signal && checkSignal(signal) === "stopped") {
26
+ return {
27
+ statusCode: 0,
28
+ roundtripTime: 0,
29
+ responseSize: 0,
30
+ rawRequest: raw,
31
+ rawResponse: "",
32
+ error: "Stopped"
33
+ };
34
+ }
35
+ if (attempt > 0) {
36
+ await delay(retryDelayMs * attempt);
37
+ if (signal) {
38
+ await waitForUnpause(signal);
39
+ if (checkSignal(signal) === "stopped") {
40
+ return {
41
+ statusCode: 0,
42
+ roundtripTime: 0,
43
+ responseSize: 0,
44
+ rawRequest: raw,
45
+ rawResponse: "",
46
+ error: "Stopped"
47
+ };
48
+ }
49
+ }
50
+ }
51
+ try {
52
+ let response;
53
+ if (useRawSpec) {
54
+ const spec = new RequestSpecRaw(url);
55
+ spec.setRaw(raw);
56
+ response = await sdk.requests.send(spec);
57
+ } else {
58
+ const spec = new RequestSpec(url);
59
+ response = await sdk.requests.send(spec);
60
+ }
61
+ const statusCode = response.response.getCode();
62
+ if (attempt < maxRetries && (statusCode === 429 || statusCode >= 500)) {
63
+ lastError = `HTTP ${statusCode}`;
64
+ continue;
65
+ }
66
+ return {
67
+ statusCode,
68
+ roundtripTime: response.response.getRoundtripTime(),
69
+ responseSize: response.response.getRaw().toBytes().length,
70
+ rawRequest: response.request.getRaw().toText(),
71
+ rawResponse: response.response.getRaw().toText()
72
+ };
73
+ } catch (err) {
74
+ lastError = err instanceof Error ? err.message : "Unknown error";
75
+ if (attempt < maxRetries) continue;
76
+ }
77
+ }
78
+ return {
79
+ statusCode: 0,
80
+ roundtripTime: 0,
81
+ responseSize: 0,
82
+ rawRequest: raw,
83
+ rawResponse: "",
84
+ error: lastError
85
+ };
86
+ }
87
+ export async function sendRawRequest(sdk, url, raw, config, signal) {
88
+ const maxRetries = config?.maxRetries ?? 0;
89
+ const retryDelayMs = config?.retryDelayMs ?? 1e3;
90
+ const redirectPolicy = config?.redirectPolicy ?? "never";
91
+ const maxRedirects = config?.maxRedirects ?? 5;
92
+ let processedRaw = raw;
93
+ if (config?.closeConnection) {
94
+ processedRaw = applyCloseConnection(processedRaw);
95
+ }
96
+ if (config?.updateContentLength) {
97
+ processedRaw = applyContentLength(processedRaw);
98
+ }
99
+ let currentUrl = url;
100
+ let currentRaw = processedRaw;
101
+ let useRawSpec = true;
102
+ let redirectCount = 0;
103
+ for (; ; ) {
104
+ const result = await sendWithRetry(
105
+ sdk,
106
+ currentUrl,
107
+ currentRaw,
108
+ useRawSpec,
109
+ maxRetries,
110
+ retryDelayMs,
111
+ signal
112
+ );
113
+ if (redirectPolicy !== "never" && REDIRECT_CODES.includes(result.statusCode) && redirectCount < maxRedirects) {
114
+ const locationMatch = result.rawResponse.match(
115
+ /^Location\s*:\s*(.+)$/im
116
+ );
117
+ if (locationMatch?.[1]) {
118
+ const location = locationMatch[1].trim();
119
+ currentUrl = resolveRedirectUrl(currentUrl, location);
120
+ currentRaw = `GET ${new URL(currentUrl).pathname}${new URL(currentUrl).search} HTTP/1.1\r
121
+ Host: ${new URL(currentUrl).host}\r
122
+ \r
123
+ `;
124
+ useRawSpec = true;
125
+ redirectCount++;
126
+ continue;
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+ }
@@ -34,3 +34,20 @@ export type HttpClientResponse = {
34
34
  url: string;
35
35
  caidoId: string | undefined;
36
36
  };
37
+ export type RedirectPolicy = "always" | "in-scope" | "same-site" | "never";
38
+ export type RawHttpConfig = {
39
+ redirectPolicy?: RedirectPolicy;
40
+ maxRedirects?: number;
41
+ maxRetries?: number;
42
+ retryDelayMs?: number;
43
+ closeConnection?: boolean;
44
+ updateContentLength?: boolean;
45
+ };
46
+ export type RawSendResult = {
47
+ statusCode: number;
48
+ roundtripTime: number;
49
+ responseSize: number;
50
+ rawRequest: string;
51
+ rawResponse: string;
52
+ error?: string;
53
+ };
@@ -0,0 +1,5 @@
1
+ export { filterAuthHeaders } from "./headers";
2
+ export { queryRequests, type QueryRequestsOptions } from "./query";
3
+ export { HttpRequest, type HttpRequestOptions } from "./request";
4
+ export { getStatusText } from "./status";
5
+ export { parseUrl } from "./url";
@@ -0,0 +1,5 @@
1
+ export { filterAuthHeaders } from "./headers.js";
2
+ export { queryRequests } from "./query.js";
3
+ export { HttpRequest } from "./request.js";
4
+ export { getStatusText } from "./status.js";
5
+ export { parseUrl } from "./url.js";
@@ -1,6 +1,2 @@
1
- export { HttpClient } from "./client";
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
+ export { applyCloseConnection, applyContentLength, checkSignal, HttpClient, RawHttpClient, resolveRedirectUrl, sendRawRequest, waitForUnpause, type BatchCallbacks, type HttpClientOptions, type HttpClientRequest, type HttpClientResponse, type HttpMethod, type RawHttpConfig, type RawHttpSettings, type RawRequest, type RawSendResult, type RedirectPolicy, type Result, type Signal, } from "./client";
2
+ export { filterAuthHeaders, getStatusText, HttpRequest, parseUrl, queryRequests, type HttpRequestOptions, type QueryRequestsOptions, } from "./helpers";
@@ -1,5 +1,17 @@
1
- export { HttpClient } from "./client.js";
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";
1
+ export {
2
+ applyCloseConnection,
3
+ applyContentLength,
4
+ checkSignal,
5
+ HttpClient,
6
+ RawHttpClient,
7
+ resolveRedirectUrl,
8
+ sendRawRequest,
9
+ waitForUnpause
10
+ } from "./client/index.js";
11
+ export {
12
+ filterAuthHeaders,
13
+ getStatusText,
14
+ HttpRequest,
15
+ parseUrl,
16
+ queryRequests
17
+ } from "./helpers/index.js";
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { spawnCommand } from "./process";
2
2
  export { sha256 } from "./crypto";
3
3
  export { resolveFilterQuery } from "./filter";
4
- export { HttpClient, HttpRequest, checkSignal, getStatusText, parseUrl, waitForUnpause, type HttpClientOptions, type HttpClientRequest, type HttpClientResponse, type HttpMethod, type HttpRequestOptions, type Result, type Signal, } from "./http";
4
+ export { HttpClient, HttpRequest, RawHttpClient, applyCloseConnection, applyContentLength, checkSignal, filterAuthHeaders, getStatusText, parseUrl, queryRequests, resolveRedirectUrl, sendRawRequest, waitForUnpause, type BatchCallbacks, type HttpClientOptions, type HttpClientRequest, type HttpClientResponse, type HttpMethod, type HttpRequestOptions, type QueryRequestsOptions, type RawHttpConfig, type RawHttpSettings, type RawRequest, type RawSendResult, type RedirectPolicy, type Result, type Signal, } from "./http";
5
+ export { JobManager, WorkerPool } from "./jobs";
6
+ export type { Job, JobSignal, JobStatus } from "./jobs";
5
7
  export { Queue } from "./pool";
6
8
  export type { PoolCallbacks, PoolConfig } from "./pool";
7
9
  export { getProjectId } from "./project";
8
- export { Fetch, filterAuthHeaders, queryRequests, type FetchOptions, type QueryRequestsOptions, } from "./requests";
9
10
  export { deleteFile, fileExists, readFile, storeFile } from "./storage";
package/dist/index.js CHANGED
@@ -4,16 +4,19 @@ export { resolveFilterQuery } from "./filter/index.js";
4
4
  export {
5
5
  HttpClient,
6
6
  HttpRequest,
7
+ RawHttpClient,
8
+ applyCloseConnection,
9
+ applyContentLength,
7
10
  checkSignal,
11
+ filterAuthHeaders,
8
12
  getStatusText,
9
13
  parseUrl,
14
+ queryRequests,
15
+ resolveRedirectUrl,
16
+ sendRawRequest,
10
17
  waitForUnpause
11
18
  } from "./http/index.js";
19
+ export { JobManager, WorkerPool } from "./jobs/index.js";
12
20
  export { Queue } from "./pool/index.js";
13
21
  export { getProjectId } from "./project/index.js";
14
- export {
15
- Fetch,
16
- filterAuthHeaders,
17
- queryRequests
18
- } from "./requests/index.js";
19
22
  export { deleteFile, fileExists, readFile, storeFile } from "./storage/index.js";
@@ -0,0 +1,3 @@
1
+ export { JobManager } from "./manager";
2
+ export { WorkerPool } from "./worker-pool";
3
+ export type { Job, JobSignal, JobStatus } from "./types";
@@ -0,0 +1,2 @@
1
+ export { JobManager } from "./manager.js";
2
+ export { WorkerPool } from "./worker-pool.js";
@@ -0,0 +1,18 @@
1
+ import type { Job } from "./types";
2
+ export declare class JobManager<TType extends string = string> {
3
+ private jobs;
4
+ private completionListeners;
5
+ createJob(type: TType): Job<TType>;
6
+ getJob(id: string): Job<TType> | undefined;
7
+ getAllJobs(): Job<TType>[];
8
+ getJobsByType(type: TType): Job<TType>[];
9
+ completeJob(id: string): void;
10
+ failJob(id: string): void;
11
+ stopJob(id: string): void;
12
+ pauseJob(id: string): void;
13
+ resumeJob(id: string): void;
14
+ removeJob(id: string): void;
15
+ onJobDone(id: string, callback: () => void): void;
16
+ clearCompleted(): void;
17
+ private notifyDone;
18
+ }
@@ -0,0 +1,98 @@
1
+ let nextId = 1;
2
+ function generateId() {
3
+ return String(nextId++);
4
+ }
5
+ export class JobManager {
6
+ jobs = /* @__PURE__ */ new Map();
7
+ completionListeners = /* @__PURE__ */ new Map();
8
+ createJob(type) {
9
+ const id = generateId();
10
+ const job = {
11
+ id,
12
+ type,
13
+ status: "running",
14
+ signal: { stopped: false, paused: false },
15
+ createdAt: Date.now()
16
+ };
17
+ this.jobs.set(id, job);
18
+ return job;
19
+ }
20
+ getJob(id) {
21
+ return this.jobs.get(id);
22
+ }
23
+ getAllJobs() {
24
+ return [...this.jobs.values()];
25
+ }
26
+ getJobsByType(type) {
27
+ return [...this.jobs.values()].filter((j) => j.type === type);
28
+ }
29
+ completeJob(id) {
30
+ const job = this.jobs.get(id);
31
+ if (job !== void 0 && job.status === "running") {
32
+ job.status = "completed";
33
+ this.notifyDone(id);
34
+ }
35
+ }
36
+ failJob(id) {
37
+ const job = this.jobs.get(id);
38
+ if (job !== void 0) {
39
+ job.status = "error";
40
+ this.notifyDone(id);
41
+ }
42
+ }
43
+ stopJob(id) {
44
+ const job = this.jobs.get(id);
45
+ if (job === void 0) return;
46
+ if (job.status !== "running" && job.status !== "paused") return;
47
+ job.signal.stopped = true;
48
+ job.status = "stopped";
49
+ this.notifyDone(id);
50
+ }
51
+ pauseJob(id) {
52
+ const job = this.jobs.get(id);
53
+ if (job === void 0) return;
54
+ if (job.status !== "running") return;
55
+ job.signal.paused = true;
56
+ job.status = "paused";
57
+ }
58
+ resumeJob(id) {
59
+ const job = this.jobs.get(id);
60
+ if (job === void 0) return;
61
+ if (job.status !== "paused") return;
62
+ job.signal.paused = false;
63
+ job.status = "running";
64
+ }
65
+ removeJob(id) {
66
+ const job = this.jobs.get(id);
67
+ if (job === void 0) return;
68
+ if (job.status === "running" || job.status === "paused") {
69
+ job.signal.stopped = true;
70
+ job.status = "stopped";
71
+ }
72
+ this.jobs.delete(id);
73
+ }
74
+ onJobDone(id, callback) {
75
+ const existing = this.completionListeners.get(id);
76
+ if (existing !== void 0) {
77
+ existing.push(callback);
78
+ } else {
79
+ this.completionListeners.set(id, [callback]);
80
+ }
81
+ }
82
+ clearCompleted() {
83
+ for (const [id, job] of this.jobs) {
84
+ if (job.status === "completed" || job.status === "error" || job.status === "stopped") {
85
+ this.jobs.delete(id);
86
+ }
87
+ }
88
+ }
89
+ notifyDone(id) {
90
+ const listeners = this.completionListeners.get(id);
91
+ if (listeners !== void 0) {
92
+ for (const cb of listeners) {
93
+ cb();
94
+ }
95
+ this.completionListeners.delete(id);
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,10 @@
1
+ import type { Signal } from "../http/client/types";
2
+ export type JobStatus = "running" | "paused" | "completed" | "error" | "stopped";
3
+ export type { Signal as JobSignal };
4
+ export type Job<TType extends string = string> = {
5
+ id: string;
6
+ type: TType;
7
+ status: JobStatus;
8
+ signal: Signal;
9
+ createdAt: number;
10
+ };
File without changes
@@ -0,0 +1,14 @@
1
+ import type { JobManager } from "./manager";
2
+ export declare class WorkerPool<TRequest> {
3
+ private readonly maxConcurrency;
4
+ private active;
5
+ private queue;
6
+ private dispatcher;
7
+ private jobManager;
8
+ constructor(maxConcurrency: number, dispatcher: (request: TRequest) => string, jobManager: JobManager);
9
+ submit(request: TRequest): void;
10
+ get pending(): number;
11
+ get running(): number;
12
+ private dispatch;
13
+ private drain;
14
+ }
@@ -0,0 +1,41 @@
1
+ export class WorkerPool {
2
+ maxConcurrency;
3
+ active = 0;
4
+ queue = [];
5
+ dispatcher;
6
+ jobManager;
7
+ constructor(maxConcurrency, dispatcher, jobManager) {
8
+ this.maxConcurrency = maxConcurrency;
9
+ this.dispatcher = dispatcher;
10
+ this.jobManager = jobManager;
11
+ }
12
+ submit(request) {
13
+ if (this.active < this.maxConcurrency) {
14
+ this.dispatch({ request });
15
+ } else {
16
+ this.queue.push({ request });
17
+ }
18
+ }
19
+ get pending() {
20
+ return this.queue.length;
21
+ }
22
+ get running() {
23
+ return this.active;
24
+ }
25
+ dispatch(task) {
26
+ this.active++;
27
+ const jobId = this.dispatcher(task.request);
28
+ this.jobManager.onJobDone(jobId, () => {
29
+ this.active--;
30
+ this.drain();
31
+ });
32
+ }
33
+ drain() {
34
+ while (this.active < this.maxConcurrency && this.queue.length > 0) {
35
+ const next = this.queue.shift();
36
+ if (next !== void 0) {
37
+ this.dispatch(next);
38
+ }
39
+ }
40
+ }
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caido-utils/backend",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -1,17 +0,0 @@
1
- import type { SDK } from "caido:plugin";
2
- import type { RequestResponse, RequestSpec, RequestSpecRaw } from "caido:utils";
3
- export type FetchOptions = {
4
- threads?: number;
5
- delay?: number;
6
- };
7
- export declare class Fetch {
8
- private sdk;
9
- private threads;
10
- private delay;
11
- private active;
12
- private queue;
13
- constructor(sdk: SDK, options?: FetchOptions);
14
- send(request: RequestSpec | RequestSpecRaw): Promise<RequestResponse>;
15
- private drain;
16
- private execute;
17
- }
@@ -1,43 +0,0 @@
1
- function sleep(ms) {
2
- if (ms <= 0) return Promise.resolve();
3
- return new Promise((resolve) => setTimeout(resolve, ms));
4
- }
5
- export class Fetch {
6
- sdk;
7
- threads;
8
- delay;
9
- active = 0;
10
- queue = [];
11
- constructor(sdk, options = {}) {
12
- this.sdk = sdk;
13
- this.threads = options.threads ?? 10;
14
- this.delay = options.delay ?? 0;
15
- }
16
- send(request) {
17
- return new Promise((resolve, reject) => {
18
- this.queue.push({ request, resolve, reject });
19
- this.drain();
20
- });
21
- }
22
- drain() {
23
- while (this.active < this.threads && this.queue.length > 0) {
24
- const item = this.queue.shift();
25
- this.active++;
26
- this.execute(item);
27
- }
28
- }
29
- async execute(item) {
30
- try {
31
- const result = await this.sdk.requests.send(item.request);
32
- item.resolve(result);
33
- } catch (err) {
34
- item.reject(err);
35
- } finally {
36
- if (this.delay > 0) {
37
- await sleep(this.delay);
38
- }
39
- this.active--;
40
- this.drain();
41
- }
42
- }
43
- }
@@ -1,3 +0,0 @@
1
- export { Fetch, type FetchOptions } from "./fetcher";
2
- export { filterAuthHeaders } from "./headers";
3
- export { queryRequests, type QueryRequestsOptions } from "./query";
@@ -1,3 +0,0 @@
1
- export { Fetch } from "./fetcher.js";
2
- export { filterAuthHeaders } from "./headers.js";
3
- export { queryRequests } from "./query.js";
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes