2021-01-29 20:45:11 +02:00
const BaseSyncTarget = require ( './BaseSyncTarget' ) . default ;
2020-11-05 18:58:23 +02:00
const { _ } = require ( './locale' ) ;
const Setting = require ( './models/Setting' ) . default ;
const { FileApi } = require ( './file-api.js' ) ;
const Synchronizer = require ( './Synchronizer' ) . default ;
const { FileApiDriverAmazonS3 } = require ( './file-api-driver-amazon-s3.js' ) ;
2021-11-25 01:03:03 +02:00
const { S3Client , HeadBucketCommand } = require ( '@aws-sdk/client-s3' ) ;
2020-07-15 11:22:55 +02:00
class SyncTargetAmazonS3 extends BaseSyncTarget {
static id ( ) {
return 8 ;
}
static supportsConfigCheck ( ) {
return true ;
}
constructor ( db , options = null ) {
super ( db , options ) ;
this . api _ = null ;
}
static targetName ( ) {
return 'amazon_s3' ;
}
static label ( ) {
2021-11-25 01:03:03 +02:00
return ` ${ _ ( 'S3' ) } (Beta) ` ;
2020-07-15 11:22:55 +02:00
}
2021-08-16 16:20:14 +02:00
static description ( ) {
return 'A service offered by Amazon Web Services (AWS) that provides object storage through a web service interface.' ;
}
2020-07-15 11:22:55 +02:00
async isAuthenticated ( ) {
return true ;
}
2023-08-14 19:12:49 +02:00
static requiresPassword ( ) {
return true ;
}
2020-07-15 11:22:55 +02:00
static s3BucketName ( ) {
return Setting . value ( 'sync.8.path' ) ;
}
2021-11-25 01:03:03 +02:00
// These are the settings that get read from disk to instantiate the API.
2020-07-15 11:22:55 +02:00
s3AuthParameters ( ) {
return {
2021-11-25 01:03:03 +02:00
// We need to set a region. See https://github.com/aws/aws-sdk-js-v3/issues/1845#issuecomment-754832210
region : Setting . value ( 'sync.8.region' ) ,
credentials : {
accessKeyId : Setting . value ( 'sync.8.username' ) ,
secretAccessKey : Setting . value ( 'sync.8.password' ) ,
} ,
UseArnRegion : true , // override the request region with the region inferred from requested resource's ARN.
forcePathStyle : Setting . value ( 'sync.8.forcePathStyle' ) , // Older implementations may not support more modern access, so we expose this to allow people the option to toggle.
2020-10-20 12:44:11 +02:00
endpoint : Setting . value ( 'sync.8.url' ) ,
2020-07-15 11:22:55 +02:00
} ;
}
api ( ) {
if ( this . api _ ) return this . api _ ;
2021-11-25 01:03:03 +02:00
this . api _ = new S3Client ( this . s3AuthParameters ( ) ) ;
// There is a bug with auto skew correction in aws-sdk-js-v3
// and this attempts to remove the skew correction for all calls.
// There are some additional spots in the app where we reset this
// to zero as well as it appears the skew logic gets triggered
// which makes "RequestTimeTooSkewed" errors...
// See https://github.com/aws/aws-sdk-js-v3/issues/2208
this . api _ . config . systemClockOffset = 0 ;
2020-07-15 11:22:55 +02:00
return this . api _ ;
}
static async newFileApi _ ( syncTargetId , options ) {
2021-11-25 01:03:03 +02:00
// These options are read from the form on the page
// so we can test new config choices without overriding the current settings.
2020-07-15 11:22:55 +02:00
const apiOptions = {
2021-11-25 01:03:03 +02:00
region : options . region ( ) ,
credentials : {
accessKeyId : options . username ( ) ,
secretAccessKey : options . password ( ) ,
} ,
UseArnRegion : true , // override the request region with the region inferred from requested resource's ARN.
forcePathStyle : options . forcePathStyle ( ) ,
2020-10-20 12:44:11 +02:00
endpoint : options . url ( ) ,
2020-07-15 11:22:55 +02:00
} ;
2021-11-25 01:03:03 +02:00
const api = new S3Client ( apiOptions ) ;
2020-07-15 11:22:55 +02:00
const driver = new FileApiDriverAmazonS3 ( api , SyncTargetAmazonS3 . s3BucketName ( ) ) ;
const fileApi = new FileApi ( '' , driver ) ;
fileApi . setSyncTargetId ( syncTargetId ) ;
return fileApi ;
}
2021-11-25 01:03:03 +02:00
// With the aws-sdk-v3-js some errors (301/403) won't get their XML parsed properly.
// I think it's this issue: https://github.com/aws/aws-sdk-js-v3/issues/1596
// If you save the config on desktop, restart the app and attempt a sync, we should get a clearer error message because the sync logic has more robust XML error parsing.
// We could implement that here, but the above workaround saves some code.
2020-07-15 11:22:55 +02:00
static async checkConfig ( options ) {
const output = {
ok : false ,
errorMessage : '' ,
} ;
try {
2022-01-09 13:24:24 +02:00
const fileApi = await SyncTargetAmazonS3 . newFileApi _ ( SyncTargetAmazonS3 . id ( ) , options ) ;
fileApi . requestRepeatCount _ = 0 ;
2020-07-15 11:22:55 +02:00
const headBucketReq = new Promise ( ( resolve , reject ) => {
2021-11-25 01:03:03 +02:00
fileApi . driver ( ) . api ( ) . send (
new HeadBucketCommand ( {
Bucket : options . path ( ) ,
2023-02-16 12:55:24 +02:00
} ) , ( error , response ) => {
if ( error ) reject ( error ) ;
2021-11-25 01:03:03 +02:00
else resolve ( response ) ;
} ) ;
2020-07-15 11:22:55 +02:00
} ) ;
const result = await headBucketReq ;
2021-11-25 01:03:03 +02:00
2020-07-15 11:22:55 +02:00
if ( ! result ) throw new Error ( ` AWS S3 bucket not found: ${ SyncTargetAmazonS3 . s3BucketName ( ) } ` ) ;
output . ok = true ;
} catch ( error ) {
2021-11-25 01:03:03 +02:00
if ( error . message ) {
output . errorMessage = error . message ;
}
if ( error . code ) {
output . errorMessage += ` (Code ${ error . code } ) ` ;
}
2020-07-15 11:22:55 +02:00
}
return output ;
}
async initFileApi ( ) {
const appDir = '' ;
const fileApi = new FileApi ( appDir , new FileApiDriverAmazonS3 ( this . api ( ) , SyncTargetAmazonS3 . s3BucketName ( ) ) ) ;
fileApi . setSyncTargetId ( SyncTargetAmazonS3 . id ( ) ) ;
return fileApi ;
}
async initSynchronizer ( ) {
return new Synchronizer ( this . db ( ) , await this . fileApi ( ) , Setting . value ( 'appType' ) ) ;
}
}
module . exports = SyncTargetAmazonS3 ;