@abyss-project/tools 2.1.0 → 2.3.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.
Files changed (152) hide show
  1. package/dist/api/index.d.ts +6 -0
  2. package/dist/api/index.js +6 -0
  3. package/dist/api/index.js.map +1 -1
  4. package/dist/api/probe-scan.api.d.ts +15 -0
  5. package/dist/api/probe-scan.api.js +74 -0
  6. package/dist/api/probe-scan.api.js.map +1 -0
  7. package/dist/api/probe-scan.api.test.d.ts +1 -0
  8. package/dist/api/probe-scan.api.test.js +37 -0
  9. package/dist/api/probe-scan.api.test.js.map +1 -0
  10. package/dist/api/probe-scan.application.api.d.ts +7 -0
  11. package/dist/api/probe-scan.application.api.js +40 -0
  12. package/dist/api/probe-scan.application.api.js.map +1 -0
  13. package/dist/api/probe-scan.application.api.test.d.ts +1 -0
  14. package/dist/api/probe-scan.application.api.test.js +37 -0
  15. package/dist/api/probe-scan.application.api.test.js.map +1 -0
  16. package/dist/api/probe-usage.api.d.ts +2 -0
  17. package/dist/api/probe-usage.api.js +24 -0
  18. package/dist/api/probe-usage.api.js.map +1 -0
  19. package/dist/api/probe-usage.application.api.d.ts +2 -0
  20. package/dist/api/probe-usage.application.api.js +12 -0
  21. package/dist/api/probe-usage.application.api.js.map +1 -0
  22. package/dist/api/probe-webhook.api.d.ts +6 -0
  23. package/dist/api/probe-webhook.api.js +41 -0
  24. package/dist/api/probe-webhook.api.js.map +1 -0
  25. package/dist/api/probe-webhook.api.test.d.ts +1 -0
  26. package/dist/api/probe-webhook.api.test.js +33 -0
  27. package/dist/api/probe-webhook.api.test.js.map +1 -0
  28. package/dist/api/probe-webhook.application.api.d.ts +6 -0
  29. package/dist/api/probe-webhook.application.api.js +27 -0
  30. package/dist/api/probe-webhook.application.api.js.map +1 -0
  31. package/dist/api/probe-webhook.application.api.test.d.ts +1 -0
  32. package/dist/api/probe-webhook.application.api.test.js +33 -0
  33. package/dist/api/probe-webhook.application.api.test.js.map +1 -0
  34. package/dist/index.d.ts +43 -0
  35. package/dist/index.js +44 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/types/enum/api-error.enum.d.ts +1 -5
  38. package/dist/types/enum/api-error.enum.js +0 -4
  39. package/dist/types/enum/api-error.enum.js.map +1 -1
  40. package/dist/types/enum/index.d.ts +1 -1
  41. package/dist/types/enum/index.js +1 -1
  42. package/dist/types/enum/index.js.map +1 -1
  43. package/dist/types/enum/probe-scan.enum.d.ts +14 -0
  44. package/dist/types/enum/probe-scan.enum.js +21 -0
  45. package/dist/types/enum/probe-scan.enum.js.map +1 -0
  46. package/dist/types/interface/api/requests/index.d.ts +3 -0
  47. package/dist/types/interface/api/requests/index.js +3 -0
  48. package/dist/types/interface/api/requests/index.js.map +1 -1
  49. package/dist/types/interface/api/requests/probe-scan.request.d.ts +22 -0
  50. package/dist/types/interface/api/{responses/file-scan.response.js → requests/probe-scan.request.js} +1 -1
  51. package/dist/types/interface/api/requests/probe-scan.request.js.map +1 -0
  52. package/dist/types/interface/api/requests/probe-usage.request.d.ts +3 -0
  53. package/dist/types/{enum/file-scan-status.enum.js → interface/api/requests/probe-usage.request.js} +1 -1
  54. package/dist/types/interface/api/requests/probe-usage.request.js.map +1 -0
  55. package/dist/types/interface/api/requests/probe-webhook.request.d.ts +14 -0
  56. package/dist/types/interface/api/requests/probe-webhook.request.js +3 -0
  57. package/dist/types/interface/api/requests/probe-webhook.request.js.map +1 -0
  58. package/dist/types/interface/api/responses/beam.response.d.ts +1 -3
  59. package/dist/types/interface/api/responses/index.d.ts +3 -0
  60. package/dist/types/interface/api/responses/index.js +3 -0
  61. package/dist/types/interface/api/responses/index.js.map +1 -1
  62. package/dist/types/interface/api/responses/probe-scan.response.d.ts +19 -0
  63. package/dist/types/interface/api/responses/probe-scan.response.js +3 -0
  64. package/dist/types/interface/api/responses/probe-scan.response.js.map +1 -0
  65. package/dist/types/interface/api/responses/probe-usage.response.d.ts +7 -0
  66. package/dist/types/interface/api/responses/probe-usage.response.js +3 -0
  67. package/dist/types/interface/api/responses/probe-usage.response.js.map +1 -0
  68. package/dist/types/interface/api/responses/probe-webhook.response.d.ts +13 -0
  69. package/dist/types/interface/api/responses/probe-webhook.response.js +3 -0
  70. package/dist/types/interface/api/responses/probe-webhook.response.js.map +1 -0
  71. package/dist/types/interface/models/atlas-document.dto.d.ts +0 -3
  72. package/dist/types/interface/models/beam.dto.d.ts +0 -3
  73. package/dist/types/interface/models/board-card-document.dto.d.ts +0 -3
  74. package/dist/types/interface/models/extraction.dto.d.ts +0 -2
  75. package/dist/types/interface/models/extraction.dto.js.map +1 -1
  76. package/dist/types/interface/models/index.d.ts +3 -0
  77. package/dist/types/interface/models/index.js +3 -0
  78. package/dist/types/interface/models/index.js.map +1 -1
  79. package/dist/types/interface/models/probe-scan.dto.d.ts +30 -0
  80. package/dist/types/interface/models/{file-scan.dto.js → probe-scan.dto.js} +1 -1
  81. package/dist/types/interface/models/probe-scan.dto.js.map +1 -0
  82. package/dist/types/interface/models/probe-webhook-delivery.dto.d.ts +19 -0
  83. package/dist/types/interface/models/probe-webhook-delivery.dto.js +3 -0
  84. package/dist/types/interface/models/probe-webhook-delivery.dto.js.map +1 -0
  85. package/dist/types/interface/models/probe-webhook.dto.d.ts +8 -0
  86. package/dist/types/interface/{api/requests/file-scan.request.js → models/probe-webhook.dto.js} +1 -1
  87. package/dist/types/interface/models/probe-webhook.dto.js.map +1 -0
  88. package/dist/types/tests/probe.dto.test.d.ts +1 -0
  89. package/dist/types/tests/probe.dto.test.js +101 -0
  90. package/dist/types/tests/probe.dto.test.js.map +1 -0
  91. package/dist/utils/index.d.ts +2 -0
  92. package/dist/utils/index.js +2 -0
  93. package/dist/utils/index.js.map +1 -1
  94. package/dist/utils/probe-scan-cost.utils.d.ts +5 -0
  95. package/dist/utils/probe-scan-cost.utils.js +21 -0
  96. package/dist/utils/probe-scan-cost.utils.js.map +1 -0
  97. package/dist/utils/probe-webhook-signature.utils.d.ts +8 -0
  98. package/dist/utils/probe-webhook-signature.utils.js +22 -0
  99. package/dist/utils/probe-webhook-signature.utils.js.map +1 -0
  100. package/package.json +1 -1
  101. package/src/api/index.ts +8 -0
  102. package/src/api/probe-scan.api.test.ts +21 -0
  103. package/src/api/probe-scan.api.ts +191 -0
  104. package/src/api/probe-scan.application.api.test.ts +21 -0
  105. package/src/api/probe-scan.application.api.ts +157 -0
  106. package/src/api/probe-usage.api.ts +47 -0
  107. package/src/api/probe-usage.application.api.ts +25 -0
  108. package/src/api/probe-webhook.api.test.ts +17 -0
  109. package/src/api/probe-webhook.api.ts +119 -0
  110. package/src/api/probe-webhook.application.api.test.ts +18 -0
  111. package/src/api/probe-webhook.application.api.ts +141 -0
  112. package/src/index.ts +114 -0
  113. package/src/types/enum/api-error.enum.ts +0 -25
  114. package/src/types/enum/index.ts +1 -1
  115. package/src/types/enum/probe-scan.enum.ts +44 -0
  116. package/src/types/interface/api/requests/index.ts +3 -0
  117. package/src/types/interface/api/requests/probe-scan.request.ts +50 -0
  118. package/src/types/interface/api/requests/probe-usage.request.ts +7 -0
  119. package/src/types/interface/api/requests/probe-webhook.request.ts +33 -0
  120. package/src/types/interface/api/responses/beam.response.ts +1 -15
  121. package/src/types/interface/api/responses/index.ts +3 -0
  122. package/src/types/interface/api/responses/probe-scan.response.ts +45 -0
  123. package/src/types/interface/api/responses/probe-usage.response.ts +25 -0
  124. package/src/types/interface/api/responses/probe-webhook.response.ts +24 -0
  125. package/src/types/interface/models/atlas-document.dto.ts +0 -18
  126. package/src/types/interface/models/beam.dto.ts +0 -22
  127. package/src/types/interface/models/board-card-document.dto.ts +0 -18
  128. package/src/types/interface/models/extraction.dto.ts +0 -17
  129. package/src/types/interface/models/index.ts +3 -0
  130. package/src/types/interface/models/probe-scan.dto.ts +67 -0
  131. package/src/types/interface/models/probe-webhook-delivery.dto.ts +73 -0
  132. package/src/types/interface/models/probe-webhook.dto.ts +39 -0
  133. package/src/types/tests/beam.dto.spec.ts +0 -2
  134. package/src/types/tests/probe.dto.test.ts +159 -0
  135. package/src/utils/index.ts +2 -0
  136. package/src/utils/probe-scan-cost.utils.ts +101 -0
  137. package/src/utils/probe-webhook-signature.utils.ts +56 -0
  138. package/dist/api/file-scan.api.d.ts +0 -5
  139. package/dist/api/file-scan.api.js +0 -49
  140. package/dist/api/file-scan.api.js.map +0 -1
  141. package/dist/types/enum/analysis-status.enum.d.ts +0 -7
  142. package/dist/types/enum/analysis-status.enum.js +0 -12
  143. package/dist/types/enum/analysis-status.enum.js.map +0 -1
  144. package/dist/types/enum/file-scan-status.enum.d.ts +0 -1
  145. package/dist/types/enum/file-scan-status.enum.js.map +0 -1
  146. package/dist/types/interface/api/requests/file-scan.request.d.ts +0 -3
  147. package/dist/types/interface/api/requests/file-scan.request.js.map +0 -1
  148. package/dist/types/interface/api/responses/file-scan.response.d.ts +0 -7
  149. package/dist/types/interface/api/responses/file-scan.response.js.map +0 -1
  150. package/dist/types/interface/models/file-scan.dto.d.ts +0 -11
  151. package/dist/types/interface/models/file-scan.dto.js.map +0 -1
  152. package/src/types/enum/file-scan-status.enum.ts +0 -23
