@@ -4,14 +4,19 @@ import bodyParser from 'body-parser';
44import path from 'path' ;
55import fs from 'fs' ;
66import dotenv from 'dotenv' ;
7- import { sendSSEUpdate , addSSEClient , removeSSEClient , sendStageUpdate , sendPaymentUpdate } from './stage' ;
7+ import { fileURLToPath } from 'url' ;
8+ import { dirname } from 'path' ;
89
9- // Import the ATXP client SDK
10- import { atxpClient , ATXPAccount } from '@atxp/client' ;
11- import { ConsoleLogger , LogLevel } from '@atxp/common' ;
10+ // ESM __dirname polyfill
11+ const __filename = fileURLToPath ( import . meta. url ) ;
12+ const __dirname = dirname ( __filename ) ;
13+ import { sendSSEUpdate , addSSEClient , removeSSEClient , sendStageUpdate , sendPaymentUpdate } from './stage.js' ;
14+
15+ // ATXP client SDK imports (will be dynamically imported due to ES module compatibility)
1216
1317// Import ATXP utility functions
14- import { getATXPConnectionString , findATXPAccount , validateATXPConnectionString } from './atxp-utils' ;
18+ import { getATXPConnectionString , findATXPAccount , validateATXPConnectionString } from './atxp-utils.js' ;
19+ import type { ATXPAccount } from '@atxp/client' ;
1520
1621// Load environment variables
1722// In production, __dirname points to dist/, but .env is in the parent directory
@@ -26,12 +31,34 @@ const PORT = process.env.PORT || 3001;
2631const FRONTEND_PORT = process . env . FRONTEND_PORT || 3000 ;
2732
2833// Set up CORS and body parsing middleware
29- app . use ( cors ( {
30- origin : [ `http://localhost:${ FRONTEND_PORT } ` , `http://localhost:${ PORT } ` ] ,
34+ const corsOptions = {
35+ origin : ( origin : string | undefined , callback : ( error : Error | null , allow ?: boolean ) => void ) => {
36+ // Allow requests with no origin (mobile apps, curl, etc.)
37+ if ( ! origin ) return callback ( null , true ) ;
38+
39+ // In development, allow localhost
40+ if ( process . env . NODE_ENV !== 'production' ) {
41+ const allowedOrigins = [ `http://localhost:${ FRONTEND_PORT } ` , `http://localhost:${ PORT } ` ] ;
42+ if ( allowedOrigins . includes ( origin ) ) {
43+ return callback ( null , true ) ;
44+ }
45+ }
46+
47+ // In production, allow any origin since we're serving both API and frontend from same domain
48+ // This is safe because in production, the frontend is served by the same Express server
49+ if ( process . env . NODE_ENV === 'production' ) {
50+ return callback ( null , true ) ;
51+ }
52+
53+ // For development, reject unknown origins
54+ callback ( new Error ( 'Not allowed by CORS' ) , false ) ;
55+ } ,
3156 credentials : true ,
3257 methods : [ 'GET' , 'POST' , 'PUT' , 'DELETE' , 'OPTIONS' ] ,
3358 allowedHeaders : [ 'Content-Type' , 'Authorization' , 'Cache-Control' , 'x-atxp-connection-string' ]
34- } ) ) ;
59+ } ;
60+
61+ app . use ( cors ( corsOptions ) ) ;
3562app . use ( bodyParser . json ( ) ) ;
3663app . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
3764
@@ -84,8 +111,9 @@ const filestoreService = {
84111
85112// Handle OPTIONS for SSE endpoint
86113app . options ( '/api/progress' , ( req : Request , res : Response ) => {
114+ const origin = req . headers . origin || req . headers . host || `http://localhost:${ FRONTEND_PORT } ` ;
87115 res . writeHead ( 200 , {
88- 'Access-Control-Allow-Origin' : `http://localhost: ${ FRONTEND_PORT } ` ,
116+ 'Access-Control-Allow-Origin' : origin ,
89117 'Access-Control-Allow-Credentials' : 'true' ,
90118 'Access-Control-Allow-Headers' : 'Cache-Control, Content-Type, x-atxp-connection-string' ,
91119 'Access-Control-Allow-Methods' : 'GET, OPTIONS'
@@ -95,11 +123,12 @@ app.options('/api/progress', (req: Request, res: Response) => {
95123
96124// SSE endpoint for progress updates
97125app . get ( '/api/progress' , ( req : Request , res : Response ) => {
126+ const origin = req . headers . origin || req . headers . host || `http://localhost:${ FRONTEND_PORT } ` ;
98127 res . writeHead ( 200 , {
99128 'Content-Type' : 'text/event-stream' ,
100129 'Cache-Control' : 'no-cache, no-store, must-revalidate' ,
101130 'Connection' : 'keep-alive' ,
102- 'Access-Control-Allow-Origin' : `http://localhost: ${ FRONTEND_PORT } ` ,
131+ 'Access-Control-Allow-Origin' : origin ,
103132 'Access-Control-Allow-Credentials' : 'true' ,
104133 'Access-Control-Allow-Headers' : 'Cache-Control, Content-Type, x-atxp-connection-string' ,
105134 'Access-Control-Allow-Methods' : 'GET, OPTIONS'
@@ -167,11 +196,12 @@ async function pollForTaskCompletion(
167196 // Send stage update for file storage
168197 sendStageUpdate ( requestId , 'storing-file' , 'Storing image in ATXP Filestore...' , 'in-progress' ) ;
169198
170- // Create filestore client
171- const filestoreClient = await atxpClient ( {
199+ // Create filestore client with dynamic import
200+ const { atxpClient : filestoreAtxpClient } = await import ( '@atxp/client' ) ;
201+ const filestoreClient = await filestoreAtxpClient ( {
172202 mcpServer : filestoreService . mcpServer ,
173203 account : account ,
174- onPayment : async ( { payment } ) => {
204+ onPayment : async ( { payment } : { payment : any } ) => {
175205 console . log ( 'Payment made to filestore:' , payment ) ;
176206 sendPaymentUpdate ( {
177207 accountId : payment . accountId ,
@@ -302,13 +332,17 @@ app.post('/api/texts', async (req: Request, res: Response) => {
302332 // Send stage update for client creation
303333 sendStageUpdate ( requestId , 'creating-clients' , 'Initializing ATXP clients...' , 'in-progress' ) ;
304334
335+ // Dynamically import ATXP modules
336+ const { atxpClient } = await import ( '@atxp/client' ) ;
337+ const { ConsoleLogger, LogLevel } = await import ( '@atxp/common' ) ;
338+
305339 // Create a client using the `atxpClient` function for the ATXP Image MCP Server
306340 const imageClient = await atxpClient ( {
307341 mcpServer : imageService . mcpServer ,
308342 account : account ,
309343 allowedAuthorizationServers : [ `http://localhost:${ PORT } ` , 'https://auth.atxp.ai' , 'https://atxp-accounts-staging.onrender.com/' ] ,
310344 logger : new ConsoleLogger ( { level : LogLevel . DEBUG } ) ,
311- onPayment : async ( { payment } ) => {
345+ onPayment : async ( { payment } : { payment : any } ) => {
312346 console . log ( 'Payment made to image service:' , payment ) ;
313347 sendPaymentUpdate ( {
314348 accountId : payment . accountId ,
@@ -393,23 +427,47 @@ app.get('/api/validate-connection', (req: Request, res: Response) => {
393427
394428// Helper to resolve static path for frontend build
395429function getStaticPath ( ) {
396- // Try ./frontend/build first (works when running from project root in development)
397- let candidate = path . join ( __dirname , './frontend/build' ) ;
398- if ( fs . existsSync ( candidate ) ) {
399- return candidate ;
400- }
401- // Try ../frontend/build (works when running from backend/ directory)
402- candidate = path . join ( __dirname , '../frontend/build' ) ;
403- if ( fs . existsSync ( candidate ) ) {
404- return candidate ;
430+ const candidates = [
431+ // Development: running from project root
432+ path . join ( __dirname , './frontend/build' ) ,
433+ // Development: running from backend/ directory
434+ path . join ( __dirname , '../frontend/build' ) ,
435+ // Production: running from backend/dist/
436+ path . join ( __dirname , '../../frontend/build' ) ,
437+ // Vercel: frontend build copied to backend/public directory
438+ path . join ( __dirname , './public' ) ,
439+ path . join ( __dirname , '../public' ) ,
440+ // Vercel: alternative paths
441+ '/var/task/backend/public' ,
442+ // Development fallback
443+ path . join ( __dirname , '../build' )
444+ ] ;
445+
446+ for ( const candidate of candidates ) {
447+ if ( fs . existsSync ( candidate ) ) {
448+ return candidate ;
449+ }
405450 }
406- // Try ../../frontend/build (works when running from backend/dist/ in production)
407- candidate = path . join ( __dirname , '../../frontend/build' ) ;
408- if ( fs . existsSync ( candidate ) ) {
409- return candidate ;
451+
452+ // List contents of current directory for debugging
453+ try {
454+ const currentDirContents = fs . readdirSync ( __dirname ) ;
455+
456+ // Also check if build directory exists but is empty
457+ const buildPath = path . join ( __dirname , './build' ) ;
458+ if ( fs . existsSync ( buildPath ) ) {
459+ try {
460+ const buildContents = fs . readdirSync ( buildPath ) ;
461+ } catch ( error ) {
462+ console . log ( 'Could not read build directory contents:' , error ) ;
463+ }
464+ }
465+ } catch ( error ) {
466+ console . log ( 'Could not read __dirname contents:' , error ) ;
410467 }
411- // Fallback: throw error
412- throw new Error ( 'No frontend build directory found. Make sure to run "npm run build" first.' ) ;
468+
469+ // Fallback: throw error with more debugging info
470+ throw new Error ( `No frontend build directory found. __dirname: ${ __dirname } . Checked paths: ${ candidates . join ( ', ' ) } ` ) ;
413471}
414472
415473// Serve static files in production
@@ -428,6 +486,12 @@ if (process.env.NODE_ENV === 'production') {
428486 } ) ;
429487}
430488
431- app . listen ( PORT , ( ) => {
432- console . log ( `Server running on port ${ PORT } ` ) ;
433- } ) ;
489+ // For Vercel serverless deployment, export the app
490+ export default app ;
491+
492+ // For local development, start the server
493+ if ( process . env . NODE_ENV !== 'production' || process . env . VERCEL !== '1' ) {
494+ app . listen ( PORT , ( ) => {
495+ console . log ( `Server running on port ${ PORT } ` ) ;
496+ } ) ;
497+ }
0 commit comments