11import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
2- import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
32import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
43import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
54import { AdapterResponse , makeLogger , sleep } from '@chainlink/external-adapter-framework/util'
65import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
7- import SftpClient , { FileInfo } from 'ssh2-sftp-client'
6+ import { ConnectOptions } from 'ssh2-sftp-client'
87import { BaseEndpointTypes , IndexResponseData , inputParameters } from '../endpoint/sftp'
98import { CSVParserFactory } from '../parsing/factory'
10- import {
11- instrumentToFilePathMap ,
12- instrumentToFileRegexMap ,
13- isInstrumentSupported ,
14- } from './constants'
9+ import { instrumentToDirectoryMap , instrumentToFileRegexMap , validateInstrument } from './constants'
10+ import { getFileContentsFromFileRegex } from './utils'
1511
1612const logger = makeLogger ( 'FTSE SFTP Adapter' )
1713
1814type RequestParams = typeof inputParameters . validated
1915
20- interface SftpConnectionConfig {
21- host : string
22- port : number
23- username : string
24- password : string
25- readyTimeout : number
26- }
27-
2816export class SftpTransport extends SubscriptionTransport < BaseEndpointTypes > {
2917 config ! : BaseEndpointTypes [ 'Settings' ]
3018 endpointName ! : string
31- name ! : string
32- responseCache ! : ResponseCache < BaseEndpointTypes >
33- sftpClient : SftpClient
3419
3520 constructor ( ) {
3621 super ( )
37- this . sftpClient = new SftpClient ( )
3822 }
3923
4024 async initialize (
@@ -45,9 +29,6 @@ export class SftpTransport extends SubscriptionTransport<BaseEndpointTypes> {
4529 ) : Promise < void > {
4630 await super . initialize ( dependencies , adapterSettings , endpointName , transportName )
4731 this . config = adapterSettings
48- this . endpointName = endpointName
49- this . name = transportName
50- this . responseCache = dependencies . responseCache
5132 }
5233
5334 async backgroundHandler (
@@ -73,13 +54,6 @@ export class SftpTransport extends SubscriptionTransport<BaseEndpointTypes> {
7354 providerIndicatedTimeUnixMs : undefined ,
7455 } ,
7556 }
76- } finally {
77- try {
78- await this . sftpClient . end ( )
79- logger . info ( 'SFTP connection closed' )
80- } catch ( error ) {
81- logger . error ( 'Error closing SFTP connection:' , error )
82- }
8357 }
8458
8559 await this . responseCache . write ( this . name , [ { params : param , response } ] )
@@ -90,25 +64,12 @@ export class SftpTransport extends SubscriptionTransport<BaseEndpointTypes> {
9064 ) : Promise < AdapterResponse < BaseEndpointTypes [ 'Response' ] > > {
9165 const providerDataRequestedUnixMs = Date . now ( )
9266
93- await this . connectToSftp ( )
94-
95- const parsedData = await this . tryDownloadAndParseFile ( param . instrument )
67+ const { filename, result, parsedData } = await this . tryDownloadAndParseFile ( param . instrument )
9668
97- // Extract the numeric result based on the data type
98- let result : number
99- if ( 'gbpIndex' in parsedData ) {
100- // FTSE data
101- result = ( parsedData . gbpIndex as number ) ?? 0
102- } else if ( 'close' in parsedData ) {
103- // Russell data
104- result = parsedData . close as number
105- } else {
106- throw new Error ( 'Unknown data format received from parser' )
107- }
108-
109- logger . info ( `Successfully processed data for instrument: ${ param . instrument } ` )
69+ logger . debug ( `Successfully processed data for instrument: ${ param . instrument } ` )
11070 return {
11171 data : {
72+ filename,
11273 result : parsedData ,
11374 } ,
11475 statusCode : 200 ,
@@ -121,66 +82,30 @@ export class SftpTransport extends SubscriptionTransport<BaseEndpointTypes> {
12182 }
12283 }
12384
124- private async connectToSftp ( ) : Promise < void > {
125- const connectConfig : SftpConnectionConfig = {
85+ private async tryDownloadAndParseFile ( instrument : string ) : Promise < {
86+ filename : string
87+ result : number
88+ parsedData : IndexResponseData
89+ } > {
90+ validateInstrument ( instrument )
91+
92+ const connectOptions : ConnectOptions = {
12693 host : this . config . SFTP_HOST ,
127- port : this . config . SFTP_PORT || 22 ,
94+ port : this . config . SFTP_PORT ,
12895 username : this . config . SFTP_USERNAME ,
12996 password : this . config . SFTP_PASSWORD ,
13097 readyTimeout : 30000 ,
13198 }
13299
133- try {
134- // Create a new client instance to avoid connection state issues
135- this . sftpClient = new SftpClient ( )
136- await this . sftpClient . connect ( connectConfig )
137- logger . info ( 'Successfully connected to SFTP server' )
138- } catch ( error ) {
139- logger . error ( error , 'Failed to connect to SFTP server' )
140- throw new AdapterInputError ( {
141- statusCode : 500 ,
142- message : `Failed to connect to SFTP server: ${
143- error instanceof Error ? error . message : 'Unknown error'
144- } `,
145- } )
146- }
147- }
100+ const directory = instrumentToDirectoryMap [ instrument ]
101+ const filenameRegex = instrumentToFileRegexMap [ instrument ]
148102
149- private async tryDownloadAndParseFile ( instrument : string ) : Promise < IndexResponseData > {
150- // Validate that the instrument is supported
151- if ( ! isInstrumentSupported ( instrument ) ) {
152- throw new AdapterInputError ( {
153- statusCode : 400 ,
154- message : `Unsupported instrument: ${ instrument } ` ,
155- } )
156- }
103+ const { filename, fileContent } = await getFileContentsFromFileRegex ( {
104+ connectOptions,
105+ directory,
106+ filenameRegex,
107+ } )
157108
158- const filePath = instrumentToFilePathMap [ instrument ]
159- const fileRegex = instrumentToFileRegexMap [ instrument ]
160-
161- const fileList = await this . sftpClient . list ( filePath )
162- // Filter files based on the regex pattern
163- const matchingFiles = fileList
164- . map ( ( file : FileInfo ) => file . name )
165- . filter ( ( fileName : string ) => fileRegex . test ( fileName ) )
166-
167- if ( matchingFiles . length === 0 ) {
168- throw new AdapterInputError ( {
169- statusCode : 500 ,
170- message : `No files matching pattern ${ fileRegex } found in directory: ${ filePath } ` ,
171- } )
172- } else if ( matchingFiles . length > 1 ) {
173- throw new AdapterInputError ( {
174- statusCode : 500 ,
175- message : `Multiple files matching pattern ${ fileRegex } found in directory: ${ filePath } .` ,
176- } )
177- }
178- const fullPath = `${ filePath } ${ matchingFiles [ 0 ] } `
179-
180- // Log the download attempt
181- logger . info ( `Downloading file: ${ fullPath } ` )
182-
183- const fileContent = await this . sftpClient . get ( fullPath )
184109 // we need latin1 here because the file contains special characters like "®"
185110 const csvContent = fileContent . toString ( 'latin1' )
186111
@@ -193,7 +118,13 @@ export class SftpTransport extends SubscriptionTransport<BaseEndpointTypes> {
193118 } )
194119 }
195120
196- return ( await parser . parse ( csvContent ) ) as IndexResponseData
121+ const { result, parsedData } = await parser . parse ( csvContent )
122+
123+ return {
124+ filename,
125+ result,
126+ parsedData : parsedData as IndexResponseData ,
127+ }
197128 }
198129
199130 getSubscriptionTtlFromConfig ( adapterSettings : BaseEndpointTypes [ 'Settings' ] ) : number {
0 commit comments