@@ -52,7 +52,6 @@ import {
5252} from '../utils/PrinterUtils' ;
5353import { applyPerPrinterDefaults } from '../utils/printerSettingsDefaults' ;
5454import { TimeoutError , withTimeout } from '../utils/ShutdownTimeout' ;
55- import { IPAddressSchema } from '../utils/validation.utils' ;
5655import { getLoadingManager } from './LoadingManager' ;
5756import { getPrinterBackendManager } from './PrinterBackendManager' ;
5857import { getPrinterContextManager } from './PrinterContextManager' ;
@@ -274,7 +273,7 @@ export class ConnectionFlowManager extends EventEmitter {
274273 return await this . showSavedPrintersForSelection ( ) ;
275274
276275 case 'manual-ip' :
277- return await this . offerManualIPEntry ( ) ;
276+ return this . manualEntryUnavailable ( ) ;
278277
279278 default :
280279 return { success : false , error : 'Unknown choice' } ;
@@ -283,7 +282,7 @@ export class ConnectionFlowManager extends EventEmitter {
283282 // No saved printers - go directly to manual IP entry
284283 this . loadingManager . hide ( ) ;
285284 console . log ( 'No printers discovered and no saved printers - offering manual IP entry' ) ;
286- return await this . offerManualIPEntry ( ) ;
285+ return this . manualEntryUnavailable ( ) ;
287286 }
288287 }
289288
@@ -406,7 +405,7 @@ export class ConnectionFlowManager extends EventEmitter {
406405 }
407406
408407 case 'manual-ip' :
409- return await this . offerManualIPEntry ( ) ;
408+ return this . manualEntryUnavailable ( ) ;
410409
411410 default :
412411 return { success : false , error : 'Auto-connect cancelled by user' } ;
@@ -820,73 +819,27 @@ export class ConnectionFlowManager extends EventEmitter {
820819 }
821820 }
822821
823- /** Offer manual IP entry to user */
824- private async offerManualIPEntry ( ) : Promise < ConnectionResult > {
825- if ( ! this . inputDialogHandler ) {
826- return {
827- success : false ,
828- error : 'Manual IP entry not available - input dialog handler not set' ,
829- } ;
830- }
831-
832- try {
833- const ipAddress = await this . inputDialogHandler ( {
834- title : 'Manual Printer Connection' ,
835- message : 'No printers found on network. Enter printer IP address manually:' ,
836- defaultValue : '' ,
837- inputType : 'text' ,
838- placeholder : 'e.g., 192.168.1.100' ,
839- } ) ;
840-
841- if ( ! ipAddress ) {
842- return { success : false , error : 'No IP address provided' } ;
843- }
844-
845- // Validate IP address format
846- const validation = IPAddressSchema . safeParse ( ipAddress . trim ( ) ) ;
847- if ( ! validation . success ) {
848- this . loadingManager . showError ( 'Invalid IP address format' , 3000 ) ;
849- return { success : false , error : 'Invalid IP address format' } ;
850- }
851-
852- return await this . connectDirectlyToIP ( validation . data ) ;
853- } catch ( error ) {
854- const errorMessage = getConnectionErrorMessage ( error ) ;
855- this . loadingManager . showError ( `Manual connection failed: ${ errorMessage } ` , 4000 ) ;
856- return { success : false , error : errorMessage } ;
857- }
858- }
859-
860- /** Connect directly to an IP address */
861- public async connectDirectlyToIP ( ipAddress : string ) : Promise < ConnectionResult > {
862- try {
863- this . loadingManager . show ( {
864- message : `Connecting to printer at ${ ipAddress } ...` ,
865- canCancel : false ,
866- } ) ;
867-
868- // Create a mock discovered printer for the connection process
869- // The actual name and serial will be determined during temporary connection
870- const mockDiscoveredPrinter : DiscoveredPrinter = {
871- name : `Printer at ${ ipAddress } ` , // Temporary name, will be updated
872- ipAddress : ipAddress ,
873- serialNumber : '' , // Will be determined during connection
874- model : undefined , // Will be determined during connection
875- } ;
876-
877- console . log ( 'Starting direct IP connection to:' , ipAddress ) ;
878-
879- // Use the standard connection flow which will:
880- // 1. Create temporary connection to get printer info
881- // 2. Extract proper name and serial number
882- // 3. Establish final connection with correct details
883- return await this . connectToPrinter ( mockDiscoveredPrinter ) ;
884- } catch ( error ) {
885- const errorMessage = getConnectionErrorMessage ( error ) ;
886- console . error ( 'Direct IP connection failed:' , error ) ;
887- this . loadingManager . showError ( `Direct connection failed: ${ errorMessage } ` , 4000 ) ;
888- return { success : false , error : errorMessage } ;
889- }
822+ /**
823+ * Direct the user to the typed manual-connect form.
824+ *
825+ * This replaces `offerManualIPEntry()` / `connectDirectlyToIP(ip)`, which
826+ * built a DiscoveredPrinter with NO productId from an IP address alone. With
827+ * no product ID, `createTemporaryConnection` cannot tell that the printer is
828+ * HTTP-only, falls through to `createLegacyClient()`, and probes TCP 8899 -
829+ * which the Creator 5 series refuses outright (it runs no TCP server), so
830+ * adding one by IP failed with ECONNREFUSED. Reported against FlashForgeWebUI
831+ * in ff-5mp-hass#18.
832+ *
833+ * The browser's "Add Printer" form requires a printer type and sends the
834+ * matching product ID, so it identifies an HTTP-only model before any socket
835+ * is opened. That is the only manual path that is safe to offer.
836+ */
837+ private manualEntryUnavailable ( ) : ConnectionResult {
838+ const message =
839+ 'Use the "Add Printer" form to add a printer by IP - it asks for the printer type, ' +
840+ 'which is required to connect models that have no legacy TCP service (Creator 5 / 5 Pro).' ;
841+ this . loadingManager . showError ( message , 5000 ) ;
842+ return { success : false , error : message } ;
890843 }
891844
892845 /** Show saved printers for manual selection */
0 commit comments