@blindfold/sdk 1.0.2
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/LICENSE +21 -0
- package/README.md +427 -0
- package/dist/index-CsE6Vhax.d.mts +177 -0
- package/dist/index-CsE6Vhax.d.mts.map +1 -0
- package/dist/index-Dfv8zV_d.d.ts +177 -0
- package/dist/index-Dfv8zV_d.d.ts.map +1 -0
- package/dist/index.d.mts +450 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +450 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +575 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +568 -0
- package/dist/index.mjs.map +1 -0
- package/dist/regex/index.d.mts +2 -0
- package/dist/regex/index.d.ts +2 -0
- package/dist/regex/index.js +5 -0
- package/dist/regex/index.mjs +4 -0
- package/dist/regex-BEaK0E7Y.js +4881 -0
- package/dist/regex-BEaK0E7Y.js.map +1 -0
- package/dist/regex-ByjZg3Zy.mjs +4839 -0
- package/dist/regex-ByjZg3Zy.mjs.map +1 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_regex = require('./regex-BEaK0E7Y.js');
|
|
3
|
+
const fs = require_regex.__toESM(require("fs"));
|
|
4
|
+
const path = require_regex.__toESM(require("path"));
|
|
5
|
+
|
|
6
|
+
//#region src/errors.ts
|
|
7
|
+
/**
|
|
8
|
+
* Base error class for Blindfold SDK
|
|
9
|
+
*/
|
|
10
|
+
var BlindfoldError = class BlindfoldError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "BlindfoldError";
|
|
14
|
+
Object.setPrototypeOf(this, BlindfoldError.prototype);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when authentication fails
|
|
19
|
+
*/
|
|
20
|
+
var AuthenticationError = class AuthenticationError extends BlindfoldError {
|
|
21
|
+
constructor(message = "Authentication failed. Please check your API key.") {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "AuthenticationError";
|
|
24
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Error thrown when API request fails
|
|
29
|
+
*/
|
|
30
|
+
var APIError = class APIError extends BlindfoldError {
|
|
31
|
+
statusCode;
|
|
32
|
+
responseBody;
|
|
33
|
+
constructor(message, statusCode, responseBody) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "APIError";
|
|
36
|
+
this.statusCode = statusCode;
|
|
37
|
+
this.responseBody = responseBody;
|
|
38
|
+
Object.setPrototypeOf(this, APIError.prototype);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when network request fails
|
|
43
|
+
*/
|
|
44
|
+
var NetworkError = class NetworkError extends BlindfoldError {
|
|
45
|
+
constructor(message = "Network request failed. Please check your connection.") {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "NetworkError";
|
|
48
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/client.ts
|
|
54
|
+
const DEFAULT_BASE_URL = "https://api.blindfold.dev/api/public/v1";
|
|
55
|
+
const RETRYABLE_STATUS_CODES = new Set([
|
|
56
|
+
429,
|
|
57
|
+
500,
|
|
58
|
+
502,
|
|
59
|
+
503,
|
|
60
|
+
504
|
|
61
|
+
]);
|
|
62
|
+
const REGION_URLS = {
|
|
63
|
+
eu: "https://eu-api.blindfold.dev/api/public/v1",
|
|
64
|
+
us: "https://us-api.blindfold.dev/api/public/v1"
|
|
65
|
+
};
|
|
66
|
+
function sleep(ms) {
|
|
67
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
|
+
}
|
|
69
|
+
const BUNDLED_POLICIES_PATH = path.join(__dirname, "policies.json");
|
|
70
|
+
function loadPolicies(policiesFile) {
|
|
71
|
+
const bundled = JSON.parse(fs.readFileSync(BUNDLED_POLICIES_PATH, "utf-8"));
|
|
72
|
+
if (policiesFile) {
|
|
73
|
+
const user = JSON.parse(fs.readFileSync(policiesFile, "utf-8"));
|
|
74
|
+
return {
|
|
75
|
+
...bundled,
|
|
76
|
+
...user
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return bundled;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Blindfold client for tokenization and detokenization.
|
|
83
|
+
*
|
|
84
|
+
* When no `apiKey` is provided, all methods run locally using the
|
|
85
|
+
* built-in regex PII scanner. Set `mode: "local"` to force local
|
|
86
|
+
* mode even when an API key is present.
|
|
87
|
+
*/
|
|
88
|
+
var Blindfold = class {
|
|
89
|
+
apiKey;
|
|
90
|
+
baseUrl;
|
|
91
|
+
userId;
|
|
92
|
+
maxRetries;
|
|
93
|
+
retryDelay;
|
|
94
|
+
mode;
|
|
95
|
+
locales;
|
|
96
|
+
policies;
|
|
97
|
+
_scanner;
|
|
98
|
+
/**
|
|
99
|
+
* Create a new Blindfold client
|
|
100
|
+
* @param config - Configuration options. Can be omitted entirely for local-only mode.
|
|
101
|
+
*/
|
|
102
|
+
constructor(config = {}) {
|
|
103
|
+
this.apiKey = config.apiKey;
|
|
104
|
+
this.mode = config.mode;
|
|
105
|
+
this.locales = config.locales;
|
|
106
|
+
this.policies = loadPolicies(config.policiesFile);
|
|
107
|
+
if (config.region && !config.baseUrl) {
|
|
108
|
+
const regionUrl = REGION_URLS[config.region];
|
|
109
|
+
if (!regionUrl) throw new Error(`Invalid region '${config.region}'. Must be one of: ${Object.keys(REGION_URLS).join(", ")}`);
|
|
110
|
+
this.baseUrl = regionUrl;
|
|
111
|
+
} else this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
112
|
+
this.userId = config.userId;
|
|
113
|
+
this.maxRetries = config.maxRetries ?? 2;
|
|
114
|
+
this.retryDelay = config.retryDelay ?? .5;
|
|
115
|
+
if (this.useLocal && config.region) console.warn(`region='${config.region}' has no effect in local mode. Set an apiKey to use region-based routing.`);
|
|
116
|
+
}
|
|
117
|
+
get useLocal() {
|
|
118
|
+
if (this.mode === "local") return true;
|
|
119
|
+
return !this.apiKey;
|
|
120
|
+
}
|
|
121
|
+
getScanner() {
|
|
122
|
+
if (!this._scanner) {
|
|
123
|
+
const { PIIScanner: PIIScanner$1 } = (require_regex.init_regex(), require_regex.__toCommonJS(require_regex.regex_exports));
|
|
124
|
+
this._scanner = new PIIScanner$1(this.locales ? { locales: this.locales } : void 0);
|
|
125
|
+
}
|
|
126
|
+
return this._scanner;
|
|
127
|
+
}
|
|
128
|
+
resolvePolicy(policy, entities, scoreThreshold) {
|
|
129
|
+
if (entities) return {
|
|
130
|
+
entities,
|
|
131
|
+
threshold: scoreThreshold
|
|
132
|
+
};
|
|
133
|
+
if (policy) {
|
|
134
|
+
const policyDef = this.policies[policy];
|
|
135
|
+
if (policyDef) return {
|
|
136
|
+
entities: policyDef.entities,
|
|
137
|
+
threshold: scoreThreshold ?? policyDef.threshold
|
|
138
|
+
};
|
|
139
|
+
else console.warn(`Unknown policy '${policy}' in local mode, detecting all entities`);
|
|
140
|
+
}
|
|
141
|
+
return { threshold: scoreThreshold };
|
|
142
|
+
}
|
|
143
|
+
retryWait(attempt, error) {
|
|
144
|
+
if (error && error.statusCode === 429) {
|
|
145
|
+
const body = error.responseBody;
|
|
146
|
+
if (body && typeof body.retry_after === "number") return body.retry_after * 1e3;
|
|
147
|
+
}
|
|
148
|
+
const delay = this.retryDelay * 2 ** attempt * 1e3;
|
|
149
|
+
const jitter = delay * .1 * Math.random();
|
|
150
|
+
return delay + jitter;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Make an authenticated request to the API
|
|
154
|
+
*/
|
|
155
|
+
async request(endpoint, method, body) {
|
|
156
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
157
|
+
const headers = { "Content-Type": "application/json" };
|
|
158
|
+
if (this.apiKey) headers["X-API-Key"] = this.apiKey;
|
|
159
|
+
if (this.userId) headers["X-Blindfold-User-Id"] = this.userId;
|
|
160
|
+
let lastError = new NetworkError("Request failed");
|
|
161
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) try {
|
|
162
|
+
const response = await fetch(url, {
|
|
163
|
+
method,
|
|
164
|
+
headers,
|
|
165
|
+
body: body ? JSON.stringify(body) : void 0
|
|
166
|
+
});
|
|
167
|
+
if (response.status === 401 || response.status === 403) throw new AuthenticationError("Authentication failed. Please check your API key.");
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
let errorMessage = `API request failed with status ${response.status}`;
|
|
170
|
+
let responseBody;
|
|
171
|
+
try {
|
|
172
|
+
responseBody = await response.json();
|
|
173
|
+
const errorData = responseBody;
|
|
174
|
+
errorMessage = errorData.detail || errorData.message || errorMessage;
|
|
175
|
+
} catch {
|
|
176
|
+
errorMessage = `${errorMessage}: ${response.statusText}`;
|
|
177
|
+
}
|
|
178
|
+
throw new APIError(errorMessage, response.status, responseBody);
|
|
179
|
+
}
|
|
180
|
+
return await response.json();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof AuthenticationError) throw error;
|
|
183
|
+
if (error instanceof APIError) {
|
|
184
|
+
if (RETRYABLE_STATUS_CODES.has(error.statusCode) && attempt < this.maxRetries) {
|
|
185
|
+
await sleep(this.retryWait(attempt, error));
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
if (error instanceof NetworkError) {
|
|
191
|
+
lastError = error;
|
|
192
|
+
if (attempt < this.maxRetries) {
|
|
193
|
+
await sleep(this.retryWait(attempt));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
199
|
+
lastError = new NetworkError("Network request failed. Please check your connection and the API URL.");
|
|
200
|
+
if (attempt < this.maxRetries) {
|
|
201
|
+
await sleep(this.retryWait(attempt));
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw lastError;
|
|
205
|
+
}
|
|
206
|
+
throw new NetworkError(error instanceof Error ? error.message : "Unknown error occurred");
|
|
207
|
+
}
|
|
208
|
+
throw lastError;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Tokenize text by replacing sensitive information with tokens
|
|
212
|
+
* @param text - Text to tokenize
|
|
213
|
+
* @param config - Optional configuration
|
|
214
|
+
* @returns Promise with tokenized text and mapping
|
|
215
|
+
*/
|
|
216
|
+
async tokenize(text, config) {
|
|
217
|
+
if (this.useLocal) return this.tokenizeLocal(text, config);
|
|
218
|
+
return this.request("/tokenize", "POST", {
|
|
219
|
+
text,
|
|
220
|
+
...config
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
tokenizeLocal(text, config) {
|
|
224
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
225
|
+
const scanner = this.getScanner();
|
|
226
|
+
const result = scanner.tokenize(text, resolved);
|
|
227
|
+
const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
|
|
228
|
+
type: m.entityType,
|
|
229
|
+
text: m.text,
|
|
230
|
+
start: m.start,
|
|
231
|
+
end: m.end,
|
|
232
|
+
score: m.score
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
text: result.text,
|
|
236
|
+
mapping: result.mapping,
|
|
237
|
+
detected_entities: detected,
|
|
238
|
+
entities_count: detected.length
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Detect PII in text without modifying it
|
|
243
|
+
*
|
|
244
|
+
* Returns only the detected entities with their types, positions,
|
|
245
|
+
* and confidence scores. The original text is not transformed.
|
|
246
|
+
*
|
|
247
|
+
* When no API key is set (or `mode: "local"`), detection runs locally
|
|
248
|
+
* using the built-in regex scanner.
|
|
249
|
+
*
|
|
250
|
+
* @param text - Text to analyze for PII
|
|
251
|
+
* @param config - Optional configuration (entities, score_threshold, policy)
|
|
252
|
+
* @returns Promise with detected entities
|
|
253
|
+
*/
|
|
254
|
+
async detect(text, config) {
|
|
255
|
+
if (this.useLocal) return this.detectLocal(text, config);
|
|
256
|
+
return this.request("/detect", "POST", {
|
|
257
|
+
text,
|
|
258
|
+
...config
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
detectLocal(text, config) {
|
|
262
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
263
|
+
const scanner = this.getScanner();
|
|
264
|
+
const matches = scanner.detect(text, resolved);
|
|
265
|
+
const detected = [];
|
|
266
|
+
for (const m of matches) {
|
|
267
|
+
if (threshold != null && m.score < threshold) continue;
|
|
268
|
+
detected.push({
|
|
269
|
+
type: m.entityType,
|
|
270
|
+
text: m.text,
|
|
271
|
+
start: m.start,
|
|
272
|
+
end: m.end,
|
|
273
|
+
score: m.score
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
detected_entities: detected,
|
|
278
|
+
entities_count: detected.length
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Detokenize text by replacing tokens with original values
|
|
283
|
+
*
|
|
284
|
+
* This method performs detokenization CLIENT-SIDE for better performance,
|
|
285
|
+
* security, and to work offline. No API call is made.
|
|
286
|
+
*
|
|
287
|
+
* @param text - Tokenized text
|
|
288
|
+
* @param mapping - Token mapping from tokenize response
|
|
289
|
+
* @returns DetokenizeResponse with original text
|
|
290
|
+
*/
|
|
291
|
+
detokenize(text, mapping) {
|
|
292
|
+
let result = text;
|
|
293
|
+
let replacements = 0;
|
|
294
|
+
const sortedTokens = Object.keys(mapping).sort((a, b) => b.length - a.length);
|
|
295
|
+
for (const token of sortedTokens) {
|
|
296
|
+
const originalValue = mapping[token];
|
|
297
|
+
const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
298
|
+
const matches = result.match(regex);
|
|
299
|
+
if (matches) {
|
|
300
|
+
result = result.replace(regex, originalValue);
|
|
301
|
+
replacements += matches.length;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
text: result,
|
|
306
|
+
replacements_made: replacements
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Redact (permanently remove) sensitive information from text
|
|
311
|
+
*
|
|
312
|
+
* WARNING: Redaction is irreversible - original data cannot be restored!
|
|
313
|
+
*
|
|
314
|
+
* When no API key is set (or `mode: "local"`), redaction runs locally
|
|
315
|
+
* using the built-in regex scanner, replacing PII with `<Entity Name>` placeholders.
|
|
316
|
+
*
|
|
317
|
+
* @param text - Text to redact
|
|
318
|
+
* @param config - Optional configuration (masking_char, entities)
|
|
319
|
+
* @returns Promise with redacted text and detected entities
|
|
320
|
+
*/
|
|
321
|
+
async redact(text, config) {
|
|
322
|
+
if (this.useLocal) return this.redactLocal(text, config);
|
|
323
|
+
return this.request("/redact", "POST", {
|
|
324
|
+
text,
|
|
325
|
+
...config
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
redactLocal(text, config) {
|
|
329
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
330
|
+
const scanner = this.getScanner();
|
|
331
|
+
const [redactedText, matches] = scanner.redact(text, resolved);
|
|
332
|
+
const detected = [];
|
|
333
|
+
for (const m of matches) {
|
|
334
|
+
if (threshold != null && m.score < threshold) continue;
|
|
335
|
+
detected.push({
|
|
336
|
+
type: m.entityType,
|
|
337
|
+
text: m.text,
|
|
338
|
+
start: m.start,
|
|
339
|
+
end: m.end,
|
|
340
|
+
score: m.score
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
text: redactedText,
|
|
345
|
+
detected_entities: detected,
|
|
346
|
+
entities_count: detected.length
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Mask (partially hide) sensitive information from text
|
|
351
|
+
*
|
|
352
|
+
* @param text - Text to mask
|
|
353
|
+
* @param config - Optional configuration (chars_to_show, from_end, masking_char, entities)
|
|
354
|
+
* @returns Promise with masked text and detected entities
|
|
355
|
+
*/
|
|
356
|
+
async mask(text, config) {
|
|
357
|
+
if (this.useLocal) return this.maskLocal(text, config);
|
|
358
|
+
return this.request("/mask", "POST", {
|
|
359
|
+
text,
|
|
360
|
+
...config
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
maskLocal(text, config) {
|
|
364
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
365
|
+
const scanner = this.getScanner();
|
|
366
|
+
const result = scanner.mask(text, config?.chars_to_show ?? 3, config?.from_end ?? false, config?.masking_char ?? "*", resolved);
|
|
367
|
+
const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
|
|
368
|
+
type: m.entityType,
|
|
369
|
+
text: m.text,
|
|
370
|
+
start: m.start,
|
|
371
|
+
end: m.end,
|
|
372
|
+
score: m.score
|
|
373
|
+
}));
|
|
374
|
+
return {
|
|
375
|
+
text: result.text,
|
|
376
|
+
detected_entities: detected,
|
|
377
|
+
entities_count: detected.length
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Synthesize (replace real data with synthetic fake data)
|
|
382
|
+
*
|
|
383
|
+
* When no API key is set (or `mode: "local"`), synthesis runs locally
|
|
384
|
+
* using format-preserving random generation.
|
|
385
|
+
*
|
|
386
|
+
* @param text - Text to synthesize
|
|
387
|
+
* @param config - Optional configuration (language, entities)
|
|
388
|
+
* @returns Promise with synthetic text and detected entities
|
|
389
|
+
*/
|
|
390
|
+
async synthesize(text, config) {
|
|
391
|
+
if (this.useLocal) return this.synthesizeLocal(text, config);
|
|
392
|
+
return this.request("/synthesize", "POST", {
|
|
393
|
+
text,
|
|
394
|
+
...config
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
synthesizeLocal(text, config) {
|
|
398
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
399
|
+
const scanner = this.getScanner();
|
|
400
|
+
const result = scanner.synthesize(text, void 0, resolved);
|
|
401
|
+
const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
|
|
402
|
+
type: m.entityType,
|
|
403
|
+
text: m.text,
|
|
404
|
+
start: m.start,
|
|
405
|
+
end: m.end,
|
|
406
|
+
score: m.score
|
|
407
|
+
}));
|
|
408
|
+
return {
|
|
409
|
+
text: result.text,
|
|
410
|
+
detected_entities: detected,
|
|
411
|
+
entities_count: detected.length
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Hash (replace with deterministic hash values)
|
|
416
|
+
*
|
|
417
|
+
* @param text - Text to hash
|
|
418
|
+
* @param config - Optional configuration (hash_type, hash_prefix, hash_length, entities)
|
|
419
|
+
* @returns Promise with hashed text and detected entities
|
|
420
|
+
*/
|
|
421
|
+
async hash(text, config) {
|
|
422
|
+
if (this.useLocal) return this.hashLocal(text, config);
|
|
423
|
+
return this.request("/hash", "POST", {
|
|
424
|
+
text,
|
|
425
|
+
...config
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
hashLocal(text, config) {
|
|
429
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
430
|
+
const scanner = this.getScanner();
|
|
431
|
+
const result = scanner.hash(text, config?.hash_type ?? "sha256", config?.hash_prefix ?? "HASH_", config?.hash_length ?? 16, resolved);
|
|
432
|
+
const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
|
|
433
|
+
type: m.entityType,
|
|
434
|
+
text: m.text,
|
|
435
|
+
start: m.start,
|
|
436
|
+
end: m.end,
|
|
437
|
+
score: m.score
|
|
438
|
+
}));
|
|
439
|
+
return {
|
|
440
|
+
text: result.text,
|
|
441
|
+
detected_entities: detected,
|
|
442
|
+
entities_count: detected.length
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Encrypt (reversibly protect) sensitive data in text using AES encryption
|
|
447
|
+
*
|
|
448
|
+
* @param text - Text to encrypt
|
|
449
|
+
* @param config - Optional configuration (encryption_key, entities)
|
|
450
|
+
* @returns Promise with encrypted text and detected entities
|
|
451
|
+
*/
|
|
452
|
+
async encrypt(text, config) {
|
|
453
|
+
if (this.useLocal) return this.encryptLocal(text, config);
|
|
454
|
+
return this.request("/encrypt", "POST", {
|
|
455
|
+
text,
|
|
456
|
+
...config
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
encryptLocal(text, config) {
|
|
460
|
+
if (!config?.encryption_key) throw new Error("encryption_key is required for local encryption mode");
|
|
461
|
+
const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
|
|
462
|
+
const scanner = this.getScanner();
|
|
463
|
+
const result = scanner.encrypt(text, config.encryption_key, resolved);
|
|
464
|
+
const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
|
|
465
|
+
type: m.entityType,
|
|
466
|
+
text: m.text,
|
|
467
|
+
start: m.start,
|
|
468
|
+
end: m.end,
|
|
469
|
+
score: m.score
|
|
470
|
+
}));
|
|
471
|
+
return {
|
|
472
|
+
text: result.text,
|
|
473
|
+
detected_entities: detected,
|
|
474
|
+
entities_count: detected.length
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Tokenize multiple texts in a single request
|
|
479
|
+
* @param texts - Array of texts to tokenize (max 100)
|
|
480
|
+
* @param config - Optional configuration
|
|
481
|
+
* @returns Promise with batch results
|
|
482
|
+
*/
|
|
483
|
+
async tokenizeBatch(texts, config) {
|
|
484
|
+
return this.request("/tokenize", "POST", {
|
|
485
|
+
texts,
|
|
486
|
+
...config
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Detect PII in multiple texts in a single request
|
|
491
|
+
* @param texts - Array of texts to analyze (max 100)
|
|
492
|
+
* @param config - Optional configuration
|
|
493
|
+
* @returns Promise with batch results
|
|
494
|
+
*/
|
|
495
|
+
async detectBatch(texts, config) {
|
|
496
|
+
return this.request("/detect", "POST", {
|
|
497
|
+
texts,
|
|
498
|
+
...config
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Redact PII from multiple texts in a single request
|
|
503
|
+
* @param texts - Array of texts to redact (max 100)
|
|
504
|
+
* @param config - Optional configuration
|
|
505
|
+
* @returns Promise with batch results
|
|
506
|
+
*/
|
|
507
|
+
async redactBatch(texts, config) {
|
|
508
|
+
return this.request("/redact", "POST", {
|
|
509
|
+
texts,
|
|
510
|
+
...config
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Mask PII in multiple texts in a single request
|
|
515
|
+
* @param texts - Array of texts to mask (max 100)
|
|
516
|
+
* @param config - Optional configuration
|
|
517
|
+
* @returns Promise with batch results
|
|
518
|
+
*/
|
|
519
|
+
async maskBatch(texts, config) {
|
|
520
|
+
return this.request("/mask", "POST", {
|
|
521
|
+
texts,
|
|
522
|
+
...config
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Synthesize multiple texts in a single request
|
|
527
|
+
* @param texts - Array of texts to synthesize (max 100)
|
|
528
|
+
* @param config - Optional configuration
|
|
529
|
+
* @returns Promise with batch results
|
|
530
|
+
*/
|
|
531
|
+
async synthesizeBatch(texts, config) {
|
|
532
|
+
return this.request("/synthesize", "POST", {
|
|
533
|
+
texts,
|
|
534
|
+
...config
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Hash PII in multiple texts in a single request
|
|
539
|
+
* @param texts - Array of texts to hash (max 100)
|
|
540
|
+
* @param config - Optional configuration
|
|
541
|
+
* @returns Promise with batch results
|
|
542
|
+
*/
|
|
543
|
+
async hashBatch(texts, config) {
|
|
544
|
+
return this.request("/hash", "POST", {
|
|
545
|
+
texts,
|
|
546
|
+
...config
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Encrypt PII in multiple texts in a single request
|
|
551
|
+
* @param texts - Array of texts to encrypt (max 100)
|
|
552
|
+
* @param config - Optional configuration
|
|
553
|
+
* @returns Promise with batch results
|
|
554
|
+
*/
|
|
555
|
+
async encryptBatch(texts, config) {
|
|
556
|
+
return this.request("/encrypt", "POST", {
|
|
557
|
+
texts,
|
|
558
|
+
...config
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/index.ts
|
|
565
|
+
require_regex.init_regex();
|
|
566
|
+
|
|
567
|
+
//#endregion
|
|
568
|
+
exports.APIError = APIError
|
|
569
|
+
exports.AuthenticationError = AuthenticationError
|
|
570
|
+
exports.Blindfold = Blindfold
|
|
571
|
+
exports.BlindfoldError = BlindfoldError
|
|
572
|
+
exports.EntityType = require_regex.EntityType
|
|
573
|
+
exports.NetworkError = NetworkError
|
|
574
|
+
exports.PIIScanner = require_regex.PIIScanner
|
|
575
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["message: string","statusCode: number","responseBody?: unknown","REGION_URLS: Record<string, string>","ms: number","policiesFile?: string","bundled: PolicyMap","user: PolicyMap","config: BlindfoldConfig","PIIScanner","policy?: string","entities?: string[]","scoreThreshold?: number","attempt: number","error?: APIError","endpoint: string","method: string","body?: Record<string, unknown>","headers: Record<string, string>","lastError: Error","responseBody: unknown","text: string","config?: TokenizeConfig","detected: DetectedEntity[]","config?: DetectConfig","mapping: Record<string, string>","config?: RedactConfig","config?: MaskConfig","config?: SynthesizeConfig","config?: HashConfig","config?: EncryptConfig","texts: string[]"],"sources":["../src/errors.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["/**\n * Base error class for Blindfold SDK\n */\nexport class BlindfoldError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BlindfoldError'\n Object.setPrototypeOf(this, BlindfoldError.prototype)\n }\n}\n\n/**\n * Error thrown when authentication fails\n */\nexport class AuthenticationError extends BlindfoldError {\n constructor(message: string = 'Authentication failed. Please check your API key.') {\n super(message)\n this.name = 'AuthenticationError'\n Object.setPrototypeOf(this, AuthenticationError.prototype)\n }\n}\n\n/**\n * Error thrown when API request fails\n */\nexport class APIError extends BlindfoldError {\n statusCode: number\n responseBody?: unknown\n\n constructor(message: string, statusCode: number, responseBody?: unknown) {\n super(message)\n this.name = 'APIError'\n this.statusCode = statusCode\n this.responseBody = responseBody\n Object.setPrototypeOf(this, APIError.prototype)\n }\n}\n\n/**\n * Error thrown when network request fails\n */\nexport class NetworkError extends BlindfoldError {\n constructor(message: string = 'Network request failed. Please check your connection.') {\n super(message)\n this.name = 'NetworkError'\n Object.setPrototypeOf(this, NetworkError.prototype)\n }\n}\n","import type {\n BlindfoldConfig,\n DetectConfig,\n DetectResponse,\n DetectedEntity,\n TokenizeConfig,\n TokenizeResponse,\n DetokenizeResponse,\n RedactConfig,\n RedactResponse,\n MaskConfig,\n MaskResponse,\n SynthesizeConfig,\n SynthesizeResponse,\n HashConfig,\n HashResponse,\n EncryptConfig,\n EncryptResponse,\n BatchResponse,\n APIErrorResponse,\n} from './types'\nimport { AuthenticationError, APIError, NetworkError } from './errors'\nimport type { PIIScanner as PIIScannerType } from './regex'\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nconst DEFAULT_BASE_URL = 'https://api.blindfold.dev/api/public/v1'\nconst RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504])\n\nconst REGION_URLS: Record<string, string> = {\n eu: 'https://eu-api.blindfold.dev/api/public/v1',\n us: 'https://us-api.blindfold.dev/api/public/v1',\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\ninterface PolicyDefinition {\n entities: string[]\n threshold?: number\n}\n\ntype PolicyMap = Record<string, PolicyDefinition>\n\nconst BUNDLED_POLICIES_PATH = path.join(__dirname, 'policies.json')\n\nfunction loadPolicies(policiesFile?: string): PolicyMap {\n const bundled: PolicyMap = JSON.parse(fs.readFileSync(BUNDLED_POLICIES_PATH, 'utf-8'))\n if (policiesFile) {\n const user: PolicyMap = JSON.parse(fs.readFileSync(policiesFile, 'utf-8'))\n return { ...bundled, ...user }\n }\n return bundled\n}\n\n/**\n * Blindfold client for tokenization and detokenization.\n *\n * When no `apiKey` is provided, all methods run locally using the\n * built-in regex PII scanner. Set `mode: \"local\"` to force local\n * mode even when an API key is present.\n */\nexport class Blindfold {\n private apiKey?: string\n private baseUrl: string\n private userId?: string\n private maxRetries: number\n private retryDelay: number\n private mode?: string\n private locales?: string[]\n private policies: PolicyMap\n private _scanner?: PIIScannerType\n\n /**\n * Create a new Blindfold client\n * @param config - Configuration options. Can be omitted entirely for local-only mode.\n */\n constructor(config: BlindfoldConfig = {}) {\n this.apiKey = config.apiKey\n this.mode = config.mode\n this.locales = config.locales\n this.policies = loadPolicies(config.policiesFile)\n if (config.region && !config.baseUrl) {\n const regionUrl = REGION_URLS[config.region]\n if (!regionUrl) {\n throw new Error(\n `Invalid region '${config.region}'. Must be one of: ${Object.keys(REGION_URLS).join(', ')}`\n )\n }\n this.baseUrl = regionUrl\n } else {\n this.baseUrl = config.baseUrl || DEFAULT_BASE_URL\n }\n this.userId = config.userId\n this.maxRetries = config.maxRetries ?? 2\n this.retryDelay = config.retryDelay ?? 0.5\n\n if (this.useLocal && config.region) {\n console.warn(\n `region='${config.region}' has no effect in local mode. ` +\n 'Set an apiKey to use region-based routing.'\n )\n }\n }\n\n private get useLocal(): boolean {\n if (this.mode === 'local') return true\n return !this.apiKey\n }\n\n private getScanner(): PIIScannerType {\n if (!this._scanner) {\n const { PIIScanner } = require('./regex') as typeof import('./regex')\n this._scanner = new PIIScanner(this.locales ? { locales: this.locales } : undefined)\n }\n return this._scanner\n }\n\n private resolvePolicy(\n policy?: string,\n entities?: string[],\n scoreThreshold?: number\n ): { entities?: string[]; threshold?: number } {\n if (entities) {\n return { entities, threshold: scoreThreshold }\n }\n if (policy) {\n const policyDef = this.policies[policy]\n if (policyDef) {\n return {\n entities: policyDef.entities,\n threshold: scoreThreshold ?? policyDef.threshold,\n }\n } else {\n console.warn(`Unknown policy '${policy}' in local mode, detecting all entities`)\n }\n }\n return { threshold: scoreThreshold }\n }\n\n private retryWait(attempt: number, error?: APIError): number {\n if (error && error.statusCode === 429) {\n const body = error.responseBody as Record<string, unknown> | undefined\n if (body && typeof body.retry_after === 'number') {\n return body.retry_after * 1000\n }\n }\n const delay = this.retryDelay * 2 ** attempt * 1000\n const jitter = delay * 0.1 * Math.random()\n return delay + jitter\n }\n\n /**\n * Make an authenticated request to the API\n */\n private async request<T>(\n endpoint: string,\n method: string,\n body?: Record<string, unknown>\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.apiKey) {\n headers['X-API-Key'] = this.apiKey\n }\n\n if (this.userId) {\n headers['X-Blindfold-User-Id'] = this.userId\n }\n\n let lastError: Error = new NetworkError('Request failed')\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n // Handle authentication errors\n if (response.status === 401 || response.status === 403) {\n throw new AuthenticationError('Authentication failed. Please check your API key.')\n }\n\n // Handle other error responses\n if (!response.ok) {\n let errorMessage = `API request failed with status ${response.status}`\n let responseBody: unknown\n\n try {\n responseBody = await response.json()\n const errorData = responseBody as APIErrorResponse\n errorMessage = errorData.detail || errorData.message || errorMessage\n } catch {\n // If we can't parse the error response, use the status text\n errorMessage = `${errorMessage}: ${response.statusText}`\n }\n\n throw new APIError(errorMessage, response.status, responseBody)\n }\n\n return (await response.json()) as T\n } catch (error) {\n // Never retry auth errors\n if (error instanceof AuthenticationError) {\n throw error\n }\n\n // Retry retryable API errors\n if (error instanceof APIError) {\n if (RETRYABLE_STATUS_CODES.has(error.statusCode) && attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt, error))\n continue\n }\n throw error\n }\n\n // Retry network errors\n if (error instanceof NetworkError) {\n lastError = error\n if (attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt))\n continue\n }\n throw error\n }\n\n // Handle raw fetch errors (network failures)\n if (error instanceof TypeError && error.message.includes('fetch')) {\n lastError = new NetworkError(\n 'Network request failed. Please check your connection and the API URL.'\n )\n if (attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt))\n continue\n }\n throw lastError\n }\n\n // Non-retryable unknown errors\n throw new NetworkError(error instanceof Error ? error.message : 'Unknown error occurred')\n }\n }\n\n throw lastError\n }\n\n /**\n * Tokenize text by replacing sensitive information with tokens\n * @param text - Text to tokenize\n * @param config - Optional configuration\n * @returns Promise with tokenized text and mapping\n */\n async tokenize(text: string, config?: TokenizeConfig): Promise<TokenizeResponse> {\n if (this.useLocal) {\n return this.tokenizeLocal(text, config)\n }\n return this.request<TokenizeResponse>('/tokenize', 'POST', {\n text,\n ...config,\n })\n }\n\n private tokenizeLocal(text: string, config?: TokenizeConfig): TokenizeResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.tokenize(text, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, mapping: result.mapping, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Detect PII in text without modifying it\n *\n * Returns only the detected entities with their types, positions,\n * and confidence scores. The original text is not transformed.\n *\n * When no API key is set (or `mode: \"local\"`), detection runs locally\n * using the built-in regex scanner.\n *\n * @param text - Text to analyze for PII\n * @param config - Optional configuration (entities, score_threshold, policy)\n * @returns Promise with detected entities\n */\n async detect(text: string, config?: DetectConfig): Promise<DetectResponse> {\n if (this.useLocal) {\n return this.detectLocal(text, config)\n }\n return this.request<DetectResponse>('/detect', 'POST', {\n text,\n ...config,\n })\n }\n\n private detectLocal(text: string, config?: DetectConfig): DetectResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const matches = scanner.detect(text, resolved)\n\n const detected: DetectedEntity[] = []\n for (const m of matches) {\n if (threshold != null && m.score < threshold) continue\n detected.push({ type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score })\n }\n\n return { detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Detokenize text by replacing tokens with original values\n *\n * This method performs detokenization CLIENT-SIDE for better performance,\n * security, and to work offline. No API call is made.\n *\n * @param text - Tokenized text\n * @param mapping - Token mapping from tokenize response\n * @returns DetokenizeResponse with original text\n */\n detokenize(text: string, mapping: Record<string, string>): DetokenizeResponse {\n let result = text\n let replacements = 0\n\n // Sort tokens by length (longest first) to avoid partial replacements\n const sortedTokens = Object.keys(mapping).sort((a, b) => b.length - a.length)\n\n for (const token of sortedTokens) {\n const originalValue = mapping[token]\n const regex = new RegExp(token.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')\n const matches = result.match(regex)\n\n if (matches) {\n result = result.replace(regex, originalValue)\n replacements += matches.length\n }\n }\n\n return {\n text: result,\n replacements_made: replacements,\n }\n }\n\n /**\n * Redact (permanently remove) sensitive information from text\n *\n * WARNING: Redaction is irreversible - original data cannot be restored!\n *\n * When no API key is set (or `mode: \"local\"`), redaction runs locally\n * using the built-in regex scanner, replacing PII with `<Entity Name>` placeholders.\n *\n * @param text - Text to redact\n * @param config - Optional configuration (masking_char, entities)\n * @returns Promise with redacted text and detected entities\n */\n async redact(text: string, config?: RedactConfig): Promise<RedactResponse> {\n if (this.useLocal) {\n return this.redactLocal(text, config)\n }\n return this.request<RedactResponse>('/redact', 'POST', {\n text,\n ...config,\n })\n }\n\n private redactLocal(text: string, config?: RedactConfig): RedactResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const [redactedText, matches] = scanner.redact(text, resolved)\n\n const detected: DetectedEntity[] = []\n for (const m of matches) {\n if (threshold != null && m.score < threshold) continue\n detected.push({ type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score })\n }\n\n return { text: redactedText, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Mask (partially hide) sensitive information from text\n *\n * @param text - Text to mask\n * @param config - Optional configuration (chars_to_show, from_end, masking_char, entities)\n * @returns Promise with masked text and detected entities\n */\n async mask(text: string, config?: MaskConfig): Promise<MaskResponse> {\n if (this.useLocal) {\n return this.maskLocal(text, config)\n }\n return this.request<MaskResponse>('/mask', 'POST', {\n text,\n ...config,\n })\n }\n\n private maskLocal(text: string, config?: MaskConfig): MaskResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.mask(\n text,\n config?.chars_to_show ?? 3,\n config?.from_end ?? false,\n config?.masking_char ?? '*',\n resolved\n )\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Synthesize (replace real data with synthetic fake data)\n *\n * When no API key is set (or `mode: \"local\"`), synthesis runs locally\n * using format-preserving random generation.\n *\n * @param text - Text to synthesize\n * @param config - Optional configuration (language, entities)\n * @returns Promise with synthetic text and detected entities\n */\n async synthesize(text: string, config?: SynthesizeConfig): Promise<SynthesizeResponse> {\n if (this.useLocal) {\n return this.synthesizeLocal(text, config)\n }\n return this.request<SynthesizeResponse>('/synthesize', 'POST', {\n text,\n ...config,\n })\n }\n\n private synthesizeLocal(text: string, config?: SynthesizeConfig): SynthesizeResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.synthesize(text, undefined, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Hash (replace with deterministic hash values)\n *\n * @param text - Text to hash\n * @param config - Optional configuration (hash_type, hash_prefix, hash_length, entities)\n * @returns Promise with hashed text and detected entities\n */\n async hash(text: string, config?: HashConfig): Promise<HashResponse> {\n if (this.useLocal) {\n return this.hashLocal(text, config)\n }\n return this.request<HashResponse>('/hash', 'POST', {\n text,\n ...config,\n })\n }\n\n private hashLocal(text: string, config?: HashConfig): HashResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.hash(\n text,\n config?.hash_type ?? 'sha256',\n config?.hash_prefix ?? 'HASH_',\n config?.hash_length ?? 16,\n resolved\n )\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Encrypt (reversibly protect) sensitive data in text using AES encryption\n *\n * @param text - Text to encrypt\n * @param config - Optional configuration (encryption_key, entities)\n * @returns Promise with encrypted text and detected entities\n */\n async encrypt(text: string, config?: EncryptConfig): Promise<EncryptResponse> {\n if (this.useLocal) {\n return this.encryptLocal(text, config)\n }\n return this.request<EncryptResponse>('/encrypt', 'POST', {\n text,\n ...config,\n })\n }\n\n private encryptLocal(text: string, config?: EncryptConfig): EncryptResponse {\n if (!config?.encryption_key) {\n throw new Error('encryption_key is required for local encryption mode')\n }\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.encrypt(text, config.encryption_key, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n // ===== Batch methods =====\n\n /**\n * Tokenize multiple texts in a single request\n * @param texts - Array of texts to tokenize (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async tokenizeBatch(texts: string[], config?: TokenizeConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/tokenize', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Detect PII in multiple texts in a single request\n * @param texts - Array of texts to analyze (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async detectBatch(texts: string[], config?: DetectConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/detect', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Redact PII from multiple texts in a single request\n * @param texts - Array of texts to redact (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async redactBatch(texts: string[], config?: RedactConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/redact', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Mask PII in multiple texts in a single request\n * @param texts - Array of texts to mask (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async maskBatch(texts: string[], config?: MaskConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/mask', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Synthesize multiple texts in a single request\n * @param texts - Array of texts to synthesize (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async synthesizeBatch(texts: string[], config?: SynthesizeConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/synthesize', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Hash PII in multiple texts in a single request\n * @param texts - Array of texts to hash (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async hashBatch(texts: string[], config?: HashConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/hash', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Encrypt PII in multiple texts in a single request\n * @param texts - Array of texts to encrypt (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async encryptBatch(texts: string[], config?: EncryptConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/encrypt', 'POST', {\n texts,\n ...config,\n })\n }\n}\n","export { Blindfold } from './client'\nexport type {\n BlindfoldConfig,\n DetectConfig,\n DetectResponse,\n TokenizeConfig,\n TokenizeResponse,\n DetokenizeResponse,\n DetectedEntity,\n BatchResponse,\n APIErrorResponse,\n} from './types'\nexport { BlindfoldError, AuthenticationError, APIError, NetworkError } from './errors'\nexport { PIIScanner, EntityType } from './regex'\nexport type { PIIMatch } from './regex'\n"],"mappings":"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"}
|