@abyss-project/probe 2.0.0 → 2.0.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/.eslintrc.js +32 -32
- package/.prettierrc +5 -5
- package/README.md +22 -22
- package/package.json +10 -15
- package/src/api/index.ts +6 -6
- package/src/api/probe-scan.api.test.ts +47 -47
- package/src/api/probe-scan.api.ts +203 -203
- package/src/api/probe-scan.application.api.test.ts +35 -35
- package/src/api/probe-scan.application.api.ts +157 -157
- package/src/api/probe-usage.api.ts +47 -47
- package/src/api/probe-usage.application.api.ts +25 -25
- package/src/api/probe-webhook.api.test.ts +17 -17
- package/src/api/probe-webhook.api.ts +119 -119
- package/src/api/probe-webhook.application.api.test.ts +18 -18
- package/src/api/probe-webhook.application.api.ts +141 -141
- package/src/constants/index.ts +1 -1
- package/src/index.ts +205 -205
- package/src/types/enum/api-error.enum.ts +33 -33
- package/src/types/enum/index.ts +2 -2
- package/src/types/enum/probe-scan.enum.ts +44 -44
- package/src/types/index.ts +2 -2
- package/src/types/interface/api/index.ts +5 -5
- package/src/types/interface/api/requests/index.ts +3 -3
- package/src/types/interface/api/requests/probe-scan.request.ts +54 -54
- package/src/types/interface/api/requests/probe-usage.request.ts +7 -7
- package/src/types/interface/api/requests/probe-webhook.request.ts +33 -33
- package/src/types/interface/api/responses/index.ts +3 -3
- package/src/types/interface/api/responses/probe-scan.response.ts +45 -45
- package/src/types/interface/api/responses/probe-usage.response.ts +25 -25
- package/src/types/interface/api/responses/probe-webhook.response.ts +24 -24
- package/src/types/interface/api/type-message/api-error.ts +7 -7
- package/src/types/interface/api/type-message/base-paginate.ts +11 -11
- package/src/types/interface/api/type-message/response.ts +6 -6
- package/src/types/interface/index.ts +2 -2
- package/src/types/interface/models/index.ts +3 -3
- package/src/types/interface/models/probe-scan.dto.ts +67 -67
- package/src/types/interface/models/probe-webhook-delivery.dto.ts +73 -73
- package/src/types/interface/models/probe-webhook.dto.ts +29 -29
- package/src/types/tests/probe.dto.test.ts +156 -156
- package/src/utils/enum.utils.ts +1 -1
- package/src/utils/error.utils.ts +30 -30
- package/src/utils/index.ts +4 -4
- package/src/utils/probe-scan-cost.utils.ts +101 -101
- package/src/utils/probe-webhook-signature.utils.ts +56 -56
- package/tsconfig.check.json +15 -15
- package/tsconfig.json +36 -36
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
// ──────────────────────────────────────────────
|
|
2
|
-
// AbyssProbe scan API — POST /scan (sync + async), GET /scan/:scanId (poll),
|
|
3
|
-
// GET /scan (history/search). Mirrors
|
|
4
|
-
// apps/api/src/api/controllers/probe-scan.controller.ts EXACTLY (see that
|
|
5
|
-
// file for the full contract this client targets). AbyssProbe's controllers
|
|
6
|
-
// add no extra path segment (`@Controller({ prefixPath: '/scan' })`), so
|
|
7
|
-
// these calls hit `scan`/`scan/:id` directly — no `probe/` prefix.
|
|
8
|
-
//
|
|
9
|
-
// Multipart bodies are built with the global `FormData` + `Blob` — the same
|
|
10
|
-
// code in a browser and on Node, mirroring this package's own
|
|
11
|
-
// extraction.api.ts. axios serializes a spec-compliant FormData on both and
|
|
12
|
-
// sets the multipart boundary itself, so no content-type header is ever set by
|
|
13
|
-
// hand. AbyssProbe is called from the abyss-main UI (a browser) as well as
|
|
14
|
-
// from Node services (abyss-storage, external integrators).
|
|
15
|
-
//
|
|
16
|
-
// A Node caller holding a file ON DISK passes `await fs.openAsBlob(path)`,
|
|
17
|
-
// which requires Node >= 19.8 — hence `engines.node` in package.json.
|
|
18
|
-
// `FormData` and `Blob` alone would only need Node 18, but openAsBlob is this
|
|
19
|
-
// client's documented way to send a file, and promising 18.x only to fail at
|
|
20
|
-
// runtime is worse than refusing the install. A caller that already holds its
|
|
21
|
-
// bytes passes a `Buffer`/`Uint8Array` and has no such constraint (see
|
|
22
|
-
// {@link ProbeScanFileInput}).
|
|
23
|
-
//
|
|
24
|
-
// `authHeader`/`serializeProbeQuery` are kept LOCAL and unexported (not a
|
|
25
|
-
// shared utils file) — same convention as every other extract/*.api.ts file
|
|
26
|
-
// in this package (each redefines its own tiny per-call apiKey helper; see
|
|
27
|
-
// extraction.api.ts / extraction-settings.api.ts / extraction-plan-preset.api.ts).
|
|
28
|
-
//
|
|
29
|
-
// See probe-scan.application.api.ts for the application/API-key-auth
|
|
30
|
-
// counterpart of this client (`/application/:applicationId/scan...`, Phase
|
|
31
|
-
// 3a) — it reuses this file's `buildScanForm` and every request/response
|
|
32
|
-
// type verbatim; only the URL prefix, the extra `applicationId` argument,
|
|
33
|
-
// and the auth header differ.
|
|
34
|
-
// ──────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
import { AbyssProbeCore } from '..';
|
|
37
|
-
import { ProbeScanMode } from '../types';
|
|
38
|
-
import type {
|
|
39
|
-
IScanOptions,
|
|
40
|
-
IListScansQuery,
|
|
41
|
-
IScanResponse,
|
|
42
|
-
IScanSyncResponse,
|
|
43
|
-
IScanAsyncResponse,
|
|
44
|
-
IGetScanResponse,
|
|
45
|
-
IPaginateScanResponse,
|
|
46
|
-
} from '../types';
|
|
47
|
-
|
|
48
|
-
const DEFAULT_API_KEY_HEADER = 'abyss-api-key';
|
|
49
|
-
|
|
50
|
-
function authHeader(apiKey?: string): Record<string, string> {
|
|
51
|
-
return apiKey ? { [DEFAULT_API_KEY_HEADER]: apiKey } : {};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Turn a request/query options object into axios `params`: drops
|
|
56
|
-
* undefined/null, ISO-stringifies `Date`s, and renames `organizationId` to
|
|
57
|
-
* the wire's `organization_id` (the one snake_case key AbyssProbe's own
|
|
58
|
-
* controllers actually expect — see probe-scope.helper.ts#readOrganizationId
|
|
59
|
-
* — every other field name is already camelCase on both sides).
|
|
60
|
-
*/
|
|
61
|
-
function serializeProbeQuery<T extends object>(query: T): Record<string, unknown> {
|
|
62
|
-
const out: Record<string, unknown> = {};
|
|
63
|
-
for (const [key, value] of Object.entries(query as Record<string, unknown>)) {
|
|
64
|
-
if (value === undefined || value === null) continue;
|
|
65
|
-
const wireKey = key === 'organizationId' ? 'organization_id' : key;
|
|
66
|
-
out[wireKey] = value instanceof Date ? value.toISOString() : value;
|
|
67
|
-
}
|
|
68
|
-
return out;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Anything `sdk.probe.scan` accepts as the file to submit.
|
|
73
|
-
*
|
|
74
|
-
* `Blob` covers the browser's `File` (a `Blob` subclass) and, on Node, a
|
|
75
|
-
* FILE-BACKED blob from `await fs.openAsBlob(path)` (Node >= 19.8): its bytes
|
|
76
|
-
* are read from disk as the request body is written, so scanning a file at the
|
|
77
|
-
* 2 GiB ceiling never holds the file in memory. `Uint8Array` (and therefore
|
|
78
|
-
* `Buffer`) is for a caller that already holds the bytes.
|
|
79
|
-
*
|
|
80
|
-
* A Node `Readable` is NOT accepted. The body is a spec `FormData`, whose
|
|
81
|
-
* parts must have a known length, so a stream could only be honoured by
|
|
82
|
-
* buffering it whole — precisely what a 2 GiB scan must not do. A caller
|
|
83
|
-
* holding a path passes `await fs.openAsBlob(path)` instead.
|
|
84
|
-
*/
|
|
85
|
-
export type ProbeScanFileInput = Blob | Uint8Array;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The file as one multipart part.
|
|
89
|
-
*
|
|
90
|
-
* A `Blob` is used as-is, keeping whatever streaming its implementation gives
|
|
91
|
-
* (a file-backed `openAsBlob` reads from disk lazily); raw bytes are wrapped
|
|
92
|
-
* in a `Blob` carrying `mimeType`. `Buffer` is never named here — this code
|
|
93
|
-
* also runs in a browser bundle, where the global does not exist — and
|
|
94
|
-
* `Buffer` is a `Uint8Array` subclass, so the check below already covers it.
|
|
95
|
-
*
|
|
96
|
-
* Anything else is refused with an actionable message. Until 2.12.0 the same
|
|
97
|
-
* call died on Node with `ReferenceError: require is not defined`, thrown by a
|
|
98
|
-
* `form-data` loader before any request was made (#519): it named neither the
|
|
99
|
-
* cause nor the fix.
|
|
100
|
-
*/
|
|
101
|
-
function toScanFilePart(file: ProbeScanFileInput, mimeType?: string): Blob {
|
|
102
|
-
if (file instanceof Blob) return file;
|
|
103
|
-
if (file instanceof Uint8Array) return new Blob([new Uint8Array(file)], { type: mimeType ?? '' });
|
|
104
|
-
|
|
105
|
-
const isStream = typeof (file as { pipe?: unknown } | null | undefined)?.pipe === 'function';
|
|
106
|
-
throw new TypeError(
|
|
107
|
-
`A probe scan file must be a Blob (a browser File, or fs.openAsBlob(path) on Node), ` +
|
|
108
|
-
`a Buffer or a Uint8Array — received ${isStream ? 'a stream' : typeof file}. ` +
|
|
109
|
-
`On Node, pass \`await fs.openAsBlob(path)\`: a file-backed Blob is streamed from ` +
|
|
110
|
-
`disk, so the file is never buffered in memory.`,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Exported (not just this file's own `scan` internals) so
|
|
116
|
-
* probe-scan.application.api.ts reuses this EXACT multipart-building logic
|
|
117
|
-
* rather than duplicating it — the file-upload contract is identical for a
|
|
118
|
-
* personal/org caller and an application caller; only the URL/auth differ
|
|
119
|
-
* (see that file's header comment).
|
|
120
|
-
*
|
|
121
|
-
* Returns the form alone: axios sets the multipart content type, boundary
|
|
122
|
-
* included, so there is no header for the caller to merge in.
|
|
123
|
-
*/
|
|
124
|
-
export function buildScanForm(file: ProbeScanFileInput, options: IScanOptions): FormData {
|
|
125
|
-
const form = new FormData();
|
|
126
|
-
form.append('file', toScanFilePart(file, options.mimeType), options.filename ?? 'file');
|
|
127
|
-
if (options.context) form.append('context', JSON.stringify(options.context));
|
|
128
|
-
if (options.callbackUrl) form.append('callbackUrl', options.callbackUrl);
|
|
129
|
-
return form;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* `POST /scan` — SYNC by default, ASYNC when `options.mode = ProbeScanMode.ASYNC`
|
|
134
|
-
* (same endpoint, query-param-selected — see the controller's doc comment).
|
|
135
|
-
* `mode` and `organizationId` travel as query params; `context`
|
|
136
|
-
* (JSON-encoded) and `callbackUrl` travel as multipart text fields alongside
|
|
137
|
-
* the file.
|
|
138
|
-
*
|
|
139
|
-
* Returns the SYNC (200, verdict inline) or ASYNC (202, `{ scanId, status:
|
|
140
|
-
* 'queued' }`) shape depending on `options.mode` — see {@link IScanResponse}.
|
|
141
|
-
* Prefer {@link scanSync} / {@link scanAsync} for a precisely-typed return
|
|
142
|
-
* instead of the union.
|
|
143
|
-
*/
|
|
144
|
-
export const scan = async (
|
|
145
|
-
file: ProbeScanFileInput,
|
|
146
|
-
options: IScanOptions = {},
|
|
147
|
-
apiKey?: string,
|
|
148
|
-
): Promise<IScanResponse> => {
|
|
149
|
-
const form = buildScanForm(file, options);
|
|
150
|
-
return (
|
|
151
|
-
await AbyssProbeCore.axios.post<IScanResponse>('scan', form, {
|
|
152
|
-
params: serializeProbeQuery({ mode: options.mode, organizationId: options.organizationId }),
|
|
153
|
-
headers: authHeader(apiKey),
|
|
154
|
-
maxBodyLength: Infinity,
|
|
155
|
-
maxContentLength: Infinity,
|
|
156
|
-
})
|
|
157
|
-
).data;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
/** {@link scan}, forced to `mode: sync` — HTTP 200, verdict returned inline. */
|
|
161
|
-
export const scanSync = async (
|
|
162
|
-
file: ProbeScanFileInput,
|
|
163
|
-
options: Omit<IScanOptions, 'mode' | 'callbackUrl'> = {},
|
|
164
|
-
apiKey?: string,
|
|
165
|
-
): Promise<IScanSyncResponse> =>
|
|
166
|
-
scan(file, { ...options, mode: ProbeScanMode.SYNC }, apiKey) as Promise<IScanSyncResponse>;
|
|
167
|
-
|
|
168
|
-
/** {@link scan}, forced to `mode: async` — HTTP 202, job queued. */
|
|
169
|
-
export const scanAsync = async (
|
|
170
|
-
file: ProbeScanFileInput,
|
|
171
|
-
options: Omit<IScanOptions, 'mode'> = {},
|
|
172
|
-
apiKey?: string,
|
|
173
|
-
): Promise<IScanAsyncResponse> =>
|
|
174
|
-
scan(file, { ...options, mode: ProbeScanMode.ASYNC }, apiKey) as Promise<IScanAsyncResponse>;
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* `GET /scan/:scanId` — poll the queued -> scanning -> done/error
|
|
178
|
-
* progression of an ASYNC scan (also answers for a SYNC scan's row). Cross-
|
|
179
|
-
* tenant guarded server-side (404, never 403) — needs no scope query param,
|
|
180
|
-
* the scanId alone determines it.
|
|
181
|
-
*/
|
|
182
|
-
export const getScan = async (scanId: string, apiKey?: string): Promise<IGetScanResponse> =>
|
|
183
|
-
(
|
|
184
|
-
await AbyssProbeCore.axios.get<IGetScanResponse>(`scan/${scanId}`, {
|
|
185
|
-
headers: authHeader(apiKey),
|
|
186
|
-
})
|
|
187
|
-
).data;
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* `GET /scan` — paginated scan history/search for the caller's scope
|
|
191
|
-
* (personal by default, `organizationId` for an organization scope), newest
|
|
192
|
-
* first. See {@link IListScansQuery} for the available filters.
|
|
193
|
-
*/
|
|
194
|
-
export const listScans = async (
|
|
195
|
-
query: IListScansQuery = {},
|
|
196
|
-
apiKey?: string,
|
|
197
|
-
): Promise<IPaginateScanResponse> =>
|
|
198
|
-
(
|
|
199
|
-
await AbyssProbeCore.axios.get<IPaginateScanResponse>('scan', {
|
|
200
|
-
params: serializeProbeQuery(query),
|
|
201
|
-
headers: authHeader(apiKey),
|
|
202
|
-
})
|
|
203
|
-
).data;
|
|
1
|
+
// ──────────────────────────────────────────────
|
|
2
|
+
// AbyssProbe scan API — POST /scan (sync + async), GET /scan/:scanId (poll),
|
|
3
|
+
// GET /scan (history/search). Mirrors
|
|
4
|
+
// apps/api/src/api/controllers/probe-scan.controller.ts EXACTLY (see that
|
|
5
|
+
// file for the full contract this client targets). AbyssProbe's controllers
|
|
6
|
+
// add no extra path segment (`@Controller({ prefixPath: '/scan' })`), so
|
|
7
|
+
// these calls hit `scan`/`scan/:id` directly — no `probe/` prefix.
|
|
8
|
+
//
|
|
9
|
+
// Multipart bodies are built with the global `FormData` + `Blob` — the same
|
|
10
|
+
// code in a browser and on Node, mirroring this package's own
|
|
11
|
+
// extraction.api.ts. axios serializes a spec-compliant FormData on both and
|
|
12
|
+
// sets the multipart boundary itself, so no content-type header is ever set by
|
|
13
|
+
// hand. AbyssProbe is called from the abyss-main UI (a browser) as well as
|
|
14
|
+
// from Node services (abyss-storage, external integrators).
|
|
15
|
+
//
|
|
16
|
+
// A Node caller holding a file ON DISK passes `await fs.openAsBlob(path)`,
|
|
17
|
+
// which requires Node >= 19.8 — hence `engines.node` in package.json.
|
|
18
|
+
// `FormData` and `Blob` alone would only need Node 18, but openAsBlob is this
|
|
19
|
+
// client's documented way to send a file, and promising 18.x only to fail at
|
|
20
|
+
// runtime is worse than refusing the install. A caller that already holds its
|
|
21
|
+
// bytes passes a `Buffer`/`Uint8Array` and has no such constraint (see
|
|
22
|
+
// {@link ProbeScanFileInput}).
|
|
23
|
+
//
|
|
24
|
+
// `authHeader`/`serializeProbeQuery` are kept LOCAL and unexported (not a
|
|
25
|
+
// shared utils file) — same convention as every other extract/*.api.ts file
|
|
26
|
+
// in this package (each redefines its own tiny per-call apiKey helper; see
|
|
27
|
+
// extraction.api.ts / extraction-settings.api.ts / extraction-plan-preset.api.ts).
|
|
28
|
+
//
|
|
29
|
+
// See probe-scan.application.api.ts for the application/API-key-auth
|
|
30
|
+
// counterpart of this client (`/application/:applicationId/scan...`, Phase
|
|
31
|
+
// 3a) — it reuses this file's `buildScanForm` and every request/response
|
|
32
|
+
// type verbatim; only the URL prefix, the extra `applicationId` argument,
|
|
33
|
+
// and the auth header differ.
|
|
34
|
+
// ──────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
import { AbyssProbeCore } from '..';
|
|
37
|
+
import { ProbeScanMode } from '../types';
|
|
38
|
+
import type {
|
|
39
|
+
IScanOptions,
|
|
40
|
+
IListScansQuery,
|
|
41
|
+
IScanResponse,
|
|
42
|
+
IScanSyncResponse,
|
|
43
|
+
IScanAsyncResponse,
|
|
44
|
+
IGetScanResponse,
|
|
45
|
+
IPaginateScanResponse,
|
|
46
|
+
} from '../types';
|
|
47
|
+
|
|
48
|
+
const DEFAULT_API_KEY_HEADER = 'abyss-api-key';
|
|
49
|
+
|
|
50
|
+
function authHeader(apiKey?: string): Record<string, string> {
|
|
51
|
+
return apiKey ? { [DEFAULT_API_KEY_HEADER]: apiKey } : {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Turn a request/query options object into axios `params`: drops
|
|
56
|
+
* undefined/null, ISO-stringifies `Date`s, and renames `organizationId` to
|
|
57
|
+
* the wire's `organization_id` (the one snake_case key AbyssProbe's own
|
|
58
|
+
* controllers actually expect — see probe-scope.helper.ts#readOrganizationId
|
|
59
|
+
* — every other field name is already camelCase on both sides).
|
|
60
|
+
*/
|
|
61
|
+
function serializeProbeQuery<T extends object>(query: T): Record<string, unknown> {
|
|
62
|
+
const out: Record<string, unknown> = {};
|
|
63
|
+
for (const [key, value] of Object.entries(query as Record<string, unknown>)) {
|
|
64
|
+
if (value === undefined || value === null) continue;
|
|
65
|
+
const wireKey = key === 'organizationId' ? 'organization_id' : key;
|
|
66
|
+
out[wireKey] = value instanceof Date ? value.toISOString() : value;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Anything `sdk.probe.scan` accepts as the file to submit.
|
|
73
|
+
*
|
|
74
|
+
* `Blob` covers the browser's `File` (a `Blob` subclass) and, on Node, a
|
|
75
|
+
* FILE-BACKED blob from `await fs.openAsBlob(path)` (Node >= 19.8): its bytes
|
|
76
|
+
* are read from disk as the request body is written, so scanning a file at the
|
|
77
|
+
* 2 GiB ceiling never holds the file in memory. `Uint8Array` (and therefore
|
|
78
|
+
* `Buffer`) is for a caller that already holds the bytes.
|
|
79
|
+
*
|
|
80
|
+
* A Node `Readable` is NOT accepted. The body is a spec `FormData`, whose
|
|
81
|
+
* parts must have a known length, so a stream could only be honoured by
|
|
82
|
+
* buffering it whole — precisely what a 2 GiB scan must not do. A caller
|
|
83
|
+
* holding a path passes `await fs.openAsBlob(path)` instead.
|
|
84
|
+
*/
|
|
85
|
+
export type ProbeScanFileInput = Blob | Uint8Array;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The file as one multipart part.
|
|
89
|
+
*
|
|
90
|
+
* A `Blob` is used as-is, keeping whatever streaming its implementation gives
|
|
91
|
+
* (a file-backed `openAsBlob` reads from disk lazily); raw bytes are wrapped
|
|
92
|
+
* in a `Blob` carrying `mimeType`. `Buffer` is never named here — this code
|
|
93
|
+
* also runs in a browser bundle, where the global does not exist — and
|
|
94
|
+
* `Buffer` is a `Uint8Array` subclass, so the check below already covers it.
|
|
95
|
+
*
|
|
96
|
+
* Anything else is refused with an actionable message. Until 2.12.0 the same
|
|
97
|
+
* call died on Node with `ReferenceError: require is not defined`, thrown by a
|
|
98
|
+
* `form-data` loader before any request was made (#519): it named neither the
|
|
99
|
+
* cause nor the fix.
|
|
100
|
+
*/
|
|
101
|
+
function toScanFilePart(file: ProbeScanFileInput, mimeType?: string): Blob {
|
|
102
|
+
if (file instanceof Blob) return file;
|
|
103
|
+
if (file instanceof Uint8Array) return new Blob([new Uint8Array(file)], { type: mimeType ?? '' });
|
|
104
|
+
|
|
105
|
+
const isStream = typeof (file as { pipe?: unknown } | null | undefined)?.pipe === 'function';
|
|
106
|
+
throw new TypeError(
|
|
107
|
+
`A probe scan file must be a Blob (a browser File, or fs.openAsBlob(path) on Node), ` +
|
|
108
|
+
`a Buffer or a Uint8Array — received ${isStream ? 'a stream' : typeof file}. ` +
|
|
109
|
+
`On Node, pass \`await fs.openAsBlob(path)\`: a file-backed Blob is streamed from ` +
|
|
110
|
+
`disk, so the file is never buffered in memory.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Exported (not just this file's own `scan` internals) so
|
|
116
|
+
* probe-scan.application.api.ts reuses this EXACT multipart-building logic
|
|
117
|
+
* rather than duplicating it — the file-upload contract is identical for a
|
|
118
|
+
* personal/org caller and an application caller; only the URL/auth differ
|
|
119
|
+
* (see that file's header comment).
|
|
120
|
+
*
|
|
121
|
+
* Returns the form alone: axios sets the multipart content type, boundary
|
|
122
|
+
* included, so there is no header for the caller to merge in.
|
|
123
|
+
*/
|
|
124
|
+
export function buildScanForm(file: ProbeScanFileInput, options: IScanOptions): FormData {
|
|
125
|
+
const form = new FormData();
|
|
126
|
+
form.append('file', toScanFilePart(file, options.mimeType), options.filename ?? 'file');
|
|
127
|
+
if (options.context) form.append('context', JSON.stringify(options.context));
|
|
128
|
+
if (options.callbackUrl) form.append('callbackUrl', options.callbackUrl);
|
|
129
|
+
return form;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `POST /scan` — SYNC by default, ASYNC when `options.mode = ProbeScanMode.ASYNC`
|
|
134
|
+
* (same endpoint, query-param-selected — see the controller's doc comment).
|
|
135
|
+
* `mode` and `organizationId` travel as query params; `context`
|
|
136
|
+
* (JSON-encoded) and `callbackUrl` travel as multipart text fields alongside
|
|
137
|
+
* the file.
|
|
138
|
+
*
|
|
139
|
+
* Returns the SYNC (200, verdict inline) or ASYNC (202, `{ scanId, status:
|
|
140
|
+
* 'queued' }`) shape depending on `options.mode` — see {@link IScanResponse}.
|
|
141
|
+
* Prefer {@link scanSync} / {@link scanAsync} for a precisely-typed return
|
|
142
|
+
* instead of the union.
|
|
143
|
+
*/
|
|
144
|
+
export const scan = async (
|
|
145
|
+
file: ProbeScanFileInput,
|
|
146
|
+
options: IScanOptions = {},
|
|
147
|
+
apiKey?: string,
|
|
148
|
+
): Promise<IScanResponse> => {
|
|
149
|
+
const form = buildScanForm(file, options);
|
|
150
|
+
return (
|
|
151
|
+
await AbyssProbeCore.axios.post<IScanResponse>('scan', form, {
|
|
152
|
+
params: serializeProbeQuery({ mode: options.mode, organizationId: options.organizationId }),
|
|
153
|
+
headers: authHeader(apiKey),
|
|
154
|
+
maxBodyLength: Infinity,
|
|
155
|
+
maxContentLength: Infinity,
|
|
156
|
+
})
|
|
157
|
+
).data;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** {@link scan}, forced to `mode: sync` — HTTP 200, verdict returned inline. */
|
|
161
|
+
export const scanSync = async (
|
|
162
|
+
file: ProbeScanFileInput,
|
|
163
|
+
options: Omit<IScanOptions, 'mode' | 'callbackUrl'> = {},
|
|
164
|
+
apiKey?: string,
|
|
165
|
+
): Promise<IScanSyncResponse> =>
|
|
166
|
+
scan(file, { ...options, mode: ProbeScanMode.SYNC }, apiKey) as Promise<IScanSyncResponse>;
|
|
167
|
+
|
|
168
|
+
/** {@link scan}, forced to `mode: async` — HTTP 202, job queued. */
|
|
169
|
+
export const scanAsync = async (
|
|
170
|
+
file: ProbeScanFileInput,
|
|
171
|
+
options: Omit<IScanOptions, 'mode'> = {},
|
|
172
|
+
apiKey?: string,
|
|
173
|
+
): Promise<IScanAsyncResponse> =>
|
|
174
|
+
scan(file, { ...options, mode: ProbeScanMode.ASYNC }, apiKey) as Promise<IScanAsyncResponse>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* `GET /scan/:scanId` — poll the queued -> scanning -> done/error
|
|
178
|
+
* progression of an ASYNC scan (also answers for a SYNC scan's row). Cross-
|
|
179
|
+
* tenant guarded server-side (404, never 403) — needs no scope query param,
|
|
180
|
+
* the scanId alone determines it.
|
|
181
|
+
*/
|
|
182
|
+
export const getScan = async (scanId: string, apiKey?: string): Promise<IGetScanResponse> =>
|
|
183
|
+
(
|
|
184
|
+
await AbyssProbeCore.axios.get<IGetScanResponse>(`scan/${scanId}`, {
|
|
185
|
+
headers: authHeader(apiKey),
|
|
186
|
+
})
|
|
187
|
+
).data;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* `GET /scan` — paginated scan history/search for the caller's scope
|
|
191
|
+
* (personal by default, `organizationId` for an organization scope), newest
|
|
192
|
+
* first. See {@link IListScansQuery} for the available filters.
|
|
193
|
+
*/
|
|
194
|
+
export const listScans = async (
|
|
195
|
+
query: IListScansQuery = {},
|
|
196
|
+
apiKey?: string,
|
|
197
|
+
): Promise<IPaginateScanResponse> =>
|
|
198
|
+
(
|
|
199
|
+
await AbyssProbeCore.axios.get<IPaginateScanResponse>('scan', {
|
|
200
|
+
params: serializeProbeQuery(query),
|
|
201
|
+
headers: authHeader(apiKey),
|
|
202
|
+
})
|
|
203
|
+
).data;
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
// Type-level compile test for the probe-scan APPLICATION api surface.
|
|
2
|
-
// No jest runner in this SDK package; compile guard only (mirrors
|
|
3
|
-
// probe-scan.api.test.ts).
|
|
4
|
-
// Run: npx tsc --noEmit (from packages/abyss_probe_core), or `npm run build`.
|
|
5
|
-
|
|
6
|
-
import * as api from './probe-scan.application.api';
|
|
7
|
-
|
|
8
|
-
// All five functions must be exported with these exact signatures.
|
|
9
|
-
const _scan: typeof api.scanApplication = api.scanApplication;
|
|
10
|
-
const _scanSync: typeof api.scanSyncApplication = api.scanSyncApplication;
|
|
11
|
-
const _scanAsync: typeof api.scanAsyncApplication = api.scanAsyncApplication;
|
|
12
|
-
const _getScan: typeof api.getScanApplication = api.getScanApplication;
|
|
13
|
-
const _listScans: typeof api.listScansApplication = api.listScansApplication;
|
|
14
|
-
|
|
15
|
-
void _scan;
|
|
16
|
-
void _scanSync;
|
|
17
|
-
void _scanAsync;
|
|
18
|
-
void _getScan;
|
|
19
|
-
void _listScans;
|
|
20
|
-
|
|
21
|
-
// The application client takes the SAME file input as the personal one (it
|
|
22
|
-
// reuses buildScanForm verbatim) — abyss-storage, its main caller, hands over
|
|
23
|
-
// `await fs.openAsBlob(path)`. A stream is refused at compile time (#519); see
|
|
24
|
-
// probe-scan.api.test.ts for the full input matrix.
|
|
25
|
-
declare const blob: Blob;
|
|
26
|
-
declare const stream: NodeJS.ReadableStream;
|
|
27
|
-
|
|
28
|
-
const _fromBlob: Parameters<typeof api.scanSyncApplication>[1] = blob;
|
|
29
|
-
// @ts-expect-error a Readable stream is not an accepted scan input since 2.12.0
|
|
30
|
-
const _fromStream: Parameters<typeof api.scanSyncApplication>[1] = stream;
|
|
31
|
-
|
|
32
|
-
void _fromBlob;
|
|
33
|
-
void _fromStream;
|
|
34
|
-
|
|
35
|
-
export {};
|
|
1
|
+
// Type-level compile test for the probe-scan APPLICATION api surface.
|
|
2
|
+
// No jest runner in this SDK package; compile guard only (mirrors
|
|
3
|
+
// probe-scan.api.test.ts).
|
|
4
|
+
// Run: npx tsc --noEmit (from packages/abyss_probe_core), or `npm run build`.
|
|
5
|
+
|
|
6
|
+
import * as api from './probe-scan.application.api';
|
|
7
|
+
|
|
8
|
+
// All five functions must be exported with these exact signatures.
|
|
9
|
+
const _scan: typeof api.scanApplication = api.scanApplication;
|
|
10
|
+
const _scanSync: typeof api.scanSyncApplication = api.scanSyncApplication;
|
|
11
|
+
const _scanAsync: typeof api.scanAsyncApplication = api.scanAsyncApplication;
|
|
12
|
+
const _getScan: typeof api.getScanApplication = api.getScanApplication;
|
|
13
|
+
const _listScans: typeof api.listScansApplication = api.listScansApplication;
|
|
14
|
+
|
|
15
|
+
void _scan;
|
|
16
|
+
void _scanSync;
|
|
17
|
+
void _scanAsync;
|
|
18
|
+
void _getScan;
|
|
19
|
+
void _listScans;
|
|
20
|
+
|
|
21
|
+
// The application client takes the SAME file input as the personal one (it
|
|
22
|
+
// reuses buildScanForm verbatim) — abyss-storage, its main caller, hands over
|
|
23
|
+
// `await fs.openAsBlob(path)`. A stream is refused at compile time (#519); see
|
|
24
|
+
// probe-scan.api.test.ts for the full input matrix.
|
|
25
|
+
declare const blob: Blob;
|
|
26
|
+
declare const stream: NodeJS.ReadableStream;
|
|
27
|
+
|
|
28
|
+
const _fromBlob: Parameters<typeof api.scanSyncApplication>[1] = blob;
|
|
29
|
+
// @ts-expect-error a Readable stream is not an accepted scan input since 2.12.0
|
|
30
|
+
const _fromStream: Parameters<typeof api.scanSyncApplication>[1] = stream;
|
|
31
|
+
|
|
32
|
+
void _fromBlob;
|
|
33
|
+
void _fromStream;
|
|
34
|
+
|
|
35
|
+
export {};
|