@@ -0,0 +1,159 @@
1
+ // Type-level compile test for AbyssProbe's DTO/response shapes — asserts
2
+ // they match the server contract (probe-scan.controller.ts /
3
+ // probe-webhook.controller.ts / probe-usage.controller.ts /
4
+ // probe-webhook-delivery.service.ts) at compile time: any incompatible
5
+ // rename/removal on either side fails `npm run build` here. No jest runner
6
+ // in this SDK package; compile guard only (mirrors
7
+ // ../api/extraction-plan-preset.api.test.ts). Run: npx tsc --noEmit.
8
+
9
+ import {
10
+ ProbeScanVerdict,
11
+ ProbeScanMode,
12
+ ProbeScanStatus,
13
+ IProbeScanDetail,
14
+ IProbeScanHistoryItem,
15
+ IProbeWebhook,
16
+ IProbeWebhookCreated,
17
+ IProbeWebhookDelivery,
18
+ IProbeWebhookDeliveryPayload,
19
+ IScanSyncData,
20
+ IScanAsyncData,
21
+ IUpdateProbeWebhookBody,
22
+ IProbeUsageData,
23
+ } from '..';
24
+ // estimateProbeScanCost lives in src/utils/ (a SIBLING of src/types/, not
25
+ // re-exported by the types barrel this file otherwise imports from) — see
26
+ // probe-scan-cost.utils.ts's own header comment for why it is a plain
27
+ // top-level export rather than a `sdk.probe.*` method.
28
+ import { estimateProbeScanCost } from '../../utils';
29
+
30
+ // Enum exhaustiveness: fails to compile if a member is added/removed/renamed
31
+ // here without updating this file — a cheap drift guard for values
32
+ // transported verbatim over the wire (this SDK does not control the
33
+ // server's enum, so a silent value drift is exactly what this must catch).
34
+ const _verdicts: Record<ProbeScanVerdict, true> = { clean: true, infected: true };
35
+ const _modes: Record<ProbeScanMode, true> = { sync: true, async: true };
36
+ const _statuses: Record<ProbeScanStatus, true> = {
37
+ queued: true,
38
+ scanning: true,
39
+ done: true,
40
+ error: true,
41
+ };
42
+
43
+ // GET /scan/:scanId — WS5-4: full metadata parity with IProbeScanHistoryItem
44
+ // (fileName/fileSize/mimeType/mode/startedAt/finishedAt/costUnits).
45
+ const _scanDetail: IProbeScanDetail = {
46
+ scanId: 's1',
47
+ status: ProbeScanStatus.DONE,
48
+ verdict: ProbeScanVerdict.CLEAN,
49
+ viruses: [],
50
+ context: null,
51
+ fileName: 'invoice.pdf',
52
+ fileSize: 1024,
53
+ mimeType: 'application/pdf',
54
+ mode: ProbeScanMode.SYNC,
55
+ startedAt: new Date(),
56
+ finishedAt: new Date(),
57
+ costUnits: 3,
58
+ };
59
+
60
+ // GET /scan history row — WS5-1: costUnits added.
61
+ const _historyItem: IProbeScanHistoryItem = {
62
+ scanId: 's1',
63
+ fileName: 'invoice.pdf',
64
+ fileSize: 1024,
65
+ mimeType: 'application/pdf',
66
+ verdict: ProbeScanVerdict.INFECTED,
67
+ viruses: ['Eicar-Test-Signature'],
68
+ mode: ProbeScanMode.ASYNC,
69
+ status: ProbeScanStatus.DONE,
70
+ context: { applicationFileId: 'af1' },
71
+ startedAt: new Date(),
72
+ finishedAt: new Date(),
73
+ costUnits: 1,
74
+ createdAt: new Date(),
75
+ };
76
+
77
+ // POST /scan — sync (200) / async (202) data shapes. WS5-1: costUnits added to the sync shape (always known/billed by the time a 200 is returned).
78
+ const _scanSyncData: IScanSyncData = {
79
+ scanId: 's1',
80
+ verdict: ProbeScanVerdict.CLEAN,
81
+ viruses: [],
82
+ costUnits: 3,
83
+ };
84
+ const _scanAsyncData: IScanAsyncData = { scanId: 's1', status: ProbeScanStatus.QUEUED };
85
+
86
+ // Webhook subscription — WS5-7: secret is now ALWAYS present (list/create/
87
+ // update), IProbeWebhookCreated is a source-compatible alias of IProbeWebhook.
88
+ const _webhook: IProbeWebhook = {
89
+ id: 'w1',
90
+ callbackUrl: 'https://example.com/hook',
91
+ secret: 'shh',
92
+ active: true,
93
+ createdAt: new Date(),
94
+ };
95
+ const _webhookCreated: IProbeWebhookCreated = _webhook;
96
+
97
+ // PATCH /webhook/:webhookId body (WS5-5) — every field optional (at least one required server-side).
98
+ const _updateBody: IUpdateProbeWebhookBody = { active: false };
99
+ const _updateBody2: IUpdateProbeWebhookBody = { callbackUrl: 'https://example.com/hook2' };
100
+
101
+ // Webhook delivery payload — the exact body signed by x-probe-signature
102
+ // (see verifyProbeWebhookSignature). WS5-1: costUnits added.
103
+ const _deliveryPayload: IProbeWebhookDeliveryPayload = {
104
+ scanId: 's1',
105
+ context: null,
106
+ verdict: ProbeScanVerdict.INFECTED,
107
+ viruses: ['Eicar-Test-Signature'],
108
+ costUnits: 5,
109
+ };
110
+
111
+ // GET /webhook/:webhookId/deliveries row (WS5-6) — a persisted delivery-attempt.
112
+ const _deliveryRecord: IProbeWebhookDelivery = {
113
+ id: 'd1',
114
+ webhookId: 'w1',
115
+ scanId: 's1',
116
+ url: 'https://example.com/hook',
117
+ statusCode: 200,
118
+ success: true,
119
+ attemptedAt: new Date(),
120
+ error: null,
121
+ createdAt: new Date(),
122
+ };
123
+ // webhookId is nullable (ad-hoc per-request callbackUrl with no matching registration).
124
+ const _deliveryRecordAdHoc: IProbeWebhookDelivery = { ..._deliveryRecord, webhookId: null };
125
+
126
+ // GET /usage data shape (WS5-3).
127
+ const _usage: IProbeUsageData = { used: 10, limit: 1000, remaining: 990 };
128
+ // -1 is the established "unlimited" sentinel (see estimateProbeScanCost's
129
+ // server-side counterpart, utils/billing-emit.util.ts#UNLIMITED_EXPIRY_DAYS).
130
+ const _usageUnlimited: IProbeUsageData = { used: 10, limit: -1, remaining: -1 };
131
+
132
+ // estimateProbeScanCost (WS5-2) — pure shared cost model, ASYNC must cost
133
+ // strictly less than SYNC for the same size (the scanner UI's "background
134
+ // analysis saves tokens" tooltip depends on this). A one-byte compile-time
135
+ // smoke check; the full behavioural suite lives in probe-scan-cost.utils.test.ts.
136
+ const _asyncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.ASYNC);
137
+ const _syncCost: number = estimateProbeScanCost(5 * 1024 * 1024, ProbeScanMode.SYNC);
138
+ if (_asyncCost >= _syncCost) {
139
+ throw new Error('estimateProbeScanCost: ASYNC must cost strictly less than SYNC');
140
+ }
141
+
142
+ void _verdicts;
143
+ void _modes;
144
+ void _statuses;
145
+ void _scanDetail;
146
+ void _historyItem;
147
+ void _scanSyncData;
148
+ void _scanAsyncData;
149
+ void _webhook;
150
+ void _webhookCreated;
151
+ void _updateBody;
152
+ void _updateBody2;
153
+ void _deliveryPayload;
154
+ void _deliveryRecord;
155
+ void _deliveryRecordAdHoc;
156
+ void _usage;
157
+ void _usageUnlimited;
158
+
159
+ export {};
@@ -4,3 +4,5 @@ export * from './shape.utils';
4
4
  export * from './icons.utils';
