@@ -17,6 +17,7 @@ enum FileSystemType {
1717 GITHUB ,
1818 FLASK ,
1919 LAKEFS ,
20+ S3 ,
2021}
2122
2223naturalSort . insensitive = true
@@ -42,6 +43,7 @@ class HTTPFileSystem {
4243 private isGithub : boolean
4344 private isZIB : boolean
4445 private isFlask : boolean
46+ private isS3 : boolean
4547 private type : FileSystemType
4648 private fileLinkLookup : any = { }
4749
@@ -54,12 +56,14 @@ class HTTPFileSystem {
5456 this . isGithub = ! ! project . isGithub
5557 this . isFlask = ! ! project . flask
5658 this . isZIB = ! ! project . isZIB
59+ this . isS3 = ! ! project . isS3
5760
5861 this . type = FileSystemType . FETCH
5962 if ( this . fsHandle ) this . type = FileSystemType . CHROME
6063 if ( this . isGithub ) this . type = FileSystemType . GITHUB
6164 if ( this . isFlask ) this . type = FileSystemType . FLASK
6265 if ( this . isZIB ) this . type = FileSystemType . LAKEFS
66+ if ( this . isS3 ) this . type = FileSystemType . S3
6367
6468 this . baseUrl = project . baseURL
6569 if ( ! project . baseURL . endsWith ( '/' ) ) this . baseUrl += '/'
@@ -533,6 +537,9 @@ class HTTPFileSystem {
533537 case FileSystemType . FLASK :
534538 dirEntry = await this . _getDirectoryFromAzure ( stillScaryPath )
535539 break
540+ case FileSystemType . S3 :
541+ dirEntry = await this . _getDirectoryFromS3 ( stillScaryPath )
542+ break
536543 case FileSystemType . LAKEFS :
537544 case FileSystemType . FETCH :
538545 default :
@@ -617,6 +624,73 @@ class HTTPFileSystem {
617624 return contents
618625 }
619626
627+ async _getDirectoryFromS3 ( stillScaryPath : string ) : Promise < DirectoryEntry > {
628+ // S3 uses a list API with prefix and delimiter to simulate directories
629+ // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
630+
631+ let prefix = stillScaryPath . replace ( / ^ \/ + / , '' ) // remove leading slashes
632+ prefix = prefix . replaceAll ( '//' , '/' )
633+
634+ // Build the S3 list URL with query parameters
635+ const listUrl = `${ this . baseUrl } ?list-type=2&delimiter=/&prefix=${ encodeURIComponent ( prefix ) } `
636+
637+ const response = await fetch ( listUrl )
638+ if ( response . status !== 200 ) {
639+ console . warn ( 'S3 list status:' , response . status )
640+ throw response
641+ }
642+
643+ const xmlText = await response . text ( )
644+ return this . buildListFromS3Xml ( xmlText , prefix )
645+ }
646+
647+ private buildListFromS3Xml ( xmlText : string , prefix : string ) : DirectoryEntry {
648+ const dirs : string [ ] = [ ]
649+ const files : string [ ] = [ ]
650+
651+ const parser = new DOMParser ( )
652+ const xmlDoc = parser . parseFromString ( xmlText , 'text/xml' )
653+
654+ // Get files from <Contents> elements
655+ const contents = xmlDoc . getElementsByTagName ( 'Contents' )
656+ for ( let i = 0 ; i < contents . length ; i ++ ) {
657+ const keyElement = contents [ i ] . getElementsByTagName ( 'Key' ) [ 0 ]
658+ if ( keyElement && keyElement . textContent ) {
659+ let key = keyElement . textContent
660+ // Remove the prefix to get the relative filename
661+ if ( key . startsWith ( prefix ) ) {
662+ key = key . substring ( prefix . length )
663+ }
664+ // Skip if it's the directory itself or empty
665+ if ( key && key !== '' && ! key . endsWith ( '/' ) ) {
666+ files . push ( key )
667+ }
668+ }
669+ }
670+
671+ // Get directories from <CommonPrefixes> elements
672+ const commonPrefixes = xmlDoc . getElementsByTagName ( 'CommonPrefixes' )
673+ for ( let i = 0 ; i < commonPrefixes . length ; i ++ ) {
674+ const prefixElement = commonPrefixes [ i ] . getElementsByTagName ( 'Prefix' ) [ 0 ]
675+ if ( prefixElement && prefixElement . textContent ) {
676+ let dirPath = prefixElement . textContent
677+ // Remove the base prefix to get the relative directory name
678+ if ( dirPath . startsWith ( prefix ) ) {
679+ dirPath = dirPath . substring ( prefix . length )
680+ }
681+ // Remove trailing slash
682+ if ( dirPath . endsWith ( '/' ) ) {
683+ dirPath = dirPath . slice ( 0 , - 1 )
684+ }
685+ if ( dirPath && dirPath !== '' ) {
686+ dirs . push ( dirPath )
687+ }
688+ }
689+ }
690+
691+ return { dirs, files, handles : { } }
692+ }
693+
620694 async _getDirectoryFromURL ( stillScaryPath : string ) {
621695 const response = await this . _getFileResponse ( stillScaryPath )
622696 // console.log(response)
0 commit comments