@echoscan/echoscan 0.1.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.
- package/README.md +149 -0
- package/package.json +19 -0
- package/src/index.d.ts +51 -0
- package/src/index.mjs +286 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# `@echoscan/echoscan` (Node)
|
|
2
|
+
|
|
3
|
+
中文 | [English](#english)
|
|
4
|
+
|
|
5
|
+
## 中文
|
|
6
|
+
|
|
7
|
+
### 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @echoscan/echoscan
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 可用调用方式
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { createLiteClient, createProClient } from '@echoscan/echoscan';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Lite(匿名):
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const lite = createLiteClient();
|
|
23
|
+
const report = await lite.getReport(imprint);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Pro(需要 apiKey):
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
const pro = createProClient({ apiKey: process.env.ECHOSCAN_PRO_KEY });
|
|
30
|
+
const report = await pro.getReport(imprint);
|
|
31
|
+
const history = await pro.getHistory(imprint, { days: 7 });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### History 查询模式
|
|
36
|
+
|
|
37
|
+
仅 Pro 客户端可用(需要 Pro API Key)。
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
await pro.getHistory(imprint, { days: 7 });
|
|
41
|
+
await pro.getHistory(imprint, { from: '2026-03-01', to: '2026-03-18' });
|
|
42
|
+
await pro.getHistory(imprint, { from: '2026-03-01', to: '2026-03-18', recent: 20 });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
规则:
|
|
46
|
+
|
|
47
|
+
- `days` 与 `from/to` 互斥
|
|
48
|
+
- `from` 和 `to` 必须成对出现
|
|
49
|
+
- 日期格式必须是 `YYYY-MM-DD`
|
|
50
|
+
|
|
51
|
+
### 返回值与错误
|
|
52
|
+
|
|
53
|
+
`getReport()` 顶层结构:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"analysis": {},
|
|
58
|
+
"lyingCount": 0,
|
|
59
|
+
"projection": {}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`getHistory()` 顶层结构:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"imprint": "fp_session_...",
|
|
68
|
+
"range": {},
|
|
69
|
+
"recent": [],
|
|
70
|
+
"summary": {},
|
|
71
|
+
"timeline": []
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
完整字段说明链接:[echoscan](https://echoscan.org)
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## English
|
|
80
|
+
|
|
81
|
+
### Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install @echoscan/echoscan
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Usage
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { createLiteClient, createProClient } from '@echoscan/echoscan';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Lite (anonymous):
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const lite = createLiteClient();
|
|
97
|
+
const report = await lite.getReport(imprint);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Pro (apiKey required):
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const pro = createProClient({ apiKey: process.env.ECHOSCAN_PRO_KEY });
|
|
104
|
+
const report = await pro.getReport(imprint);
|
|
105
|
+
const history = await pro.getHistory(imprint, { days: 7 });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
### History query modes
|
|
110
|
+
|
|
111
|
+
Pro-only (requires a Pro API key).
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
await pro.getHistory(imprint, { days: 7 });
|
|
115
|
+
await pro.getHistory(imprint, { from: '2026-03-01', to: '2026-03-18' });
|
|
116
|
+
await pro.getHistory(imprint, { from: '2026-03-01', to: '2026-03-18', recent: 20 });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Rules:
|
|
120
|
+
|
|
121
|
+
- `days` and `from/to` are mutually exclusive
|
|
122
|
+
- `from` and `to` must be provided together
|
|
123
|
+
- date format must be `YYYY-MM-DD`
|
|
124
|
+
|
|
125
|
+
### Response and errors
|
|
126
|
+
|
|
127
|
+
`getReport()` top-level shape:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"analysis": {},
|
|
132
|
+
"lyingCount": 0,
|
|
133
|
+
"projection": {}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`getHistory()` top-level shape:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"imprint": "fp_session_...",
|
|
142
|
+
"range": {},
|
|
143
|
+
"recent": [],
|
|
144
|
+
"summary": {},
|
|
145
|
+
"timeline": []
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Full field reference: [echoscan](https://echoscan.org)
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@echoscan/echoscan",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "EchoScan package for Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.mjs",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.mjs",
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"default": "./src/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type EchoScanApiErrorCode =
|
|
2
|
+
| 'invalid_request'
|
|
3
|
+
| 'auth_failed'
|
|
4
|
+
| 'forbidden'
|
|
5
|
+
| 'not_found'
|
|
6
|
+
| 'quota_exceeded'
|
|
7
|
+
| 'timeout'
|
|
8
|
+
| 'upstream_unavailable'
|
|
9
|
+
| 'network_error'
|
|
10
|
+
| 'unknown_error';
|
|
11
|
+
|
|
12
|
+
export type EchoScanApiError = Error & {
|
|
13
|
+
name: 'EchoScanApiError';
|
|
14
|
+
code: EchoScanApiErrorCode;
|
|
15
|
+
httpStatus: number;
|
|
16
|
+
message: string;
|
|
17
|
+
requestId: string | null;
|
|
18
|
+
retryable: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type HistoryQuery =
|
|
22
|
+
| {
|
|
23
|
+
days: number;
|
|
24
|
+
from?: never;
|
|
25
|
+
to?: never;
|
|
26
|
+
recent?: number;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
days?: never;
|
|
30
|
+
from: string;
|
|
31
|
+
to: string;
|
|
32
|
+
recent?: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type LiteClient = {
|
|
36
|
+
getReport(imprint: string): Promise<unknown>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ProClient = {
|
|
40
|
+
getReport(imprint: string): Promise<unknown>;
|
|
41
|
+
getHistory(imprint: string, query?: HistoryQuery): Promise<unknown>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type DebugClient = {
|
|
45
|
+
getReport(imprint: string): Promise<unknown>;
|
|
46
|
+
getDetails(imprint: string): Promise<unknown>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function createLiteClient(): LiteClient;
|
|
50
|
+
export function createProClient(input: { apiKey: string }): ProClient;
|
|
51
|
+
export function createDebugClient(input: { apiKey: string }): DebugClient;
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = 'https://api.echoscan.org';
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
3
|
+
const DEFAULT_RETRIES = 2;
|
|
4
|
+
const SDK_USER_AGENT = 'echoscan-node/0.1.0';
|
|
5
|
+
|
|
6
|
+
function parseIntEnv(name, fallback) {
|
|
7
|
+
const value = process.env[name];
|
|
8
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
const parsed = Number.parseInt(value, 10);
|
|
12
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeBaseUrl(rawBaseUrl) {
|
|
19
|
+
const baseUrl = typeof rawBaseUrl === 'string' && rawBaseUrl.trim().length > 0
|
|
20
|
+
? rawBaseUrl.trim()
|
|
21
|
+
: DEFAULT_BASE_URL;
|
|
22
|
+
return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildRuntimeConfig() {
|
|
26
|
+
return {
|
|
27
|
+
baseUrl: normalizeBaseUrl(process.env.ECHOSCAN_SERVER_BASE_URL),
|
|
28
|
+
timeoutMs: parseIntEnv('ECHOSCAN_SERVER_TIMEOUT_MS', DEFAULT_TIMEOUT_MS),
|
|
29
|
+
retries: parseIntEnv('ECHOSCAN_SERVER_RETRIES', DEFAULT_RETRIES),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function generateRequestId() {
|
|
34
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
35
|
+
return `req_${crypto.randomUUID()}`;
|
|
36
|
+
}
|
|
37
|
+
return `req_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildApiError(code, httpStatus, requestId, retryable) {
|
|
41
|
+
const messages = {
|
|
42
|
+
invalid_request: 'Invalid request',
|
|
43
|
+
auth_failed: 'Authentication failed',
|
|
44
|
+
forbidden: 'Forbidden',
|
|
45
|
+
not_found: 'Not found',
|
|
46
|
+
quota_exceeded: 'Quota exceeded',
|
|
47
|
+
timeout: 'Request timed out',
|
|
48
|
+
upstream_unavailable: 'Upstream service unavailable',
|
|
49
|
+
network_error: 'Network error',
|
|
50
|
+
unknown_error: 'Unexpected error',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const error = new Error(messages[code] ?? messages.unknown_error);
|
|
54
|
+
error.name = 'EchoScanApiError';
|
|
55
|
+
error.code = code;
|
|
56
|
+
error.httpStatus = httpStatus;
|
|
57
|
+
error.requestId = requestId ?? null;
|
|
58
|
+
error.retryable = retryable;
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function mapHttpStatusToCode(status) {
|
|
63
|
+
if (status === 400) return 'invalid_request';
|
|
64
|
+
if (status === 401) return 'auth_failed';
|
|
65
|
+
if (status === 403) return 'forbidden';
|
|
66
|
+
if (status === 404) return 'not_found';
|
|
67
|
+
if (status === 408) return 'timeout';
|
|
68
|
+
if (status === 429) return 'quota_exceeded';
|
|
69
|
+
if (status === 500 || status === 502 || status === 503 || status === 504) {
|
|
70
|
+
return 'upstream_unavailable';
|
|
71
|
+
}
|
|
72
|
+
return 'unknown_error';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertApiKey(apiKey, role) {
|
|
76
|
+
const normalized = typeof apiKey === 'string' ? apiKey.trim() : '';
|
|
77
|
+
if (normalized.length === 0) {
|
|
78
|
+
throw new Error(`apiKey is required for ${role} client`);
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function assertImprint(imprint) {
|
|
84
|
+
if (typeof imprint !== 'string' || imprint.trim().length === 0) {
|
|
85
|
+
throw new Error('imprint must be a non-empty string');
|
|
86
|
+
}
|
|
87
|
+
return imprint.trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function assertDateValue(value, field) {
|
|
91
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
92
|
+
throw new Error(`${field} must use YYYY-MM-DD format`);
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildHistoryQuery(query = {}) {
|
|
98
|
+
if (!query || typeof query !== 'object') {
|
|
99
|
+
throw new Error('history query must be an object');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const hasDays = Object.prototype.hasOwnProperty.call(query, 'days');
|
|
103
|
+
const hasFrom = Object.prototype.hasOwnProperty.call(query, 'from');
|
|
104
|
+
const hasTo = Object.prototype.hasOwnProperty.call(query, 'to');
|
|
105
|
+
|
|
106
|
+
if (hasDays && (hasFrom || hasTo)) {
|
|
107
|
+
throw new Error('history query: days and from/to are mutually exclusive');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (hasFrom !== hasTo) {
|
|
111
|
+
throw new Error('history query: from and to must be provided together');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const params = new URLSearchParams();
|
|
115
|
+
|
|
116
|
+
if (hasDays) {
|
|
117
|
+
const days = Number(query.days);
|
|
118
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
119
|
+
throw new Error('history query: days must be a positive number');
|
|
120
|
+
}
|
|
121
|
+
params.set('days', String(Math.trunc(days)));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (hasFrom && hasTo) {
|
|
125
|
+
const from = assertDateValue(query.from, 'from');
|
|
126
|
+
const to = assertDateValue(query.to, 'to');
|
|
127
|
+
if (from > to) {
|
|
128
|
+
throw new Error('history query: from must be <= to');
|
|
129
|
+
}
|
|
130
|
+
params.set('from', from);
|
|
131
|
+
params.set('to', to);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Object.prototype.hasOwnProperty.call(query, 'recent')) {
|
|
135
|
+
const recent = Number(query.recent);
|
|
136
|
+
if (!Number.isFinite(recent) || recent <= 0) {
|
|
137
|
+
throw new Error('history query: recent must be a positive number');
|
|
138
|
+
}
|
|
139
|
+
params.set('recent', String(Math.trunc(recent)));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const queryString = params.toString();
|
|
143
|
+
return queryString.length > 0 ? `?${queryString}` : '';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function decodeJsonSafely(response) {
|
|
147
|
+
try {
|
|
148
|
+
return await response.json();
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function performGet({
|
|
155
|
+
baseUrl,
|
|
156
|
+
path,
|
|
157
|
+
apiKey,
|
|
158
|
+
timeoutMs,
|
|
159
|
+
retries,
|
|
160
|
+
fetchImpl,
|
|
161
|
+
}) {
|
|
162
|
+
const url = `${baseUrl}${path}`;
|
|
163
|
+
const maxAttempts = retries + 1;
|
|
164
|
+
let lastError = null;
|
|
165
|
+
|
|
166
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
167
|
+
const requestId = generateRequestId();
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const headers = {
|
|
173
|
+
Accept: 'application/json',
|
|
174
|
+
'User-Agent': SDK_USER_AGENT,
|
|
175
|
+
'X-Request-Id': requestId,
|
|
176
|
+
};
|
|
177
|
+
if (apiKey) {
|
|
178
|
+
headers['X-API-Key'] = apiKey;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const response = await fetchImpl(url, {
|
|
182
|
+
method: 'GET',
|
|
183
|
+
headers,
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
});
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
|
|
188
|
+
if (response.ok) {
|
|
189
|
+
return await decodeJsonSafely(response);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const body = await decodeJsonSafely(response);
|
|
193
|
+
const responseRequestId = response.headers?.get?.('x-request-id')
|
|
194
|
+
?? body?.requestId
|
|
195
|
+
?? body?.request_id
|
|
196
|
+
?? requestId;
|
|
197
|
+
const code = mapHttpStatusToCode(response.status);
|
|
198
|
+
const retryable = attempt < maxAttempts && (response.status >= 500 || response.status === 429);
|
|
199
|
+
lastError = buildApiError(code, response.status, responseRequestId, retryable);
|
|
200
|
+
if (retryable) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw lastError;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
|
|
207
|
+
if (error?.name === 'EchoScanApiError') {
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const isAbortError = error?.name === 'AbortError';
|
|
212
|
+
const code = isAbortError ? 'timeout' : 'network_error';
|
|
213
|
+
const retryable = attempt < maxAttempts;
|
|
214
|
+
lastError = buildApiError(code, isAbortError ? 408 : 0, requestId, retryable);
|
|
215
|
+
|
|
216
|
+
if (retryable) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
throw lastError;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw lastError ?? buildApiError('unknown_error', 0, null, false);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function createBaseClient(apiKey) {
|
|
227
|
+
const runtime = buildRuntimeConfig();
|
|
228
|
+
const fetchImpl = globalThis.fetch?.bind(globalThis);
|
|
229
|
+
if (typeof fetchImpl !== 'function') {
|
|
230
|
+
throw new Error('global fetch is unavailable in this runtime');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const get = (path) => performGet({
|
|
234
|
+
baseUrl: runtime.baseUrl,
|
|
235
|
+
path,
|
|
236
|
+
apiKey,
|
|
237
|
+
timeoutMs: runtime.timeoutMs,
|
|
238
|
+
retries: runtime.retries,
|
|
239
|
+
fetchImpl,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return { get };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function createLiteClient() {
|
|
246
|
+
const client = createBaseClient(null);
|
|
247
|
+
return {
|
|
248
|
+
getReport(imprint) {
|
|
249
|
+
const normalizedImprint = assertImprint(imprint);
|
|
250
|
+
return client.get(`/api/v1/fingerprint/report-lite/${encodeURIComponent(normalizedImprint)}`);
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function createProClient({ apiKey } = {}) {
|
|
256
|
+
const normalizedApiKey = assertApiKey(apiKey, 'pro');
|
|
257
|
+
const client = createBaseClient(normalizedApiKey);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
getReport(imprint) {
|
|
261
|
+
const normalizedImprint = assertImprint(imprint);
|
|
262
|
+
return client.get(`/api/v1/fingerprint/report/${encodeURIComponent(normalizedImprint)}`);
|
|
263
|
+
},
|
|
264
|
+
getHistory(imprint, query = {}) {
|
|
265
|
+
const normalizedImprint = assertImprint(imprint);
|
|
266
|
+
const queryString = buildHistoryQuery(query);
|
|
267
|
+
return client.get(`/api/v1/fingerprint/imprint/${encodeURIComponent(normalizedImprint)}/history${queryString}`);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function createDebugClient({ apiKey } = {}) {
|
|
273
|
+
const normalizedApiKey = assertApiKey(apiKey, 'debug');
|
|
274
|
+
const client = createBaseClient(normalizedApiKey);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
getReport(imprint) {
|
|
278
|
+
const normalizedImprint = assertImprint(imprint);
|
|
279
|
+
return client.get(`/api/v1/internal/fingerprint/report/${encodeURIComponent(normalizedImprint)}`);
|
|
280
|
+
},
|
|
281
|
+
getDetails(imprint) {
|
|
282
|
+
const normalizedImprint = assertImprint(imprint);
|
|
283
|
+
return client.get(`/api/v1/internal/fingerprint/details/${encodeURIComponent(normalizedImprint)}`);
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|