5
5
  export * from './enum.utils';
6
6
  export * from './extraction-webhook.utils';
7
+ export * from './probe-webhook-signature.utils';
8
+ export * from './probe-scan-cost.utils';
@@ -0,0 +1,101 @@
1
+ // ──────────────────────────────────────────────
2
+ // AbyssProbe scan cost model — pure, shared (client + server, no endpoint).
3
+ //
4
+ // Ported from abyss-storage's OWN weighted AV-scan cost model
5
+ // (`apps/api/src/services/av-scan-usage.ts#computeAvScanCost`, introduced in
6
+ // storage commit 317e4a0 "wire the weighted AV-scan quota", since made dead
7
+ // code there by the AV v3 Phase 3b-2 hand-off — "storage never bills —
8
+ // AbyssProbe does" — but the FORMULA itself is exactly what AbyssProbe's own
9
+ // weighted billing needs, so it is ported here verbatim rather than
10
+ // reinvented):
11
+ //
12
+ // cost = round( (BASE + K_MB * ceil(sizeOctet / 1 MiB)) * MODE_WEIGHT[mode] )
13
+ //
14
+ // ASYNC costs LESS than SYNC (MODE_WEIGHT.async=1 < MODE_WEIGHT.sync=3) — a
15
+ // synchronous scan holds the caller's request (and an engine worker) until
16
+ // the verdict and cannot be batched/load-balanced, so it costs more than an
17
+ // asynchronous (queued) scan of the SAME bytes. This is the numeric backing
18
+ // for the scanner UI's "l'analyse en arrière-plan économise des tokens"
19
+ // tooltip (WS1).
20
+ //
21
+ // PURE + DETERMINISTIC + no I/O: this is the SINGLE source of truth for
22
+ // AbyssProbe's scan cost, used on BOTH sides of the wire —
23
+ // - server (apps/api/src/services/probe-scan.service.ts): the REAL cost
24
+ // billed via recordProbeScanUsage and persisted on probe_scans.costUnits
25
+ // once a scan reaches a verdict (DONE).
26
+ // - client (the AbyssProbe UI, or any external caller): a cost PREVIEW
27
+ // before scanning — call this with the picked file's size and the
28
+ // intended mode to show the estimated token cost, matching exactly what
29
+ // the server will bill (no separate "estimate" formula to drift out of
30
+ // sync with the real one — same function, same constants, both sides).
31
+ //
32
+ // Deliberately NOT exposed as an HTTP endpoint (no `POST /scan/estimate`):
33
+ // the whole point of a pure function is that the client can compute it
34
+ // locally, instantly, with zero round-trip — see this package's root README
35
+ // note on `sdk.probe.*`'s HTTP-call-only convention; a helper like this one
36
+ // (and `verifyProbeWebhookSignature` before it) is instead a plain top-level
37
+ // export, not a `sdk.probe.*` method.
38
+ // ──────────────────────────────────────────────
39
+
40
+ import { ProbeScanMode } from '../types';
41
+
42
+ /** Flat floor every scanned file pays (the "one scan" cost). Tunable. */
43
+ export const PROBE_SCAN_COST_BASE = 1;
44
+
45
+ /** Additional units per (ceil'd) mebibyte of scanned bytes. Tunable. */
46
+ export const PROBE_SCAN_COST_K_MB = 1;
47
+
48
+ /**
49
+ * Per-mode cost multiplier. A synchronous scan blocks the caller's request
50
+ * (and an engine worker) until the verdict and cannot be queued/batched, so
51
+ * it costs strictly MORE than an asynchronous scan of the same bytes —
52
+ * ASYNC < SYNC is a load-bearing invariant relied on by the scanner UI's
53
+ * "background analysis saves tokens" messaging (WS1). Tunable, but this
54
+ * ordering must never be inverted.
55
+ */
56
+ export const PROBE_SCAN_COST_MODE_WEIGHT: Record<ProbeScanMode, number> = {
57
+ [ProbeScanMode.ASYNC]: 1,
58
+ [ProbeScanMode.SYNC]: 3,
59
+ };
60
+
61
+ /** One mebibyte, the unit the per-MB term is quantised to. */
62
+ const ONE_MB = 1024 * 1024;
63
+
64
+ /**
65
+ * Weighted cost of one AbyssProbe scan, in `ACCOUNT_AV_SCAN_UNITS` ("tokens"
66
+ * in the UI). Pure + deterministic — same inputs always produce the same
67
+ * output, no I/O, safe to call from a browser or a server.
68
+ *
69
+ * Returns `0` for a non-finite / non-positive `sizeOctet` (nothing to scan,
70
+ * nothing to bill) rather than throwing — a defensive, never-crash contract
71
+ * appropriate for a client-side cost preview that might run ahead of full
72
+ * input validation (e.g. while a file is still being selected).
73
+ *
74
+ * `mode` defaults to the (cheaper) ASYNC weight when given an unrecognised
75
+ * value, mirroring the ported formula's own `MODE_WEIGHT[mode] ?? MODE_WEIGHT.async`
76
+ * fallback — never silently the MORE expensive weight.
77
+ *
78
+ * `mode`'s type is widened to `| 'sync' | 'async'` alongside the enum
79
+ * itself, NOT just `ProbeScanMode`: apps/api's own server-side
80
+ * `database/probe-scan.enum.ts#ProbeScanMode` is a SEPARATE TypeScript enum
81
+ * declaration (kept server-local — see that file's header comment on why —
82
+ * with the identical 'sync'/'async' runtime values, so the wire format never
83
+ * drifts). TypeScript string enums are nominal: a same-valued member of that
84
+ * OTHER enum is not directly assignable to THIS package's `ProbeScanMode`
85
+ * parameter type, but IS assignable to a plain string-literal type matching
86
+ * its value — so the server can call this exact function (`scan.mode`,
87
+ * `estimateProbeScanCost`'s whole reason for living being ONE shared formula
88
+ * on both sides of the wire, see this file's header comment) without a cast.
89
+ */
90
+ export const estimateProbeScanCost = (
91
+ sizeOctet: number,
92
+ mode: ProbeScanMode | 'sync' | 'async',
93
+ ): number => {
94
+ if (!Number.isFinite(sizeOctet) || sizeOctet <= 0) {
95
+ return 0;
96
+ }
97
+ const megabytes = Math.ceil(sizeOctet / ONE_MB);
98
+ const weight =
99
+ PROBE_SCAN_COST_MODE_WEIGHT[mode] ?? PROBE_SCAN_COST_MODE_WEIGHT[ProbeScanMode.ASYNC];
100
+ return Math.round((PROBE_SCAN_COST_BASE + PROBE_SCAN_COST_K_MB * megabytes) * weight);
101
+ };
@@ -0,0 +1,56 @@
1
+ // ──────────────────────────────────────────────
2
+ // AbyssProbe webhook signature verification (helper for RECEIVERS of a
3
+ // webhook delivery — the abyss-main UI/abyss-storage/any external
4
+ // integrator that registered a callbackUrl).
5
+ //
6
+ // Mirrors this package's own extraction-webhook.utils.ts
7
+ // (verifyExtractionWebhook) — the same package's own precedent for this
8
+ // exact problem — adapted to AbyssProbe's actual wire format: the signature
9
+ // header (see PROBE_WEBHOOK_SIGNATURE_HEADER in this package's root index)
10
+ // carries the RAW hex HMAC digest with no `sha256=` prefix (unlike
11
+ // extraction's `X-RCB-Signature: sha256=<hex>`) — see
12
+ // apps/api/src/services/probe-webhook-delivery.service.ts's `sign()`.
13
+ //
14
+ // Lazy-requires `crypto` so the SDK stays browser-compatible when this
15
+ // utility is never invoked (a browser page is never the receiving end of a
16
+ // server-to-server webhook).
17
+ // ──────────────────────────────────────────────
18
+
19
+ export interface VerifyProbeWebhookSignatureInput {
20
+ /** The exact bytes/string the receiver's HTTP framework handed it — must be the RAW body, not a re-serialized JSON.parse/stringify round trip (whitespace/key-order would change the HMAC). */
21
+ rawBody: string | Buffer;
22
+ /** The incoming `x-probe-signature` header value. */
23
+ signatureHeader: string | undefined;
24
+ /** This webhook registration's secret (returned once at creation — see IProbeWebhookCreated). */
25
+ secret: string;
26
+ }
27
+
28
+ /**
29
+ * Recompute the HMAC-SHA256 of `rawBody` with `secret` and constant-time
30
+ * compare it against `signatureHeader`. Returns `false` (never throws) for
31
+ * any malformed input — a hostile/truncated header must not crash the
32
+ * receiver.
33
+ */
34
+ export function verifyProbeWebhookSignature(input: VerifyProbeWebhookSignatureInput): boolean {
35
+ if (!input.signatureHeader || !input.secret) return false;
36
+
37
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
38
+ const crypto = require('crypto');
39
+ const computed: string = crypto
40
+ .createHmac('sha256', input.secret)
41
+ .update(input.rawBody)
42
+ .digest('hex');
43
+
44
+ if (input.signatureHeader.length !== computed.length) return false;
45
+
46
+ try {
47
+ return crypto.timingSafeEqual(
48
+ Buffer.from(input.signatureHeader, 'hex'),
49
+ Buffer.from(computed, 'hex'),
50
+ );
51
+ } catch {
52
+ // Buffer.from(..., 'hex') on a non-hex string yields a shorter buffer,
53
+ // which would otherwise make timingSafeEqual throw on a length mismatch.
54
+ return false;
55
+ }
56
+ }
@@ -1,5 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { IFileScanOptions } from '../types';
4
- import { IFileScanResponse } from '../types';
5
- export declare const scan: (file: Buffer | Uint8Array, opts?: IFileScanOptions, apiKey?: string) => Promise<IFileScanResponse>;
@@ -1,49 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.scan = void 0;
4
- const __1 = require("..");
5
- const DEFAULT_API_KEY_HEADER = 'abyss-api-key';
6
- function authHeader(apiKey) {
7
- return apiKey ? { [DEFAULT_API_KEY_HEADER]: apiKey } : {};
8
- }
9
- async function loadFormData() {
10
- if (typeof FormData !== 'undefined' && typeof window !== 'undefined') {
11
- return FormData;
12
- }
13
- const nodeRequire = new Function('return require')();
14
- return nodeRequire('form-data');
15
- }
16
- const scan = async (file, opts = {}, apiKey) => {
17
- const fileName = 'file';
18
- const isBrowser = typeof window !== 'undefined' && typeof FormData !== 'undefined';
19
- if (isBrowser) {
20
- const form = new FormData();
21
- const blob = new Blob([file]);
22
- form.append('file', blob, fileName);
23
- if (opts.organizationId) {
24
- form.append('organizationId', opts.organizationId);
25
- }
26
- return (await __1.AbyssToolsCore.axios.post(`file-scan/scan`, form, {
27
- headers: authHeader(apiKey),
28
- maxBodyLength: Infinity,
29
- maxContentLength: Infinity,
30
- })).data;
31
- }
32
- const FormDataCtor = await loadFormData();
33
- const form = new FormDataCtor();
34
- const buf = Buffer.isBuffer(file) ? file : Buffer.from(file);
35
- form.append('file', buf, { filename: fileName });
36
- if (opts.organizationId) {
37
- form.append('organizationId', opts.organizationId);
38
- }
39
- return (await __1.AbyssToolsCore.axios.post(`file-scan/scan`, form, {
40
- headers: {
41
- ...(typeof form.getHeaders === 'function' ? form.getHeaders() : {}),
42
- ...authHeader(apiKey),
43
- },
44
- maxBodyLength: Infinity,
45
- maxContentLength: Infinity,
46
- })).data;
47
- };
48
- exports.scan = scan;
49
- //# sourceMappingURL=file-scan.api.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-scan.api.js","sourceRoot":"","sources":["../../src/api/file-scan.api.ts"],"names":[],"mappings":";;;AAUA,0BAAoC;AAIpC,MAAM,sBAAsB,GAAG,eAAe,CAAC;AAE/C,SAAS,UAAU,CAAC,MAAe;IACjC,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,KAAK,UAAU,YAAY;IACzB,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACrE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAKD,MAAM,WAAW,GAAgB,IAAI,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;IAClE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;AAClC,CAAC;AAQM,MAAM,IAAI,GAAG,KAAK,EACvB,IAAyB,EACzB,OAAyB,EAAE,EAC3B,MAAe,EACa,EAAE;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC;IACxB,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,CAAC;IACnF,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAkB,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,CACL,MAAM,kBAAc,CAAC,KAAK,CAAC,IAAI,CAAoB,gBAAgB,EAAE,IAAI,EAAE;YACzE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC;YAC3B,aAAa,EAAE,QAAQ;YACvB,gBAAgB,EAAE,QAAQ;SAC3B,CAAC,CACH,CAAC,IAAI,CAAC;IACT,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,YAAY,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,CACL,MAAM,kBAAc,CAAC,KAAK,CAAC,IAAI,CAAoB,gBAAgB,EAAE,IAAI,EAAE;QACzE,OAAO,EAAE;YACP,GAAG,CAAC,OAAO,IAAI,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,UAAU,CAAC,MAAM,CAAC;SACtB;QACD,aAAa,EAAE,QAAQ;QACvB,gBAAgB,EAAE,QAAQ;KAC3B,CAAC,CACH,CAAC,IAAI,CAAC;AACT,CAAC,CAAC;AAvCW,QAAA,IAAI,QAuCf"}
@@ -1,7 +0,0 @@
1
- export declare enum AnalysisStatus {
2
- PENDING = "pending",
3
- CLEAN = "clean",
4
- INFECTED = "infected",
5
- SKIPPED = "skipped",
6
- ERROR = "error"
7
- }
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AnalysisStatus = void 0;
4
- var AnalysisStatus;
5
- (function (AnalysisStatus) {
6
- AnalysisStatus["PENDING"] = "pending";
7
- AnalysisStatus["CLEAN"] = "clean";
8
- AnalysisStatus["INFECTED"] = "infected";
9
- AnalysisStatus["SKIPPED"] = "skipped";
10
- AnalysisStatus["ERROR"] = "error";
11
- })(AnalysisStatus || (exports.AnalysisStatus = AnalysisStatus = {}));
12
- //# sourceMappingURL=analysis-status.enum.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"analysis-status.enum.js","sourceRoot":"","sources":["../../../src/types/enum/analysis-status.enum.ts"],"names":[],"mappings":";;;AAOA,IAAY,cAMX;AAND,WAAY,cAAc;IACxB,qCAAmB,CAAA;IACnB,iCAAe,CAAA;IACf,uCAAqB,CAAA;IACrB,qCAAmB,CAAA;IACnB,iCAAe,CAAA;AACjB,CAAC,EANW,cAAc,8BAAd,cAAc,QAMzB"}
@@ -1 +0,0 @@
1
- export type FileScanStatus = 'pending_scan' | 'clean' | 'infected' | 'skipped' | 'error';
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-scan-status.enum.js","sourceRoot":"","sources":["../../../src/types/enum/file-scan-status.enum.ts"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- export interface IFileScanOptions {
2
- organizationId?: string;
3
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-scan.request.js","sourceRoot":"","sources":["../../../../../src/types/interface/api/requests/file-scan.request.ts"],"names":[],"mappings":""}
@@ -1,7 +0,0 @@
1
- import { IResponse } from '../type-message/response';
2
- import { IFileAnalysis } from '../../models/file-scan.dto';
3
- export interface IFileScanData {
4
- analysis: IFileAnalysis;
5
- scanCostUnits: number;
6
- }
7
- export type IFileScanResponse = IResponse<IFileScanData>;
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-scan.response.js","sourceRoot":"","sources":["../../../../../src/types/interface/api/responses/file-scan.response.ts"],"names":[],"mappings":""}
@@ -1,11 +0,0 @@
1
- import { AnalysisStatus } from '../../enum/analysis-status.enum';
2
- export interface IFileAnalysis {
3
- status: AnalysisStatus;
4
- engine: string | null;
5
- engineVersion: string | null;
6
- contentHash: string | null;
7
- viruses: string[];
8
- startedAt: string | null;
9
- finishedAt: string | null;
10
- durationMs: number | null;
11
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"file-scan.dto.js","sourceRoot":"","sources":["../../../../src/types/interface/models/file-scan.dto.ts"],"names":[],"mappings":""}
@@ -1,23 +0,0 @@
1
- /**
2
- * Antivirus scan verdict for a stored file, mirrored from AbyssStorage.
3
- *
4
- * This is the SAME vocabulary as `@abyss-project/storage`'s `FileScanStatus`
5
- * enum (`application_files.scanStatus`): `pending_scan` | `clean` | `infected`
6
- * | `skipped` | `error`. It is intentionally re-declared here as a string
7
- * union — NOT imported — because abyss-tools-core is standalone (does not
8
- * depend on `@abyss-project/storage`), the same approach every other core
9
- * package (abyss-main asset-core, abyss-banking-core, abyss-memories-core)
10
- * takes. A string union (rather than a local TS enum) is deliberate: the
11
- * backend assigns a storage `FileScanStatus` enum value straight into this
12
- * type with no cast (a string-enum member is assignable to the matching
13
- * string union, but NOT to a distinct local enum with identical values).
14
- * Keep the members in sync with storage's enum.
15
- *
16
- * Used by both async-enforce document surfaces (AV v3 P2): BoardCardDocument
17
- * and AtlasDocument. `pending_scan` gates `get()` (see
18
- * board-card-document.service.ts#assertNotPendingScan /
19
- * atlas-document.service.ts#assertNotPendingScan) with a 423; `infected`
20
- * never persists on a row (the row is purged instead, see purgeInfected on
21
- * both services).
22
- */
23
- export type FileScanStatus = 'pending_scan' | 'clean' | 'infected' | 'skipped' | 'error';