@@ -62,6 +62,9 @@ export class ProxyServer extends EventEmitter {
6262 private proxyHandler : ProxyHandler | null = null ;
6363 private orgInfo : OrgInfo | undefined ;
6464
65+ // AC1: Proxy-only mode (skip proxying to dev server, use proxy for Salesforce API only)
66+ private proxyOnlyMode = false ;
67+
6568 // Constructor
6669 public constructor ( config : ProxyServerConfig ) {
6770 super ( ) ;
@@ -347,18 +350,139 @@ export class ProxyServer extends EventEmitter {
347350 }
348351 }
349352
353+ /**
354+ * AC1: Handle internal proxy API requests from the interactive error page.
355+ * Returns true if the request was handled, false if it should continue to the normal flow.
356+ */
357+ private async handleProxyApi ( req : IncomingMessage , res : ServerResponse , url : string ) : Promise < boolean > {
358+ if ( ! url . startsWith ( '/_proxy/' ) ) {
359+ return false ;
360+ }
361+
362+ const setCorsHeaders = ( ) : void => {
363+ res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
364+ res . setHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, OPTIONS' ) ;
365+ res . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type' ) ;
366+ } ;
367+
368+ if ( req . method === 'OPTIONS' ) {
369+ setCorsHeaders ( ) ;
370+ res . writeHead ( 204 ) ;
371+ res . end ( ) ;
372+ return true ;
373+ }
374+
375+ setCorsHeaders ( ) ;
376+
377+ const sendJson = ( statusCode : number , data : Record < string , unknown > ) : void => {
378+ res . writeHead ( statusCode , { 'Content-Type' : 'application/json' } ) ;
379+ res . end ( JSON . stringify ( data ) ) ;
380+ } ;
381+
382+ const readBody = ( ) : Promise < string > =>
383+ new Promise ( ( resolve ) => {
384+ let body = '' ;
385+ req . on ( 'data' , ( chunk : Buffer ) => {
386+ body += chunk . toString ( ) ;
387+ } ) ;
388+ req . on ( 'end' , ( ) => resolve ( body ) ) ;
389+ } ) ;
390+
391+ try {
392+ switch ( url ) {
393+ case '/_proxy/status' : {
394+ sendJson ( 200 , {
395+ devServerStatus : this . devServerStatus ,
396+ devServerUrl : this . config . devServerUrl ,
397+ proxyOnlyMode : this . proxyOnlyMode ,
398+ proxyUrl : this . getProxyUrl ( ) ,
399+ workspaceScript : this . workspaceScript ,
400+ activeError : this . activeDevServerError
401+ ? { title : this . activeDevServerError . title , message : this . activeDevServerError . message }
402+ : null ,
403+ } ) ;
404+ return true ;
405+ }
406+
407+ case '/_proxy/set-url' : {
408+ const body = await readBody ( ) ;
409+ const parsed = JSON . parse ( body ) as { url ?: string } ;
410+ if ( ! parsed . url ) {
411+ sendJson ( 400 , { error : 'Missing "url" in request body' } ) ;
412+ return true ;
413+ }
414+ this . logger . info ( `[Proxy API] Updating dev server URL to: ${ parsed . url } ` ) ;
415+ this . updateDevServerUrl ( parsed . url ) ;
416+ sendJson ( 200 , { ok : true , devServerUrl : parsed . url } ) ;
417+ return true ;
418+ }
419+
420+ case '/_proxy/retry' : {
421+ this . logger . info ( '[Proxy API] Retrying dev server detection' ) ;
422+ await this . checkDevServerHealth ( ) ;
423+ sendJson ( 200 , { ok : true , devServerStatus : this . devServerStatus } ) ;
424+ return true ;
425+ }
426+
427+ case '/_proxy/start-dev' : {
428+ this . logger . info ( '[Proxy API] Request to start dev server' ) ;
429+ this . emit ( 'startDevServer' ) ;
430+ sendJson ( 200 , { ok : true , message : 'Dev server start requested' } ) ;
431+ return true ;
432+ }
433+
434+ case '/_proxy/proxy-only' : {
435+ this . proxyOnlyMode = ! this . proxyOnlyMode ;
436+ this . logger . info ( `[Proxy API] Proxy-only mode: ${ this . proxyOnlyMode ? 'ON' : 'OFF' } ` ) ;
437+ sendJson ( 200 , { ok : true , proxyOnlyMode : this . proxyOnlyMode } ) ;
438+ return true ;
439+ }
440+
441+ case '/_proxy/restart' : {
442+ this . logger . info ( '[Proxy API] Request to restart dev server' ) ;
443+ this . emit ( 'restartDevServer' ) ;
444+ sendJson ( 200 , { ok : true , message : 'Dev server restart requested' } ) ;
445+ return true ;
446+ }
447+
448+ case '/_proxy/force-kill' : {
449+ this . logger . info ( '[Proxy API] Request to force-kill dev server' ) ;
450+ this . emit ( 'forceKillDevServer' ) ;
451+ sendJson ( 200 , { ok : true , message : 'Dev server force-kill requested' } ) ;
452+ return true ;
453+ }
454+
455+ default : {
456+ sendJson ( 404 , { error : `Unknown proxy API endpoint: ${ url } ` } ) ;
457+ return true ;
458+ }
459+ }
460+ } catch ( error ) {
461+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
462+ this . logger . error ( `[Proxy API] Error handling ${ url } : ${ errorMessage } ` ) ;
463+ sendJson ( 500 , { error : errorMessage } ) ;
464+ return true ;
465+ }
466+ }
467+
350468 private async handleRequest ( req : IncomingMessage , res : ServerResponse ) : Promise < void > {
351469 const url = req . url ?? '/' ;
352470 const method = req . method ?? 'GET' ;
353471 this . logger . debug ( `[${ method } ] ${ url } ` ) ;
354472
473+ // AC1: Handle internal proxy API requests first
474+ if ( await this . handleProxyApi ( req , res , url ) ) {
475+ return ;
476+ }
477+
355478 if ( this . activeDevServerError ) {
356479 this . logger . debug ( 'Active dev server error - serving error page' ) ;
357480 this . serveDevServerErrorPage ( this . activeDevServerError , res ) ;
358481 return ;
359482 }
360483
361- if ( this . devServerStatus === 'down' && ! url . includes ( '/services' ) ) {
484+ // AC1: In proxy-only mode, skip the dev server "down" check
485+ if ( this . devServerStatus === 'down' && ! this . proxyOnlyMode && ! url . includes ( '/services' ) ) {
362486 this . serveErrorPage ( res ) ;
363487 return ;
364488 }
0 commit comments