1+ import NodeRSA from 'node-rsa' ;
2+ import { ChainStep , ChainStepResult , ChainExecutionResult , ChainStepType , SavedKey } from '../shared/types' ;
3+ import Store from 'electron-store' ;
4+
5+ interface StoreData {
6+ keys : SavedKey [ ] ;
7+ }
8+
9+ const store = new Store < StoreData > ( ) ;
10+
11+ export class ChainExecutor {
12+ private keys : SavedKey [ ] = [ ] ;
13+
14+ constructor ( ) {
15+ this . loadKeys ( ) ;
16+ }
17+
18+ private loadKeys ( ) : void {
19+ this . keys = ( store as any ) . get ( 'keys' , [ ] ) ;
20+ }
21+
22+ private getKey ( keyId : string ) : SavedKey | null {
23+ return this . keys . find ( key => key . id === keyId ) || null ;
24+ }
25+
26+ private async executeStep ( step : ChainStep , input : string ) : Promise < ChainStepResult > {
27+ const startTime = Date . now ( ) ;
28+ const result : ChainStepResult = {
29+ stepId : step . id ,
30+ stepType : step . type ,
31+ input,
32+ output : '' ,
33+ success : false ,
34+ duration : 0 ,
35+ } ;
36+
37+ try {
38+ let output : string ;
39+
40+ switch ( step . type ) {
41+ case 'url-encode' :
42+ output = encodeURIComponent ( input ) ;
43+ break ;
44+
45+ case 'url-decode' :
46+ output = decodeURIComponent ( input ) ;
47+ break ;
48+
49+ case 'base64-encode' :
50+ output = Buffer . from ( input , 'utf8' ) . toString ( 'base64' ) ;
51+ break ;
52+
53+ case 'base64-decode' :
54+ output = Buffer . from ( input , 'base64' ) . toString ( 'utf8' ) ;
55+ break ;
56+
57+ case 'rsa-encrypt' :
58+ output = await this . rsaEncrypt ( input , step ) ;
59+ break ;
60+
61+ case 'rsa-decrypt' :
62+ output = await this . rsaDecrypt ( input , step ) ;
63+ break ;
64+
65+ default :
66+ throw new Error ( `Unsupported step type: ${ step . type } ` ) ;
67+ }
68+
69+ result . output = output ;
70+ result . success = true ;
71+ } catch ( error ) {
72+ result . error = error instanceof Error ? error . message : 'Unknown error occurred' ;
73+ result . success = false ;
74+ result . output = input ; // Pass through input on error
75+ } finally {
76+ result . duration = Date . now ( ) - startTime ;
77+ }
78+
79+ return result ;
80+ }
81+
82+ private async rsaEncrypt ( input : string , step : ChainStep ) : Promise < string > {
83+ const keyId = step . params ?. keyId ;
84+ if ( ! keyId ) {
85+ throw new Error ( 'RSA encryption requires a key ID' ) ;
86+ }
87+
88+ const key = this . getKey ( keyId ) ;
89+ if ( ! key ) {
90+ throw new Error ( `Key with ID ${ keyId } not found` ) ;
91+ }
92+
93+ const algorithm = step . params ?. algorithm || 'RSA-OAEP' ;
94+ const rsaKey = new NodeRSA ( ) ;
95+
96+ try {
97+ rsaKey . importKey ( key . publicKey , 'pkcs8-public-pem' ) ;
98+
99+ if ( algorithm === 'RSA-OAEP' ) {
100+ rsaKey . setOptions ( {
101+ encryptionScheme : 'pkcs1_oaep' ,
102+ } as any ) ;
103+ } else if ( algorithm === 'RSA-PKCS1' ) {
104+ rsaKey . setOptions ( {
105+ encryptionScheme : 'pkcs1' ,
106+ } ) ;
107+ }
108+
109+ const encrypted = rsaKey . encrypt ( input , 'base64' ) ;
110+ return encrypted ;
111+ } catch ( error ) {
112+ throw new Error ( `RSA encryption failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
113+ }
114+ }
115+
116+ private async rsaDecrypt ( input : string , step : ChainStep ) : Promise < string > {
117+ const keyId = step . params ?. keyId ;
118+ if ( ! keyId ) {
119+ throw new Error ( 'RSA decryption requires a key ID' ) ;
120+ }
121+
122+ const key = this . getKey ( keyId ) ;
123+ if ( ! key ) {
124+ throw new Error ( `Key with ID ${ keyId } not found` ) ;
125+ }
126+
127+ const algorithm = step . params ?. algorithm || 'RSA-OAEP' ;
128+ const rsaKey = new NodeRSA ( ) ;
129+
130+ try {
131+ rsaKey . importKey ( key . privateKey , 'pkcs8-private-pem' ) ;
132+
133+ if ( algorithm === 'RSA-OAEP' ) {
134+ rsaKey . setOptions ( {
135+ encryptionScheme : 'pkcs1_oaep' ,
136+ } as any ) ;
137+ } else if ( algorithm === 'RSA-PKCS1' ) {
138+ rsaKey . setOptions ( {
139+ encryptionScheme : 'pkcs1' ,
140+ } ) ;
141+ }
142+
143+ const decrypted = rsaKey . decrypt ( input , 'utf8' ) ;
144+ return decrypted ;
145+ } catch ( error ) {
146+ throw new Error ( `RSA decryption failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
147+ }
148+ }
149+
150+ public async executeChain (
151+ steps : ChainStep [ ] ,
152+ inputText : string ,
153+ templateId ?: string ,
154+ templateName ?: string
155+ ) : Promise < ChainExecutionResult > {
156+ const startTime = Date . now ( ) ;
157+ const executionId = crypto . randomUUID ( ) ;
158+ const stepResults : ChainStepResult [ ] = [ ] ;
159+ let currentInput = inputText ;
160+ let overallSuccess = true ;
161+
162+ // Refresh keys before execution
163+ this . loadKeys ( ) ;
164+
165+ // Filter enabled steps and execute them sequentially
166+ const enabledSteps = steps . filter ( step => step . enabled ) ;
167+
168+ for ( const step of enabledSteps ) {
169+ const stepResult = await this . executeStep ( step , currentInput ) ;
170+ stepResults . push ( stepResult ) ;
171+
172+ if ( stepResult . success ) {
173+ currentInput = stepResult . output ;
174+ } else {
175+ overallSuccess = false ;
176+ // Stop execution on first failure
177+ break ;
178+ }
179+ }
180+
181+ const totalDuration = Date . now ( ) - startTime ;
182+
183+ const result : ChainExecutionResult = {
184+ id : executionId ,
185+ templateId,
186+ templateName,
187+ success : overallSuccess ,
188+ steps : stepResults ,
189+ finalOutput : overallSuccess ? currentInput : inputText ,
190+ totalDuration,
191+ timestamp : new Date ( ) ,
192+ inputText,
193+ } ;
194+
195+ return result ;
196+ }
197+
198+ public validateChain ( steps : ChainStep [ ] ) : { valid : boolean ; errors : string [ ] } {
199+ const errors : string [ ] = [ ] ;
200+ const enabledSteps = steps . filter ( step => step . enabled ) ;
201+
202+ if ( enabledSteps . length === 0 ) {
203+ errors . push ( 'At least one step must be enabled' ) ;
204+ return { valid : false , errors } ;
205+ }
206+
207+ for ( const step of enabledSteps ) {
208+ // Validate RSA steps have required keys
209+ if ( ( step . type === 'rsa-encrypt' || step . type === 'rsa-decrypt' ) && ! step . params ?. keyId ) {
210+ errors . push ( `Step "${ step . name || step . type } " requires a key to be selected` ) ;
211+ }
212+
213+ // Validate key exists
214+ if ( step . params ?. keyId ) {
215+ const key = this . getKey ( step . params . keyId ) ;
216+ if ( ! key ) {
217+ errors . push ( `Step "${ step . name || step . type } " references a key that no longer exists` ) ;
218+ }
219+ }
220+ }
221+
222+ return { valid : errors . length === 0 , errors } ;
223+ }
224+
225+ public getAvailableModules ( ) : Record < ChainStepType , { name : string ; description : string ; category : string ; requiredParams ?: string [ ] } > {
226+ return {
227+ 'url-encode' : {
228+ name : 'URL 인코딩' ,
229+ description : 'URL 안전 문자로 인코딩합니다' ,
230+ category : 'encoding' ,
231+ } ,
232+ 'url-decode' : {
233+ name : 'URL 디코딩' ,
234+ description : 'URL 인코딩된 문자를 디코딩합니다' ,
235+ category : 'encoding' ,
236+ } ,
237+ 'base64-encode' : {
238+ name : 'Base64 인코딩' ,
239+ description : 'Base64로 인코딩합니다' ,
240+ category : 'encoding' ,
241+ } ,
242+ 'base64-decode' : {
243+ name : 'Base64 디코딩' ,
244+ description : 'Base64를 디코딩합니다' ,
245+ category : 'encoding' ,
246+ } ,
247+ 'rsa-encrypt' : {
248+ name : 'RSA 암호화' ,
249+ description : 'RSA 공개키로 암호화합니다' ,
250+ category : 'crypto' ,
251+ requiredParams : [ 'keyId' ] ,
252+ } ,
253+ 'rsa-decrypt' : {
254+ name : 'RSA 복호화' ,
255+ description : 'RSA 개인키로 복호화합니다' ,
256+ category : 'crypto' ,
257+ requiredParams : [ 'keyId' ] ,
258+ } ,
259+ } ;
260+ }
261+ }
262+
263+ // Export singleton instance
264+ export const chainExecutor = new ChainExecutor ( ) ;
0 commit comments