diff --git a/lib/topstepx-class.ts b/lib/topstepx-class.ts index 0102e82..c3a964a 100644 --- a/lib/topstepx-class.ts +++ b/lib/topstepx-class.ts @@ -172,7 +172,14 @@ export class TopstepXClient implements PlatformClient { if (this.syncInterval) { clearInterval(this.syncInterval); this.syncInterval = null; } this.syncComplete = false; - await this.startHub(); + // Hub failure must never read as a login failure: REST polling + // carries every feature (slower), and subscribeAll retries the + // hub on each sync tick. + try { + await this.startHub(); + } catch (err) { + console.error('[topstepx] hub setup failed — running on REST polling only', err instanceof Error ? err.message : err); + } await this.requestSync(); this.syncInterval = setInterval(() => void this.requestSync(), 60_000); @@ -240,6 +247,12 @@ export class TopstepXClient implements PlatformClient { } this.subscribedAccountIds.clear(); + // signalr's Node path does an unconditional dynamic require('ws') / + // require('eventsource') when constructing the connection — bundled + // builds throw "dynamic usage of require is not supported" on it, so + // the package is listed in next.config serverExternalPackages and any + // residual failure here is caught by the caller (REST polling covers + // everything the hub does, just slower). const hub = new signalR.HubConnectionBuilder() .withUrl(USER_HUB, { skipNegotiation: true, diff --git a/next.config.ts b/next.config.ts index 40988d5..baf59c1 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,10 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - serverExternalPackages: ['better-sqlite3'], + // @microsoft/signalr does dynamic require('ws')/require('eventsource') for + // its Node transports — bundling it (turbopack production builds) throws + // "dynamic usage of require is not supported" the moment a hub is built. + serverExternalPackages: ['better-sqlite3', '@microsoft/signalr'], }; export default nextConfig;