@secured-ai/react 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 ADDED
@@ -0,0 +1,55 @@
1
+ # @secured-ai/react
2
+
3
+ React bindings for `@secured-ai/core`. This package provides a provider and hooks for detecting sensitive data, obfuscating user input, restoring protected responses, reviewing detected text, and working with privacy sessions from React components.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @secured-ai/react @secured-ai/core
9
+ ```
10
+
11
+ `react` is a peer dependency and should already be installed by your application.
12
+
13
+ ## What it provides
14
+
15
+ - `SecuredProvider` for sharing a configured `PrivacyClient` across a React app.
16
+ - Hooks including `usePrivacyClient`, `useDetect`, `useScan`, `useObfuscate`, `useRestore`, `useSessions`, `useTextReview`, `useVault`, `useFileDetect`, and `useFileObfuscate`.
17
+ - Re-exported core types and utilities so most React consumers can import from `@secured-ai/react`.
18
+
19
+ ## Basic usage
20
+
21
+ ```tsx
22
+ import { PrivacyClient } from '@secured-ai/core'
23
+ import { SecuredProvider, useObfuscate } from '@secured-ai/react'
24
+
25
+ const client = new PrivacyClient()
26
+
27
+ function Composer() {
28
+ const { obfuscate, restore, lastResult, isPending } = useObfuscate()
29
+
30
+ async function sendMessage(text: string) {
31
+ const result = await obfuscate(text)
32
+ // Send result.processed to your AI provider.
33
+ // Use restore(aiResponse, result.sessionId) before displaying the response.
34
+ return result
35
+ }
36
+
37
+ return null
38
+ }
39
+
40
+ export function App() {
41
+ return (
42
+ <SecuredProvider client={client}>
43
+ <Composer />
44
+ </SecuredProvider>
45
+ )
46
+ }
47
+ ```
48
+
49
+ ## Publishing contents
50
+
51
+ The npm package is built from `dist` only. Source files, tests, coverage output, and source maps are not included in the published package.
52
+
53
+ ## Docs
54
+
55
+ See the developer documentation at https://dev-docs.securedai.com/docs.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime'),core=require('@secured-ai/core');var f=react.createContext(null);var j={isReady:false,isFullyReady:false,initProgress:{overall:0,engines:{}},readyEngines:[],error:null};function U(){let e={...j},t=new Set;function r(){for(let s of t)s();}return {subscribe(s){return t.add(s),()=>t.delete(s)},getSnapshot(){return e},onProgress(s){e={...e,initProgress:{overall:s.percent,engines:{...e.initProgress.engines,[s.engine]:s.percent}}},r();},onReady(s,o,p){e={...e,isReady:s,isFullyReady:o,readyEngines:p,error:null},r();},onError(s){e={...e,error:s},r();}}}function K({client:e,autoInitialize:t=true,children:r}){let s=react.useMemo(()=>U(),[e]),o=react.useRef(false),p=react.useRef(0),a=react.useRef(new Set),c=react.useCallback(()=>{p.current+=1;for(let l of a.current)l();},[]),S=react.useCallback(l=>(a.current.add(l),()=>a.current.delete(l)),[]),g=react.useCallback(()=>p.current,[]);react.useEffect(()=>{typeof window>"u"||t&&(o.current||(o.current=true,e.initialize().then(()=>{s.onReady(e.isReady,e.isFullyReady,e.readyEngines);}).catch(l=>{s.onError(l instanceof Error?l:new Error(String(l)));})));},[e,t,s]);let d=react.useMemo(()=>({client:e,store:s,notifySessionChange:c,subscribeToSessions:S,getSessionVersion:g}),[e,s,c,S,g]);return jsxRuntime.jsx(f.Provider,{value:d,children:r})}function Z(){let e=react.useContext(f);if(!e)throw new Error("[usePrivacyClient] must be used inside a <SecuredProvider>. Wrap your component tree with <SecuredProvider client={client}>.");return e.client}var te={isEnabled:false,isInitialized:false,activeThreadId:null,cacheNamespace:null,hasGlobalSnapshot:false,isGlobalSnapshotStale:false};function re(){let e=react.useContext(f);if(!e)throw new Error("[useVault] must be used inside a <SecuredProvider>. Wrap your component tree with <SecuredProvider client={client}>.");let{vault:t}=e.client,[r,s]=react.useState(t.getStatus?.()??te),o=react.useCallback(()=>{s(t.getStatus());},[t]),p=react.useCallback(async c=>{await t.setThreadContext(c),o();},[o,t]),a=react.useCallback(async()=>{await t.refreshContext(),o();},[o,t]);return {vault:t,status:r,getGlobalSnapshot:()=>t.getGlobalSnapshot(),getThreadSnapshot:c=>t.getThreadSnapshot(c),getRuntimeThreadEntries:c=>t.getRuntimeThreadEntries(c),getPendingEntries:()=>t.getPendingEntries(),setThreadContext:p,refreshContext:a}}var k={isReady:false,isFullyReady:false,initProgress:{overall:0,engines:{}},readyEngines:[],error:null};function oe(){let e=react.useContext(f);return react.useSyncExternalStore(e?e.store.subscribe:()=>()=>{},e?e.store.getSnapshot:()=>k,()=>k)}function le(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(false),[p,a]=react.useState(null),c=react.useCallback(async g=>{if(!e)return Promise.resolve({entities:[],sensitiveEntities:[],processingTime:0,sourceStats:{"regex-patterns":0,"compromise-nlp":0,"compromise-regex":0,huggingface:0,gliner:0,custom:0},isClean:true});o(true),a(null);try{let d=await e.client.detect(g);return r(d),d}catch(d){let l=d instanceof Error?d:new Error(String(d));return a(l),r(null),{entities:[],sensitiveEntities:[],processingTime:0,sourceStats:{"regex-patterns":0,"compromise-nlp":0,"compromise-regex":0,huggingface:0,gliner:0,custom:0},isClean:true}}finally{o(false);}},[e]),S=react.useCallback(()=>{r(null),a(null),o(false);},[]);return {detect:c,data:t,isPending:s,error:p,reset:S}}function fe(e={}){let{debounce:t=300,enabled:r=true}=e,s=react.useContext(f),[o,p]=react.useState([]),[a,c]=react.useState([]),[S,g]=react.useState(null),[d,l]=react.useState(false),[m,y]=react.useState(false),[n,i]=react.useState(null),u=react.useRef(null),R=react.useCallback(()=>{u.current!==null&&(clearTimeout(u.current),u.current=null);},[]);react.useEffect(()=>()=>R(),[R]);let L=react.useCallback(W=>{!r||!s||(R(),y(true),u.current=setTimeout(async()=>{u.current=null,y(false),l(true),i(null);try{let x=await s.client.detect(W);g(x),p(x.entities),c(x.sensitiveEntities);}catch(x){i(x instanceof Error?x:new Error(String(x))),g(null),p([]),c([]);}finally{l(false);}},t));},[s,r,t,R]),Y=react.useCallback(()=>{R(),g(null),p([]),c([]),l(false),y(false),i(null);},[R]);return {scan:L,data:S,entities:o,sensitiveEntities:a,isScanning:d,isPending:m,error:n,reset:Y}}var O={restored:"",mappingsApplied:0,success:false};function pe(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(null),[p,a]=react.useState(false),[c,S]=react.useState(null),g=react.useCallback(async(m,y)=>{if(!e)return {processed:m,sessionId:"",hasSensitiveData:false};a(true),S(null);try{let n=await e.client.obfuscate(m,y);return r(n),o(n.sessionId),e.notifySessionChange(),n}catch(n){let i=n instanceof Error?n:new Error(String(n));return S(i),r(null),{processed:m,sessionId:"",hasSensitiveData:false}}finally{a(false);}},[e]),d=react.useCallback(async(m,y)=>{if(!e)return O;let n=y??s;if(!n)return O;a(true),S(null);try{return await e.client.restore(m,n)}catch(i){let u=i instanceof Error?i:new Error(String(i));return S(u),O}finally{a(false);}},[e,s]),l=react.useCallback(()=>{r(null),o(null),S(null),a(false);},[]);return {obfuscate:g,restore:d,lastResult:t,sessionId:s,isPending:p,error:c,reset:l}}function ge(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(null),p=react.useCallback((n,i,u)=>{if(!e)return;let R=e.client.createTextReview(n,i,u);r(R),o(null);},[e]),a=react.useCallback((n,i)=>{e&&r(u=>u&&e.client.setTextReviewItemMode(u,n,i));},[e]),c=react.useCallback((n,i)=>{e&&r(u=>u&&e.client.setTextReviewReplacement(u,n,i));},[e]),S=react.useCallback(n=>{e&&r(i=>i&&e.client.removeTextReviewItem(i,n));},[e]),g=react.useCallback(n=>{e&&r(i=>i&&e.client.restoreTextReviewItem(i,n));},[e]),d=react.useCallback((n,i)=>{e&&r(u=>u&&e.client.addTextReviewEntity(u,n,i));},[e]),l=react.useCallback(async()=>{if(!e||!t)return null;let n=await e.client.finalizeTextReview(t);return o(n),e.notifySessionChange(),n},[e,t]),m=react.useCallback(n=>{r(n),o(null);},[]),y=react.useCallback(()=>{r(null),o(null);},[]);return {review:t,items:t?.items??[],preview:e&&t?e.client.previewTextReview(t):null,lastFinalized:s,initialize:p,setReplacement:c,setMode:a,removeItem:S,restoreItem:g,addEntity:d,finalize:l,hydrate:m,reset:y}}function ye(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(false),[p,a]=react.useState(null),c=react.useCallback(async(g,d)=>{if(!e)return {restored:g,mappingsApplied:0,success:false};o(true),a(null);try{let l=await e.client.restore(g,d);return r(l),l}catch(l){let m=l instanceof Error?l:new Error(String(l));return a(m),r(null),{restored:g,mappingsApplied:0,success:false}}finally{o(false);}},[e]),S=react.useCallback(()=>{r(null),a(null),o(false);},[]);return {restore:c,data:t,isPending:s,error:p,reset:S}}var xe=[];function Pe(){let e=react.useContext(f);react.useSyncExternalStore(e?e.subscribeToSessions:()=>()=>{},e?e.getSessionVersion:()=>0,()=>0);let t=e?e.client.getSessions():xe,r=react.useCallback(o=>{e&&(e.client.clearSession(o),e.notifySessionChange());},[e]),s=react.useCallback(()=>{e&&(e.client.clearAllSessions(),e.notifySessionChange());},[e]);return {sessions:t,clearSession:r,clearAll:s}}function he(e,t,r){t.current=t.current.filter(s=>s.token!==e);for(let[s,o]of r.current.entries())o.token===e&&r.current.delete(s);}function Ce(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(false),[p,a]=react.useState(null),[c,S]=react.useState(null),g=react.useRef([]),d=react.useRef(new Map);react.useEffect(()=>{let y=e?.client;if(y?.subscribeToJobProgress)return y.subscribeToJobProgress(n=>{if(n.progress.operation!=="detectInFile")return;let i=d.current.get(n.requestId);!i&&n.progress.phase==="starting"&&(i=g.current.shift(),i&&(i.requestId=n.requestId,d.current.set(n.requestId,i))),i&&(S(n.progress),i.onProgress?.(n.progress),n.progress.phase==="done"&&d.current.delete(n.requestId));})},[e]);let l=react.useCallback(async(y,n)=>{if(!e)return {entities:[],text:"",processingTime:0,fileType:"txt"};let i=Symbol("detectInFile");g.current.push({token:i,onProgress:n?.onProgress}),o(true),a(null),S(null);try{let u=await e.client.detectInFile(y);return r(u),u}catch(u){let R=u instanceof Error?u:new Error(String(u));return a(R),r(null),{entities:[],text:"",processingTime:0,fileType:"txt"}}finally{he(i,g,d),o(false);}},[e]),m=react.useCallback(()=>{r(null),a(null),o(false),S(null);},[]);return {detectInFile:l,data:t,isPending:s,error:p,progress:c,reset:m}}function we(){let e=react.useContext(f),[t,r]=react.useState(null),[s,o]=react.useState(false),[p,a]=react.useState(null),c=react.useCallback(async(g,d)=>{if(!e)return new Blob;o(true),a(null);try{let l=await e.client.obfuscateFile(g,d);return r(l),l}catch(l){let m=l instanceof Error?l:new Error(String(l));return a(m),r(null),new Blob}finally{o(false);}},[e]),S=react.useCallback(()=>{r(null),a(null),o(false);},[]);return {obfuscateFile:c,data:t,isPending:s,error:p,reset:S}}var mt="0.1.0";Object.defineProperty(exports,"DefaultVaultRepository",{enumerable:true,get:function(){return core.DefaultVaultRepository}});Object.defineProperty(exports,"ENTITY_TYPES",{enumerable:true,get:function(){return core.ENTITY_TYPES}});Object.defineProperty(exports,"IndexedDbVaultCache",{enumerable:true,get:function(){return core.IndexedDbVaultCache}});Object.defineProperty(exports,"LocalStorageVaultCache",{enumerable:true,get:function(){return core.LocalStorageVaultCache}});Object.defineProperty(exports,"MemoryVaultCache",{enumerable:true,get:function(){return core.MemoryVaultCache}});Object.defineProperty(exports,"PrivacyClient",{enumerable:true,get:function(){return core.PrivacyClient}});Object.defineProperty(exports,"SessionStorageVaultCache",{enumerable:true,get:function(){return core.SessionStorageVaultCache}});Object.defineProperty(exports,"StringMatcher",{enumerable:true,get:function(){return core.StringMatcher}});Object.defineProperty(exports,"VaultManager",{enumerable:true,get:function(){return core.VaultManager}});Object.defineProperty(exports,"WebCryptoVaultCrypto",{enumerable:true,get:function(){return core.WebCryptoVaultCrypto}});Object.defineProperty(exports,"canonicalizeVaultEntityType",{enumerable:true,get:function(){return core.canonicalizeVaultEntityType}});Object.defineProperty(exports,"defaultStringMatcher",{enumerable:true,get:function(){return core.defaultStringMatcher}});Object.defineProperty(exports,"deobfuscateWithVaultEntries",{enumerable:true,get:function(){return core.deobfuscateWithVaultEntries}});Object.defineProperty(exports,"lenientStringMatcher",{enumerable:true,get:function(){return core.lenientStringMatcher}});Object.defineProperty(exports,"normalizeVaultEntryKey",{enumerable:true,get:function(){return core.normalizeVaultEntryKey}});Object.defineProperty(exports,"strictStringMatcher",{enumerable:true,get:function(){return core.strictStringMatcher}});Object.defineProperty(exports,"vaultEntityTypesMatch",{enumerable:true,get:function(){return core.vaultEntityTypesMatch}});Object.defineProperty(exports,"vaultEntryMatchesEntity",{enumerable:true,get:function(){return core.vaultEntryMatchesEntity}});exports.SecuredProvider=K;exports.useDetect=le;exports.useFileDetect=Ce;exports.useFileObfuscate=we;exports.useObfuscate=pe;exports.usePrivacyClient=Z;exports.usePrivacyStatus=oe;exports.useRestore=ye;exports.useScan=fe;exports.useSessions=Pe;exports.useTextReview=ge;exports.useVault=re;exports.version=mt;
@@ -0,0 +1,179 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { PrivacyClient, VaultManager, VaultStatus, VaultSnapshotState, VaultEntry, InitProgress, PrivacyScanResult, ExtendedSensitiveEntity, SensitiveEntity, ObfuscationOptions, ObfuscationResult, RestorationResult, TextReview, FinalizedTextReview, TextReviewOptions, ObfuscationMode, PrivacySession, PrivacyJobProgress, FileProcessingResult } from '@secured-ai/core';
4
+ export { CustomPattern, DefaultVaultRepository, DefaultVaultRepositoryOptions, DetectionResult, DetectionSource, ENTITY_TYPES, EncryptedVaultPayload, EncryptedVaultSnapshot, EngineConfig, EntityType, ExtendedSensitiveEntity, FileProcessingResult, FinalizedTextReview, IndexedDbVaultCache, InitProgress, InitProgressEvent, LocalStorageVaultCache, MatchConfig, MatchResult, MemoryVaultCache, ObfuscationMode, ObfuscationOptions, ObfuscationResult, PrivacyClient, PrivacyClientConfig, PrivacyJobProgress, PrivacyJobProgressEvent, PrivacyScanResult, PrivacySession, PrivacyVaultConfig, ReplacementPoolConfig, ReplacementSource, RestorationResult, RuntimeValue, SensitiveEntity, SerializedPrivacySession, SessionMapping, SessionStorageVaultCache, StringMatcher, SupportedFileType, TextReview, TextReviewItem, TextReviewOptions, VaultCache, VaultCrypto, VaultEntry, VaultManager, VaultRepository, VaultRepositoryRequestContext, VaultResolveOptions, VaultSnapshot, VaultSnapshotState, VaultStatus, WebCryptoVaultCrypto, canonicalizeVaultEntityType, defaultStringMatcher, deobfuscateWithVaultEntries, lenientStringMatcher, normalizeVaultEntryKey, strictStringMatcher, vaultEntityTypesMatch, vaultEntryMatchesEntity } from '@secured-ai/core';
5
+
6
+ interface SecuredProviderProps {
7
+ client: PrivacyClient;
8
+ autoInitialize?: boolean;
9
+ children: React.ReactNode;
10
+ }
11
+ declare function SecuredProvider({ client, autoInitialize, children, }: SecuredProviderProps): react_jsx_runtime.JSX.Element;
12
+
13
+ /**
14
+ * Returns the raw PrivacyClient instance provided to the nearest SecuredProvider.
15
+ * Use this as an escape hatch for advanced operations (addPattern, removePattern, etc.).
16
+ *
17
+ * @throws if called outside of a SecuredProvider
18
+ */
19
+ declare function usePrivacyClient(): PrivacyClient;
20
+
21
+ interface UseVaultReturn {
22
+ vault: VaultManager;
23
+ status: VaultStatus;
24
+ getGlobalSnapshot: () => VaultSnapshotState | null;
25
+ getThreadSnapshot: (threadId: string) => VaultSnapshotState | null;
26
+ getRuntimeThreadEntries: (threadId: string) => VaultEntry[];
27
+ getPendingEntries: () => VaultEntry[];
28
+ setThreadContext: (threadId: string | null) => Promise<void>;
29
+ refreshContext: () => Promise<void>;
30
+ }
31
+ declare function useVault(): UseVaultReturn;
32
+
33
+ interface StatusSnapshot {
34
+ isReady: boolean;
35
+ isFullyReady: boolean;
36
+ initProgress: InitProgress;
37
+ readyEngines: string[];
38
+ error: Error | null;
39
+ }
40
+
41
+ /**
42
+ * Returns the current initialization status of the PrivacyClient.
43
+ * Updates are event-driven via the statusStore — no polling.
44
+ */
45
+ declare function usePrivacyStatus(): StatusSnapshot;
46
+
47
+ interface UseDetectReturn {
48
+ detect: (text: string) => Promise<PrivacyScanResult>;
49
+ data: PrivacyScanResult | null;
50
+ isPending: boolean;
51
+ error: Error | null;
52
+ reset: () => void;
53
+ }
54
+ /**
55
+ * Imperative hook for on-demand PII detection.
56
+ * Call detect(text) whenever you need a scan — e.g. on form submit.
57
+ * Errors are captured as state; the returned promise never rejects.
58
+ */
59
+ declare function useDetect(): UseDetectReturn;
60
+
61
+ interface UseScanOptions {
62
+ debounce?: number;
63
+ enabled?: boolean;
64
+ }
65
+ interface UseScanReturn {
66
+ scan: (text: string) => void;
67
+ data: PrivacyScanResult | null;
68
+ entities: ExtendedSensitiveEntity[];
69
+ sensitiveEntities: SensitiveEntity[];
70
+ isScanning: boolean;
71
+ isPending: boolean;
72
+ error: Error | null;
73
+ reset: () => void;
74
+ }
75
+ /**
76
+ * Debounced-imperative scanning hook.
77
+ * Call scan(text) in your onChange handler — the hook debounces internally.
78
+ * isPending = debounce window ticking. isScanning = detect() call in flight.
79
+ */
80
+ declare function useScan(options?: UseScanOptions): UseScanReturn;
81
+
82
+ interface UseObfuscateReturn {
83
+ obfuscate: (text: string, options?: ObfuscationOptions) => Promise<ObfuscationResult>;
84
+ restore: (processedText: string, sessionId?: string) => Promise<RestorationResult>;
85
+ lastResult: ObfuscationResult | null;
86
+ sessionId: string | null;
87
+ isPending: boolean;
88
+ error: Error | null;
89
+ reset: () => void;
90
+ }
91
+ /**
92
+ * Handles the full obfuscate→restore lifecycle.
93
+ * Automatically tracks the sessionId from the last obfuscation (last-wins).
94
+ * restore() uses the tracked sessionId by default; pass an explicit id to override.
95
+ */
96
+ declare function useObfuscate(): UseObfuscateReturn;
97
+
98
+ interface UseTextReviewReturn {
99
+ review: TextReview | null;
100
+ items: TextReview['items'];
101
+ preview: Omit<FinalizedTextReview, 'sessionId' | 'session' | 'serializedSession'> | null;
102
+ lastFinalized: FinalizedTextReview | null;
103
+ initialize: (text: string, entities: SensitiveEntity[], optionsOrResolveReplacement?: TextReviewOptions | ((entity: SensitiveEntity) => string)) => void;
104
+ setReplacement: (itemId: string, replacement: string) => void;
105
+ setMode: (itemId: string, mode: ObfuscationMode) => void;
106
+ removeItem: (itemId: string) => void;
107
+ restoreItem: (itemId: string) => void;
108
+ addEntity: (entity: SensitiveEntity, replacement: string) => void;
109
+ finalize: () => Promise<FinalizedTextReview | null>;
110
+ hydrate: (review: TextReview | null) => void;
111
+ reset: () => void;
112
+ }
113
+ declare function useTextReview(): UseTextReviewReturn;
114
+
115
+ interface UseRestoreReturn {
116
+ restore: (text: string, sessionId: string) => Promise<RestorationResult>;
117
+ data: RestorationResult | null;
118
+ isPending: boolean;
119
+ error: Error | null;
120
+ reset: () => void;
121
+ }
122
+ /**
123
+ * Standalone restoration hook for cross-component use.
124
+ * Unlike useObfuscate().restore(), this always requires an explicit sessionId —
125
+ * useful when an AI response arrives in a different component from where
126
+ * obfuscation happened and sessionId is passed via props/state.
127
+ */
128
+ declare function useRestore(): UseRestoreReturn;
129
+
130
+ interface UseSessionsReturn {
131
+ sessions: PrivacySession[];
132
+ clearSession: (id: string) => void;
133
+ clearAll: () => void;
134
+ }
135
+ /**
136
+ * Reactively lists all active PrivacySessions held by the client.
137
+ * Updates whenever obfuscate() creates a session or clear* mutates the list.
138
+ * Reactivity is driven by a version counter in SecuredProvider — no polling.
139
+ */
140
+ declare function useSessions(): UseSessionsReturn;
141
+
142
+ type FileDetectProgress = Extract<PrivacyJobProgress, {
143
+ operation: 'detectInFile';
144
+ }>;
145
+ interface FileDetectOptions {
146
+ onProgress?: (progress: FileDetectProgress) => void;
147
+ }
148
+ interface UseFileDetectReturn {
149
+ detectInFile: (file: File, options?: FileDetectOptions) => Promise<FileProcessingResult>;
150
+ data: FileProcessingResult | null;
151
+ isPending: boolean;
152
+ error: Error | null;
153
+ progress: FileDetectProgress | null;
154
+ reset: () => void;
155
+ }
156
+ /**
157
+ * Detects PII in uploaded files (PDF, DOCX, XLSX, CSV, TXT, JSON, image).
158
+ * Returns FileProcessingResult which includes fileType metadata
159
+ * unlike the text-based PrivacyScanResult.
160
+ */
161
+ declare function useFileDetect(): UseFileDetectReturn;
162
+
163
+ interface UseFileObfuscateReturn {
164
+ obfuscateFile: (file: File, entities?: SensitiveEntity[]) => Promise<Blob>;
165
+ data: Blob | null;
166
+ isPending: boolean;
167
+ error: Error | null;
168
+ reset: () => void;
169
+ }
170
+ /**
171
+ * Produces a redacted copy of an uploaded file as a Blob.
172
+ * Pass optional `entities` from a preview/review UI to skip re-detection.
173
+ * The returned Blob can be passed directly to a download link or re-upload.
174
+ */
175
+ declare function useFileObfuscate(): UseFileObfuscateReturn;
176
+
177
+ declare const version = "0.1.0";
178
+
179
+ export { SecuredProvider, type SecuredProviderProps, type UseDetectReturn, type UseFileDetectReturn, type UseFileObfuscateReturn, type UseObfuscateReturn, type UseRestoreReturn, type UseScanOptions, type UseScanReturn, type UseSessionsReturn, type UseTextReviewReturn, type UseVaultReturn, useDetect, useFileDetect, useFileObfuscate, useObfuscate, usePrivacyClient, usePrivacyStatus, useRestore, useScan, useSessions, useTextReview, useVault, version };
@@ -0,0 +1,179 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { PrivacyClient, VaultManager, VaultStatus, VaultSnapshotState, VaultEntry, InitProgress, PrivacyScanResult, ExtendedSensitiveEntity, SensitiveEntity, ObfuscationOptions, ObfuscationResult, RestorationResult, TextReview, FinalizedTextReview, TextReviewOptions, ObfuscationMode, PrivacySession, PrivacyJobProgress, FileProcessingResult } from '@secured-ai/core';
4
+ export { CustomPattern, DefaultVaultRepository, DefaultVaultRepositoryOptions, DetectionResult, DetectionSource, ENTITY_TYPES, EncryptedVaultPayload, EncryptedVaultSnapshot, EngineConfig, EntityType, ExtendedSensitiveEntity, FileProcessingResult, FinalizedTextReview, IndexedDbVaultCache, InitProgress, InitProgressEvent, LocalStorageVaultCache, MatchConfig, MatchResult, MemoryVaultCache, ObfuscationMode, ObfuscationOptions, ObfuscationResult, PrivacyClient, PrivacyClientConfig, PrivacyJobProgress, PrivacyJobProgressEvent, PrivacyScanResult, PrivacySession, PrivacyVaultConfig, ReplacementPoolConfig, ReplacementSource, RestorationResult, RuntimeValue, SensitiveEntity, SerializedPrivacySession, SessionMapping, SessionStorageVaultCache, StringMatcher, SupportedFileType, TextReview, TextReviewItem, TextReviewOptions, VaultCache, VaultCrypto, VaultEntry, VaultManager, VaultRepository, VaultRepositoryRequestContext, VaultResolveOptions, VaultSnapshot, VaultSnapshotState, VaultStatus, WebCryptoVaultCrypto, canonicalizeVaultEntityType, defaultStringMatcher, deobfuscateWithVaultEntries, lenientStringMatcher, normalizeVaultEntryKey, strictStringMatcher, vaultEntityTypesMatch, vaultEntryMatchesEntity } from '@secured-ai/core';
5
+
6
+ interface SecuredProviderProps {
7
+ client: PrivacyClient;
8
+ autoInitialize?: boolean;
9
+ children: React.ReactNode;
10
+ }
11
+ declare function SecuredProvider({ client, autoInitialize, children, }: SecuredProviderProps): react_jsx_runtime.JSX.Element;
12
+
13
+ /**
14
+ * Returns the raw PrivacyClient instance provided to the nearest SecuredProvider.
15
+ * Use this as an escape hatch for advanced operations (addPattern, removePattern, etc.).
16
+ *
17
+ * @throws if called outside of a SecuredProvider
18
+ */
19
+ declare function usePrivacyClient(): PrivacyClient;
20
+
21
+ interface UseVaultReturn {
22
+ vault: VaultManager;
23
+ status: VaultStatus;
24
+ getGlobalSnapshot: () => VaultSnapshotState | null;
25
+ getThreadSnapshot: (threadId: string) => VaultSnapshotState | null;
26
+ getRuntimeThreadEntries: (threadId: string) => VaultEntry[];
27
+ getPendingEntries: () => VaultEntry[];
28
+ setThreadContext: (threadId: string | null) => Promise<void>;
29
+ refreshContext: () => Promise<void>;
30
+ }
31
+ declare function useVault(): UseVaultReturn;
32
+
33
+ interface StatusSnapshot {
34
+ isReady: boolean;
35
+ isFullyReady: boolean;
36
+ initProgress: InitProgress;
37
+ readyEngines: string[];
38
+ error: Error | null;
39
+ }
40
+
41
+ /**
42
+ * Returns the current initialization status of the PrivacyClient.
43
+ * Updates are event-driven via the statusStore — no polling.
44
+ */
45
+ declare function usePrivacyStatus(): StatusSnapshot;
46
+
47
+ interface UseDetectReturn {
48
+ detect: (text: string) => Promise<PrivacyScanResult>;
49
+ data: PrivacyScanResult | null;
50
+ isPending: boolean;
51
+ error: Error | null;
52
+ reset: () => void;
53
+ }
54
+ /**
55
+ * Imperative hook for on-demand PII detection.
56
+ * Call detect(text) whenever you need a scan — e.g. on form submit.
57
+ * Errors are captured as state; the returned promise never rejects.
58
+ */
59
+ declare function useDetect(): UseDetectReturn;
60
+
61
+ interface UseScanOptions {
62
+ debounce?: number;
63
+ enabled?: boolean;
64
+ }
65
+ interface UseScanReturn {
66
+ scan: (text: string) => void;
67
+ data: PrivacyScanResult | null;
68
+ entities: ExtendedSensitiveEntity[];
69
+ sensitiveEntities: SensitiveEntity[];
70
+ isScanning: boolean;
71
+ isPending: boolean;
72
+ error: Error | null;
73
+ reset: () => void;
74
+ }
75
+ /**
76
+ * Debounced-imperative scanning hook.
77
+ * Call scan(text) in your onChange handler — the hook debounces internally.
78
+ * isPending = debounce window ticking. isScanning = detect() call in flight.
79
+ */
80
+ declare function useScan(options?: UseScanOptions): UseScanReturn;
81
+
82
+ interface UseObfuscateReturn {
83
+ obfuscate: (text: string, options?: ObfuscationOptions) => Promise<ObfuscationResult>;
84
+ restore: (processedText: string, sessionId?: string) => Promise<RestorationResult>;
85
+ lastResult: ObfuscationResult | null;
86
+ sessionId: string | null;
87
+ isPending: boolean;
88
+ error: Error | null;
89
+ reset: () => void;
90
+ }
91
+ /**
92
+ * Handles the full obfuscate→restore lifecycle.
93
+ * Automatically tracks the sessionId from the last obfuscation (last-wins).
94
+ * restore() uses the tracked sessionId by default; pass an explicit id to override.
95
+ */
96
+ declare function useObfuscate(): UseObfuscateReturn;
97
+
98
+ interface UseTextReviewReturn {
99
+ review: TextReview | null;
100
+ items: TextReview['items'];
101
+ preview: Omit<FinalizedTextReview, 'sessionId' | 'session' | 'serializedSession'> | null;
102
+ lastFinalized: FinalizedTextReview | null;
103
+ initialize: (text: string, entities: SensitiveEntity[], optionsOrResolveReplacement?: TextReviewOptions | ((entity: SensitiveEntity) => string)) => void;
104
+ setReplacement: (itemId: string, replacement: string) => void;
105
+ setMode: (itemId: string, mode: ObfuscationMode) => void;
106
+ removeItem: (itemId: string) => void;
107
+ restoreItem: (itemId: string) => void;
108
+ addEntity: (entity: SensitiveEntity, replacement: string) => void;
109
+ finalize: () => Promise<FinalizedTextReview | null>;
110
+ hydrate: (review: TextReview | null) => void;
111
+ reset: () => void;
112
+ }
113
+ declare function useTextReview(): UseTextReviewReturn;
114
+
115
+ interface UseRestoreReturn {
116
+ restore: (text: string, sessionId: string) => Promise<RestorationResult>;
117
+ data: RestorationResult | null;
118
+ isPending: boolean;
119
+ error: Error | null;
120
+ reset: () => void;
121
+ }
122
+ /**
123
+ * Standalone restoration hook for cross-component use.
124
+ * Unlike useObfuscate().restore(), this always requires an explicit sessionId —
125
+ * useful when an AI response arrives in a different component from where
126
+ * obfuscation happened and sessionId is passed via props/state.
127
+ */
128
+ declare function useRestore(): UseRestoreReturn;
129
+
130
+ interface UseSessionsReturn {
131
+ sessions: PrivacySession[];
132
+ clearSession: (id: string) => void;
133
+ clearAll: () => void;
134
+ }
135
+ /**
136
+ * Reactively lists all active PrivacySessions held by the client.
137
+ * Updates whenever obfuscate() creates a session or clear* mutates the list.
138
+ * Reactivity is driven by a version counter in SecuredProvider — no polling.
139
+ */
140
+ declare function useSessions(): UseSessionsReturn;
141
+
142
+ type FileDetectProgress = Extract<PrivacyJobProgress, {
143
+ operation: 'detectInFile';
144
+ }>;
145
+ interface FileDetectOptions {
146
+ onProgress?: (progress: FileDetectProgress) => void;
147
+ }
148
+ interface UseFileDetectReturn {
149
+ detectInFile: (file: File, options?: FileDetectOptions) => Promise<FileProcessingResult>;
150
+ data: FileProcessingResult | null;
151
+ isPending: boolean;
152
+ error: Error | null;
153
+ progress: FileDetectProgress | null;
154
+ reset: () => void;
155
+ }
156
+ /**
157
+ * Detects PII in uploaded files (PDF, DOCX, XLSX, CSV, TXT, JSON, image).
158
+ * Returns FileProcessingResult which includes fileType metadata
159
+ * unlike the text-based PrivacyScanResult.
160
+ */
161
+ declare function useFileDetect(): UseFileDetectReturn;
162
+
163
+ interface UseFileObfuscateReturn {
164
+ obfuscateFile: (file: File, entities?: SensitiveEntity[]) => Promise<Blob>;
165
+ data: Blob | null;
166
+ isPending: boolean;
167
+ error: Error | null;
168
+ reset: () => void;
169
+ }
170
+ /**
171
+ * Produces a redacted copy of an uploaded file as a Blob.
172
+ * Pass optional `entities` from a preview/review UI to skip re-detection.
173
+ * The returned Blob can be passed directly to a download link or re-upload.
174
+ */
175
+ declare function useFileObfuscate(): UseFileObfuscateReturn;
176
+
177
+ declare const version = "0.1.0";
178
+
179
+ export { SecuredProvider, type SecuredProviderProps, type UseDetectReturn, type UseFileDetectReturn, type UseFileObfuscateReturn, type UseObfuscateReturn, type UseRestoreReturn, type UseScanOptions, type UseScanReturn, type UseSessionsReturn, type UseTextReviewReturn, type UseVaultReturn, useDetect, useFileDetect, useFileObfuscate, useObfuscate, usePrivacyClient, usePrivacyStatus, useRestore, useScan, useSessions, useTextReview, useVault, version };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import {createContext,useMemo,useRef,useCallback,useEffect,useContext,useState,useSyncExternalStore}from'react';import {jsx}from'react/jsx-runtime';export{DefaultVaultRepository,ENTITY_TYPES,IndexedDbVaultCache,LocalStorageVaultCache,MemoryVaultCache,PrivacyClient,SessionStorageVaultCache,StringMatcher,VaultManager,WebCryptoVaultCrypto,canonicalizeVaultEntityType,defaultStringMatcher,deobfuscateWithVaultEntries,lenientStringMatcher,normalizeVaultEntryKey,strictStringMatcher,vaultEntityTypesMatch,vaultEntryMatchesEntity}from'@secured-ai/core';var f=createContext(null);var j={isReady:false,isFullyReady:false,initProgress:{overall:0,engines:{}},readyEngines:[],error:null};function U(){let e={...j},t=new Set;function r(){for(let s of t)s();}return {subscribe(s){return t.add(s),()=>t.delete(s)},getSnapshot(){return e},onProgress(s){e={...e,initProgress:{overall:s.percent,engines:{...e.initProgress.engines,[s.engine]:s.percent}}},r();},onReady(s,o,p){e={...e,isReady:s,isFullyReady:o,readyEngines:p,error:null},r();},onError(s){e={...e,error:s},r();}}}function K({client:e,autoInitialize:t=true,children:r}){let s=useMemo(()=>U(),[e]),o=useRef(false),p=useRef(0),a=useRef(new Set),c=useCallback(()=>{p.current+=1;for(let l of a.current)l();},[]),S=useCallback(l=>(a.current.add(l),()=>a.current.delete(l)),[]),g=useCallback(()=>p.current,[]);useEffect(()=>{typeof window>"u"||t&&(o.current||(o.current=true,e.initialize().then(()=>{s.onReady(e.isReady,e.isFullyReady,e.readyEngines);}).catch(l=>{s.onError(l instanceof Error?l:new Error(String(l)));})));},[e,t,s]);let d=useMemo(()=>({client:e,store:s,notifySessionChange:c,subscribeToSessions:S,getSessionVersion:g}),[e,s,c,S,g]);return jsx(f.Provider,{value:d,children:r})}function Z(){let e=useContext(f);if(!e)throw new Error("[usePrivacyClient] must be used inside a <SecuredProvider>. Wrap your component tree with <SecuredProvider client={client}>.");return e.client}var te={isEnabled:false,isInitialized:false,activeThreadId:null,cacheNamespace:null,hasGlobalSnapshot:false,isGlobalSnapshotStale:false};function re(){let e=useContext(f);if(!e)throw new Error("[useVault] must be used inside a <SecuredProvider>. Wrap your component tree with <SecuredProvider client={client}>.");let{vault:t}=e.client,[r,s]=useState(t.getStatus?.()??te),o=useCallback(()=>{s(t.getStatus());},[t]),p=useCallback(async c=>{await t.setThreadContext(c),o();},[o,t]),a=useCallback(async()=>{await t.refreshContext(),o();},[o,t]);return {vault:t,status:r,getGlobalSnapshot:()=>t.getGlobalSnapshot(),getThreadSnapshot:c=>t.getThreadSnapshot(c),getRuntimeThreadEntries:c=>t.getRuntimeThreadEntries(c),getPendingEntries:()=>t.getPendingEntries(),setThreadContext:p,refreshContext:a}}var k={isReady:false,isFullyReady:false,initProgress:{overall:0,engines:{}},readyEngines:[],error:null};function oe(){let e=useContext(f);return useSyncExternalStore(e?e.store.subscribe:()=>()=>{},e?e.store.getSnapshot:()=>k,()=>k)}function le(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(false),[p,a]=useState(null),c=useCallback(async g=>{if(!e)return Promise.resolve({entities:[],sensitiveEntities:[],processingTime:0,sourceStats:{"regex-patterns":0,"compromise-nlp":0,"compromise-regex":0,huggingface:0,gliner:0,custom:0},isClean:true});o(true),a(null);try{let d=await e.client.detect(g);return r(d),d}catch(d){let l=d instanceof Error?d:new Error(String(d));return a(l),r(null),{entities:[],sensitiveEntities:[],processingTime:0,sourceStats:{"regex-patterns":0,"compromise-nlp":0,"compromise-regex":0,huggingface:0,gliner:0,custom:0},isClean:true}}finally{o(false);}},[e]),S=useCallback(()=>{r(null),a(null),o(false);},[]);return {detect:c,data:t,isPending:s,error:p,reset:S}}function fe(e={}){let{debounce:t=300,enabled:r=true}=e,s=useContext(f),[o,p]=useState([]),[a,c]=useState([]),[S,g]=useState(null),[d,l]=useState(false),[m,y]=useState(false),[n,i]=useState(null),u=useRef(null),R=useCallback(()=>{u.current!==null&&(clearTimeout(u.current),u.current=null);},[]);useEffect(()=>()=>R(),[R]);let L=useCallback(W=>{!r||!s||(R(),y(true),u.current=setTimeout(async()=>{u.current=null,y(false),l(true),i(null);try{let x=await s.client.detect(W);g(x),p(x.entities),c(x.sensitiveEntities);}catch(x){i(x instanceof Error?x:new Error(String(x))),g(null),p([]),c([]);}finally{l(false);}},t));},[s,r,t,R]),Y=useCallback(()=>{R(),g(null),p([]),c([]),l(false),y(false),i(null);},[R]);return {scan:L,data:S,entities:o,sensitiveEntities:a,isScanning:d,isPending:m,error:n,reset:Y}}var O={restored:"",mappingsApplied:0,success:false};function pe(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(null),[p,a]=useState(false),[c,S]=useState(null),g=useCallback(async(m,y)=>{if(!e)return {processed:m,sessionId:"",hasSensitiveData:false};a(true),S(null);try{let n=await e.client.obfuscate(m,y);return r(n),o(n.sessionId),e.notifySessionChange(),n}catch(n){let i=n instanceof Error?n:new Error(String(n));return S(i),r(null),{processed:m,sessionId:"",hasSensitiveData:false}}finally{a(false);}},[e]),d=useCallback(async(m,y)=>{if(!e)return O;let n=y??s;if(!n)return O;a(true),S(null);try{return await e.client.restore(m,n)}catch(i){let u=i instanceof Error?i:new Error(String(i));return S(u),O}finally{a(false);}},[e,s]),l=useCallback(()=>{r(null),o(null),S(null),a(false);},[]);return {obfuscate:g,restore:d,lastResult:t,sessionId:s,isPending:p,error:c,reset:l}}function ge(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(null),p=useCallback((n,i,u)=>{if(!e)return;let R=e.client.createTextReview(n,i,u);r(R),o(null);},[e]),a=useCallback((n,i)=>{e&&r(u=>u&&e.client.setTextReviewItemMode(u,n,i));},[e]),c=useCallback((n,i)=>{e&&r(u=>u&&e.client.setTextReviewReplacement(u,n,i));},[e]),S=useCallback(n=>{e&&r(i=>i&&e.client.removeTextReviewItem(i,n));},[e]),g=useCallback(n=>{e&&r(i=>i&&e.client.restoreTextReviewItem(i,n));},[e]),d=useCallback((n,i)=>{e&&r(u=>u&&e.client.addTextReviewEntity(u,n,i));},[e]),l=useCallback(async()=>{if(!e||!t)return null;let n=await e.client.finalizeTextReview(t);return o(n),e.notifySessionChange(),n},[e,t]),m=useCallback(n=>{r(n),o(null);},[]),y=useCallback(()=>{r(null),o(null);},[]);return {review:t,items:t?.items??[],preview:e&&t?e.client.previewTextReview(t):null,lastFinalized:s,initialize:p,setReplacement:c,setMode:a,removeItem:S,restoreItem:g,addEntity:d,finalize:l,hydrate:m,reset:y}}function ye(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(false),[p,a]=useState(null),c=useCallback(async(g,d)=>{if(!e)return {restored:g,mappingsApplied:0,success:false};o(true),a(null);try{let l=await e.client.restore(g,d);return r(l),l}catch(l){let m=l instanceof Error?l:new Error(String(l));return a(m),r(null),{restored:g,mappingsApplied:0,success:false}}finally{o(false);}},[e]),S=useCallback(()=>{r(null),a(null),o(false);},[]);return {restore:c,data:t,isPending:s,error:p,reset:S}}var xe=[];function Pe(){let e=useContext(f);useSyncExternalStore(e?e.subscribeToSessions:()=>()=>{},e?e.getSessionVersion:()=>0,()=>0);let t=e?e.client.getSessions():xe,r=useCallback(o=>{e&&(e.client.clearSession(o),e.notifySessionChange());},[e]),s=useCallback(()=>{e&&(e.client.clearAllSessions(),e.notifySessionChange());},[e]);return {sessions:t,clearSession:r,clearAll:s}}function he(e,t,r){t.current=t.current.filter(s=>s.token!==e);for(let[s,o]of r.current.entries())o.token===e&&r.current.delete(s);}function Ce(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(false),[p,a]=useState(null),[c,S]=useState(null),g=useRef([]),d=useRef(new Map);useEffect(()=>{let y=e?.client;if(y?.subscribeToJobProgress)return y.subscribeToJobProgress(n=>{if(n.progress.operation!=="detectInFile")return;let i=d.current.get(n.requestId);!i&&n.progress.phase==="starting"&&(i=g.current.shift(),i&&(i.requestId=n.requestId,d.current.set(n.requestId,i))),i&&(S(n.progress),i.onProgress?.(n.progress),n.progress.phase==="done"&&d.current.delete(n.requestId));})},[e]);let l=useCallback(async(y,n)=>{if(!e)return {entities:[],text:"",processingTime:0,fileType:"txt"};let i=Symbol("detectInFile");g.current.push({token:i,onProgress:n?.onProgress}),o(true),a(null),S(null);try{let u=await e.client.detectInFile(y);return r(u),u}catch(u){let R=u instanceof Error?u:new Error(String(u));return a(R),r(null),{entities:[],text:"",processingTime:0,fileType:"txt"}}finally{he(i,g,d),o(false);}},[e]),m=useCallback(()=>{r(null),a(null),o(false),S(null);},[]);return {detectInFile:l,data:t,isPending:s,error:p,progress:c,reset:m}}function we(){let e=useContext(f),[t,r]=useState(null),[s,o]=useState(false),[p,a]=useState(null),c=useCallback(async(g,d)=>{if(!e)return new Blob;o(true),a(null);try{let l=await e.client.obfuscateFile(g,d);return r(l),l}catch(l){let m=l instanceof Error?l:new Error(String(l));return a(m),r(null),new Blob}finally{o(false);}},[e]),S=useCallback(()=>{r(null),a(null),o(false);},[]);return {obfuscateFile:c,data:t,isPending:s,error:p,reset:S}}var mt="0.1.0";export{K as SecuredProvider,le as useDetect,Ce as useFileDetect,we as useFileObfuscate,pe as useObfuscate,Z as usePrivacyClient,oe as usePrivacyStatus,ye as useRestore,fe as useScan,Pe as useSessions,ge as useTextReview,re as useVault,mt as version};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@secured-ai/react",
3
+ "version": "0.1.0",
4
+ "description": "React bindings for @secured-ai/core — hooks and provider for PII detection, obfuscation, and restoration",
5
+ "homepage": "https://dev-docs.securedai.com/docs",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "typecheck": "tsc --noEmit"
34
+ },
35
+ "peerDependencies": {
36
+ "@secured-ai/core": "*",
37
+ "react": "^18.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@secured-ai/core": "workspace:*",
41
+ "@testing-library/react": "^16.0.0",
42
+ "@testing-library/jest-dom": "^6.0.0",
43
+ "@types/react": "^18.0.0",
44
+ "@types/react-dom": "^18.0.0",
45
+ "jsdom": "^25.0.0",
46
+ "react": "^18.3.0",
47
+ "react-dom": "^18.3.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.5.0",
50
+ "vitest": "^2.0.0"
51
+ }
52
+ }